feat: 实现初始应用程序结构,包含检查UI、会话管理、数据库和文件系统工具。

This commit is contained in:
zhu-mengmeng 2026-01-21 13:26:53 +08:00
commit b87d94cebd
35 changed files with 6596 additions and 0 deletions

View File

@ -0,0 +1,15 @@
{
"permissions": {
"allow": [
"mcp__auggie-mcp__codebase-retrieval",
"Skill(collaborating-with-codex)",
"Skill(collaborating-with-gemini)",
"Bash(find:*)",
"Bash(source .venv/bin/activate)",
"Bash(pip freeze:*)",
"Bash(.venv/bin/pip freeze:*)",
"Bash(/Users/meng/work/tengzhi/readFileSystem/.venv/bin/pip freeze:*)",
"Bash(/Users/meng/work/tengzhi/readFileSystem/.venv/bin/pip list:*)"
]
}
}

274
AGENTS.md Normal file
View File

@ -0,0 +1,274 @@
# AGENTS.md
This file provides coding guidelines and commands for AI agents working in this repository.
## Project Overview
This is a **PySide6-based desktop application** for inspection management and file system browsing. The project includes:
- GUI components for inspection workflows (incoming/manual inspection)
- Database integration with PostgreSQL
- XML parsing for element analysis data
- File system browser utility
**Tech Stack**: Python 3.8+, PySide6 6.7.1, PostgreSQL (psycopg2), pywin32
---
## Build & Run Commands
### Installation
```bash
# Create virtual environment (recommended)
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
```
### Running the Application
```bash
# Main inspection application
python run.py
# File system browser (legacy)
python src/main.py
```
### Testing
**Note**: This project currently has no test suite. When adding tests:
```bash
# Install pytest
pip install pytest pytest-qt
# Run all tests
pytest
# Run single test file
pytest tests/test_xml_parser.py
# Run single test function
pytest tests/test_xml_parser.py::test_parse_valid_xml
# Run with verbose output
pytest -v
# Run with coverage
pytest --cov=src --cov-report=html
```
### Linting & Formatting
**Note**: No linter/formatter is currently configured. Recommended setup:
```bash
# Install tools
pip install black flake8 mypy
# Format code
black src/ ui/ utils/
# Lint code
flake8 src/ ui/ utils/ --max-line-length=120
# Type checking
mypy src/ ui/ utils/
```
---
## Code Style Guidelines
### 1. Imports
- **Order**: Standard library → Third-party → Local modules
- **Style**: Absolute imports preferred; relative imports allowed within packages
- **Grouping**: Separate groups with blank lines
```python
# Standard library
import os
from pathlib import Path
from dataclasses import dataclass
from typing import List, Dict, Optional
from datetime import datetime
# Third-party
from PySide6.QtWidgets import QMainWindow, QWidget
from PySide6.QtCore import Qt
import psycopg2
# Local
from src.db_manager import DatabaseManager
from ui.inspection_card import InspectionCard
```
### 2. Formatting
- **Line length**: ~120 characters (flexible, not enforced)
- **Indentation**: 4 spaces (no tabs)
- **Quotes**: Double quotes for strings (not strictly enforced)
- **Blank lines**: 2 between top-level classes/functions, 1 within classes
### 3. Naming Conventions
- **Classes**: `PascalCase` (e.g., `FileSystemReader`, `InspectionWindow`)
- **Functions/Methods**: `snake_case` (e.g., `get_files_and_folders`, `parse_file`)
- **Variables**: `snake_case` (e.g., `sample_name`, `data_list`)
- **Constants**: `UPPER_SNAKE_CASE` (e.g., `MAX_RETRIES`)
- **Private members**: Prefix with `_` (e.g., `_get_file_type`)
### 4. Type Hints
- **Usage**: Encouraged but not mandatory
- **Style**: Use `typing` module for complex types
- **Return types**: Always specify for public methods
```python
from typing import List, Dict, Optional
def get_files_and_folders(self) -> List[FileInfo]:
"""Returns list of file information objects"""
pass
def parse_file(self, file_path: str) -> tuple[Optional[List[Dict]], str]:
"""Returns (data, error_message)"""
pass
```
### 5. Docstrings
- **Style**: Google-style docstrings (simple format)
- **Required for**: Public classes, public methods, complex functions
- **Not required for**: Simple getters/setters, obvious utility functions
```python
def get_files_and_folders(self) -> List[FileInfo]:
"""
Read all files and folders from the root path
Returns:
List[FileInfo]: List of file information objects
"""
pass
```
### 6. Error Handling
- **Philosophy**: Fail gracefully, skip inaccessible resources
- **Pattern**: Try-except with specific exceptions, return None/empty on failure
- **Logging**: Use `print()` for errors (no logging framework configured)
```python
try:
conn = psycopg2.connect(**self.config)
return conn
except Exception as e:
print(f"Database connection error: {e}")
return None
```
### 7. Data Classes
- Use `@dataclass` for simple data containers
- Include type hints for all fields
- Add `@property` methods for computed values
```python
from dataclasses import dataclass
@dataclass
class FileInfo:
name: str
path: str
size: int
file_type: str
is_dir: bool
modified_time: str
@property
def size_readable(self) -> str:
"""Convert size to human-readable format"""
# Implementation
```
### 8. PySide6 GUI Patterns
- **Initialization**: All UI setup in `__init__`
- **Layouts**: Use explicit layout objects (QVBoxLayout, QHBoxLayout)
- **Styling**: Inline stylesheets for component-specific styles
- **Font**: Microsoft YaHei for Chinese text support
```python
class InspectionWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("检验管理系统")
# Setup layouts
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
```
### 9. Database Patterns
- **Connection**: Create connection per operation, close immediately
- **Queries**: Use parameterized queries (never string interpolation)
- **Schema**: Explicitly specify schema with `self.schema` variable
- **Cursors**: Use `RealDictCursor` for dict-like results
```python
def get_connection(self):
try:
conn = psycopg2.connect(**self.config)
return conn
except Exception as e:
print(f"Database connection error: {e}")
return None
```
### 10. File Operations
- **Path handling**: Use `pathlib.Path` (not `os.path`)
- **Existence checks**: Always check before operations
- **Encoding**: UTF-8 default (not explicitly specified)
```python
from pathlib import Path
def parse_file(self, file_path):
if not os.path.exists(file_path):
return None, f"文件未找到: {file_path}"
```
---
## Project-Specific Rules
1. **Minimal Comments**: Code should be self-explanatory; avoid redundant comments
2. **Chinese UI Text**: All user-facing strings in Chinese (e.g., "入检", "手检")
3. **No Tests**: Currently no test suite; add tests when implementing new features
4. **Entry Points**: `run.py` for inspection app, `src/main.py` for file browser
5. **Database Credentials**: Hardcoded in `db_manager.py` (not production-ready)
6. **Error Messages**: Return tuple `(data, error_message)` pattern for parsers
---
## Common Patterns
### Adding a New UI Component
1. Create class in `ui/` directory
2. Inherit from appropriate PySide6 widget
3. Initialize layout in `__init__`
4. Use inline stylesheets for custom styling
### Adding Database Operations
1. Add method to `DatabaseManager` class
2. Use `get_connection()` to obtain connection
3. Always close connection in `finally` block or after operation
4. Return tuple `(success: bool, message: str)` for write operations
### Parsing New XML Structures
1. Add method to `XmlParser` class
2. Use `xml.etree.ElementTree` for parsing
3. Return tuple `(data: Optional[List[Dict]], error: str)`
4. Handle missing elements gracefully with `None` defaults
---
## Notes for AI Agents
- **Preserve existing functionality**: Only modify code directly related to the task
- **No unnecessary refactoring**: Keep changes minimal and targeted
- **Test manually**: No automated tests; verify changes by running the application
- **Database access**: Be cautious with database operations; test queries carefully
- **Chinese text**: Maintain Chinese for UI elements, English for code/comments

83
CLAUDE.md Normal file
View File

