from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import os
import numpy as np
import pandas as pd
import logging
import time
import json
import traceback
from werkzeug.utils import secure_filename
from new_algorithm import GasSensorDataAnalyzer, AlgorithmSelector, detect_dataset_type, extract_gas_type, extract_concentration, extract_sensor_type
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(), # 输出到控制台
logging.FileHandler('app.log', encoding='utf-8') # 输出到文件
]
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB 文件大小限制
# 配置CORS允许Vue前端访问
CORS(app, resources={
r"/*": {
"origins": "http://localhost:5177", # 指定Vue前端地址
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True
}
})
# 配置上传文件夹
UPLOAD_FOLDER = 'uploads'
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# 允许的文件扩展名
ALLOWED_EXTENSIONS = {'csv', 'xlsx', 'xls'}
# 全局数据集存储
data_analyzer = GasSensorDataAnalyzer()
X_combined = None
y_combined = None
gas_types = []
concentrations = []
sensor_types = []
last_activity = time.time()
algorithm_selector = AlgorithmSelector(use_chinese=True)
def allowed_file(filename):
"""检查文件扩展名是否合法"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def save_and_extract_file_info(file):
"""保存文件并提取气体信息"""
try:
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
logger.info(f"Saved file: {file_path}")
# 从文件名提取信息
sensor_type = extract_sensor_type(filename)
gas_type = extract_gas_type(filename)
concentration = extract_concentration(filename)
return file_path, sensor_type, gas_type, concentration
except Exception as e:
logger.error(f"Error processing file {file.filename}: {str(e)}\n{traceback.format_exc()}")
return None, None, None, None
@app.route('/')
def index():
"""健康检查端点"""
global last_activity
status = {
'status': 'running',
'version': '1.0.0',
'last_activity': time.ctime(last_activity),
'endpoints': {
'/upload': 'POST - Upload data files',
'/analyze': 'POST - Analyze data',
'/reset': 'POST - Reset data',
'/columns': 'GET - Get dataset columns',
'/status': 'GET - Service status'
}
}
logger.info(f"Status request: {status}")
return jsonify(status)
@app.route('/status')
def status():
"""服务状态检查"""
global X_combined, last_activity
return jsonify({
'status': 'active',
'timestamp': time.time(),
'dataset_loaded': X_combined is not None,
'dataset_shape': X_combined.shape if X_combined is not None else None,
'num_classes': len(np.unique(y_combined)) if y_combined is not None else 0
})
@app.route('/upload', methods=['POST'])
def upload_files():
"""处理文件上传"""
global X_combined, y_combined, gas_types, concentrations, sensor_types, last_activity
logger.info("Received upload request")
# 检查是否有文件
if 'files' not in request.files:
logger.error("No file part in request")
return jsonify({'error': 'No file part'}), 400
files = request.files.getlist('files')
if len(files) == 0 or files[0].filename == '':
logger.error("No selected files")
return jsonify({'error': 'No selected files'}), 400
# 过滤合法文件
valid_files = [f for f in files if allowed_file(f.filename)]
if len(valid_files) < 2:
logger.error("Need at least two files for analysis")
return jsonify({'error': '需要至少两个文件进行分析'}), 400
# 保存文件并提取信息
file_paths = []
extracted_info = []
for file in valid_files:
file_path, sensor_type, gas_type, concentration = save_and_extract_file_info(file)
if file_path:
file_paths.append(file_path)
extracted_info.append({
'sensor_type': sensor_type,
'gas_type': gas_type,
'concentration': concentration,
'filename': file.filename
})
else:
logger.error(f"Failed to process file: {file.filename}")
if len(file_paths) < 2:
logger.error("Failed to process enough files")
return jsonify({
'error': '文件处理失败,请检查文件格式',
'processed_files': [info['filename'] for info in extracted_info],
'details': extracted_info
}), 500
# 准备数据加载参数
sensor_types = [info['sensor_type'] for info in extracted_info]
gas_types = [info['gas_type'] for info in extracted_info]
concentrations = [info['concentration'] for info in extracted_info]
# 加载数据
try:
X_combined, y_combined = data_analyzer.load_multiple_gas_data(
file_paths, gas_types, concentrations, sensor_types
)
if X_combined is None or len(X_combined) == 0:
logger.error("Failed to load data from files")
return jsonify({
'error': '数据加载失败,请检查文件内容',
'file_details': extracted_info
}), 500
logger.info(f"Loaded combined data: {len(X_combined)} samples, {X_combined.shape[1]} features, {len(np.unique(y_combined))} classes")
# 获取多维度标签信息
label_info = []
for label in np.unique(y_combined):
# 查找对应的标签信息
for key, label_id in data_analyzer.multi_dimension_labels.items():
if label_id == label:
parts = key.split('_')
if len(parts) >= 3:
sensor = parts[0]
gas = parts[1]
conc = parts[2].replace('ppm', '')
try:
_, name_dict = data_analyzer.get_or_create_multi_dimension_label(sensor, gas, int(conc))
label_info.append({
'id': int(label),
'sensor': sensor,
'gas': gas,
'concentration': conc,
'name_cn': name_dict['cn'],
'name_en': name_dict['en']
})
except Exception as e:
logger.error(f"Error getting label info for {key}: {str(e)}")
label_info.append({
'id': int(label),
'sensor': sensor,
'gas': gas,
'concentration': conc,
'name_cn': f"未知标签 {label}",
'name_en': f"Unknown Label {label}"
})
else:
logger.warning(f"Invalid key format: {key}")
# 更新最后活动时间
last_activity = time.time()
# 返回成功响应
response = {
'message': f'成功上传并合并 {len(file_paths)} 个文件',
'sample_count': len(X_combined),
'feature_count': X_combined.shape[1],
'num_classes': len(np.unique(y_combined)),
'gas_types': gas_types,
'concentrations': concentrations,
'sensor_types': sensor_types,
'label_info': label_info
}
return jsonify(response), 200
except Exception as e:
logger.error(f"Error loading data: {str(e)}\n{traceback.format_exc()}")
return jsonify({
'error': f'数据加载错误: {str(e)}',
'traceback': traceback.format_exc().splitlines()
}), 500
@app.route('/analyze', methods=['POST'])
def analyze_data():
"""执行数据分析"""
global X_combined, y_combined, algorithm_selector, last_activity
logger.info("Received analyze request")
# 检查数据是否已加载
if X_combined is None or y_combined is None:
logger.error("No dataset available")
return jsonify({'error': '没有可用数据,请先上传文件'}), 400
# 获取前端传递的算法参数
try:
data = request.get_json()
if not data or 'params' not in data:
logger.error("Invalid request parameters")
return jsonify({'error': '无效请求参数'}), 400
params = data.get('params', {})
# 设置算法参数
for algo_name, algo_params in params.items():
if algo_name in algorithm_selector.algorithms:
logger.info(f"Setting parameters for {algo_name}: {algo_params}")
algorithm_selector.set_algorithm_params(algo_name, algo_params)
except Exception as e:
logger.error(f"Error parsing JSON data: {str(e)}\n{traceback.format_exc()}")
return jsonify({'error': 'JSON数据解析错误'}), 400
# 训练模型
try:
logger.info(f"Starting model training with {len(X_combined)} samples and {len(np.unique(y_combined))} classes")
results = algorithm_selector.train_models(X_combined, y_combined)
logger.info("Algorithm training completed")
except Exception as e:
logger.error(f"Error training models: {str(e)}\n{traceback.format_exc()}")
return jsonify({'error': f'模型训练错误: {str(e)}'}), 500
# 提取需要返回的结果(关键修复)
response_results = {}
for algo_name, result in results.items():
# 如果训练出错,记录错误信息
if 'error' in result:
response_results[algo_name] = {
'name': result['name'],
'error': result['error']
}
else:
# 转换分类报告为字符串(如果存在)
if 'classification_report' in result:
report = result['classification_report']
if isinstance(report, dict):
# 将分类报告字典转换为格式化的字符串
report_str = "\n".join([f"{key}: {value}" for key, value in report.items()])
else:
report_str = str(report)
else:
report_str = "无分类报告"
# 提取特征重要性(如果存在)
feature_importances = None
if 'feature_importances' in result and result['feature_importances'] is not None:
# 确保特征重要性是字典格式
if isinstance(result['feature_importances'], dict):
feature_importances = result['feature_importances']
elif hasattr(result['model'], 'feature_importances_'):
# 尝试从模型中提取特征重要性
try:
feature_importances = dict(zip(
result['model'].feature_names_in_,
result['model'].feature_importances_
))
except Exception as e:
logger.warning(f"无法提取特征重要性: {str(e)}")
feature_importances = None
response_results[algo_name] = {
'name': result['name'],
'train_accuracy': result['train_accuracy'],
'test_accuracy': result['test_accuracy'],
'classification_report': report_str,
'feature_importances': feature_importances
}
# 更新最后活动时间
last_activity = time.time()
return jsonify({
'message': '分析成功完成',
'results': response_results,
'num_classes': len(np.unique(y_combined)),
'sample_count': len(X_combined)
}), 200
@app.route('/reset', methods=['POST'])
def reset_data():
"""重置数据集"""
global X_combined, y_combined, gas_types, concentrations, sensor_types, last_activity
X_combined = None
y_combined = None
gas_types = []
concentrations = []
sensor_types = []
last_activity = time.time()
logger.info("Data reset")
return jsonify({'message': '数据已重置'}), 200
@app.route('/uploads/<filename>', methods=['GET'])
def uploaded_file(filename):
"""访问上传的文件"""
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
if __name__ == '__main__':
# 开发环境 - 允许Vue前端访问
app.run(host='0.0.0.0', port=5000, debug=True)
这是你上次修改的代码,上次的代码还能够上传数据,这个功能也要保留的同时,能够分析数据
最新发布