feat: 预测页面显示可用模型列表(含精度),默认最优,可手动切换

This commit is contained in:
tian 2026-06-10 10:42:14 +08:00
parent a8a15fe0b0
commit 003dc851f3
3 changed files with 106 additions and 13 deletions

View File

@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<title>装备成本估算系统</title> <title>装备成本估算系统</title>
<script type="module" crossorigin src="/assets/index-9zP5mdZu.js"></script> <script type="module" crossorigin src="/assets/index-BuNHhthn.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-xz9THzb6.css"> <link rel="stylesheet" crossorigin href="/assets/index-BEQL0wGd.css">
</head> </head>
<body> <body>
<noscript> <noscript>

View File

@ -14,6 +14,22 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 预测模型选择 -->
<el-form-item label="预测模型">
<el-select v-model="selectedModelId" placeholder="自动选择最优" style="width: 100%" @change="handleModelChange">
<el-option label="自动选择(最优模型)" :value="null" />
<el-option
v-for="m in availableModels"
:key="m.id"
:label="`${getModelLabel(m)}`"
:value="m.id"
>
<span>{{ getModelLabel(m) }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">={{ m.r2 }}</span>
</el-option>
</el-select>
</el-form-item>
<!-- 通用参数 --> <!-- 通用参数 -->
<el-form-item label="总长(m)" prop="length_m"> <el-form-item label="总长(m)" prop="length_m">
<el-input-number v-model="formData.length_m" :precision="2"></el-input-number> <el-input-number v-model="formData.length_m" :precision="2"></el-input-number>
@ -238,6 +254,8 @@ const formData = reactive({
const predictionResults = ref(null) const predictionResults = ref(null)
const mlPrediction = ref(null) const mlPrediction = ref(null)
const plsPrediction = ref(null) const plsPrediction = ref(null)
const availableModels = ref([])
const selectedModelId = ref(null)
const submitForm = async () => { const submitForm = async () => {
try { try {
@ -277,10 +295,16 @@ const submitForm = async () => {
} }
} }
//
const predictData = { ...formData }
if (selectedModelId.value) {
predictData.model_id = selectedModelId.value
}
// //
const [mlResponse, plsResponse] = await Promise.all([ const [mlResponse, plsResponse] = await Promise.all([
axios.post(`${API_BASE_URL}/predict`, formData), axios.post(`${API_BASE_URL}/predict`, predictData),
axios.post(`${API_BASE_URL}/pls/predict`, formData) axios.post(`${API_BASE_URL}/pls/predict`, predictData)
]) ])
mlPrediction.value = mlResponse.data mlPrediction.value = mlResponse.data
@ -352,6 +376,13 @@ const handleTypeChange = () => {
predictionResults.value = false predictionResults.value = false
mlPrediction.value = null mlPrediction.value = null
plsPrediction.value = null plsPrediction.value = null
selectedModelId.value = null
availableModels.value = []
//
if (formData.type) {
loadModels(formData.type)
}
// //
if (formData.type === '火箭炮') { if (formData.type === '火箭炮') {
@ -425,6 +456,30 @@ const handleTypeChange = () => {
formData.travel_range_km = null formData.travel_range_km = null
} }
} }
const loadModels = async (equipmentType) => {
try {
const res = await axios.get(`${API_BASE_URL}/models/predict`, {
params: { equipment_type: equipmentType }
})
availableModels.value = res.data
} catch (e) {
console.error('Failed to load models:', e)
}
}
const handleModelChange = () => {
predictionResults.value = false
mlPrediction.value = null
}
const getModelLabel = (m) => {
const names = {
'pytorch': 'PyTorch', 'xgboost': 'XGBoost', 'lightgbm': 'LightGBM',
'gbm': 'GBM', 'rf': 'Random Forest', 'pls': 'PLS回归'
}
return `${names[m.type] || m.type} R²=${m.r2} MAE=${Number(m.mae).toLocaleString()}`
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -104,19 +104,27 @@ def predict():
try: try:
data = request.get_json() data = request.get_json()
equipment_type = data.get('type') equipment_type = data.get('type')
model_id = data.get('model_id') # 可选指定模型ID
logger.info(f"Received prediction request for equipment type: {equipment_type}") logger.info(f"Received prediction request for equipment type: {equipment_type}")
# 获取最新的激活模型非PLS尝试加载直到成功 # 获取可用模型默认R²最高的可选指定model_id
with get_db_connection() as conn: with get_db_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(""" if model_id:
SELECT * FROM trained_models cursor.execute("""
WHERE equipment_type = ? SELECT * FROM trained_models
AND model_type != 'pls' WHERE id = ? AND is_active = TRUE
AND is_active = TRUE """, (model_id,))
ORDER BY training_date DESC models = cursor.fetchall()
""", (equipment_type,)) else:
models = cursor.fetchall() cursor.execute("""
SELECT * FROM trained_models
WHERE equipment_type = ?
AND model_type != 'pls'
AND is_active = TRUE
ORDER BY r2_score DESC
""", (equipment_type,))
models = cursor.fetchall()
if not models: if not models:
return jsonify({'error': '未找到可用的模型'}), 404 return jsonify({'error': '未找到可用的模型'}), 404
@ -148,6 +156,36 @@ def predict():
logger.error("Detailed traceback:", exc_info=True) logger.error("Detailed traceback:", exc_info=True)
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@api_bp.route('/models/predict', methods=['GET'])
def get_predict_models():
"""获取可用于预测的模型列表含精度指标按R²排序"""
try:
equipment_type = request.args.get('equipment_type', '')
if not equipment_type:
return jsonify({'error': '请提供装备类型'}), 400
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT id, model_name, model_type, r2_score, mae, rmse
FROM trained_models
WHERE equipment_type = ? AND is_active = TRUE
ORDER BY r2_score DESC
""", (equipment_type,))
models = cursor.fetchall()
return jsonify([{
'id': m['id'],
'name': m['model_name'],
'type': m['model_type'],
'r2': round(m['r2_score'], 4) if m['r2_score'] else 0,
'mae': round(m['mae'], 2) if m['mae'] else 0,
'rmse': round(m['rmse'], 2) if m['rmse'] else 0,
} for m in models])
except Exception as e:
logger.error(f"Error fetching predict models: {str(e)}")
return jsonify({'error': str(e)}), 500
@api_bp.route('/analyze-features', methods=['POST']) @api_bp.route('/analyze-features', methods=['POST'])
def analyze_features(): def analyze_features():
"""基于数据集进行特征分析""" """基于数据集进行特征分析"""