@ -0,0 +1,83 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a **File System Browser** - a Python desktop application built with PySide6 that displays files and folders from a specified directory in a table format. The default target directory is `C://OEM` (Windows).
## Architecture
The application follows a simple two-layer architecture:
### Layer 1: File System Reading (`src/file_system.py`)
- **FileSystemReader**: Core utility class that handles OS-level file operations
- Reads directory contents using `pathlib.Path`
- Handles permission errors gracefully (skips inaccessible files)
- Returns sorted list of `FileInfo` objects (folders first, then alphabetically)
- Extracts file metadata: name, size, type, modification timestamp
- **FileInfo**: Data class that represents a single file/folder entry
- Includes `size_readable` property for human-readable file sizes (B, KB, MB, GB)
- Distinguishes between folders and files via `is_dir` flag
### Layer 2: PySide6 GUI (`src/main.py`)
- **FileSystemBrowser**: Main window that integrates file reading with UI display
- Initializes UI layout with path label, refresh button, table widget, and status bar
- Calls `FileSystemReader.get_files_and_folders()` to populate the table
- Renders file metadata in 4-column table: Name, Type, Size, Modified
- Visual feedback: folders displayed in blue, alternating row colors
- Status bar updates with total item count and current path
- `load_files()` method handles UI updates with exception handling
### Entry Point (`run.py`)
- Imports and runs the `main()` function from `src/main.py`
- Creates a `FileSystemBrowser` instance with default path `C://OEM`
## Common Commands
### Running the Application
```bash
python run.py
```
### Installing Dependencies
```bash
pip install -r requirements.txt
```
### Development Setup
```bash
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python run.py
```
## Key Design Decisions
1. **Path Handling**: Uses `pathlib.Path` for cross-platform compatibility (Windows, macOS, Linux)
2. **Error Handling**: Files/folders that cannot be read due to permissions are silently skipped rather than throwing exceptions
3. **Sorting**: Folders are displayed first, followed by files, all sorted alphabetically (case-insensitive)
4. **File Type Detection**: Uses file extension (suffix) or labels as "File" if no extension; folders labeled as "Folder"
5. **Size Display**: Folders show "—" for size since folder size isn't meaningful; files show converted size with unit
6. **UI Responsiveness**: `QApplication.processEvents()` called during `load_files()` to prevent UI freezing
## Modifying the Target Directory
To browse a different directory, change the `target_path` parameter in [run.py](run.py):
```python
window = FileSystemBrowser(target_path="Your/Custom/Path")
```
Or modify the default in [src/main.py:14](src/main.py#L14).
## Dependencies
- **PySide6 6.7.1**: Official Python bindings for Qt framework (GUI framework)
## Notes for Future Development
- The application currently only reads one directory level (non-recursive)
- File I/O is synchronous; large directories may cause brief UI pause during initial load
- No persistent state is saved between sessions

84
GEMINI.md Normal file
View File

@ -0,0 +1,84 @@
# Gemini Project Context
## Project Overview
This project is a Python-based desktop application utilizing the **PySide6** framework (Qt for Python).
Currently, the active application is an **Inspection Management System (检验管理系统)** designed for industrial PDA (handheld) devices. It allows users to view inspection records and initiate new inspections.
The project also contains legacy code for a **File System Browser**, which is currently inactive but preserved.
## Key Technologies
* **Language**: Python 3
* **GUI Framework**: PySide6
* **Fonts**: Microsoft YaHei (SimHei/微软雅黑) is the standard font.
## Architecture
The project is structured to separate business logic from UI components.
### Directory Structure
```text
readFileSystem/
├── src/
│ ├── inspection_app.py # MAIN LOGIC: Inspection app setup & mock data
│ ├── main.py # Entry hook (imports inspection_app)
│ ├── main_old.py # LEGACY: Old file browser entry point
│ └── file_system.py # LEGACY: File system reading logic
├── ui/
│ ├── inspection_window.py # Main Window (List view + Buttons)
│ └── inspection_card.py # Component: Single inspection record card
├── run.py # Application Entry Point
└── requirements.txt # Dependencies
```
### Components (Inspection App)
* **`src/inspection_app.py`**:
* Sets up the `QApplication`.
* Applies global fonts.
* Generates mock data for demonstration.
* Populates the window with `InspectionCard` widgets.
* **`ui/inspection_window.py`**:
* **Layout**: Vertical layout with a scrollable area for cards and a fixed bottom button bar.
* **Resolution**: Default `480x800` to simulate a phone/PDA screen.
* **Buttons**: "Incoming Inspection" (入检) and "Manual Inspection" (手检).
* **`ui/inspection_card.py`**:
* Displays detailed inspection data (Date, Inspector, Batch, Heat, Material, Spec, Weight) in a grid layout with borders.
## Building and Running
### Prerequisites
* Python 3.10+
* Install dependencies:
```bash
pip install -r requirements.txt
```
### Execution
To run the application:
```bash
python run.py
```
*Note: This script imports `src/main.py`, which currently launches the Inspection App.*
## Development Conventions
* **UI Design**:
* **Style**: Simple, high-contrast (Black/White main), Industrial style.
* **Buttons**: Blue background (`#0086fa`), White text.
* **Font**: Microsoft YaHei.
* **Borders**: Explicit black borders for data cards.
* **Language**:
* **Code**: All variables, comments, and internal logic must be in **English**.
* **User Interface**: All visible text (Labels, Buttons, Alerts) must be in **Chinese**.
* **Code Organization**:
* New UI components must go into `ui/`.
* Application logic resides in `src/`.
* Do not modify `run.py` logic unless changing the entry point mechanism.

78
README.md Normal file
View File

@ -0,0 +1,78 @@
# File System Browser
A Python application using PySide6 to browse and display files and folders from a specified directory (default: C://OEM).
## Features
- Display all files and folders in a table format
- Shows file name, type, size, and last modified date
- Folders are highlighted in blue
- Refresh button to reload the directory
- Handles permission errors gracefully
## Project Structure
```
readFileSystem/
├── src/
│ ├── __init__.py
│ ├── main.py # Main PySide6 application
│ └── file_system.py # File system reading module
├── run.py # Entry point
├── requirements.txt # Python dependencies
└── README.md # This file
```
## Installation
1. Create a virtual environment (optional but recommended):
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
2. Install dependencies:
```bash
pip install -r requirements.txt
```
## Usage
Run the application:
```bash
python run.py
```
### Modifying the Target Directory
To browse a different directory, edit the `target_path` parameter in `src/main.py`:
```python
window = FileSystemBrowser(target_path="C://Your/Path/Here")
```
## Components
### FileSystemReader (src/file_system.py)
- Reads files and folders from a specified path
- Provides file information (name, size, type, modified time)
- Handles permission errors gracefully
- Converts file sizes to human-readable format (B, KB, MB, GB)
### FileSystemBrowser (src/main.py)
- Main GUI window using PySide6
- Displays files in a table widget
- Shows file details: name, type, size, modified date
- Refresh functionality to reload the directory
## System Requirements
- Python 3.8+
- PySide6 6.7.1+
## Notes
- Folders are displayed in blue text for easy identification
- Folders appear first in the list, sorted alphabetically
- Files that cannot be read due to permissions are skipped
- The application works on Windows, macOS, and Linux

44
implementation_plan.md Normal file
View File

@ -0,0 +1,44 @@
# 实施方案 - 同步数值数据左对齐
## 背景
用户要求同步过来的数值数据需要左对齐。目前的 UI 界面中,数值数据(如化学元素含量、批次信息等)在某些地方可能是居中对齐的。
## 方案步骤
### 1. 修改 `ui/incoming_inspection_page.py`
- 修改 `create_input` 方法,为生成的 `QLineEdit` 设置 `Qt.AlignLeft | Qt.AlignVCenter`
- 修改 `update_ui_with_data` 中的样式设置(如果有影响)。
### 2. 修改 `ui/manual_inspection_page.py`
- 同样修改 `create_input` 方法,设置左对齐。
### 3. 修改 `ui/inspection_card.py`
- 调整 `add_cell` 方法。目前的 `add_cell` 统一设置了 `Qt.AlignCenter`
- 需要区分标头Header和内容Value。标头可以保持居中但内容Value应设置为左对齐。
## 修改细节
### Incoming/Manual Inspection Page
```python
def create_input(self, key):
inp = QLineEdit()
inp.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) # 显式设置左对齐
...
```
### Inspection Card
```python
def add_cell(text, row, col, row_span=1, col_span=1, is_header=False):
lbl = QLabel(text)
if is_header:
lbl.setAlignment(Qt.AlignCenter) # 标头居中
else:
lbl.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) # 数值左对齐
...
```
## 验证计划
- 启动应用程序。
- 进入“入检”和“手检”界面。
- 执行同步操作,观察元素数值是否左对齐。
- 观察主页面的卡片数据是否左对齐。

File diff suppressed because it is too large Load Diff

View File

View File

View File

@ -0,0 +1,358 @@
2026-01-14 15:39:29.704 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-14 15:39:29.704 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-14 15:39:29.704 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-14 15:39:29.704 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-14 15:39:29.704 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-14 15:39:29.704 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-14 15:39:29.705 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-14 15:40:47.568 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-14 15:40:47.569 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-14 15:40:47.569 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-14 15:40:47.569 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-14 15:40:47.569 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-14 15:40:47.569 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-14 15:40:47.569 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-14 16:36:35.837 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-14 16:36:35.838 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-14 16:36:35.839 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-14 16:36:35.839 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-14 16:36:35.839 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-14 16:36:35.839 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-14 16:36:35.839 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-14 16:53:58.019 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-14 16:53:58.020 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-14 16:53:58.020 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-14 16:53:58.020 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-14 16:53:58.020 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-14 16:53:58.020 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-14 16:53:58.020 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-14 22:32:53.105 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-14 22:32:53.106 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-14 22:32:53.106 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-14 22:32:53.106 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-14 22:32:53.106 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-14 22:32:53.106 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-14 22:32:53.106 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-14 22:40:11.990 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-14 22:40:11.991 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-14 22:40:11.991 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-14 22:40:11.991 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-14 22:40:11.991 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-14 22:40:11.991 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-14 22:40:11.991 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-14 22:59:43.883 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-14 22:59:43.885 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-14 22:59:43.885 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-14 22:59:43.885 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-14 22:59:43.885 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-14 22:59:43.885 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-14 22:59:43.885 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-16 14:23:45.397 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-16 14:23:45.398 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-16 14:23:45.398 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-16 14:23:45.398 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-16 14:23:45.398 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-16 14:23:45.398 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-16 14:23:45.398 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-16 14:24:23.058 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-16 14:24:23.059 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-16 14:24:23.059 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-16 14:24:23.059 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-16 14:24:23.059 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-16 14:24:23.059 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-16 14:24:23.059 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-16 14:25:59.439 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-16 14:25:59.440 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-16 14:25:59.440 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-16 14:25:59.440 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-16 14:25:59.440 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-16 14:25:59.440 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-16 14:25:59.440 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-16 15:25:16.423 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-16 15:25:16.425 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-16 15:25:16.425 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-16 15:25:16.425 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-16 15:25:16.425 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-16 15:25:16.425 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-16 15:25:16.425 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-17 09:14:54.443 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-17 09:14:54.444 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-17 09:14:54.444 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-17 09:14:54.444 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-17 09:14:54.444 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-17 09:14:54.444 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-17 09:14:54.444 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-17 09:14:54.534 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-17 09:14:54.535 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11d323380>
2026-01-17 09:14:54.535 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-17 09:14:54.536 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:14:54.539 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11bb98290>
2026-01-17 09:14:54.539 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-17 09:14:54.541 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:14:54.545 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1284b7c20>
2026-01-17 09:14:54.545 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:14:54.546 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x107c603e0>
2026-01-17 09:14:54.547 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:14:54.548 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:14:54.548 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11d323380>
2026-01-17 09:14:54.549 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:14:54.550 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1285ce750>
2026-01-17 09:14:54.550 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:14:54.551 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:14:54.551 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1288533e0>
2026-01-17 09:14:54.551 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:14:54.554 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11d323380>
2026-01-17 09:14:54.554 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:14:54.555 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:14:54.556 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:14:54.557 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:14:54.662 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.660 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-17 09:16:18.662 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-17 09:16:18.662 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-17 09:16:18.662 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-17 09:16:18.662 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-17 09:16:18.662 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-17 09:16:18.662 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-17 09:16:18.726 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-17 09:16:18.726 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11fb13530>
2026-01-17 09:16:18.727 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-17 09:16:18.727 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.731 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11bb4a690>
2026-01-17 09:16:18.732 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-17 09:16:18.732 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.740 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1290d92e0>
2026-01-17 09:16:18.740 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:16:18.740 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11db62990>
2026-01-17 09:16:18.741 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:16:18.742 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.744 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1284229f0>
2026-01-17 09:16:18.745 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:16:18.752 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11bb49b50>
2026-01-17 09:16:18.752 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:16:18.752 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.753 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x129093e90>
2026-01-17 09:16:18.754 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:16:18.755 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11daf2e10>
2026-01-17 09:16:18.755 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.755 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:16:18.756 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.758 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:16:18.830 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:17:25.154 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1289108f0>
2026-01-17 09:17:25.160 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:17:25.162 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:17:25.168 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x12914f4a0>
2026-01-17 09:17:25.169 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:17:25.175 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x129180d10>
2026-01-17 09:17:25.176 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:17:25.177 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:17:25.179 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x12899c4d0>
2026-01-17 09:17:25.179 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:17:25.189 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1291bb800>
2026-01-17 09:17:25.193 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:17:25.195 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:17:25.221 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x128c041a0>
2026-01-17 09:17:25.226 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:17:25.233 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:17:25.246 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:17:25.332 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:25.983 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-17 09:19:25.984 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-17 09:19:25.984 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-17 09:19:25.984 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-17 09:19:25.984 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-17 09:19:25.984 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-17 09:19:25.984 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-17 09:19:26.065 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-17 09:19:26.066 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x107490d70>
2026-01-17 09:19:26.066 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-17 09:19:26.067 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:26.073 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10a880560>
2026-01-17 09:19:26.074 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-17 09:19:26.074 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:26.077 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1074912e0>
2026-01-17 09:19:26.077 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:19:26.078 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10a9e7770>
2026-01-17 09:19:26.078 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:19:26.079 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:26.079 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x107490d70>
2026-01-17 09:19:26.080 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:19:26.083 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10aa4f890>
2026-01-17 09:19:26.083 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:19:26.084 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:26.085 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10a0ff860>
2026-01-17 09:19:26.085 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:19:26.086 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10aa4fa70>
2026-01-17 09:19:26.086 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 09:19:26.087 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:26.088 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:26.089 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 09:19:26.166 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.087 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-17 15:18:06.088 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-17 15:18:06.088 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-17 15:18:06.088 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-17 15:18:06.088 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-17 15:18:06.088 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-17 15:18:06.088 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-17 15:18:06.139 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-17 15:18:06.140 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x121433410>
2026-01-17 15:18:06.141 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-17 15:18:06.141 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.145 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x121e4ec60>
2026-01-17 15:18:06.145 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-17 15:18:06.146 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.150 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1219e3020>
2026-01-17 15:18:06.151 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 15:18:06.154 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1155ada30>
2026-01-17 15:18:06.154 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 15:18:06.156 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.158 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x121e1c980>
2026-01-17 15:18:06.158 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 15:18:06.159 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x121e4f0e0>
2026-01-17 15:18:06.160 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 15:18:06.163 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x121e4f680>
2026-01-17 15:18:06.163 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.163 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 15:18:06.165 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x121e4f4d0>
2026-01-17 15:18:06.165 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-17 15:18:06.167 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.168 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.170 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-17 15:18:06.346 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.105 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-18 01:04:06.106 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-18 01:04:06.106 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-18 01:04:06.106 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-18 01:04:06.106 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-18 01:04:06.106 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-18 01:04:06.106 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-18 01:04:06.163 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-18 01:04:06.164 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10f428890>
2026-01-18 01:04:06.165 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-18 01:04:06.165 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.167 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10f08a990>
2026-01-18 01:04:06.168 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-18 01:04:06.170 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.175 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10ec2fc20>
2026-01-18 01:04:06.175 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 01:04:06.179 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10eb73b30>
2026-01-18 01:04:06.179 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 01:04:06.182 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.183 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10ea9b320>
2026-01-18 01:04:06.183 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 01:04:06.186 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10f54f920>
2026-01-18 01:04:06.186 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 01:04:06.187 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10f4d9220>
2026-01-18 01:04:06.187 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.188 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 01:04:06.189 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10f54f290>
2026-01-18 01:04:06.190 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 01:04:06.190 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.191 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.191 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 01:04:06.231 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.554 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-18 14:49:46.555 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-18 14:49:46.555 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-18 14:49:46.555 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-18 14:49:46.555 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-18 14:49:46.555 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-18 14:49:46.555 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-18 14:49:46.601 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-18 14:49:46.602 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1158ca900>
2026-01-18 14:49:46.602 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-18 14:49:46.603 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.607 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x116ff17f0>
2026-01-18 14:49:46.608 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-18 14:49:46.609 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.612 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x116f5e3c0>
2026-01-18 14:49:46.612 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 14:49:46.616 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x112c31430>
2026-01-18 14:49:46.616 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 14:49:46.618 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.618 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x116ff08c0>
2026-01-18 14:49:46.618 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 14:49:46.619 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11703f3e0>
2026-01-18 14:49:46.619 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 14:49:46.621 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x107c0e090>
2026-01-18 14:49:46.621 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.621 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 14:49:46.624 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11703f6e0>
2026-01-18 14:49:46.625 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 14:49:46.626 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.627 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.627 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 14:49:46.699 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:37.745 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-18 22:39:37.746 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-18 22:39:37.746 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-18 22:39:37.746 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-18 22:39:37.746 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-18 22:39:37.746 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-18 22:39:37.746 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-18 22:39:37.833 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-18 22:39:37.834 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11aed8650>
2026-01-18 22:39:37.834 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-18 22:39:37.835 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:37.837 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x10649af90>
2026-01-18 22:39:37.838 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-18 22:39:37.838 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:37.842 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11a8cbe90>
2026-01-18 22:39:37.842 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 22:39:37.843 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x119f8df10>
2026-01-18 22:39:37.843 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 22:39:37.844 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:37.844 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11af1cad0>
2026-01-18 22:39:37.845 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 22:39:37.845 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11af4f320>
2026-01-18 22:39:37.845 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 22:39:37.846 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11a431d00>
2026-01-18 22:39:37.847 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:37.847 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 22:39:37.848 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x11af4f5c0>
2026-01-18 22:39:37.848 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-18 22:39:37.848 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:37.849 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:37.850 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-18 22:39:38.208 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-19 22:31:25.114 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-19 22:31:25.115 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-19 22:31:25.115 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-19 22:31:25.116 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-19 22:31:25.116 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-19 22:31:25.116 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-19 22:31:25.116 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-20 23:23:31.865 [DEBUG] mcp.server.lowlevel.server:162 - Initializing server 'doris-mcp-server'
2026-01-20 23:23:31.866 [DEBUG] mcp.server.lowlevel.server:301 - Registering handler for ListResourcesRequest
2026-01-20 23:23:31.866 [DEBUG] mcp.server.lowlevel.server:336 - Registering handler for ReadResourceRequest
2026-01-20 23:23:31.866 [DEBUG] mcp.server.lowlevel.server:432 - Registering handler for ListToolsRequest
2026-01-20 23:23:31.866 [DEBUG] mcp.server.lowlevel.server:512 - Registering handler for CallToolRequest
2026-01-20 23:23:31.866 [DEBUG] mcp.server.lowlevel.server:263 - Registering handler for PromptListRequest
2026-01-20 23:23:31.866 [DEBUG] mcp.server.lowlevel.server:285 - Registering handler for GetPromptRequest
2026-01-20 23:23:31.935 [DEBUG] mcp.server.lowlevel.server:668 - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-01-20 23:23:31.936 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x13079e3f0>
2026-01-20 23:23:31.936 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListToolsRequest
2026-01-20 23:23:31.937 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-20 23:23:31.940 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1051ee330>
2026-01-20 23:23:31.941 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type ListPromptsRequest
2026-01-20 23:23:31.941 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-20 23:23:31.945 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x13076cb60>
2026-01-20 23:23:31.946 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-20 23:23:31.948 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1338053a0>
2026-01-20 23:23:31.949 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-20 23:23:31.951 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-20 23:23:31.954 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x130949430>
2026-01-20 23:23:31.954 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-20 23:23:31.956 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x133853170>
2026-01-20 23:23:31.957 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-20 23:23:31.959 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x104c7bbf0>
2026-01-20 23:23:31.959 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-20 23:23:31.959 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-20 23:23:31.962 [DEBUG] mcp.server.lowlevel.server:668 - Received message: <mcp.shared.session.RequestResponder object at 0x1338535f0>
2026-01-20 23:23:31.963 [DEBUG] mcp.server.lowlevel.server:716 - Dispatching request of type GetPromptRequest
2026-01-20 23:23:31.964 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-20 23:23:31.966 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-20 23:23:31.975 [DEBUG] mcp.server.lowlevel.server:783 - Response sent
2026-01-20 23:23:31.990 [DEBUG] mcp.server.lowlevel.server:783 - Response sent

View File

@ -0,0 +1,175 @@
2026-01-14 15:39:29.705 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-14 15:39:29.705 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-14 15:39:29.705 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-14 15:39:29.705 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-14 15:39:29.705 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-14 15:40:47.570 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-14 15:40:47.570 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-14 15:40:47.570 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-14 15:40:47.570 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-14 15:40:47.570 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-14 16:36:35.839 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-14 16:36:35.840 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-14 16:36:35.840 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-14 16:36:35.840 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-14 16:36:35.840 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-14 16:53:58.021 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-14 16:53:58.021 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-14 16:53:58.021 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-14 16:53:58.021 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-14 16:53:58.021 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-14 22:32:53.107 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-14 22:32:53.107 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-14 22:32:53.107 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-14 22:32:53.107 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-14 22:32:53.107 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-14 22:40:11.992 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-14 22:40:11.992 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-14 22:40:11.992 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-14 22:40:11.992 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-14 22:40:11.992 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-14 22:59:43.887 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2013, 'Lost connection to MySQL server during query')
2026-01-14 22:59:43.887 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-14 22:59:43.888 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-14 22:59:43.888 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-14 22:59:43.888 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-16 14:23:45.399 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-16 14:23:45.399 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-16 14:23:45.399 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-16 14:23:45.399 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-16 14:23:45.399 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-16 14:24:23.061 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-16 14:24:23.062 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-16 14:24:23.062 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-16 14:24:23.062 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-16 14:24:23.062 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-16 14:25:59.441 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-16 14:25:59.441 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-16 14:25:59.441 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-16 14:25:59.441 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-16 14:25:59.441 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-16 15:25:16.426 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-16 15:25:16.426 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-16 15:25:16.426 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-16 15:25:16.426 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-16 15:25:16.426 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-17 09:14:54.547 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:14:54.549 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:14:54.550 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:14:54.552 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:14:54.554 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:16:18.741 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:16:18.749 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:16:18.752 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:16:18.754 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:16:18.755 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:17:25.179 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:17:25.194 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:17:25.227 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:19:26.078 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:19:26.080 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:19:26.083 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:19:26.085 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 09:19:26.087 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 15:18:06.154 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 15:18:06.159 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 15:18:06.160 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 15:18:06.163 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-17 15:18:06.166 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 01:04:06.180 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 01:04:06.183 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 01:04:06.186 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 01:04:06.188 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 01:04:06.190 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 14:49:46.617 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 14:49:46.619 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 14:49:46.620 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 14:49:46.621 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 14:49:46.625 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 22:39:37.843 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 22:39:37.845 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 22:39:37.846 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 22:39:37.847 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-18 22:39:37.848 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-19 10:06:27.229 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:06:30.236 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:06:32.243 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:06:34.245 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:06:34.250 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:07:04.252 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:07:04.254 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:07:06.257 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:07:08.259 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:07:08.260 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:07:26.180 [ERROR] doris_mcp_server.utils.db:767 - Stale connection cleanup error: 'NoneType' object has no attribute 'size'
2026-01-19 10:07:38.262 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:07:38.263 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:07:40.267 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:07:42.272 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:07:42.274 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:08:12.277 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:08:12.283 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:08:14.285 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:08:16.288 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:08:16.288 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:08:26.183 [ERROR] doris_mcp_server.utils.db:767 - Stale connection cleanup error: 'NoneType' object has no attribute 'size'
2026-01-19 10:08:46.268 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:08:46.284 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:08:48.283 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:08:50.284 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:08:50.284 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:09:20.270 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:09:20.275 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:09:22.279 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:09:24.281 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:09:24.281 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:09:26.139 [ERROR] doris_mcp_server.utils.db:767 - Stale connection cleanup error: 'NoneType' object has no attribute 'size'
2026-01-19 10:09:54.286 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:09:54.290 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:09:56.292 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:09:58.298 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:09:58.298 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:10:26.138 [ERROR] doris_mcp_server.utils.db:767 - Stale connection cleanup error: 'NoneType' object has no attribute 'size'
2026-01-19 10:10:28.304 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:10:28.307 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:10:30.310 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:10:32.312 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:10:32.312 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:11:02.315 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:11:02.318 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:11:04.323 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:11:06.329 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:11:06.330 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:11:26.140 [ERROR] doris_mcp_server.utils.db:767 - Stale connection cleanup error: 'NoneType' object has no attribute 'size'
2026-01-19 10:11:36.332 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:11:36.333 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:11:38.336 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:11:40.337 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:11:40.338 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:12:10.339 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:12:10.339 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:12:12.341 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:12:14.345 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:12:14.345 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:12:26.141 [ERROR] doris_mcp_server.utils.db:767 - Stale connection cleanup error: 'NoneType' object has no attribute 'size'
2026-01-19 10:12:44.346 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:12:44.347 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:12:46.349 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:12:48.352 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:12:48.352 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:13:18.353 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: 'NoneType' object has no attribute 'acquire'
2026-01-19 10:13:18.361 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:13:20.362 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:13:22.364 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 10:13:22.366 [ERROR] doris_mcp_server.utils.db:858 - ❌ Pool recovery failed after all attempts
2026-01-19 10:13:26.142 [ERROR] doris_mcp_server.utils.db:767 - Stale connection cleanup error: 'NoneType' object has no attribute 'size'
2026-01-19 22:31:25.116 [ERROR] doris_mcp_server.utils.db:638 - Pool health test failed: (2003, "Can't connect to MySQL server on '127.0.0.1'")
2026-01-19 22:31:25.117 [ERROR] doris_mcp_server.utils.db:626 - Failed to initialize connection pool: Connection pool health check failed
2026-01-19 22:31:25.117 [ERROR] doris_mcp_server.main.DorisServer:490 - stdio server startup failed: Connection pool health check failed
2026-01-19 22:31:25.117 [ERROR] doris_mcp_server.main.DorisServer:491 - Error type: <class 'RuntimeError'>
2026-01-19 22:31:25.117 [ERROR] doris_mcp_server.main:929 - Server runtime error: Connection pool health check failed
2026-01-20 23:23:31.949 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-20 23:23:31.955 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-20 23:23:31.957 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-20 23:23:31.960 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data
2026-01-20 23:23:31.963 [ERROR] root:137 - Query execution failed: readexactly() called while another coroutine is already waiting for incoming data

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
2026-01-19 10:06:27.232 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:06:30.234 [WARNING] doris_mcp_server.utils.db:795 - Pool close timeout, forcing cleanup
2026-01-19 10:06:30.238 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:06:32.243 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:06:34.246 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:07:04.253 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:07:04.254 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:07:06.257 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:07:08.260 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:07:38.262 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:07:38.265 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:07:40.268 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:07:42.274 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:08:12.280 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:08:12.283 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:08:14.286 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:08:16.288 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:08:46.275 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:08:46.284 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:08:48.284 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:08:50.284 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:09:20.273 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:09:20.276 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:09:22.279 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:09:24.281 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:09:54.287 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:09:54.290 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:09:56.292 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:09:58.298 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:10:28.306 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:10:28.308 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:10:30.311 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:10:32.312 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:11:02.317 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:11:02.318 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:11:04.323 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:11:06.329 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:11:36.332 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:11:36.334 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:11:38.336 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:11:40.338 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:12:10.339 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:12:10.339 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:12:12.342 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:12:14.345 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:12:44.346 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:12:44.347 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:12:46.350 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:12:48.352 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3
2026-01-19 10:13:18.357 [WARNING] doris_mcp_server.utils.db:718 - ❌ Pool health check failed, attempting recovery
2026-01-19 10:13:18.361 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 1
2026-01-19 10:13:20.362 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 2
2026-01-19 10:13:22.365 [WARNING] doris_mcp_server.utils.db:834 - ❌ Pool recovery health check failed on attempt 3

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
PySide6>=6.7.0
psycopg2-binary>=2.9.0
openpyxl>=3.1.0
pywin32

0
requirments.txt Normal file
View File

11
run.py Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python3
"""
File System Browser - Entry point
Reads files and folders from a specified directory and displays them in a GUI
"""
from src.main import main
if __name__ == "__main__":
main()

0
src/__init__.py Normal file
View File

432
src/db_manager.py Normal file
View File

@ -0,0 +1,432 @@
import psycopg2
from psycopg2.extras import RealDictCursor
import datetime
import uuid
class DatabaseManager:
def __init__(self):
self.config = {
"host": "10.10.200.180",
"port": "5432",
"database": "tz_tzwork",
"user": "tzwork",
"password": "tzwork",
}
self.schema = '"tzwork"'
def get_connection(self):
try:
conn = psycopg2.connect(**self.config)
return conn
except Exception as e:
print(f"Database connection error: {e}")
return None
def insert_analysis_data(self, data_list):
conn = self.get_connection()
if not conn:
return False, "Database connection failed"
try:
cur = conn.cursor()
insert_query = f"""
INSERT INTO {self.schema}.element_analysis_data (
sample_name, sample_grade_id, sample_grade_alias, operator_name, instrument, method_name, sample_type,
replicate_no, measure_datetime, measure_duration, check_type, check_status, grade_name, base_element, rsd_check,
element_name, element_type, concentration_value, concentration_unit, std_dev_value,
uncertainty_abs, uncertainty_rel, lower_acceptance_limit, upper_acceptance_limit,
result_status, calibration_status, acceptance_status, is_deleted,
xml_version, xml_creation_datetime, data_source
) VALUES (
%(sample_name)s, %(sample_grade_id)s, %(sample_grade_alias)s, %(operator_name)s, %(instrument)s, %(method_name)s, %(sample_type)s,
%(replicate_no)s, %(measure_datetime)s, %(measure_duration)s, %(check_type)s, %(check_status)s, %(grade_name)s, %(base_element)s, %(rsd_check)s,
%(element_name)s, %(element_type)s, %(concentration_value)s, %(concentration_unit)s, %(std_dev_value)s,
%(uncertainty_abs)s, %(uncertainty_rel)s, %(lower_acceptance_limit)s, %(upper_acceptance_limit)s,
%(result_status)s, %(calibration_status)s, %(acceptance_status)s, %(is_deleted)s,
%(xml_version)s, %(xml_creation_datetime)s, %(data_source)s
)
"""
cur.executemany(insert_query, data_list)
conn.commit()
cur.close()
conn.close()
return True, f"Successfully inserted {len(data_list)} records."
except Exception as e:
if conn:
conn.rollback()
conn.close()
return False, str(e)
def authenticate_user(self, user_id, password_md5):
conn = self.get_connection()
if not conn:
return False, "数据库连接失败"
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
query = f"""
SELECT user_id, nick_name, password, org_id as data_corp, dept_id
FROM {self.schema}.sys_user
WHERE user_id = %s
"""
cur.execute(query, (user_id,))
user = cur.fetchone()
cur.close()
conn.close()
if not user:
return False, "账号不存在"
if user['password'] != password_md5:
return False, "密码错误"
return True, {
"user_id": user['user_id'],
"nick_name": user['nick_name'],
"data_corp": user['data_corp'],
"dept_id": user['dept_id']
}
except Exception as e:
print(f"Authentication error: {e}")
if conn:
conn.close()
return False, f"认证失败: {str(e)}"
def get_latest_sample_data(self):
"""
Retrieves the most recently measured sample data, flattened.
Returns a dictionary where keys are element names and values are concentrations.
Also returns metadata.
"""
conn = self.get_connection()
if not conn:
return None
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
query = f"""
SELECT *
FROM {self.schema}.element_analysis_data
WHERE measure_datetime = (
SELECT MAX(measure_datetime) FROM {self.schema}.element_analysis_data
)
"""
cur.execute(query)
rows = cur.fetchall()
cur.close()
conn.close()
if not rows:
return {}
result = {
"metadata": {
"sample_name": rows[0]['sample_name'],
"grade_id": rows[0]['sample_grade_id'],
"heat_no": rows[0]['sample_name'],
"batch_no": rows[0]['sample_grade_id'],
},
"elements": {}
}
for row in rows:
el_name = row['element_name']
conc = row['concentration_value']
result["elements"][el_name] = conc
return result
except Exception as e:
print(f"Query error: {e}")
if conn:
conn.close()
return None
def _get_check_flag(self, check_information):
"""
根据检验信息判断检验标志
空值None'无标准值' 视为合格其他视为不合格
"""
if not check_information or check_information == '无标准值':
return "合格"
return "不合格"
def insert_inspection_data(self, inspection_data):
"""
插入检验数据到 e_pz_hjqy_import
inspection_data 结构: {
"gch": "工程号",
"heat_number": "炉号1",
"heat_number2": "炉号2",
"cz": "炉号1材质",
"cz2": "炉号2材质",
"check_information": "检验信息1",
"check_information2": "检验信息2",
"cd": "产地",
"size": "规格",
"elements": {"C": 0.15, "Si": 0.25, ...},
"user_id": "用户ID",
"dept_id": "部门ID",
"data_corp": "所属公司"
}
"""
conn = self.get_connection()
if not conn:
return False, "数据库连接失败"
try:
cur = conn.cursor()
# 生成 UUID
ksid = str(uuid.uuid4())
# 获取当前日期和时间
current_date = datetime.datetime.now().strftime("%Y-%m-%d")
current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 准备元素数据(转换为小写字段名)
elements = inspection_data.get("elements", {})
element_values = {
"c": elements.get("C"),
"si": elements.get("Si"),
"mn": elements.get("Mn"),
"p": elements.get("P"),
"s": elements.get("S"),
"cr": elements.get("Cr"),
"ni": elements.get("Ni"),
"mo": elements.get("Mo"),
"cu": elements.get("Cu"),
"ti": elements.get("Ti"),
"nb": elements.get("Nb"),
"v": elements.get("V"),
"al": elements.get("Al"),
"w": elements.get("W"),
"co": elements.get("Co"),
"fe": elements.get("Fe"),
"n": elements.get("N"),
"se": elements.get("Se")
}
# 插入查询
insert_query = f"""
INSERT INTO {self.schema}.e_pz_hjqy_import (
ksid, gch, flag, rq, dept, cd, cz, cz2, size, heat_number, heat_number2,
c, si, mn, p, s, cr, ni, mo, cu, ti, nb, v, al, w, co, fe, n, se,
check_flag,check_flag2, check_information, check_information2, data_man, data_date, data_corp, stype
) VALUES (
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s
)
"""
values = (
ksid,
inspection_data.get("gch"),
'登记',
current_date,
inspection_data.get("dept_id"),
inspection_data.get("cd"),
inspection_data.get("cz"),
inspection_data.get("cz2"),
inspection_data.get("size"),
inspection_data.get("heat_number"),
inspection_data.get("heat_number2"),
element_values["c"],
element_values["si"],
element_values["mn"],
element_values["p"],
element_values["s"],
element_values["cr"],
element_values["ni"],
element_values["mo"],
element_values["cu"],
element_values["ti"],
element_values["nb"],
element_values["v"],
element_values["al"],
element_values["w"],
element_values["co"],
element_values["fe"],
element_values["n"],
element_values["se"],
self._get_check_flag(inspection_data.get("check_information")),
self._get_check_flag(inspection_data.get("check_information2")),
inspection_data.get("check_information"),
inspection_data.get("check_information2"),
inspection_data.get("user_id"),
current_datetime,
inspection_data.get("data_corp"),
"首检"
)
cur.execute(insert_query, values)
conn.commit()
cur.close()
conn.close()
return True, f"成功插入检验数据记录ID: {ksid}"
except Exception as e:
if conn:
conn.rollback()
conn.close()
return False, f"插入失败: {str(e)}"
def query_by_gch(self, gch):
"""
根据工程号查询产地规格炉号材质等信息
"""
conn = self.get_connection()
if not conn:
return None, "数据库连接失败"
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
query = f"""
SELECT
concat(a.cd,'/',a.cz) as cd,
a.tsize as size,
coalesce(b.qy_luonot, c.qy_luonot) as heat_number,
coalesce(b.qy_luonow, c.qy_luonow) as heat_number2,
coalesce(b.qy_czt, c.qy_czt) as qy_czt,
coalesce(b.qy_czw, c.qy_czw) as qy_czw
FROM {self.schema}.v_sc_gclist a
LEFT JOIN {self.schema}.e_pz_hjqy b ON a.bgc = b.qy_gcht
LEFT JOIN {self.schema}.e_pz_hjqy c ON a.bgc = c.qy_gchw
WHERE a.bgc = %s
LIMIT 1
"""
cur.execute(query, (gch,))
row = cur.fetchone()
cur.close()
conn.close()
if not row:
return None, "未找到该工程号"
return row, None
except Exception as e:
if conn:
conn.close()
return None, f"查询失败: {str(e)}"
def get_inspection_list(self, limit=10, offset=0, user_id=''):
"""
分页获取检验数据列表
"""
conn = self.get_connection()
if not conn:
return None, "数据库连接失败"
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
# 根据是否提供 user_id 构建不同的查询
if user_id:
query = f"""
SELECT
to_char(rq,'yyyy-MM-dd') as date,
gch as batch_no,
data_man,
su.nick_name as inspector,
heat_number,
heat_number2,
cz,
cz2,
size as spec
FROM {self.schema}.e_pz_hjqy_import t
JOIN {self.schema}.sys_user su ON t.data_man = su.user_id
WHERE su.user_id = %s
ORDER BY rq DESC
LIMIT %s OFFSET %s
"""
cur.execute(query, (user_id, limit, offset))
else:
query = f"""
SELECT
to_char(rq,'yyyy-MM-dd') as date,
gch as batch_no,
data_man,
su.nick_name as inspector,
heat_number,
heat_number2,
cz,
cz2,
size as spec
FROM {self.schema}.e_pz_hjqy_import t
LEFT JOIN {self.schema}.sys_user su ON t.data_man = su.user_id
ORDER BY rq DESC
LIMIT %s OFFSET %s
"""
cur.execute(query, (limit, offset))
rows = cur.fetchall()
cur.close()
conn.close()
return rows, None
except Exception as e:
if conn:
conn.close()
return None, f"查询失败: {str(e)}"
def query_standard_range(self, heat_number, heat_number2):
"""
根据炉号1(tph)和炉号2(company_ph)查询标准值范围
返回两条记录炉号1的标准范围和炉号2的标准范围
"""
conn = self.get_connection()
if not conn:
return None, None, "数据库连接失败"
try:
cur = conn.cursor(cursor_factory=RealDictCursor)
# 只选取18个元素的范围字段: C, Si, Mn, P, S, Cr, Ni, Mo, Cu, Ti, Nb, V, Al, W, Co, N, Fe, Se
select_clause = """
SELECT c.tph, c.company_ph,
b.cmin, c_max as cmax, simin, simax, mnmin, mnmax, pmin, pmax, smin, smax,
crmin, crmax, nimin, nimax, momin, momax, cumin, cumax, timin, timax,
nbmin, nbmax, vmin, vmax, almin, almax, wmin, wmax, comin, comax,
nmin, nmax, femin, femax, semin, semax
FROM {schema}.e_pz_lhcf c,
{schema}.e_pz_bzcs b
WHERE c.relevancy_note = b.note
AND (c.tph = %s OR c.company_ph = %s)
""".format(schema=self.schema)
heat1_range = None
heat2_range = None
# 查询炉号1标准
if heat_number:
cur.execute(select_clause, (heat_number, heat_number))
heat1_range = cur.fetchone()
# 查询炉号2标准
if heat_number2:
cur.execute(select_clause, (heat_number2, heat_number2))
heat2_range = cur.fetchone()
cur.close()
conn.close()
return heat1_range, heat2_range, None
except Exception as e:
if conn:
conn.close()
return None, None, f"查询失败: {str(e)}"

84
src/file_system.py Normal file
View File

@ -0,0 +1,84 @@
import os
from pathlib import Path
from dataclasses import dataclass
from typing import List
from datetime import datetime
@dataclass
class FileInfo:
"""Data class to represent file information"""
name: str
path: str
size: int
file_type: str
is_dir: bool
modified_time: str
@property
def size_readable(self) -> str:
"""Convert size to human-readable format"""
for unit in ['B', 'KB', 'MB', 'GB']:
if self.size < 1024:
return f"{self.size:.2f} {unit}"
self.size /= 1024
return f"{self.size:.2f} TB"
class FileSystemReader:
"""Handles reading file system information"""
def __init__(self, root_path: str):
self.root_path = Path(root_path)
def get_files_and_folders(self) -> List[FileInfo]:
"""
Read all files and folders from the root path
Returns:
List[FileInfo]: List of file information objects
"""
items = []
if not self.root_path.exists():
return items
try:
for item in self.root_path.iterdir():
try:
stat_info = item.stat()
file_info = FileInfo(
name=item.name,
path=str(item),
size=stat_info.st_size,
file_type=self._get_file_type(item),
is_dir=item.is_dir(),
modified_time=datetime.fromtimestamp(
stat_info.st_mtime
).strftime("%Y-%m-%d %H:%M:%S")
)
items.append(file_info)
except (PermissionError, OSError):
# Skip files/folders we can't read
continue
# Sort: folders first, then by name
items.sort(key=lambda x: (not x.is_dir, x.name.lower()))
return items
except PermissionError:
return items
def _get_file_type(self, path: Path) -> str:
"""
Determine file type
Args:
path: Path object
Returns:
str: File type (Folder, or extension)
"""
if path.is_dir():
return "Folder"
return path.suffix[1:].upper() if path.suffix else "File"

103
src/inspection_app.py Normal file
View File

@ -0,0 +1,103 @@
import sys
from PySide6.QtWidgets import QApplication
from ui.inspection_window import InspectionWindow
from ui.login_window import LoginWindow
from src.session_manager import SessionManager
from datetime import datetime
def get_mock_data():
return [
{
"date": "2025-12-18",
"inspector": "张三",
"batch_no": "B20251218-001",
"heat_no": "H88291",
"material": "Q235B",
"spec": "φ20",
"weight": "1500kg"
},
{
"date": "2025-12-18",
"inspector": "李四",
"batch_no": "B20251218-002",
"heat_no": "H88292",
"material": "HRB400",
"spec": "φ25",
"weight": "2300kg"
},
{
"date": "2025-12-17",
"inspector": "王五",
"batch_no": "B20251217-005",
"heat_no": "H88280",
"material": "Q345",
"spec": "10x10",
"weight": "500kg"
},
{
"date": "2025-12-16",
"inspector": "赵六",
"batch_no": "B20251216-010",
"heat_no": "H88275",
"material": "304",
"spec": "2mm",
"weight": "120kg"
},
{
"date": "2025-12-16",
"inspector": "赵六",
"batch_no": "B20251216-010",
"heat_no": "H88275",
"material": "304",
"spec": "2mm",
"weight": "120kg"
},
{
"date": "2025-12-16",
"inspector": "赵六",
"batch_no": "B20251216-010",
"heat_no": "H88275",
"material": "304",
"spec": "2mm",
"weight": "120kg"
},
{
"date": "2025-12-16",
"inspector": "赵六",
"batch_no": "B20251216-010",
"heat_no": "H88275",
"material": "304",
"spec": "2mm",
"weight": "120kg"
}
]
def main():
app = QApplication(sys.argv)
font = app.font()
font.setFamily("Microsoft YaHei")
font.setPointSize(10) # Base size
app.setFont(font)
session = SessionManager()
login_window = LoginWindow()
def on_login_success(user_info):
session.set_current_user(user_info)
window = InspectionWindow()
# 使用新的分页加载方式
window.load_initial_data()
window.showMaximized()
login_window.login_success.connect(on_login_success)
login_window.showMaximized()
sys.exit(app.exec())
if __name__ == "__main__":
main()

4
src/main.py Normal file
View File

@ -0,0 +1,4 @@
from src.inspection_app import main
if __name__ == "__main__":
main()

123
src/main_old.py Normal file
View File

@ -0,0 +1,123 @@
import sys
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QLabel, QPushButton, QHeaderView
)
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QIcon, QColor
from src.file_system import FileSystemReader, FileInfo
class FileSystemBrowser(QMainWindow):
"""Main application window for file system browser"""
def __init__(self, target_path: str = "C://OEM"):
super().__init__()
self.target_path = target_path
self.file_reader = FileSystemReader(target_path)
self.init_ui()
self.load_files()
def init_ui(self):
"""Initialize the user interface"""
self.setWindowTitle("File System Browser")
# Central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(10, 10, 10, 10)
main_layout.setSpacing(10)
# Header section
header_layout = QHBoxLayout()
self.path_label = QLabel(f"Path: {self.target_path}")
self.path_label.setStyleSheet("font-weight: bold; font-size: 12px;")
header_layout.addWidget(self.path_label)
header_layout.addStretch()
# Refresh button
refresh_button = QPushButton("Refresh")
refresh_button.clicked.connect(self.load_files)
header_layout.addWidget(refresh_button)
main_layout.addLayout(header_layout)
# Table widget
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["Name", "Type", "Size", "Modified"])
# Set column resize modes for better adaptive layout
header = self.table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.Stretch) # Name column stretches
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # Type column fits content
header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # Size column fits content
header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # Modified column fits content
self.table.setAlternatingRowColors(True)
self.table.setSelectionBehavior(QTableWidget.SelectRows)
main_layout.addWidget(self.table)
# Status bar
self.status_label = QLabel("Ready")
self.status_label.setStyleSheet("padding: 5px;")
main_layout.addWidget(self.status_label)
# Set window to maximize on startup
self.showMaximized()
def load_files(self):
"""Load and display files and folders"""
self.status_label.setText("Loading...")
QApplication.processEvents()
try:
files = self.file_reader.get_files_and_folders()
# Clear existing rows
self.table.setRowCount(0)
# Populate table
for file_info in files:
row_position = self.table.rowCount()
self.table.insertRow(row_position)
# Name column
name_item = QTableWidgetItem(file_info.name)
if file_info.is_dir:
name_item.setForeground(QColor(0, 0, 255))
self.table.setItem(row_position, 0, name_item)
# Type column
type_item = QTableWidgetItem(file_info.file_type)
self.table.setItem(row_position, 1, type_item)
# Size column
size_text = "" if file_info.is_dir else file_info.size_readable
size_item = QTableWidgetItem(size_text)
size_item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.table.setItem(row_position, 2, size_item)
# Modified time column
time_item = QTableWidgetItem(file_info.modified_time)
self.table.setItem(row_position, 3, time_item)
self.status_label.setText(
f"Total items: {len(files)} | Path: {self.target_path}"
)
except Exception as e:
self.status_label.setText(f"Error: {str(e)}")
def main():
app = QApplication(sys.argv)
window = FileSystemBrowser()
# Window will be maximized in init_ui, but show() is still needed
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

