1333 lines
54 KiB
Python
1333 lines
54 KiB
Python
from flask import Blueprint, request, jsonify, send_file
|
||
from .cost_prediction import CostPredictor
|
||
from .feature_analysis import FeatureAnalysis
|
||
import pandas as pd
|
||
from datetime import datetime
|
||
import numpy as np
|
||
|
||
from sklearn.metrics import mean_absolute_error
|
||
from .create_template import create_excel_template
|
||
import json
|
||
import os
|
||
from .data_preparation import DataPreparation
|
||
from .model_trainer import ModelTrainer
|
||
from .database import get_db_connection
|
||
from .demo_service import DemoModelService
|
||
from .logger import setup_logger
|
||
|
||
# PyTorch 为可选导入
|
||
try:
|
||
import torch
|
||
_HAS_TORCH = True
|
||
except ImportError:
|
||
_HAS_TORCH = False
|
||
|
||
# 创建蓝图
|
||
api_bp = Blueprint('api', __name__)
|
||
|
||
# 获取logger
|
||
logger = setup_logger(__name__)
|
||
|
||
@api_bp.route('/', methods=['GET'])
|
||
def index():
|
||
"""
|
||
API根路由
|
||
返回API版本信息和可用端点列表
|
||
"""
|
||
return jsonify({
|
||
'name': '装备成本估算系统 API',
|
||
'version': '1.0.0',
|
||
'endpoints': {
|
||
'predict': {
|
||
'url': '/api/predict',
|
||
'method': 'POST',
|
||
'description': '成本预测'
|
||
},
|
||
'analyze-features': {
|
||
'url': '/api/analyze-features',
|
||
'method': 'POST',
|
||
'description': '特征分析'
|
||
},
|
||
'train': {
|
||
'url': '/api/train',
|
||
'method': 'POST',
|
||
'description': '模型训练'
|
||
},
|
||
'evaluate': {
|
||
'url': '/api/evaluate',
|
||
'method': 'POST',
|
||
'description': '模型评估'
|
||
},
|
||
'analyze-manufacturers': {
|
||
'url': '/api/analyze-manufacturers',
|
||
'method': 'POST',
|
||
'description': '供应商分析'
|
||
}
|
||
}
|
||
})
|
||
|
||
@api_bp.route('/demo/algorithms', methods=['GET'])
|
||
def demo_algorithms():
|
||
"""Return algorithms supported by the file-based demo."""
|
||
try:
|
||
service = DemoModelService()
|
||
return jsonify({'algorithms': service.get_algorithms()})
|
||
except Exception as e:
|
||
logger.error(f"Error getting demo algorithms: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/demo/dataset', methods=['GET'])
|
||
def demo_dataset():
|
||
"""Return the local demo dataset summary without using a database."""
|
||
try:
|
||
service = DemoModelService()
|
||
return jsonify(service.get_dataset_summary())
|
||
except Exception as e:
|
||
logger.error(f"Error getting demo dataset: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/demo/run', methods=['POST'])
|
||
def demo_run():
|
||
"""Run selected demo algorithms against the local data file."""
|
||
try:
|
||
data = request.get_json(silent=True) or {}
|
||
service = DemoModelService()
|
||
return jsonify(service.run_demo(data.get('algorithms')))
|
||
except Exception as e:
|
||
logger.error(f"Error running demo algorithms: {str(e)}")
|
||
logger.error("Detailed traceback:", exc_info=True)
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/predict', methods=['POST'])
|
||
def predict():
|
||
"""使用最优机器学习模型进行预测"""
|
||
try:
|
||
data = request.get_json()
|
||
equipment_type = data.get('type')
|
||
logger.info(f"Received prediction request for equipment type: {equipment_type}")
|
||
|
||
# 获取最新的激活模型(非PLS模型)
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT * FROM trained_models
|
||
WHERE equipment_type = ?
|
||
AND model_type != 'pls'
|
||
AND is_active = TRUE
|
||
ORDER BY training_date DESC LIMIT 1
|
||
""", (equipment_type,))
|
||
model = cursor.fetchone()
|
||
|
||
if not model:
|
||
return jsonify({'error': '未找到可用的模型'}), 404
|
||
|
||
# 使用普通预测方法
|
||
predictor = CostPredictor()
|
||
prediction = predictor.predict(data, model) # 使用普通predict方法
|
||
|
||
# 返回预测结果
|
||
return jsonify({
|
||
'model_info': {
|
||
'type': model['model_type'], # 使用数据库中的模型类型
|
||
'name': model['model_name']
|
||
},
|
||
'predicted_cost': prediction['predicted_cost'],
|
||
'confidence_interval': prediction['confidence_interval']
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in prediction: {str(e)}")
|
||
logger.error("Detailed traceback:", exc_info=True)
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/analyze-features', methods=['POST'])
|
||
def analyze_features():
|
||
"""基于数据集进行特征分析"""
|
||
try:
|
||
data = request.get_json()
|
||
dataset_id = data.get('dataset_id')
|
||
|
||
logger.info(f"Starting feature analysis for dataset {dataset_id}")
|
||
|
||
if not dataset_id:
|
||
logger.warning("No dataset_id provided")
|
||
return jsonify({'error': '请选择数据集'}), 400
|
||
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 首先获取数据集的装备类型
|
||
cursor.execute("""
|
||
SELECT DISTINCT e.type
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
WHERE de.dataset_id = ?
|
||
LIMIT 1
|
||
""", (dataset_id,))
|
||
|
||
equipment_type = cursor.fetchone()['type']
|
||
logger.info(f"Equipment type: {equipment_type}")
|
||
|
||
# 根据装备类型选择查询
|
||
if equipment_type == '火箭炮':
|
||
cursor.execute("""
|
||
SELECT e.id, e.name, e.type, e.manufacturer, e.manufacturer_id,
|
||
m.tech_level, m.scale_level, m.supply_chain_level, m.country,
|
||
cp.length_m, cp.width_m, cp.height_m, cp.weight_kg,
|
||
cd.actual_cost,
|
||
rap.max_range_km, rap.firing_angle_horizontal, rap.firing_angle_vertical,
|
||
rap.rocket_length_m, rap.rocket_diameter_mm, rap.rocket_weight_kg,
|
||
rap.rate_of_fire, rap.combat_weight_kg, rap.speed_kmh,
|
||
rap.min_range_km, rap.power_hp, rap.travel_range_km,
|
||
rap.fire_density, rap.range_ratio, rap.mobility_score,
|
||
rap.combat_readiness_score, rap.deployment_score, rap.terrain_adaptability_score,
|
||
rap.rocket_power_ratio, rap.platform_efficiency
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
LEFT JOIN manufacturers m ON e.manufacturer_id = m.id
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN rocket_artillery_params rap ON e.id = rap.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
WHERE de.dataset_id = ?
|
||
AND cd.actual_cost IS NOT NULL
|
||
""", (dataset_id,))
|
||
else:
|
||
cursor.execute("""
|
||
SELECT e.id, e.name, e.type, e.manufacturer, e.manufacturer_id,
|
||
m.tech_level, m.scale_level, m.supply_chain_level, m.country,
|
||
cp.length_m, cp.width_m, cp.height_m, cp.weight_kg,
|
||
cd.actual_cost,
|
||
lmp.max_range_km, lmp.wingspan_m, lmp.warhead_weight_kg,
|
||
lmp.max_speed_ms, lmp.cruise_speed_kmh, lmp.endurance_min,
|
||
lmp.length_width_ratio, lmp.weight_range_ratio,
|
||
lmp.speed_weight_ratio, lmp.ceiling_altitude_m,
|
||
lmp.guidance_system_score, lmp.warhead_power_score,
|
||
lmp.engine_power_kw, lmp.engine_thrust_n,
|
||
lmp.min_altitude_m, lmp.max_altitude_m,
|
||
lmp.max_payload_kg, lmp.combat_radius_km,
|
||
lmp.datalink_range_km, lmp.guidance_accuracy_m
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
LEFT JOIN manufacturers m ON e.manufacturer_id = m.id
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN loitering_munition_params lmp ON e.id = lmp.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
WHERE de.dataset_id = ?
|
||
AND cd.actual_cost IS NOT NULL
|
||
""", (dataset_id,))
|
||
|
||
equipment_data = cursor.fetchall()
|
||
|
||
# 提取特征和目标值
|
||
analyzer = FeatureAnalysis()
|
||
feature_names = analyzer.get_equipment_specific_features(equipment_data[0]['type'])
|
||
features = []
|
||
targets = []
|
||
|
||
for item in equipment_data:
|
||
# 计算生产商特征
|
||
manufacturer_features = analyzer.calculate_manufacturer_features({
|
||
'tech_level': item['tech_level'],
|
||
'scale_level': item['scale_level'],
|
||
'supply_chain_level': item['supply_chain_level'],
|
||
'country': item['country']
|
||
})
|
||
|
||
# 获取装备特征
|
||
feature_values = []
|
||
for name in feature_names:
|
||
if name in manufacturer_features:
|
||
value = manufacturer_features[name]
|
||
else:
|
||
value = item.get(name)
|
||
feature_values.append(float(value) if value is not None else 0.0)
|
||
|
||
features.append(feature_values)
|
||
targets.append(float(item['actual_cost']))
|
||
|
||
# 执行特征分析
|
||
analysis_result = analyzer.analyze_features(features, targets, feature_names)
|
||
|
||
# 添加装备名称列表
|
||
analysis_result['equipment_names'] = [item['name'] for item in equipment_data]
|
||
|
||
# 添加装备特有的分析数据
|
||
if equipment_data[0]['type'] == '火箭炮':
|
||
rocket_data = {
|
||
'fire_density': [float(item['fire_density']) if item['fire_density'] is not None else 0 for item in equipment_data],
|
||
'range_ratio': [float(item['range_ratio']) if item['range_ratio'] is not None else 0 for item in equipment_data],
|
||
'mobility_score': [float(item['mobility_score']) if item['mobility_score'] is not None else 0 for item in equipment_data],
|
||
'combat_readiness_score': [float(item['combat_readiness_score']) if item['combat_readiness_score'] is not None else 0 for item in equipment_data],
|
||
'deployment_score': [float(item['deployment_score']) if item['deployment_score'] is not None else 0 for item in equipment_data],
|
||
'terrain_adaptability_score': [float(item['terrain_adaptability_score']) if item['terrain_adaptability_score'] is not None else 0 for item in equipment_data],
|
||
'rate_of_fire': [float(item['rate_of_fire']) if item['rate_of_fire'] is not None else 0 for item in equipment_data],
|
||
'max_range_km': [float(item['max_range_km']) if item['max_range_km'] is not None else 0 for item in equipment_data],
|
||
'rocket_length_m': [float(item['rocket_length_m']) if item['rocket_length_m'] is not None else 0 for item in equipment_data],
|
||
'rocket_diameter_mm': [float(item['rocket_diameter_mm']) if item['rocket_diameter_mm'] is not None else 0 for item in equipment_data],
|
||
'rocket_weight_kg': [float(item['rocket_weight_kg']) if item['rocket_weight_kg'] is not None else 0 for item in equipment_data],
|
||
'speed_kmh': [float(item['speed_kmh']) if item['speed_kmh'] is not None else 0 for item in equipment_data],
|
||
'power_hp': [float(item['power_hp']) if item['power_hp'] is not None else 0 for item in equipment_data],
|
||
'travel_range_km': [float(item['travel_range_km']) if item['travel_range_km'] is not None else 0 for item in equipment_data],
|
||
}
|
||
analysis_result.update(rocket_data)
|
||
else:
|
||
missile_data = {
|
||
'length_width_ratio': [float(item['length_width_ratio']) if item['length_width_ratio'] is not None else 0 for item in equipment_data],
|
||
'weight_range_ratio': [float(item['weight_range_ratio']) if item['weight_range_ratio'] is not None else 0 for item in equipment_data],
|
||
'speed_weight_ratio': [float(item['speed_weight_ratio']) if item['speed_weight_ratio'] is not None else 0 for item in equipment_data],
|
||
'guidance_system_score': [float(item['guidance_system_score']) if item['guidance_system_score'] is not None else 0 for item in equipment_data],
|
||
'warhead_power_score': [float(item['warhead_power_score']) if item['warhead_power_score'] is not None else 0 for item in equipment_data],
|
||
'guidance_accuracy_m': [float(item['guidance_accuracy_m']) if item['guidance_accuracy_m'] is not None else 0 for item in equipment_data],
|
||
'datalink_range_km': [float(item['datalink_range_km']) if item['datalink_range_km'] is not None else 0 for item in equipment_data],
|
||
'max_altitude_m': [float(item['max_altitude_m']) if item['max_altitude_m'] is not None else 0 for item in equipment_data],
|
||
'min_altitude_m': [float(item['min_altitude_m']) if item['min_altitude_m'] is not None else 0 for item in equipment_data],
|
||
'engine_power_kw': [float(item['engine_power_kw']) if item['engine_power_kw'] is not None else 0 for item in equipment_data],
|
||
'engine_thrust_n': [float(item['engine_thrust_n']) if item['engine_thrust_n'] is not None else 0 for item in equipment_data]
|
||
}
|
||
analysis_result.update(missile_data)
|
||
|
||
return jsonify(analysis_result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error analyzing features: {str(e)}")
|
||
logger.error("Detailed traceback:", exc_info=True)
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/train', methods=['POST'])
|
||
def train_model():
|
||
"""训练模型"""
|
||
try:
|
||
data = request.get_json()
|
||
logger.info(f"Starting model training for {data.get('type')}")
|
||
equipment_type = data.get('type')
|
||
train_dataset_id = data.get('train_dataset_id')
|
||
validation_dataset_id = data.get('validation_dataset_id')
|
||
models = data.get('models', [])
|
||
auto_split = data.get('auto_split', False) # 自动划分训练/验证集
|
||
split_ratio = data.get('split_ratio', 0.2) # 验证集比例,默认 20%
|
||
|
||
logger.info(f"Training dataset: {train_dataset_id}")
|
||
logger.info(f"Validation dataset: {validation_dataset_id}")
|
||
logger.info(f"Selected models: {models}")
|
||
|
||
# 获取训练数据
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 获取训练集数据(包含生产商信息)
|
||
if equipment_type == '火箭炮':
|
||
cursor.execute("""
|
||
SELECT e.*, cp.*, rap.*, cd.actual_cost,
|
||
m.tech_level, m.scale_level, m.supply_chain_level,
|
||
m.id as manufacturer_id
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN rocket_artillery_params rap ON e.id = rap.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
LEFT JOIN manufacturers m ON e.manufacturer_id = m.id
|
||
WHERE de.dataset_id = ?
|
||
AND cd.actual_cost IS NOT NULL
|
||
""", (train_dataset_id,))
|
||
else:
|
||
cursor.execute("""
|
||
SELECT e.*, cp.*, lmp.*, cd.actual_cost,
|
||
m.tech_level, m.scale_level, m.supply_chain_level,
|
||
m.id as manufacturer_id
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN loitering_munition_params lmp ON e.id = lmp.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
LEFT JOIN manufacturers m ON e.manufacturer_id = m.id
|
||
WHERE de.dataset_id = ?
|
||
AND cd.actual_cost IS NOT NULL
|
||
""", (train_dataset_id,))
|
||
|
||
train_data = cursor.fetchall()
|
||
|
||
# 获取验证集数据
|
||
if validation_dataset_id:
|
||
if equipment_type == '火箭炮':
|
||
cursor.execute("""
|
||
SELECT e.*, cp.*, rap.*, cd.actual_cost,
|
||
m.tech_level, m.scale_level, m.supply_chain_level,
|
||
m.id as manufacturer_id
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN rocket_artillery_params rap ON e.id = rap.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
LEFT JOIN manufacturers m ON e.manufacturer_id = m.id
|
||
WHERE de.dataset_id = ?
|
||
AND cd.actual_cost IS NOT NULL
|
||
""", (validation_dataset_id,))
|
||
else:
|
||
cursor.execute("""
|
||
SELECT e.*, cp.*, lmp.*, cd.actual_cost,
|
||
m.tech_level, m.scale_level, m.supply_chain_level,
|
||
m.id as manufacturer_id
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN loitering_munition_params lmp ON e.id = lmp.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
LEFT JOIN manufacturers m ON e.manufacturer_id = m.id
|
||
WHERE de.dataset_id = ?
|
||
AND cd.actual_cost IS NOT NULL
|
||
""", (validation_dataset_id,))
|
||
validation_data = cursor.fetchall()
|
||
|
||
if not train_data:
|
||
return jsonify({'error': '训练数据集为空'}), 400
|
||
|
||
# 1. 准备数据
|
||
data_processor = DataPreparation()
|
||
|
||
# 准备训练数据
|
||
if auto_split and not validation_dataset_id:
|
||
# 自动划分模式:从训练数据中随机拆分验证集
|
||
logger.info(f"Auto-splitting training data with validation ratio={split_ratio}")
|
||
train_prepared = data_processor.prepare_training_data(
|
||
train_data, equipment_type, validation_split=split_ratio
|
||
)
|
||
validation_prepared = {
|
||
'X': train_prepared.pop('X_val'),
|
||
'y': train_prepared.pop('y_val'),
|
||
}
|
||
else:
|
||
# 传统模式:单独查询验证数据集
|
||
train_prepared = data_processor.prepare_training_data(train_data, equipment_type)
|
||
validation_prepared = None
|
||
if validation_data:
|
||
validation_prepared = data_processor.prepare_validation_data(
|
||
validation_data,
|
||
equipment_type,
|
||
train_prepared['feature_names'],
|
||
{
|
||
'feature_scaler': train_prepared['feature_scaler'],
|
||
'target_scaler': train_prepared['target_scaler']
|
||
}
|
||
)
|
||
|
||
# 2. 训练模型
|
||
model_trainer = ModelTrainer()
|
||
model_trainer.feature_scaler = train_prepared['feature_scaler']
|
||
model_trainer.target_scaler = train_prepared['target_scaler']
|
||
|
||
# 执行训练,传入equipment_type参数
|
||
training_result = model_trainer.fit_model(
|
||
train_prepared['X'],
|
||
train_prepared['y'],
|
||
models,
|
||
validation_prepared['X'] if validation_prepared else None,
|
||
validation_prepared['y'] if validation_prepared else None,
|
||
equipment_type=equipment_type # 添加这个参数
|
||
)
|
||
|
||
return jsonify(training_result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in model training: {str(e)}")
|
||
logger.error("Detailed traceback:", exc_info=True)
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/evaluate', methods=['POST'])
|
||
def evaluate_model():
|
||
"""
|
||
模型评估接口
|
||
"""
|
||
try:
|
||
data = request.get_json()
|
||
logger.info("Received model evaluation request")
|
||
|
||
if 'test_data' not in data:
|
||
return jsonify({'error': 'Test data is required'}), 400
|
||
|
||
predictor = CostPredictor()
|
||
evaluation_result = predictor.evaluate(
|
||
data['test_data']['actual'],
|
||
data['test_data']['predicted']
|
||
)
|
||
|
||
logger.info("Model evaluation completed")
|
||
return jsonify(evaluation_result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in model evaluation: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
def get_required_params(equipment_type):
|
||
"""
|
||
根据装备类型获取必要参数
|
||
"""
|
||
common_params = [
|
||
'length_m',
|
||
'width_m',
|
||
'height_m',
|
||
'weight_kg',
|
||
'max_range_km'
|
||
]
|
||
|
||
if equipment_type == '火箭炮':
|
||
return common_params + [
|
||
'firing_angle_horizontal',
|
||
'firing_angle_vertical',
|
||
'rocket_length_m',
|
||
'rocket_diameter_mm',
|
||
'rocket_weight_kg'
|
||
]
|
||
elif equipment_type == '巡飞弹':
|
||
return common_params + [
|
||
'max_speed_kmh',
|
||
'cruise_speed_kmh',
|
||
'flight_time_min',
|
||
'folded_length_mm',
|
||
'folded_width_mm',
|
||
'folded_height_mm'
|
||
]
|
||
|
||
return common_params
|
||
|
||
@api_bp.errorhandler(404)
|
||
def not_found(error):
|
||
return jsonify({'error': 'Not found'}), 404
|
||
|
||
@api_bp.errorhandler(500)
|
||
def internal_error(error):
|
||
logger.error(f"Internal server error: {str(error)}")
|
||
return jsonify({'error': 'Internal server error'}), 500
|
||
|
||
@api_bp.route('/data', methods=['GET'])
|
||
def get_equipment_data():
|
||
"""获取装备数据列表"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 获取所有装备数据(使用equipment_id替代id)
|
||
cursor.execute("""
|
||
SELECT e.id as equipment_id, e.name, e.type, e.manufacturer,
|
||
cp.length_m, cp.width_m, cp.height_m, cp.weight_kg,
|
||
cd.actual_cost, cd.predicted_cost,
|
||
CASE
|
||
WHEN e.type = '火箭炮' THEN (
|
||
SELECT firing_angle_horizontal || ',' ||
|
||
firing_angle_vertical || ',' ||
|
||
rocket_length_m || ',' ||
|
||
rocket_diameter_mm || ',' ||
|
||
rocket_weight_kg || ',' ||
|
||
rate_of_fire || ',' ||
|
||
combat_weight_kg || ',' ||
|
||
speed_kmh || ',' ||
|
||
min_range_km || ',' ||
|
||
max_range_km || ',' ||
|
||
mobility_type || ',' ||
|
||
structure_layout || ',' ||
|
||
engine_model || ',' ||
|
||
engine_params || ',' ||
|
||
power_hp || ',' ||
|
||
travel_range_km
|
||
FROM rocket_artillery_params
|
||
WHERE equipment_id = e.id
|
||
)
|
||
WHEN e.type = '巡飞弹' THEN (
|
||
SELECT wingspan_m || ',' ||
|
||
warhead_weight_kg || ',' ||
|
||
max_speed_ms || ',' ||
|
||
cruise_speed_kmh || ',' ||
|
||
endurance_min || ',' ||
|
||
max_range_km || ',' ||
|
||
max_payload_kg || ',' ||
|
||
ceiling_altitude_m || ',' ||
|
||
combat_radius_km || ',' ||
|
||
warhead_type || ',' ||
|
||
launch_mode || ',' ||
|
||
power_system || ',' ||
|
||
guidance_system
|
||
FROM loitering_munition_params
|
||
WHERE equipment_id = e.id
|
||
)
|
||
END as specific_params
|
||
FROM equipments e
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
ORDER BY e.id
|
||
""")
|
||
|
||
result = cursor.fetchall()
|
||
logger.info(f"Found {len(result)} equipment records")
|
||
logger.info(f"Sample data: {result[0] if result else None}")
|
||
|
||
return jsonify(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error getting equipment data: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/data/<int:id>', methods=['DELETE'])
|
||
def delete_equipment(id):
|
||
"""
|
||
删除装备数据
|
||
"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 删除相关数据
|
||
cursor.execute("DELETE FROM cost_data WHERE equipment_id = ?", (id,))
|
||
cursor.execute("DELETE FROM rocket_artillery_params WHERE equipment_id = ?", (id,))
|
||
cursor.execute("DELETE FROM loitering_munition_params WHERE equipment_id = ?", (id,))
|
||
cursor.execute("DELETE FROM common_params WHERE equipment_id = ?", (id,))
|
||
cursor.execute("DELETE FROM equipments WHERE id = ?", (id,))
|
||
|
||
conn.commit()
|
||
|
||
return jsonify({'status': 'success'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error deleting equipment: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/data/template', methods=['GET'])
|
||
def download_template():
|
||
"""
|
||
下载数据模板
|
||
"""
|
||
try:
|
||
# 创建模板文件
|
||
from .create_template import create_excel_template
|
||
template_path = create_excel_template()
|
||
|
||
# 检查文件是否存
|
||
if not os.path.exists(template_path):
|
||
raise FileNotFoundError("模板文件不存在")
|
||
|
||
# 返回文件
|
||
return send_file(
|
||
template_path,
|
||
as_attachment=True,
|
||
download_name='equipment_data_template.xlsx',
|
||
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error creating template: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
|
||
@api_bp.route('/pls/predict', methods=['POST'])
|
||
def pls_predict():
|
||
"""使用PLS模型进行预测"""
|
||
try:
|
||
data = request.get_json()
|
||
equipment_type = data.get('type')
|
||
|
||
# 获取最新的PLS模型
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT * FROM trained_models
|
||
WHERE equipment_type = ?
|
||
AND model_type = 'pls'
|
||
AND is_active = TRUE
|
||
ORDER BY training_date DESC LIMIT 1
|
||
""", (equipment_type,))
|
||
model = cursor.fetchone()
|
||
|
||
if not model:
|
||
return jsonify({'error': '未找到可用的PLS模型'}), 404
|
||
|
||
# 使用普通predict方法,传入模型信息
|
||
predictor = CostPredictor()
|
||
prediction = predictor.predict(data, model) # 传入model参数
|
||
|
||
# 返回预测结果
|
||
return jsonify({
|
||
'model_info': {
|
||
'type': 'pls',
|
||
'name': model['model_name']
|
||
},
|
||
'predicted_cost': prediction['predicted_cost'],
|
||
'confidence_interval': prediction['confidence_interval']
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in PLS prediction: {str(e)}")
|
||
logger.error("Detailed traceback:", exc_info=True)
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/data/import', methods=['POST'])
|
||
def import_data():
|
||
"""
|
||
导入数据接口
|
||
"""
|
||
try:
|
||
if 'file' not in request.files:
|
||
return jsonify({'error': '没有上传文件'}), 400
|
||
|
||
file = request.files['file']
|
||
if not file.filename.endswith(('.xls', '.xlsx')):
|
||
return jsonify({'error': '请上传Excel文件'}), 400
|
||
|
||
# 保存上的文件
|
||
upload_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data')
|
||
os.makedirs(upload_dir, exist_ok=True)
|
||
file_path = os.path.join(upload_dir, file.filename)
|
||
file.save(file_path)
|
||
|
||
# 导入数据
|
||
from .import_data import import_training_data
|
||
import_training_data(file_path)
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '数据导入成功'
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error importing data: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/data/<int:id>', methods=['PUT'])
|
||
def update_equipment(id):
|
||
"""更新装备数据"""
|
||
try:
|
||
data = request.get_json()
|
||
logger.info(f"Updating equipment with data: {data}")
|
||
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 使用 equipment_id 而不是 id
|
||
equipment_id = data.get('equipment_id')
|
||
if not equipment_id:
|
||
raise ValueError("Missing equipment_id")
|
||
|
||
# 更新装备基本信息
|
||
cursor.execute("""
|
||
UPDATE equipments
|
||
SET name = ?, manufacturer = ?
|
||
WHERE id = ?
|
||
""", (data['name'], data['manufacturer'], equipment_id))
|
||
|
||
# 更新通用参数
|
||
cursor.execute("""
|
||
UPDATE common_params
|
||
SET length_m = ?, width_m = ?, height_m = ?, weight_kg = ?
|
||
WHERE equipment_id = ?
|
||
""", (data['length_m'], data['width_m'], data['height_m'], data['weight_kg'], equipment_id))
|
||
|
||
# 根据装备类型更新特有参数
|
||
if data['type'] == '火箭炮':
|
||
cursor.execute("""
|
||
UPDATE rocket_artillery_params
|
||
SET firing_angle_horizontal = ?,
|
||
firing_angle_vertical = ?,
|
||
rocket_length_m = ?,
|
||
rocket_diameter_mm = ?,
|
||
rocket_weight_kg = ?,
|
||
rate_of_fire = ?,
|
||
combat_weight_kg = ?,
|
||
speed_kmh = ?,
|
||
min_range_km = ?,
|
||
max_range_km = ?,
|
||
mobility_type = ?,
|
||
structure_layout = ?,
|
||
engine_model = ?,
|
||
engine_params = ?,
|
||
power_hp = ?,
|
||
travel_range_km = ?
|
||
WHERE equipment_id = ?
|
||
""", (
|
||
data['firing_angle_horizontal'],
|
||
data['firing_angle_vertical'],
|
||
data['rocket_length_m'],
|
||
data['rocket_diameter_mm'],
|
||
data['rocket_weight_kg'],
|
||
data['rate_of_fire'],
|
||
data['combat_weight_kg'],
|
||
data['speed_kmh'],
|
||
data['min_range_km'],
|
||
data['max_range_km'],
|
||
data['mobility_type'],
|
||
data['structure_layout'],
|
||
data['engine_model'],
|
||
data['engine_params'],
|
||
data['power_hp'],
|
||
data['travel_range_km'],
|
||
equipment_id
|
||
))
|
||
else:
|
||
cursor.execute("""
|
||
UPDATE loitering_munition_params
|
||
SET wingspan_m = ?,
|
||
warhead_weight_kg = ?,
|
||
max_speed_ms = ?,
|
||
cruise_speed_kmh = ?,
|
||
endurance_min = ?,
|
||
max_range_km = ?,
|
||
warhead_type = ?,
|
||
launch_mode = ?,
|
||
power_system = ?,
|
||
guidance_system = ?
|
||
WHERE equipment_id = ?
|
||
""", (
|
||
data['wingspan_m'],
|
||
data['warhead_weight_kg'],
|
||
data['max_speed_ms'],
|
||
data['cruise_speed_kmh'],
|
||
data['endurance_min'],
|
||
data['max_range_km'],
|
||
data['warhead_type'],
|
||
data['launch_mode'],
|
||
data['power_system'],
|
||
data['guidance_system'],
|
||
equipment_id
|
||
))
|
||
|
||
# 更新成本数据
|
||
if 'actual_cost' in data:
|
||
cursor.execute("""
|
||
UPDATE cost_data
|
||
SET actual_cost = ?
|
||
WHERE equipment_id = ?
|
||
""", (data['actual_cost'], equipment_id))
|
||
|
||
conn.commit()
|
||
return jsonify({'success': True})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error updating equipment: {str(e)}")
|
||
logger.error("Detailed traceback:", exc_info=True)
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/data/details/<int:id>', methods=['GET'])
|
||
def get_equipment_details(id):
|
||
"""获取装备详情"""
|
||
try:
|
||
logger.info(f"Getting details for equipment ID: {id}")
|
||
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 获取装备基本信息类型
|
||
cursor.execute("""
|
||
SELECT e.*, cp.*, cd.actual_cost, cd.predicted_cost
|
||
FROM equipments e
|
||
LEFT JOIN common_params cp ON e.id = cp.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
WHERE e.id = ?
|
||
""", (id,))
|
||
|
||
result = cursor.fetchone()
|
||
if not result:
|
||
logger.warning(f"Equipment with ID {id} not found")
|
||
return jsonify({'error': '装备不在'}), 404
|
||
|
||
logger.info(f"Equipment type: {result['type']}")
|
||
logger.info(f"Found equipment details: {result['name']}")
|
||
|
||
# 根据装备类型获取特有参数
|
||
if result['type'] == '火箭炮':
|
||
cursor.execute("""
|
||
SELECT *
|
||
FROM rocket_artillery_params
|
||
WHERE equipment_id = ?
|
||
""", (id,))
|
||
custom_params = cursor.fetchone()
|
||
else:
|
||
cursor.execute("""
|
||
SELECT *
|
||
FROM loitering_munition_params
|
||
WHERE equipment_id = ?
|
||
""", (id,))
|
||
custom_params = cursor.fetchone()
|
||
|
||
# 合并特有参数到结果中
|
||
if custom_params:
|
||
result.update(custom_params)
|
||
|
||
logger.info(f"Custom params: {custom_params}")
|
||
|
||
return jsonify(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error getting equipment details: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
# 添加数据集相关的路由
|
||
@api_bp.route('/datasets', methods=['GET'])
|
||
def get_datasets():
|
||
"""
|
||
获取数据集列表
|
||
"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT d.*,
|
||
COUNT(de.equipment_id) as equipment_count,
|
||
GROUP_CONCAT(e.name) as equipment_names
|
||
FROM datasets d
|
||
LEFT JOIN dataset_equipments de ON d.id = de.dataset_id
|
||
LEFT JOIN equipments e ON de.equipment_id = e.id
|
||
GROUP BY d.id
|
||
""")
|
||
datasets = cursor.fetchall()
|
||
|
||
# 整理装备名称列表
|
||
for dataset in datasets:
|
||
if dataset['equipment_names']:
|
||
dataset['equipment_names'] = dataset['equipment_names'].split(',')
|
||
else:
|
||
dataset['equipment_names'] = []
|
||
|
||
# SQLite 已存储日期为文本格式,无需转换
|
||
|
||
return jsonify(datasets)
|
||
except Exception as e:
|
||
logger.error(f"Error getting datasets: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/datasets/<int:id>', methods=['GET'])
|
||
def get_dataset(id):
|
||
"""
|
||
获取数据集详情
|
||
"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
# 获取数据集基本信息
|
||
cursor.execute("""
|
||
SELECT d.*,
|
||
COUNT(de.equipment_id) as equipment_count
|
||
FROM datasets d
|
||
LEFT JOIN dataset_equipments de ON d.id = de.dataset_id
|
||
WHERE d.id = ?
|
||
GROUP BY d.id
|
||
""", (id,))
|
||
dataset = cursor.fetchone()
|
||
|
||
if not dataset:
|
||
return jsonify({'error': 'Dataset not found'}), 404
|
||
|
||
# 获取数据集中的装备 - 修改查询,确保返回正确的ID
|
||
cursor.execute("""
|
||
SELECT e.id as equipment_id, e.name, e.type, e.manufacturer,
|
||
cd.actual_cost
|
||
FROM equipments e
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
LEFT JOIN cost_data cd ON e.id = cd.equipment_id
|
||
WHERE de.dataset_id = ?
|
||
""", (id,))
|
||
equipment = cursor.fetchall()
|
||
|
||
# 算统计信
|
||
if equipment:
|
||
total_cost = sum(item['actual_cost'] or 0 for item in equipment)
|
||
avg_cost = total_cost / len(equipment)
|
||
dataset['statistics'] = {
|
||
'equipment_count': len(equipment),
|
||
'total_cost': total_cost,
|
||
'average_cost': avg_cost
|
||
}
|
||
else:
|
||
dataset['statistics'] = {
|
||
'equipment_count': 0,
|
||
'total_cost': 0,
|
||
'average_cost': 0
|
||
}
|
||
|
||
dataset['equipment'] = equipment
|
||
|
||
return jsonify(dataset)
|
||
except Exception as e:
|
||
logger.error(f"Error getting dataset: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/datasets', methods=['POST'])
|
||
def create_dataset():
|
||
"""创建数据集"""
|
||
try:
|
||
data = request.get_json()
|
||
logger.info(f"Creating dataset with data: {data}")
|
||
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 1. 验证装备ID是否存在
|
||
if 'equipment_ids' in data and data['equipment_ids']:
|
||
ids = data['equipment_ids']
|
||
placeholders = ','.join(['?'] * len(ids))
|
||
cursor.execute(f"""
|
||
SELECT DISTINCT id FROM equipments
|
||
WHERE id IN ({placeholders}) AND type = ?
|
||
""", (*ids, data['equipment_type']))
|
||
|
||
valid_ids = [row['id'] for row in cursor.fetchall()]
|
||
logger.info(f"Valid equipment IDs: {valid_ids}")
|
||
|
||
# 如果有无效的ID,返回错误
|
||
invalid_ids = set(data['equipment_ids']) - set(valid_ids)
|
||
if invalid_ids:
|
||
logger.error(f"Invalid equipment IDs: {invalid_ids}")
|
||
return jsonify({'error': f'无效的装备ID: {invalid_ids}'}), 400
|
||
|
||
# 2. 创建数据集
|
||
cursor.execute("""
|
||
INSERT INTO datasets (name, description, equipment_type, purpose)
|
||
VALUES (?, ?, ?, ?)
|
||
""", (data['name'], data['description'], data['equipment_type'], data['purpose']))
|
||
|
||
dataset_id = cursor.lastrowid
|
||
logger.info(f"Created dataset with ID: {dataset_id}")
|
||
|
||
# 3. 添加装备关联
|
||
if 'equipment_ids' in data and data['equipment_ids']:
|
||
values = [(dataset_id, equipment_id) for equipment_id in valid_ids]
|
||
cursor.executemany("""
|
||
INSERT INTO dataset_equipments (dataset_id, equipment_id)
|
||
VALUES (?, ?)
|
||
""", values)
|
||
logger.info(f"Added {len(values)} equipment associations")
|
||
|
||
conn.commit()
|
||
return jsonify({'id': dataset_id, 'message': '数据集创建成功'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error creating dataset: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/datasets/<int:id>', methods=['PUT'])
|
||
def update_dataset(id):
|
||
"""更新数据集"""
|
||
try:
|
||
data = request.get_json()
|
||
logger.info(f"Updating dataset {id} with data: {data}")
|
||
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 1. 验证装备ID是否存在
|
||
if 'equipment_ids' in data and data['equipment_ids']:
|
||
ids = data['equipment_ids']
|
||
placeholders = ','.join(['?'] * len(ids))
|
||
cursor.execute(f"""
|
||
SELECT id FROM equipments
|
||
WHERE id IN ({placeholders}) AND type = ?
|
||
""", (*ids, data['equipment_type']))
|
||
|
||
valid_ids = [row['id'] for row in cursor.fetchall()]
|
||
logger.info(f"Valid equipment IDs: {valid_ids}")
|
||
|
||
# 如果有无效的ID,返回错误
|
||
invalid_ids = set(data['equipment_ids']) - set(valid_ids)
|
||
if invalid_ids:
|
||
logger.error(f"Invalid equipment IDs: {invalid_ids}")
|
||
return jsonify({'error': f'无效的装备ID: {invalid_ids}'}), 400
|
||
|
||
# 2. 更新数据集基本信息
|
||
cursor.execute("""
|
||
UPDATE datasets
|
||
SET name = ?, description = ?, equipment_type = ?, purpose = ?
|
||
WHERE id = ?
|
||
""", (data['name'], data['description'], data['equipment_type'], data['purpose'], id))
|
||
|
||
# 3. 更新装备关联
|
||
if 'equipment_ids' in data:
|
||
# 先删除旧的关联
|
||
cursor.execute("DELETE FROM dataset_equipments WHERE dataset_id = ?", (id,))
|
||
|
||
# 添加新的关联
|
||
if valid_ids: # 确保有有效的ID才执行插入
|
||
values = [(id, equipment_id) for equipment_id in valid_ids]
|
||
cursor.executemany("""
|
||
INSERT INTO dataset_equipments (dataset_id, equipment_id)
|
||
VALUES (?, ?)
|
||
""", values)
|
||
logger.info(f"Updated {len(values)} equipment associations")
|
||
|
||
conn.commit()
|
||
return jsonify({'success': True})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error updating dataset: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/datasets/<int:id>', methods=['DELETE'])
|
||
def delete_dataset(id):
|
||
"""
|
||
删除数据集
|
||
"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 删除装备关联
|
||
cursor.execute("DELETE FROM dataset_equipments WHERE dataset_id = ?", (id,))
|
||
|
||
# 删除数据集
|
||
cursor.execute("DELETE FROM datasets WHERE id = ?", (id,))
|
||
|
||
conn.commit()
|
||
return jsonify({'success': True})
|
||
except Exception as e:
|
||
logger.error(f"Error deleting dataset: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/models/<equipment_type>/latest', methods=['GET'])
|
||
def get_latest_model(equipment_type):
|
||
"""
|
||
获取最新训练的模型信息
|
||
"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT * FROM trained_models
|
||
WHERE equipment_type = ? AND is_active = TRUE
|
||
ORDER BY training_date DESC LIMIT 1
|
||
""", (equipment_type,))
|
||
|
||
model = cursor.fetchone()
|
||
return jsonify(model)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error getting latest model: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/models', methods=['GET'])
|
||
def get_models():
|
||
"""获取模型列表"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT * FROM trained_models
|
||
ORDER BY training_date DESC
|
||
""")
|
||
|
||
models = cursor.fetchall()
|
||
|
||
# 格式化时间和数值字段
|
||
for model in models:
|
||
# SQLite 已存储日期为文本格式,无需转换
|
||
if model['r2_score'] is not None:
|
||
model['r2_score'] = float(model['r2_score'])
|
||
if model['mae'] is not None:
|
||
model['mae'] = float(model['mae'])
|
||
if model['rmse'] is not None:
|
||
model['rmse'] = float(model['rmse'])
|
||
|
||
# 解析特征重要性
|
||
if model['feature_importance']:
|
||
model['feature_importance'] = json.loads(model['feature_importance'])
|
||
|
||
return jsonify(models)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error getting models: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/models/<int:id>/activate', methods=['POST'])
|
||
def activate_model(id):
|
||
"""激活指定的模型"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor() # 使用字典游标
|
||
|
||
# 获取模型信息
|
||
cursor.execute("""
|
||
SELECT equipment_type, model_type FROM trained_models
|
||
WHERE id = ?
|
||
""", (id,))
|
||
model = cursor.fetchone()
|
||
|
||
if not model:
|
||
return jsonify({'error': 'Model not found'}), 404
|
||
|
||
# 将同类型的其他模型设置为非激活
|
||
cursor.execute("""
|
||
UPDATE trained_models
|
||
SET is_active = FALSE
|
||
WHERE equipment_type = ? AND model_type = ?
|
||
""", (model['equipment_type'], model['model_type']))
|
||
|
||
# 激活指定模型
|
||
cursor.execute("""
|
||
UPDATE trained_models
|
||
SET is_active = TRUE
|
||
WHERE id = ?
|
||
""", (id,))
|
||
|
||
conn.commit()
|
||
return jsonify({'success': True})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error activating model: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/models/<int:id>', methods=['DELETE'])
|
||
def delete_model(id):
|
||
"""
|
||
删除指定的模型
|
||
"""
|
||
try:
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 获取模型文件路径
|
||
cursor.execute("""
|
||
SELECT model_path, scaler_path
|
||
FROM trained_models
|
||
WHERE id = ?
|
||
""", (id,))
|
||
model = cursor.fetchone()
|
||
|
||
if not model:
|
||
return jsonify({'error': 'Model not found'}), 404
|
||
|
||
# 删除模型文件
|
||
if os.path.exists(model['model_path']):
|
||
os.remove(model['model_path'])
|
||
if os.path.exists(model['scaler_path']):
|
||
os.remove(model['scaler_path'])
|
||
|
||
# 删除数据库记录
|
||
cursor.execute("DELETE FROM trained_models WHERE id = ?", (id,))
|
||
conn.commit()
|
||
|
||
return jsonify({'success': True})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error deleting model: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/predict/all', methods=['POST'])
|
||
def predict_all():
|
||
"""
|
||
获取所有机学习模型的预测结果
|
||
"""
|
||
try:
|
||
data = request.get_json()
|
||
logger.info(f"Received prediction request for all models, equipment type: {data.get('type')}")
|
||
|
||
predictor = CostPredictor()
|
||
results = predictor.predict_all(data)
|
||
|
||
return jsonify(results)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in prediction: {str(e)}")
|
||
return jsonify({'error': str(e)}), 500
|
||
|
||
@api_bp.route('/analyze-manufacturers', methods=['POST'])
|
||
def analyze_manufacturers():
|
||
"""分析生产商数据"""
|
||
try:
|
||
data = request.get_json()
|
||
dataset_id = data.get('dataset_id')
|
||
|
||
logger.info(f"Starting manufacturer analysis for dataset {dataset_id}")
|
||
|
||
if not dataset_id:
|
||
logger.warning("No dataset_id provided")
|
||
return jsonify({'error': '请选择数据集'}), 400
|
||
|
||
with get_db_connection() as conn:
|
||
cursor = conn.cursor()
|
||
|
||
# 获取数据集中的装备和生产商数据
|
||
cursor.execute("""
|
||
SELECT DISTINCT m.*, e.type as equipment_type
|
||
FROM manufacturers m
|
||
JOIN equipments e ON e.manufacturer_id = m.id
|
||
JOIN dataset_equipments de ON e.id = de.equipment_id
|
||
WHERE de.dataset_id = ?
|
||
""", (dataset_id,))
|
||
|
||
manufacturers = cursor.fetchall()
|
||
|
||
if not manufacturers:
|
||
logger.info("No manufacturer data in dataset, returning empty result")
|
||
return jsonify({
|
||
'manufacturer_names': [],
|
||
'manufacturer_tech_levels': [],
|
||
'manufacturer_scale_levels': [],
|
||
'manufacturer_supply_chain_levels': [],
|
||
'manufacturer_composite_scores': [],
|
||
'region_distribution': [],
|
||
'manufacturer_scores': []
|
||
})
|
||
|
||
# 准备分析数据
|
||
manufacturer_names = []
|
||
tech_levels = []
|
||
scale_levels = []
|
||
supply_chain_levels = []
|
||
composite_scores = []
|
||
region_count = {}
|
||
manufacturer_scores = []
|
||
|
||
for manu in manufacturers:
|
||
manufacturer_names.append(manu['name'])
|
||
tech_levels.append(manu['tech_level'])
|
||
scale_levels.append(manu['scale_level'])
|
||
supply_chain_levels.append(manu['supply_chain_level'])
|
||
|
||
# 计算综合得分
|
||
composite_score = (
|
||
manu['tech_level'] * 0.4 +
|
||
manu['scale_level'] * 0.3 +
|
||
manu['supply_chain_level'] * 0.3
|
||
)
|
||
composite_scores.append(composite_score)
|
||
|
||
# 统计地区分布
|
||
region_count[manu['country']] = region_count.get(manu['country'], 0) + 1
|
||
|
||
# 计算区域系数
|
||
region_factors = {
|
||
'美国': 1.2, '英国': 1.15, '德国': 1.15,
|
||
'法国': 1.15, '以色列': 1.1, '中国': 0.8,
|
||
'俄罗斯': 0.85, '韩国': 0.9, '日本': 1.1
|
||
}
|
||
region_factor = region_factors.get(manu['country'], 1.0)
|
||
|
||
# 添加雷达图数据
|
||
manufacturer_scores.append({
|
||
'name': manu['name'],
|
||
'value': [
|
||
manu['tech_level'],
|
||
manu['scale_level'],
|
||
manu['supply_chain_level'],
|
||
region_factor,
|
||
composite_score
|
||
]
|
||
})
|
||
|
||
# 准备地区分布数据
|
||
region_distribution = [
|
||
{'name': country, 'value': count}
|
||
for country, count in region_count.items()
|
||
]
|
||
|
||
# 返回分析结果
|
||
result = {
|
||
'manufacturer_names': manufacturer_names,
|
||
'manufacturer_tech_levels': tech_levels,
|
||
'manufacturer_scale_levels': scale_levels,
|
||
'manufacturer_supply_chain_levels': supply_chain_levels,
|
||
'manufacturer_composite_scores': composite_scores,
|
||
'region_distribution': region_distribution,
|
||
'manufacturer_scores': manufacturer_scores
|
||
}
|
||
|
||
return jsonify(result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error analyzing manufacturers: {str(e)}")
|
||
logger.error("Detailed traceback:", exc_info=True)
|
||
return jsonify({'error': str(e)}), 500
|