40
src/session_manager.py Normal file
View File

@ -0,0 +1,40 @@
class SessionManager:
_instance = None
_current_user = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(SessionManager, cls).__new__(cls)
return cls._instance
def set_current_user(self, user_info):
self._current_user = user_info
def get_current_user(self):
return self._current_user
def get_user_id(self):
if self._current_user:
return self._current_user.get("user_id")
return None
def get_nick_name(self):
if self._current_user:
return self._current_user.get("nick_name")
return None
def get_dept_id(self):
if self._current_user:
return self._current_user.get("dept_id")
return None
def get_data_corp(self):
if self._current_user:
return self._current_user.get("data_corp")
return None
def clear_session(self):
self._current_user = None
def is_logged_in(self):
return self._current_user is not None

128
src/xml_parser.py Normal file
View File

@ -0,0 +1,128 @@
import xml.etree.ElementTree as ET
import os
class XmlParser:
def parse_file(self, file_path):
if not os.path.exists(file_path):
return None, f"文件未找到: {file_path}"
try:
tree = ET.parse(file_path)
root = tree.getroot()
# Metadata from root
xml_version = root.get('XMLVersion')
xml_creation_datetime = root.get('XMLCreationDateTime')
data_source = os.path.basename(file_path)
# SampleResult (assuming single SampleResult for now based on snippet)
sample_result = root.find('SampleResult')
if sample_result is None:
return None, "没有找到 SampleResult 标签"
# Common Sample Info
sample_info = {
"sample_name": sample_result.get('Name'),
"operator_name": sample_result.get('OperatorName'),
"instrument": sample_result.get('Instrument'),
"method_name": sample_result.get('MethodName'),
"sample_type": sample_result.get('Type'),
"xml_version": xml_version,
"xml_creation_datetime": xml_creation_datetime,
"data_source": data_source
}
# SampleIDs
sample_ids_node = sample_result.find('SampleIDs')
if sample_ids_node:
for sid in sample_ids_node.findall('SampleID'):
id_name = sid.find('IDName').text
id_value = sid.find('IDValue').text
if id_name == 'Grade ID':
sample_info['sample_grade_id'] = id_value
elif id_name == 'Grade Alias':
sample_info['sample_grade_alias'] = id_value
extracted_data = []
# MeasurementReplicates
replicates_node = sample_result.find('MeasurementReplicates')
if replicates_node:
for rep in replicates_node.findall('MeasurementReplicate'):
rep_info = sample_info.copy()
rep_info['replicate_no'] = rep.get('No')
rep_info['measure_datetime'] = rep.get('MeasureDateTime')
rep_info['is_deleted'] = rep.get('IsDeleted')
measurement = rep.find('Measurement')
if measurement:
rep_info['check_type'] = measurement.get('CheckType')
rep_info['check_status'] = measurement.get('CheckStatus')
rep_info['grade_name'] = measurement.get('GradeName')
rep_info['rsd_check'] = measurement.get('RsdCheck')
ext_data_meas = measurement.find('ExtData')
if ext_data_meas:
base_el = ext_data_meas.find('Base')
if base_el is not None:
rep_info['base_element'] = base_el.text
duration_el = ext_data_meas.find('MeasDuration')
if duration_el is not None:
rep_info['measure_duration'] = duration_el.text
# Elements
elements_node = measurement.find('Elements')
if elements_node:
for el in elements_node.findall('Element'):
el_data = rep_info.copy()
el_data['element_name'] = el.get('ElementName')
el_data['element_type'] = el.get('Type')
el_data['concentration_unit'] = el.get('DefaultConcentrationUnit')
# Concentration Result
conc_res = el.find("./ElementResult[@Kind='Concentration'][@StatType='None']")
if conc_res:
el_data['concentration_value'] = conc_res.find('ResultValue').text
el_data['result_status'] = conc_res.get('Status')
el_data['calibration_status'] = conc_res.get('CalibrationStatus')
el_data['acceptance_status'] = conc_res.get('AcceptanceStatus')
# Limits
limits = conc_res.find('ResultValueLimits')
if limits:
lower = limits.find("./ResultValueLimit[@Type='LowerAcceptanceLimit']")
upper = limits.find("./ResultValueLimit[@Type='UpperAcceptanceLimit']")
el_data['lower_acceptance_limit'] = lower.text if lower is not None else None
el_data['upper_acceptance_limit'] = upper.text if upper is not None else None
# Uncertainty
ext_res = conc_res.find('ExtData')
if ext_res:
unc_abs = ext_res.find("./ResultValue[@SubType='UncertaintyAbs']")
unc_rel = ext_res.find("./ResultValue[@SubType='UncertaintyRel']")
el_data['uncertainty_abs'] = unc_abs.text if unc_abs is not None else None
el_data['uncertainty_rel'] = unc_rel.text if unc_rel is not None else None
std_res = el.find("./ElementResult[@StatType='StdDev']")
if std_res:
el_data['std_dev_value'] = std_res.find('ResultValue').text
# 如果为空就用None填充
required_keys = [
'sample_grade_id', 'sample_grade_alias', 'base_element',
'measure_duration', 'check_type', 'check_status', 'grade_name', 'rsd_check',
'concentration_value', 'std_dev_value', 'uncertainty_abs',
'uncertainty_rel', 'lower_acceptance_limit', 'upper_acceptance_limit',
'result_status', 'calibration_status', 'acceptance_status'
]
for key in required_keys:
if key not in el_data:
el_data[key] = None
extracted_data.append(el_data)
return extracted_data, "Success"
except Exception as e:
import traceback
traceback.print_exc()
return None, f"Parse error: {str(e)}"

11
task.md Normal file
View File

@ -0,0 +1,11 @@
# 任务:调整同步数值数据为左对齐
## 待完成事项
- [x] 修改 `ui/incoming_inspection_page.py` 中的输入框对齐方式为左对齐
- [x] 修改 `ui/manual_inspection_page.py` 中的输入框对齐方式为左对齐
- [x] 修改 `ui/inspection_card.py` 中的数值显示为左对齐
- [x] 验证修改效果
- [x] 恢复 `ui/incoming_inspection_page.py` 中的 "检验信息" 标签命名
- [x] 调整布局炉号1后跟检验信息1炉号2后跟检验信息2信息输入框跨列显示
## 当前状态
- 入检界面布局已调整为交错式(炉号-信息),且检验信息输入框占据整行剩余空间。

0
ui/__init__.py Normal file
View File

311
ui/base_page.py Normal file
View File

@ -0,0 +1,311 @@
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QMessageBox, QDialog, QFileDialog
)
from PySide6.QtCore import Qt, QSettings
import os
import glob
from src.db_manager import DatabaseManager
from src.xml_parser import XmlParser
from utils.readMTP import MTPHandler
from src.session_manager import SessionManager
class BaseInspectionPage(QDialog):
def __init__(self, title, parent=None):
super().__init__(parent)
self.setWindowTitle(title)
self.setWindowFlags(self.windowFlags() | Qt.Window | Qt.WindowMaximizeButtonHint)
self.settings = QSettings("Tengzhi", "InspectionApp")
self.mtp_handler = MTPHandler()
self.session = SessionManager()
self.page_type = title # 保存页面类型("入检" 或 "手检"
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.setSpacing(0)
self.create_toolbar()
# Content Area (to be filled by subclasses)
self.content_widget = QWidget()
self.content_layout = QVBoxLayout(self.content_widget)
self.content_layout.setContentsMargins(20, 20, 20, 20)
self.content_layout.setSpacing(15)
self.content_layout.setAlignment(Qt.AlignTop)
self.main_layout.addWidget(self.content_widget, 1) # Stretch = 1 to fill space
# Bottom Button Bar
self.create_bottom_bar()
def create_toolbar(self):
toolbar = QWidget()
toolbar.setStyleSheet("background-color: #f8f8f8; border-bottom: 1px solid #ddd;")
layout = QHBoxLayout(toolbar)
layout.setContentsMargins(10, 5, 10, 5)
title_label = QPushButton("⚙ 设置读取路径")
title_label.setStyleSheet("""
QPushButton {
border: none;
background-color: transparent;
font-family: 'Microsoft YaHei';
font-size: 13px;
color: #666;
text-align: left;
}
QPushButton:hover {
color: #0086fa;
}
""")
title_label.clicked.connect(self.on_settings)
layout.addWidget(title_label)
layout.addStretch()
self.main_layout.addWidget(toolbar)
def on_settings(self):
from PySide6.QtWidgets import QFormLayout, QLineEdit, QRadioButton, QButtonGroup
dialog = QDialog(self)
dialog.setWindowTitle("同步设置")
dialog.setFixedWidth(400)
layout = QVBoxLayout(dialog)
form = QFormLayout()
# Mode Selection
mode_group = QButtonGroup(dialog)
current_mode = self.settings.value("sync_mode", "local")
rb_local = QRadioButton("本地路径 (本地硬盘)")
rb_mtp = QRadioButton("MTP设备 (连接的PDA)")
mode_group.addButton(rb_local)
mode_group.addButton(rb_mtp)
if current_mode == "local": rb_local.setChecked(True)
else: rb_mtp.setChecked(True)
form.addRow("同步模式:", rb_local)
form.addRow("", rb_mtp)
self.edit_local_path = QLineEdit(self.settings.value("xml_path", os.getcwd()))
btn_browse = QPushButton("浏览...")
btn_browse.clicked.connect(self.browse_local_path)
local_path_layout = QHBoxLayout()
local_path_layout.addWidget(self.edit_local_path)
local_path_layout.addWidget(btn_browse)
form.addRow("本地文件夹:", local_path_layout)
form.addRow(QWidget()) # Spacer
# MTP Settings
self.edit_mtp_device = QLineEdit(self.settings.value("mtp_device", "Trizeps VII"))
form.addRow("MTP设备名称:", self.edit_mtp_device)
self.edit_mtp_path = QLineEdit(self.settings.value("mtp_path", "Storage Card/Program Files/xSort/Results/Analyses"))
form.addRow("MTP 内部路径:", self.edit_mtp_path)
layout.addLayout(form)
# Save Button
btn_save = QPushButton("保存配置")
btn_save.setStyleSheet("background-color: #0086fa; color: white; padding: 10px; font-weight: bold;")
btn_save.clicked.connect(lambda: self.save_settings(dialog, rb_local.isChecked()))
layout.addWidget(btn_save)
dialog.exec()
def browse_local_path(self):
directory = QFileDialog.getExistingDirectory(self, "选择本地文件夹", self.edit_local_path.text())
if directory:
self.edit_local_path.setText(directory)
def save_settings(self, dialog, is_local):
self.settings.setValue("sync_mode", "local" if is_local else "mtp")
self.settings.setValue("xml_path", self.edit_local_path.text())
self.settings.setValue("mtp_device", self.edit_mtp_device.text())
self.settings.setValue("mtp_path", self.edit_mtp_path.text())
self.show_info("设置成功", "配置已更新。")
dialog.accept()
def create_bottom_bar(self):
button_bar = QWidget()
button_bar.setStyleSheet("background-color: #e0e0e0; border-top: 1px solid #999;")
button_layout = QHBoxLayout(button_bar)
button_layout.setContentsMargins(10, 10, 10, 10)
button_layout.setSpacing(10)
btn_style = """
QPushButton {
background-color: #0086fa;
color: white;
border-radius: 5px;
padding: 15px;
font-family: 'Microsoft YaHei';
font-size: 19px;
font-weight: bold;
}
QPushButton:pressed {
background-color: #006bbd;
}
"""
# Buttons: [Sync, Submit, Print, Close]
self.btn_sync = QPushButton("同步")
self.btn_sync.setStyleSheet(btn_style)
self.btn_sync.clicked.connect(self.on_sync)
self.btn_submit = QPushButton("提交")
self.btn_submit.setStyleSheet(btn_style)
self.btn_submit.clicked.connect(self.on_submit)
self.btn_close = QPushButton("关闭")
self.btn_close.setStyleSheet(btn_style)
self.btn_close.clicked.connect(self.close)
button_layout.addWidget(self.btn_sync)
button_layout.addWidget(self.btn_submit)
button_layout.addWidget(self.btn_close)
self.main_layout.addWidget(button_bar)
def on_sync(self):
sync_mode = self.settings.value("sync_mode", "local")
xml_path = None
target_xml_name = ""
if sync_mode == "local":
xml_dir = self.settings.value("xml_path", "xml_data")
if not os.path.exists(xml_dir):
self.show_info("同步失败", f"本地路径不存在: {xml_dir}")
return
xml_files = glob.glob(os.path.join(xml_dir, "*.xml"))
if not xml_files:
self.show_info("同步失败", "本地目录下未找到 .xml 文件")
return
latest_xml = max(xml_files, key=os.path.getmtime)
xml_path = latest_xml
target_xml_name = os.path.basename(latest_xml)
else:
device_name = self.settings.value("mtp_device", "Trizeps VII")
remote_path = self.settings.value("mtp_path", "")
self.show_info("正在同步", f"正在从设备 [{device_name}] 读取最新数据...")
local_cache, filename = self.mtp_handler.get_latest_xml_windows(device_name, remote_path)
if not local_cache:
self.show_info("同步失败", f"MTP同步错误: {filename}")
return
xml_path = local_cache
target_xml_name = filename
# 1.解析xml
parser = XmlParser()
parsed_data, msg = parser.parse_file(xml_path)
if not parsed_data:
self.show_info("同步失败", f"XML解析错误: {msg}")
return
# 2. 写入数据
db = DatabaseManager()
success, db_msg = db.insert_analysis_data(parsed_data)
if not success:
self.show_info("数据库错误", f"插入失败: {db_msg}\n(请检查数据库连接)")
return
latest_data = db.get_latest_sample_data()
if latest_data:
self.update_ui_with_data(latest_data)
self.show_info("同步成功", f"已同步 {len(parsed_data)} 条元素数据。\n源文件: {target_xml_name}")
else:
self.show_info("同步异常", "数据插入成功但无法读取回显。")
def update_ui_with_data(self, data):
### 更新UI界面如果不符合对不符合的元素字体标红并且
pass
def clear_data(self):
"""清空界面所有数据(子类实现)"""
pass
def on_submit(self):
"""提交检验数据到 e_pz_hjqy_import 表"""
try:
# 1. 收集表单数据
gch = self.inputs.get("batch_no").text().strip() if self.inputs.get("batch_no") else ""
heat_number = self.inputs.get("material1").text().strip() if self.inputs.get("material1") else ""
heat_number2 = self.inputs.get("info1").text().strip() if self.inputs.get("info1") else ""
cz = self.inputs.get("spec1").text().strip() if self.inputs.get("spec1") else ""
cz2 = self.inputs.get("spec2").text().strip() if self.inputs.get("spec2") else ""
check_information = self.inputs.get("insp_info_1").text().strip() if self.inputs.get("insp_info_1") else ""
check_information2 = self.inputs.get("insp_info_2").text().strip() if self.inputs.get("insp_info_2") else ""
# 产地/材质 拼接显示时,提交只需要产地 (cd)
display_cd = self.inputs.get("material").text().strip() if self.inputs.get("material") else ""
cd = display_cd.split('/')[0] if '/' in display_cd else display_cd
size = self.inputs.get("spec").text().strip() if self.inputs.get("spec") else ""
# 2. 验证必填字段
if not gch:
self.show_info("提交失败", "工程号不能为空")
return
# 3. 收集所有元素数据
element_names = ["C", "Si", "Mn", "P", "S", "Cr", "Ni", "Mo", "Cu", "Ti", "Nb", "V", "Al", "W", "Co", "N", "Fe", "Se"]
elements = {}
for elem in element_names:
if elem in self.inputs:
value_str = self.inputs[elem].text().strip()
if value_str:
try:
elements[elem] = float(value_str)
except ValueError:
pass
# 4. 取当前用户信息
user_id = self.session.get_user_id()
dept_id = self.session.get_dept_id()
data_corp = self.session.get_data_corp()
if not user_id:
self.show_info("提交失败", "未获取到用户信息,请重新登录")
return
# 5. 构建提交数据
inspection_data = {
"gch": gch,
"heat_number": heat_number,
"heat_number2": heat_number2,
"cz": cz,
"cz2": cz2,
"check_information": check_information,
"check_information2": check_information2,
"cd": cd,
"size": size,
"elements": elements,
"user_id": user_id,
"dept_id": dept_id,
"data_corp": data_corp
}
# 6. 插入数据库
db = DatabaseManager()
success, message = db.insert_inspection_data(inspection_data)
if success:
self.show_info("提交成功", message)
self.clear_data()
else:
self.show_info("提交失败", message)
except Exception as e:
self.show_info("提交异常", f"发生错误: {str(e)}")
def show_info(self, title, message):
QMessageBox.information(self, title, message)

View File

@ -0,0 +1,352 @@
from PySide6.QtWidgets import (
QWidget, QTableWidget, QTableWidgetItem, QHeaderView, QLineEdit, QAbstractItemView
)
from PySide6.QtCore import Qt, QTimer
from openpyxl.styles.builtins import headline_1
from ui.base_page import BaseInspectionPage
from src.db_manager import DatabaseManager
class IncomingInspectionPage(BaseInspectionPage):
def __init__(self, parent=None):
super().__init__("入检", parent)
self.inputs = {}
self.db_manager = DatabaseManager()
self.scan_timer = QTimer()
self.scan_timer.setSingleShot(True)
self.scan_timer.timeout.connect(self.query_data)
self.last_gch = ""
self.init_ui()
def init_ui(self):
self.content_layout.setContentsMargins(5, 5, 5, 5)
# Initialize Table - 使用4列
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setVisible(False)
# Style
self.table.setStyleSheet("""
QTableWidget {
border: 1px solid #999;
gridline-color: #999;
background-color: white;
font-family: 'Microsoft YaHei';
font-size: 18px;
}
QTableWidget::item {
padding: 5px;
}
QLineEdit {
border: none;
background-color: transparent;
font-family: 'Microsoft YaHei';
font-size: 18px;
}
""")
header = self.table.horizontalHeader()
header.setSectionResizeMode(QHeaderView.Stretch)
self.table.setSelectionMode(QAbstractItemView.NoSelection)
self.table.setFocusPolicy(Qt.NoFocus)
self.content_layout.addWidget(self.table)
self.render_form()
def render_form(self):
# 1. 第一行 - 工程号
self.add_row_label_input("工程号", "batch_no", span_input=3)
self.inputs["batch_no"].setPlaceholderText("扫描/输入")
self.inputs["batch_no"].returnPressed.connect(self.query_data)
self.inputs["batch_no"].textChanged.connect(self.on_text_changed)
# 2. 第二行 - 产地和规格
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, "产地")
self.table.setCellWidget(row, 1, self.create_input("material"))
self.set_label_cell(row, 2, "规格")
self.table.setCellWidget(row, 3, self.create_input("spec"))
# 3. 分组标题 - 第一组
self.add_group_header("━ 第一组 ━")
# 4. 炉号和材质(第一组)
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, "炉号", align_left=True)
inp1 = self.create_input("material1")
self.table.setCellWidget(row, 1, inp1)
self.set_label_cell(row, 2, "材质")
inp2 = self.create_input("spec1")
self.table.setCellWidget(row, 3, inp2)
# 5. 检验信息(第一组)
self.add_row_label_input("检验信息", "insp_info_1", span_input=3, align_left=True)
# 6. 分组标题 - 第二组
self.add_group_header("━ 第二组 ━")
# 7. 炉号和材质(第二组)
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, "炉号", align_left=True)
inp3 = self.create_input("info1")
self.table.setCellWidget(row, 1, inp3)
self.set_label_cell(row, 2, "材质")
inp4 = self.create_input("spec2")
self.table.setCellWidget(row, 3, inp4)
# 8. 检验信息(第二组)
self.add_row_label_input("检验信息", "insp_info_2", span_input=3, align_left=True)
self.add_group_header("━ 检验值 ━")
# 9. 元素行 - 固定顺序显示所有18个元素
element_order = ["C", "Si", "Mn", "P", "S", "Cr", "Ni", "Mo", "Cu", "Ti", "Nb", "V", "Al", "W", "Co", "N", "Fe", "Se"]
for i in range(0, len(element_order), 2):
name1 = element_order[i]
name2 = element_order[i+1] if i + 1 < len(element_order) else None
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, name1)
self.table.setCellWidget(row, 1, self.create_input(name1))
if name2:
self.set_label_cell(row, 2, name2)
self.table.setCellWidget(row, 3, self.create_input(name2))
def add_row_label_input(self, label_text, key, span_input=1, bg_color=None, group_col=True, align_left=False):
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
if not group_col:
# 不需要分组列,第一列空白
self.set_empty_cell(row, 0)
self.set_label_cell(row, 1, label_text, bg_color=bg_color, align_left=align_left)
inp = self.create_input(key)
self.table.setCellWidget(row, 2, inp)
if span_input > 1:
self.table.setSpan(row, 2, 1, span_input)
else:
self.set_label_cell(row, 0, label_text, bg_color=bg_color, align_left=align_left)
inp = self.create_input(key)
self.table.setCellWidget(row, 1, inp)
if span_input > 1:
self.table.setSpan(row, 1, 1, span_input)
if bg_color:
col_start = 2 if not group_col else 1
for col in range(col_start, col_start + span_input):
self.set_cell_background(row, col, bg_color)
def set_group_cell(self, row, col, text, rowspan=1, bg_color=None):
"""设置分组标识单元格"""
item = QTableWidgetItem(text)
item.setTextAlignment(Qt.AlignCenter)
item.setFlags(Qt.ItemIsEnabled)
from PySide6.QtGui import QColor, QBrush, QFont
if bg_color:
brush = QBrush(QColor(bg_color))
else:
brush = QBrush(QColor("#f5f5f5"))
item.setBackground(brush)
font = QFont()
font.setBold(True)
font.setPointSize(16)
item.setFont(font)
self.table.setItem(row, col, item)
if rowspan > 1:
self.table.setSpan(row, col, rowspan, 1)
def set_empty_cell(self, row, col):
"""设置空白单元格"""
item = QTableWidgetItem("")
item.setFlags(Qt.ItemIsEnabled)
self.table.setItem(row, col, item)
def add_group_header(self, text):
"""添加分组标题行"""
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 40)
item = QTableWidgetItem(text)
item.setTextAlignment(Qt.AlignCenter)
item.setFlags(Qt.ItemIsEnabled)
from PySide6.QtGui import QColor, QBrush, QFont
brush = QBrush(QColor("#f0f0f0"))
item.setBackground(brush)
font = QFont()
font.setBold(True)
font.setPointSize(14)
item.setFont(font)
self.table.setItem(row, 0, item)
self.table.setSpan(row, 0, 1, 4)
def set_cell_background(self, row, col, color):
"""设置单元格背景色"""
item = self.table.item(row, col)
if not item:
item = QTableWidgetItem("")
self.table.setItem(row, col, item)
from PySide6.QtGui import QColor, QBrush
brush = QBrush(QColor(color))
item.setBackground(brush)
def set_label_cell(self, row, col, text, bg_color=None, align_left=False):
item = QTableWidgetItem(text)
# 根据参数设置对齐方式
if align_left:
item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
else:
item.setTextAlignment(Qt.AlignCenter)
item.setFlags(Qt.ItemIsEnabled)
from PySide6.QtGui import QColor, QBrush
if bg_color:
brush = QBrush(QColor(bg_color))
else:
brush = QBrush(QColor("#f5f5f5"))
item.setBackground(brush)
self.table.setItem(row, col, item)
def create_input(self, key):
inp = QLineEdit()
if key in self.inputs:
key = f"{key}_2"
self.inputs[key] = inp
return inp
def on_text_changed(self, text):
"""PDA扫码输入检测延时300ms后触发查询"""
self.scan_timer.stop()
if text.strip() and text.strip() != self.last_gch:
self.scan_timer.start(300)
def query_data(self):
self.scan_timer.stop()
gch = self.inputs["batch_no"].text().strip()
if not gch or gch == self.last_gch:
return
self.last_gch = gch
data, err = self.db_manager.query_by_gch(gch)
if err:
self.show_info("查询失败", err)
return
self.inputs["material"].setText(data.get("cd") or "")
self.inputs["spec"].setText(data.get("size") or "")
self.inputs["material1"].setText(data.get("heat_number") or "")
self.inputs["spec1"].setText(data.get("qy_czt") or "")
self.inputs["info1"].setText(data.get("heat_number2") or "")
self.inputs["spec2"].setText(data.get("qy_czw") or "")
def update_ui_with_data(self, data):
"""
根据数据库数据更新UI
"""
elements = data.get("elements", {})
# 固定元素顺序
element_order = ["C", "Si", "Mn", "P", "S", "Cr", "Ni", "Mo", "Cu", "Ti", "Nb", "V", "Al", "W", "Co", "N", "Fe", "Se"]
# 更新所有元素值没有的显示为0
for el in element_order:
if el in self.inputs:
value = "0" if (v := float(elements.get(el, 0))) == 0 else f"{v:.6f}"
self.inputs[el].setText(str(value))
# 比对标准值范围
self.validate_elements(elements)
def clear_data(self):
"""清空所有输入框数据"""
for inp in self.inputs.values():
inp.clear()
self.last_gch = ""
def validate_elements(self, elements):
"""比对元素值与标准范围,不符合的写入检验信息框"""
heat_number = self.inputs.get("material1")
heat_number2 = self.inputs.get("info1")
if not heat_number or not heat_number2:
return
hn1 = heat_number.text().strip()
hn2 = heat_number2.text().strip()
if not hn1 and not hn2:
return
heat1_range, heat2_range, err = self.db_manager.query_standard_range(hn1, hn2)
if err:
self.show_info("标准值查询失败", err)
return
# 元素名到数据库字段的映射 (18个元素)
element_map = {
"C": "c", "Si": "si", "Mn": "mn", "P": "p", "S": "s",
"Cr": "cr", "Ni": "ni", "Mo": "mo", "Cu": "cu", "Ti": "ti",
"Nb": "nb", "V": "v", "Al": "al", "W": "w", "Co": "co",
"N": "n", "Fe": "fe", "Se": "se"
}
def check_range(std_range, elements_data):
"""检查元素是否在范围内,返回不符合的列表"""
out_of_range = []
if not std_range:
return out_of_range
for el_name, db_field in element_map.items():
if el_name not in elements_data:
continue
try:
value = float(elements_data[el_name])
except (ValueError, TypeError):
continue
min_key = f"{db_field}min"
max_key = f"{db_field}max"
min_val = std_range.get(min_key)
max_val = std_range.get(max_key)
if min_val is None and max_val is None:
continue
try:
min_val = float(min_val) if min_val is not None and min_val != '' else None
max_val = float(max_val) if max_val is not None and max_val != '' else None
except (ValueError, TypeError) as e:
print(e)
is_out = False
if min_val is not None and value < min_val:
is_out = True
if max_val is not None and value > max_val:
is_out = True
if is_out:
min_str = str(min_val) if min_val is not None else ""
max_str = str(max_val) if max_val is not None else ""
out_of_range.append(f"{el_name}({min_str}-{max_str})")
return out_of_range
def get_info_text(std_range):
if not std_range:
return "无标准值"
out = check_range(std_range, elements)
return "/".join(out) + "/" if out else ""
self.inputs["insp_info_1"].setText(get_info_text(heat1_range))
self.inputs["insp_info_2"].setText(get_info_text(heat2_range))

90
ui/inspection_card.py Normal file
View File

@ -0,0 +1,90 @@
from PySide6.QtWidgets import QFrame, QLabel, QGridLayout
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont
class InspectionCard(QFrame):
def __init__(self, data, parent=None):
super().__init__(parent)
self.data = data
self.init_ui()
def init_ui(self):
self.setFrameShape(QFrame.Box)
self.setLineWidth(1)
# Black border
self.setStyleSheet("""
InspectionCard {
background-color: white;
border: 1px solid black;
margin-bottom: 10px;
}
QLabel {
font-family: 'Microsoft YaHei';
font-size: 16px;
padding-left: 8px;
padding-right: 5px;
padding-top: 5px;
padding-bottom: 5px;
color: black;
qproperty-alignment: 'AlignLeft | AlignVCenter';
}
.HeaderLabel {
font-weight: bold;
qproperty-alignment: 'AlignCenter';
padding-left: 5px;
}
""")
layout = QGridLayout(self)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
cell_style = "border: 1px solid black; padding: 4px;"
# Function to add cell
def add_cell(text, row, col, row_span=1, col_span=1, is_header=False):
lbl = QLabel(text)
if is_header:
lbl.setAlignment(Qt.AlignCenter)
lbl.setProperty("class", "HeaderLabel")
lbl.setStyleSheet(cell_style + "font-weight: bold;")
else:
lbl.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
lbl.setStyleSheet(cell_style)
layout.addWidget(lbl, row, col, row_span, col_span)
add_cell("日期", 0, 0, 1, 1, True)
add_cell(self.data.get("date", ""), 0, 1, 1, 3)
add_cell("检验员", 0, 4, 1, 1, True)
add_cell(self.data.get("inspector", ""), 0, 5, 1, 1)
add_cell("工程号", 1, 0, 1, 1, True)
add_cell(self.data.get("batch_no", ""), 1, 1, 1, 3)
add_cell("规格", 1, 4, 1, 1, True)
add_cell(self.data.get("spec", ""), 1, 5, 1, 1)
add_cell("炉号1", 2, 0, 1, 1, True)
add_cell(self.data.get("heat_number", ""), 2, 1, 1, 2)
add_cell("材质1", 2, 3, 1, 1, True)
add_cell(self.data.get("cz", ""), 2, 4, 1, 2)
add_cell("炉号2", 3, 0, 1, 1, True)
add_cell(self.data.get("heat_number2", ""), 3, 1, 1, 2)
add_cell("材质2", 3, 3, 1, 1, True)
add_cell(self.data.get("cz2", ""), 3, 4, 1, 2)
layout.setSpacing(0)
for i in range(layout.count()):
widget = layout.itemAt(i).widget()
r, c, rs, cs = layout.getItemPosition(i)
style = "border-right: 1px solid black; border-bottom: 1px solid black; padding: 4px;"
if r == 0:
style += "border-top: 1px solid black;"
if c == 0:
style += "border-left: 1px solid black;"
if widget.property("class") == "HeaderLabel":
style += "font-weight: bold;"
widget.setStyleSheet(style)

172
ui/inspection_window.py Normal file
View File

@ -0,0 +1,172 @@
from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QScrollArea, QPushButton, QMessageBox, QLabel, QScroller
)
from PySide6.QtCore import Qt, QTimer
from ui.inspection_card import InspectionCard
from ui.incoming_inspection_page import IncomingInspectionPage
from ui.manual_inspection_page import ManualInspectionPage
from src.db_manager import DatabaseManager
from src.session_manager import SessionManager
class InspectionWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("检验管理系统")
# 初始化数据库管理器和分页参数
self.db_manager = DatabaseManager()
self.session_manager = SessionManager()
self.current_page = 0
self.page_size = 10
self.is_loading = False
self.has_more_data = True
# Central Widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Main Layout
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# Scroll Area for Cards
self.scroll_area = QScrollArea()
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setStyleSheet("background-color: #f0f0f0; border: none;")
self.cards_container = QWidget()
self.cards_container.setStyleSheet("background-color: white;")
self.cards_layout = QVBoxLayout(self.cards_container)
self.cards_layout.setContentsMargins(10, 10, 10, 10)
self.cards_layout.setSpacing(15)
# 加载提示标签
self.loading_label = QLabel("加载中...")
self.loading_label.setAlignment(Qt.AlignCenter)
self.loading_label.setStyleSheet("color: #666; padding: 10px;")
self.loading_label.hide()
self.cards_layout.addWidget(self.loading_label)
self.cards_layout.addStretch() # Push items to top
self.scroll_area.setWidget(self.cards_container)
# 启用触摸滚动
QScroller.grabGesture(self.scroll_area.viewport(), QScroller.LeftMouseButtonGesture)
# 连接滚动条信号
self.scroll_area.verticalScrollBar().valueChanged.connect(self.on_scroll)
main_layout.addWidget(self.scroll_area)
button_bar = QWidget()
button_bar.setStyleSheet("background-color: #e0e0e0; border-top: 1px solid #999;")
button_layout = QHBoxLayout(button_bar)
button_layout.setContentsMargins(20, 15, 20, 15)
button_layout.setSpacing(20)
# 按钮样式
btn_style = """
QPushButton {
background-color: #0086fa;
color: white;
border-radius: 5px;
padding: 15px;
font-family: 'Microsoft YaHei';
font-size: 19px;
font-weight: bold;
}
QPushButton:pressed {
background-color: #006bbd;
}
"""
self.btn_incoming = QPushButton("入检")
self.btn_incoming.setStyleSheet(btn_style)
self.btn_incoming.clicked.connect(lambda: self.show_dialog("入检"))
self.btn_manual = QPushButton("手检")
self.btn_manual.setStyleSheet(btn_style)
self.btn_manual.clicked.connect(lambda: self.show_dialog("手检"))
button_layout.addWidget(self.btn_incoming)
button_layout.addWidget(self.btn_manual)
main_layout.addWidget(button_bar)
def add_card(self, data):
card = InspectionCard(data)
count = self.cards_layout.count()
# 插入到 loading_label 之前
self.cards_layout.insertWidget(count - 2, card)
def on_scroll(self, value):
"""滚动条事件处理"""
scrollbar = self.scroll_area.verticalScrollBar()
# 当滚动到底部附近时触发加载
if value >= scrollbar.maximum() - 50 and not self.is_loading and self.has_more_data:
self.load_more_data()
def load_more_data(self):
"""加载更多数据"""
if self.is_loading or not self.has_more_data:
return
self.is_loading = True
self.loading_label.show()
# 使用 QTimer 异步加载,避免阻塞 UI
QTimer.singleShot(100, self._fetch_data)
def _fetch_data(self):
"""从数据库获取数据"""
offset = self.current_page * self.page_size
user_id = self.session_manager.get_user_id() or ''
rows, error = self.db_manager.get_inspection_list(self.page_size, offset, user_id=user_id)
if error:
self.loading_label.setText(f"加载失败: {error}")
self.is_loading = False
QTimer.singleShot(2000, lambda: self.loading_label.hide())
return
if not rows or len(rows) == 0:
self.has_more_data = False
self.loading_label.setText("没有更多数据")
QTimer.singleShot(1000, lambda: self.loading_label.hide())
else:
for row in rows:
self.add_card(dict(row))
self.current_page += 1
if len(rows) < self.page_size:
self.has_more_data = False
self.loading_label.hide()
self.is_loading = False
def load_initial_data(self):
"""加载初始数据"""
self.current_page = 0
self.has_more_data = True
self.load_more_data()
def show_dialog(self, title):
if title == "入检":
page = IncomingInspectionPage(self)
page.showMaximized()
page.exec()
elif title == "手检":
page = ManualInspectionPage(self)
page.showMaximized()
page.exec()
else:
msg = QMessageBox(self)
msg.setWindowTitle(title)
msg.setText(f"{title} - 功能开发中")
msg.setStandardButtons(QMessageBox.Ok)
msg.exec()

128
ui/login_window.py Normal file
View File

@ -0,0 +1,128 @@
from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QMessageBox
)
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QFont
import hashlib
from src.db_manager import DatabaseManager
class LoginWindow(QMainWindow):
login_success = Signal(dict)
def __init__(self):
super().__init__()
self.setWindowTitle("检验管理系统 - 登录")
self.db = DatabaseManager()
self.init_ui()
def init_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(60, 80, 60, 80)
main_layout.setSpacing(40)
title_label = QLabel("检验管理系统")
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("""
font-family: 'Microsoft YaHei';
font-size: 32px;
font-weight: bold;
color: #0086fa;
padding: 20px;
""")
main_layout.addWidget(title_label)
form_layout = QVBoxLayout()
form_layout.setSpacing(15)
account_label = QLabel("账号")
account_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 18px; color: #333; padding: 5px;")
self.account_input = QLineEdit()
self.account_input.setPlaceholderText("请输入账号")
self.account_input.setMinimumHeight(50)
self.account_input.setStyleSheet("""
QLineEdit {
font-family: 'Microsoft YaHei';
font-size: 16px;
padding: 12px;
border: 2px solid #ccc;
border-radius: 5px;
}
QLineEdit:focus {
border: 2px solid #0086fa;
}
""")
password_label = QLabel("密码")
password_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 18px; color: #333; padding: 5px;")
self.password_input = QLineEdit()
self.password_input.setPlaceholderText("请输入密码")
self.password_input.setEchoMode(QLineEdit.Password)
self.password_input.setMinimumHeight(50)
self.password_input.setStyleSheet("""
QLineEdit {
font-family: 'Microsoft YaHei';
font-size: 16px;
padding: 12px;
border: 2px solid #ccc;
border-radius: 5px;
}
QLineEdit:focus {
border: 2px solid #0086fa;
}
""")
self.password_input.returnPressed.connect(self.on_login)
form_layout.addWidget(account_label)
form_layout.addWidget(self.account_input)
form_layout.addSpacing(10)
form_layout.addWidget(password_label)
form_layout.addWidget(self.password_input)
main_layout.addLayout(form_layout)
self.login_button = QPushButton("登录")
self.login_button.setMinimumHeight(55)
self.login_button.setStyleSheet("""
QPushButton {
background-color: #0086fa;
color: white;
border-radius: 5px;
padding: 15px;
font-family: 'Microsoft YaHei';
font-size: 18px;
font-weight: bold;
}
QPushButton:hover {
background-color: #0077e6;
}
QPushButton:pressed {
background-color: #006bbd;
}
""")
self.login_button.clicked.connect(self.on_login)
main_layout.addWidget(self.login_button)
main_layout.addStretch()
def on_login(self):
user_id = self.account_input.text().strip()
password = self.password_input.text().strip()
if not user_id or not password:
QMessageBox.warning(self, "登录失败", "请输入账号和密码")
return
password_md5 = hashlib.md5(password.encode()).hexdigest()
success, user_info = self.db.authenticate_user(user_id, password_md5)
if success:
self.login_success.emit(user_info)
self.close()
else:
QMessageBox.warning(self, "登录失败", user_info)
self.password_input.clear()
self.password_input.setFocus()

View File

@ -0,0 +1,184 @@
from PySide6.QtWidgets import (
QWidget, QTableWidget, QTableWidgetItem, QHeaderView, QLineEdit, QAbstractItemView
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QBrush
from ui.base_page import BaseInspectionPage
class ManualInspectionPage(BaseInspectionPage):
def __init__(self, parent=None):
super().__init__("手检", parent)
self.inputs = {}
self.init_ui()
def init_ui(self):
self.content_layout.setContentsMargins(5, 5, 5, 5)
# Initialize Table
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setVisible(False)
# Style
self.table.setStyleSheet("""
QTableWidget {
border: 1px solid #999;
gridline-color: #999;
background-color: white;
font-family: 'Microsoft YaHei';
font-size: 18px;
}
QTableWidget::item {
padding: 0px;
}
QLineEdit {
border: none;
background-color: transparent;
font-family: 'Microsoft YaHei';
font-size: 18px;
padding-left: 8px;
qproperty-alignment: 'AlignLeft | AlignVCenter';
}
""")
header = self.table.horizontalHeader()
header.setSectionResizeMode(QHeaderView.Stretch)
self.table.setSelectionMode(QAbstractItemView.NoSelection)
self.table.setFocusPolicy(Qt.NoFocus)
self.content_layout.addWidget(self.table)
self.render_form()
def render_form(self):
# 1. 第一行 - 工程号
self.add_row_label_input("工程号", "batch_no", span_input=3)
# 2. 第二行 - 产地和规格
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, "产地")
self.table.setCellWidget(row, 1, self.create_input("material"))
self.set_label_cell(row, 2, "规格")
self.table.setCellWidget(row, 3, self.create_input("spec"))
# 3. 分组标题 - 第一组
self.add_group_header("━ 第一组 ━")
# 4. 炉号和材质(第一组)
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, "炉号", align_left=True)
self.table.setCellWidget(row, 1, self.create_input("material1"))
self.set_label_cell(row, 2, "材质")
self.table.setCellWidget(row, 3, self.create_input("spec1"))
# 5. 检验信息(第一组)
self.add_row_label_input("检验信息", "insp_info_1", span_input=3, align_left=True)
# 6. 分组标题 - 第二组
self.add_group_header("━ 第二组 ━")
# 7. 炉号和材质(第二组)
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, "炉号", align_left=True)
self.table.setCellWidget(row, 1, self.create_input("info1"))
self.set_label_cell(row, 2, "材质")
self.table.setCellWidget(row, 3, self.create_input("spec2"))
# 8. 检验信息(第二组)
self.add_row_label_input("检验信息", "insp_info_2", span_input=3, align_left=True)
# 9. 分组标题 - 检验值
self.add_group_header("━ 检验值 ━")
# 10. 元素行 - 固定顺序显示所有18个元素
element_order = ["C", "Si", "Mn", "P", "S", "Cr", "Ni", "Mo", "Cu", "Ti", "Nb", "V", "Al", "W", "Co", "N", "Fe", "Se"]
for i in range(0, len(element_order), 2):
name1 = element_order[i]
name2 = element_order[i+1] if i + 1 < len(element_order) else None
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, name1)
self.table.setCellWidget(row, 1, self.create_input(name1))
if name2:
self.set_label_cell(row, 2, name2)
self.table.setCellWidget(row, 3, self.create_input(name2))
def add_row_label_input(self, label_text, key, span_input=1, align_left=False):
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 50)
self.set_label_cell(row, 0, label_text, align_left=align_left)
inp = self.create_input(key)
self.table.setCellWidget(row, 1, inp)
if span_input > 1:
self.table.setSpan(row, 1, 1, span_input)
def set_label_cell(self, row, col, text, align_left=False):
item = QTableWidgetItem(text)
# 根据参数设置对齐方式
if align_left:
item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
else:
item.setTextAlignment(Qt.AlignCenter)
item.setFlags(Qt.ItemIsEnabled)
brush = QBrush(QColor("#f5f5f5"))
item.setBackground(brush)
self.table.setItem(row, col, item)
def add_group_header(self, text):
"""添加分组标题行"""
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setRowHeight(row, 40)
item = QTableWidgetItem(text)
item.setTextAlignment(Qt.AlignCenter)
item.setFlags(Qt.ItemIsEnabled)
brush = QBrush(QColor("#f0f0f0"))
item.setBackground(brush)
from PySide6.QtGui import QFont
font = QFont()
font.setBold(True)
font.setPointSize(14)
item.setFont(font)
self.table.setItem(row, 0, item)
self.table.setSpan(row, 0, 1, 4)
def create_input(self, key):
inp = QLineEdit()
inp.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.inputs[key] = inp
return inp
def clear_data(self):
"""清空所有输入框数据"""
for inp in self.inputs.values():
inp.clear()
def update_ui_with_data(self, data):
"""根据数据库数据动态更新UI"""
elements = data.get("elements", {})
# 固定元素顺序
element_order = ["C", "Si", "Mn", "P", "S", "Cr", "Ni", "Mo", "Cu", "Ti", "Nb", "V", "Al", "W", "Co", "N", "Fe", "Se"]
# 更新所有元素值没有的显示为0
for el in element_order:
if el in self.inputs:
value = elements.get(el, 0)
self.inputs[el].setText(str(value))

199
utils/readMTP.py Normal file
View File

@ -0,0 +1,199 @@
import os
import time
try:
import win32com.client
import pythoncom
except ImportError:
win32com = None
class MTPHandler:
def __init__(self):
self.cache_dir = os.path.join(os.getcwd(), "mtp_cache")
if not os.path.exists(self.cache_dir):
os.makedirs(self.cache_dir)
def get_latest_xml_windows(self, device_name, path_str):
"""
Windows MTP 设备上寻找并复制最新的 XML 文件
"""
print(f"\n[MTP] >>> 开始同步工作: 设备={device_name}")
if win32com is None:
return None, "系统未安装 pywin32。"
try:
pythoncom.CoInitialize()
shell = win32com.client.Dispatch("Shell.Application")
computer = shell.NameSpace(17) # 此电脑
# 1. 寻找设备 (增加容错:忽略首尾空格和大小写)
device_item = None
target_name = device_name.strip().lower()
print(f"[MTP] 正在检索设备,目标名称: '{target_name}'")
items = computer.Items()
available_devices = []
for item in items:
available_devices.append(item.Name)
if item.Name.strip().lower() == target_name:
device_item = item
print(f"[MTP] 匹配成功: '{item.Name}'")
break
if not device_item:
print(f"[MTP] 未找到设备: '{device_name}'")
print(f"[MTP] 当前电脑识别到的设备列表: {available_devices}")
return None, f"未找到 MTP 设备: {device_name} (请检查名称)"
current_folder = device_item.GetFolder
# 2. 深入路径
path_chain = [p.strip() for p in path_str.replace('\\', '/').split('/') if p.strip()]
for folder_name in path_chain:
found = False
for item in current_folder.Items():
if item.Name.lower() == folder_name.lower() and item.IsFolder:
current_folder = item.GetFolder
found = True
break
if not found:
print(f"[MTP] 路径中断,找不到文件夹: '{folder_name}'")
# 打印一下当前目录下有的东西,帮用户排查
content = [i.Name for i in current_folder.Items()]
print(f"[MTP] 当前所在位置 [ {current_folder.Title} ] 下的可选内容: {content}")
return None, f"路径点不存在: {folder_name}"
# 3. 扫描文件并识别“最新”
print(f"[MTP] 已到达目标目录: [ {current_folder.Title} ]")
print(f"[MTP] 正在列出该目录下所有项目...")
print("-" * 40)
xml_items = []
all_items = current_folder.Items()
print(f"[MTP] 目录内总项数: {all_items.Count}")
for i in range(all_items.Count):
try:
item = all_items.Item(i)
name = str(item.Name)
is_folder = item.IsFolder
# 打印每一个文件的详细信息用于排查
# 尝试打印前 6 个详情列,看看哪一列是类型,哪一列是时间
details = [str(current_folder.GetDetailsOf(item, j)) for j in range(6)]
if i == 0: print(f"[MTP] 属性列参考(0-5): {details}")
item_type = details[2]
# 识别逻辑:
# 1. 文件名明确以 .xml 结尾
# 2. 或者类型描述中包含 "XML" (遍历 2, 3, 4 列)
# 3. 或者针对您的 PDA 特效:文件名以 "Result_" 开头且名字里没有点 (认为后缀被隐藏了)
is_xml_name = name.lower().endswith(".xml")
is_xml_type = any("xml" in details[j].lower() for j in [2, 3, 4, 5])
is_pda_result = name.startswith("Result_") and "." not in name
if not is_folder and (is_xml_name or is_xml_type or is_pda_result):
# 尝试获取最可信的时间
mtime = self._get_best_time(current_folder, item)
# 如果文件名包含 Result_YYYYMMDD_HHMMSS提取它作为辅助排序依据
name_ts = self._extract_time_from_name(name)
xml_items.append({
'item': item,
'name': name if is_xml_name else f"{name}.xml",
'time': mtime,
'name_ts': name_ts
})
print(f"[MTP] 识别 XML: '{name}' | 创建/修改时间: {mtime} | 文件名时间: {name_ts}")
else:
pass
except Exception as e:
print(f"[MTP] 读取第 {i} 个项目时出错: {e}")
print("-" * 40)
if not xml_items:
print(f"[MTP] 报错: 在目录 [ {current_folder.Title} ] 中没有找到任何以 .xml 结尾的文件")
return None, "该目录中没有 XML 文件"
# 排序逻辑优化:
# 1. 优先按照文件名里的时间戳排序 (Result_YYYYMMDD_HHMMSS)
# 2. 其次按照系统返回的时间属性排序
# 3. 最后按全名排序
xml_items.sort(key=lambda x: (x['name_ts'], str(x['time']), x['name']), reverse=True)
latest = xml_items[0]
latest_item = latest['item']
filename = latest['name']
print(f"[MTP] 选定最新文件: {filename}")
print(f"[MTP] - 属性时间: {latest['time']}")
print(f"[MTP] - 名称日期: {latest['name_ts']}")
# 4. 复制到本地
local_path = os.path.abspath(os.path.join(self.cache_dir, filename))
# 清理旧缓存
for f in os.listdir(self.cache_dir):
try: os.remove(os.path.join(self.cache_dir, f))
except: pass
print(f"[MTP] 正在拉取文件到本地缓存...")
dest_shell_folder = shell.NameSpace(os.path.abspath(self.cache_dir))
dest_shell_folder.CopyHere(latest_item, 4 | 16)
# 等待
success = False
for i in range(20):
if os.path.exists(local_path) and os.path.getsize(local_path) > 0:
success = True
break
time.sleep(0.5)
if success:
print(f"[MTP] ✨ 同步成功: {filename}")
return local_path, filename
else:
return None, "同步超时"
except Exception as e:
print(f"[MTP] 异常: {e}")
return None, str(e)
finally:
pythoncom.CoUninitialize()
def _extract_time_from_name(self, name):
"""从文件名 Result_20251222_124145 中提取日期字符串用于排序"""
import re
# 匹配 8位数字_6位数字 (YYYYMMDD_HHMMSS)
match = re.search(r'(\d{8}_\d{6})', name)
if match:
return match.group(1)
# 如果没匹配到,试试只匹配 8位日期
match = re.search(r'(\d{8})', name)
if match:
return match.group(1)
return ""
def _get_best_time(self, folder_obj, item_obj):
"""尝试从 Shell 属性中获取文件时间"""
# 索引 3 通常是修改时间,索引 4 通常是创建时间
for idx in [3, 4, 5]:
try:
t = folder_obj.GetDetailsOf(item_obj, idx)
if t and len(t) > 5: # 简单过滤无效短字符串
return t
except: pass
# 兜底尝试标准属性
try:
return str(item_obj.ModifyDate)
except: return ""
def clear_cache(self):
if os.path.exists(self.cache_dir):
import shutil
shutil.rmtree(self.cache_dir)
os.makedirs(self.cache_dir)

19
walkthrough.md Normal file
View File

@ -0,0 +1,19 @@
# 修改记录 - 调整入检界面布局顺序
## 1. 修改内容
### `ui/incoming_inspection_page.py`
- **布局重组**:调整了控件的显示顺序,采用了交错式布局以符合业务逻辑。
- **组1**炉号1 (Row) -> 检验信息1 (Row, 跨3列)
- **组2**炉号2 (Row) -> 检验信息2 (Row, 跨3列)
- **输入框优化**:使用 `add_row_label_input(..., span_input=3)` 方法,使“检验信息”输入框占据行内剩余空间,方便输入长文本。
- **逻辑修正**:同步更新了 `update_ui_with_data` 中的 `fixed_rows` 常量为 **6**
- **计算方式**0:批次, 1:产地/规格, 2:炉号1, 3:信息1, 4:炉号2, 5:信息2。
## 2. 变更影响
- **UI 呈现**:不仅恢复了独立的 "检验信息" 行,而且将其紧跟在对应的 "炉号" 之后逻辑上更清晰炉号1对应信息1炉号2对应信息2
- **数据回显**:重新计算了固定行数,确保在扫描查询后,这些自定义输入框不会被错误清除。
## 3. 验证
- 静态代码检查通过。
- 逻辑检查:`fixed_rows=6` 覆盖了所有静态插入的行,动态元素将正确追加在第 7 行之后。

123
xml.sql Normal file
View File

@ -0,0 +1,123 @@
CREATE TABLE element_analysis_data (
-- ========== 主键 ==========
id BIGSERIAL PRIMARY KEY,
-- ========== 样品基本信息 ==========
sample_name VARCHAR(255),
sample_grade_id VARCHAR(100),
sample_grade_alias VARCHAR(255),
operator_name VARCHAR(100),
instrument VARCHAR(100),
method_name VARCHAR(255),
sample_type VARCHAR(50),
-- ========== 测量信息 ==========
replicate_no VARCHAR(100),
measure_datetime VARCHAR(100),
measure_duration VARCHAR(100),
check_type VARCHAR(50),
check_status VARCHAR(50),
grade_name VARCHAR(100),
base_element VARCHAR(10),
rsd_check VARCHAR(100),
-- ========== 元素信息 ==========
element_name VARCHAR(10) NOT NULL,
element_type VARCHAR(50),
-- ========== 测量结果(浓度值) ==========
concentration_value VARCHAR(100),
concentration_unit VARCHAR(20),
std_dev_value VARCHAR(100),
-- ========== 不确定度信息 ==========
uncertainty_abs VARCHAR(100),
uncertainty_rel VARCHAR(100),
-- ========== 验收限值 ==========
lower_acceptance_limit VARCHAR(100),
upper_acceptance_limit VARCHAR(100),
-- ========== 状态信息 ==========
result_status VARCHAR(50),
calibration_status VARCHAR(50),
acceptance_status VARCHAR(50),
is_deleted VARCHAR(100) DEFAULT 'false',
-- ========== 计算字段(便于查询) ==========
is_within_limits VARCHAR(100),
deviation_from_center VARCHAR(100),
-- ========== XML元数据 ==========
xml_version VARCHAR(20),
xml_creation_datetime VARCHAR(100),
-- ========== 系统字段 ==========
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
data_source VARCHAR(255)
);
-- ========== 索引设计 ==========
CREATE INDEX idx_element_name ON element_analysis_data(element_name);
CREATE INDEX idx_sample_grade ON element_analysis_data(sample_grade_id);
CREATE INDEX idx_measure_datetime ON element_analysis_data(measure_datetime);
CREATE INDEX idx_calibration_status ON element_analysis_data(calibration_status);
CREATE INDEX idx_acceptance_status ON element_analysis_data(acceptance_status);
CREATE INDEX idx_operator ON element_analysis_data(operator_name);
CREATE INDEX idx_instrument ON element_analysis_data(instrument);
CREATE INDEX idx_composite_element_sample ON element_analysis_data(element_name, sample_grade_id, measure_datetime);
-- ============================================
-- 表和字段注释使用单独的COMMENT语句
-- ============================================
COMMENT ON TABLE element_analysis_data IS '元素分析数据扁平化表 - 存储所有元素检测结果,每行代表一个元素的一次测量';
COMMENT ON COLUMN element_analysis_data.id IS '主键ID自增';
COMMENT ON COLUMN element_analysis_data.sample_name IS '样品名称';
COMMENT ON COLUMN element_analysis_data.sample_grade_id IS '样品材料等级标识如SS-321';
COMMENT ON COLUMN element_analysis_data.sample_grade_alias IS '样品材料等级别名如SA-213 Grade TP 321';
COMMENT ON COLUMN element_analysis_data.operator_name IS '执行测量的操作员姓名';
COMMENT ON COLUMN element_analysis_data.instrument IS '使用的检测仪器编号';
COMMENT ON COLUMN element_analysis_data.method_name IS '使用的检测方法名称';
COMMENT ON COLUMN element_analysis_data.sample_type IS '样品类型Calibration校准、Measurement测量';
COMMENT ON COLUMN element_analysis_data.replicate_no IS '重复测量的序号从0开始';
COMMENT ON COLUMN element_analysis_data.measure_datetime IS '执行测量的日期和时间';
COMMENT ON COLUMN element_analysis_data.measure_duration IS '测量持续时间,单位:秒';
COMMENT ON COLUMN element_analysis_data.check_type IS '检查类型如GradeAcceptance等级验收';
COMMENT ON COLUMN element_analysis_data.check_status IS '检查状态如Ok通过';
COMMENT ON COLUMN element_analysis_data.grade_name IS '检测使用的材料等级名称';
COMMENT ON COLUMN element_analysis_data.base_element IS '基础元素符号,用于计算其他元素的相对含量';
COMMENT ON COLUMN element_analysis_data.rsd_check IS '是否进行相对标准偏差RSD检查';
COMMENT ON COLUMN element_analysis_data.element_name IS '化学元素符号如Fe、Cr、Ni、Mn';
COMMENT ON COLUMN element_analysis_data.element_type IS '元素类型通常为Element';
COMMENT ON COLUMN element_analysis_data.concentration_value IS '元素浓度的测量值';
COMMENT ON COLUMN element_analysis_data.concentration_unit IS '浓度单位,通常为%(百分比)';
COMMENT ON COLUMN element_analysis_data.std_dev_value IS '标准偏差值,表示测量的离散程度';
COMMENT ON COLUMN element_analysis_data.uncertainty_abs IS '绝对不确定度,表示测量的绝对误差范围';
COMMENT ON COLUMN element_analysis_data.uncertainty_rel IS '相对不确定度(百分比),表示测量的相对误差';
COMMENT ON COLUMN element_analysis_data.lower_acceptance_limit IS '验收判定的下限值';
COMMENT ON COLUMN element_analysis_data.upper_acceptance_limit IS '验收判定的上限值';
COMMENT ON COLUMN element_analysis_data.result_status IS '结果状态如OK正常';
COMMENT ON COLUMN element_analysis_data.calibration_status IS '校准状态InRange在范围内、UnderRange低于范围、OverRange超出范围';
COMMENT ON COLUMN element_analysis_data.acceptance_status IS '验收状态InRange合格、OverRange超标、NotUsed不参与验收';
COMMENT ON COLUMN element_analysis_data.is_deleted IS '该测量结果是否已被标记为删除';
COMMENT ON COLUMN element_analysis_data.is_within_limits IS '测量值是否在验收范围内(自动计算)';
COMMENT ON COLUMN element_analysis_data.deviation_from_center IS '测量值偏离验收范围中心值的程度';
COMMENT ON COLUMN element_analysis_data.xml_version IS 'XML文件版本号';
COMMENT ON COLUMN element_analysis_data.xml_creation_datetime IS 'XML文件创建时间';
COMMENT ON COLUMN element_analysis_data.created_at IS '数据库记录创建时间';
COMMENT ON COLUMN element_analysis_data.updated_at IS '数据库记录最后更新时间';
COMMENT ON COLUMN element_analysis_data.data_source IS '数据来源的XML文件名';
COMMENT ON INDEX idx_element_name IS '元素名称索引,用于按元素查询';
COMMENT ON INDEX idx_sample_grade IS '样品等级索引,用于按材料等级查询';
COMMENT ON INDEX idx_measure_datetime IS '测量时间索引,用于时间范围查询';
COMMENT ON INDEX idx_calibration_status IS '校准状态索引,用于筛选校准状态';
COMMENT ON INDEX idx_acceptance_status IS '验收状态索引,用于筛选合格/不合格数据';
COMMENT ON INDEX idx_operator IS '操作员索引,用于按操作员查询';
COMMENT ON INDEX idx_instrument IS '仪器索引,用于按仪器查询';
COMMENT ON INDEX idx_composite_element_sample IS '复合索引,用于元素-样品-时间的组合查询';