CostPrediction/docs/debug.md
2024-11-08 23:43:57 +08:00

12 KiB
Raw Blame History

调试记录

特殊参数显示问题

问题描述

在数据管理页面中,装备详情对话框的特殊参数部分显示为空行或不显示。

调试步骤

  1. 后端数据查询
# 测试特殊参数查询
SELECT equipment_id, param_name, param_value, param_unit 
FROM custom_params 
WHERE param_name IS NOT NULL 
AND param_value IS NOT NULL
LIMIT 5
  1. 日志记录
logging.info(f"Getting details for equipment ID: {id}")
logging.info(f"Equipment type: {equipment_type}")
logging.info(f"Found equipment details: {result['name']}")
logging.info(f"Custom params: {result.get('custom_params')}")
  1. 前端调试
console.log('Requesting details for row:', row)
console.log('Details response:', response.data)
console.log('Custom params:', response.data.custom_params)
console.log('Selected data:', selectedData.value)

关键发现

  1. 数据库查询
  • 特殊参数表中有数据
  • JSON_ARRAYAGG 返回的格式需要处理
  • 需要过滤掉 NULL 值
  1. 数据格式
  • 后端返回的特殊参数是 JSON 字符串
  • 需要在前端解析为数组
  • 确保数组不为空
  1. 前端渲染
  • 条件判断需要更严格
  • 需要确保数据类型正确
  • 需要正确格式化显示值

解决方案

  1. 后端查询优化
(
    SELECT JSON_ARRAYAGG(
        JSON_OBJECT(
            'id', csp.id,
            'param_name', csp.param_name,
            'param_value', csp.param_value,
            'param_unit', csp.param_unit,
            'description', csp.description
        )
    )
    FROM custom_params csp
    WHERE csp.equipment_id = e.id
    AND csp.param_name IS NOT NULL
    AND csp.param_value IS NOT NULL
) as custom_params
  1. 前端数据处理
// 确保 custom_params 是数组
if (typeof response.data.custom_params === 'string') {
    response.data.custom_params = JSON.parse(response.data.custom_params)
}
  1. 渲染条件优化
<template v-if="selectedData?.custom_params && Array.isArray(selectedData.custom_params) && selectedData.custom_params.length > 0">

最佳实践

  1. 数据库查询
  • 使用子查询而不是 JOIN 获取特殊参数
  • 确保返回格式统一
  • 过滤无效数据
  1. 数据处理
  • 统一数据格式
  • 处理空值和异常
  • 保持类型一致
  1. 前端显示
  • 严格的条件判断
  • 类型检查
  • 格式化显示
  1. 调试方法
  • 使用日志跟踪数据流
  • 检查数据格式和类型
  • 验证每个环节的数据

编辑对话框问题

问题描述

在数据管理页面中,编辑对话框的成本信息分区和特殊参数分区显示不正确。

调试步骤

  1. 检查数据流
console.log('Editing row:', row)
console.log('Edit data response:', response.data)
console.log('Parsed custom params:', data.custom_params)
console.log('Edit form data:', editForm.value)
  1. 检查模板结构
<!-- 错误的嵌套结构 -->
<el-form>
  <template>
    <el-divider>成本信息</el-divider>
  </template>
</el-form>

<!-- 正确的结构 -->
<el-divider>成本信息</el-divider>
<el-form>
  <!-- 表单项 -->
</el-form>

关键发现

  1. 模板结构问题
  • el-divider 不应该嵌套在 template 中
  • 每个分区需要独立的 el-form
  • 避免不必要的 template 嵌套
  1. 数据类型问题
  • 后端返回的数值是字符串类型
  • el-input-number 组件需要数值类型
  • 需要在前端进行类型转换
  1. 条件渲染问题
  • v-if 条件过于严格可能导致内容不显示
  • 某些字段应该始终显示
  • 某些字段只在有值时显示

解决方案

  1. 修改模板结构
<!-- 成本信息 -->
<el-divider content-position="left">成本信息</el-divider>
<el-form :model="editForm" label-width="120px">
  <el-form-item label="实际成本(元)">
    <el-input-number v-model="editForm.actual_cost"></el-input-number>
  </el-form-item>
</el-form>
  1. 数据类型转换
// 转换所有数值类型字段
Object.keys(data).forEach(key => {
  if (isNumberInput(key) && data[key] !== null && data[key] !== undefined) {
    data[key] = Number(data[key])
  }
})
  1. 优化条件渲染
<!-- 始终显示必要字段 -->
<el-form-item label="实际成本(元)">
  <el-input-number v-model="editForm.actual_cost"></el-input-number>
</el-form-item>

<!-- 只在有值时显示可选字段 -->
<el-form-item label="预测成本(元)" v-if="editForm.predicted_cost">
  <el-input-number v-model="editForm.predicted_cost" disabled></el-input-number>
</el-form-item>

最佳实践

  1. 模板结构
  • 保持清晰的分区结构
  • 避免不必要的嵌套
  • 使用合适的组件层级
  1. 数据处理
  • 在获取数据后立即进行类型转换
  • 确保数据类型与组件要求匹配
  • 处理好空值和未定义值
  1. 条件渲染
  • 合理使用 v-if 和 v-show
  • 必要字段始终显示
  • 可选字段根据条件显示
  1. 调试方法
  • 使用 console.log 跟踪数据流
  • 检查组件的属性要求
  • 验证数据类型和结构

特征分析功能问题

问题描述

特征分析页面中,第一次点击分析按钮时,图表不显示,只有标题栏。第二次点击才能正常显示图表。

调试步骤

  1. 检查数据流
console.log('Analysis result:', analysisResult.value)
console.log('Charts not ready:', {
  importanceChartRef: !!importanceChartRef.value,
  correlationChartRef: !!correlationChartRef.value,
  analysisResult: !!analysisResult.value
})
  1. 检查渲染时机
// 使用 watch 监听分析结果变化
watch(() => analysisResult.value, async (newResult) => {
  if (newResult) {
    await nextTick()
    setTimeout(() => {
      renderCharts()
    }, 100)
  }
}, { deep: true })
  1. 检查图表实例管理
// 销毁旧的图表实例
if (importanceChart.value) {
  importanceChart.value.dispose()
}
if (correlationChart.value) {
  correlationChart.value.dispose()
}

关键发现

  1. 渲染时机问题
  • DOM 元素可能还未准备好
  • 数据更新后需要等待 DOM 更新
  • 需要正确管理图表实例
  1. 图表实例管理
  • 需要保存图表实例的引用
  • 重新渲染前需要销毁旧实例
  • 组件卸载时需要清理实例
  1. 数据格式问题
  • 特征名称需要中文映射
  • 相关性数据需要保留2位小数
  • 需要正确处理缺失值

解决方案

  1. 优化渲染逻辑
// 使用 nextTick 和延时确保 DOM 已更新
await nextTick()
setTimeout(() => {
  renderCharts()
}, 100)
  1. 完善图表实例管理
// 保存图表实例的引用
const importanceChart = ref(null)
const correlationChart = ref(null)

// 组件卸载时清理
onUnmounted(() => {
  importanceChart.value?.dispose()
  correlationChart.value?.dispose()
})
  1. 优化数据处理
// 使用中文特征名
chinese_feature_names = [self.feature_names_map.get(name, name) for name in feature_names]

// 保留2位小数
correlation_data.append([
  i, j, 
  round(correlation_matrix[i][j], 2)
])

最佳实践

  1. 渲染控制
  • 使用 watch 监听数据变化
  • 使用 nextTick 等待 DOM 更新
  • 添加适当的延时确保渲染
  1. 实例管理
  • 保存图表实例引用
  • 及时销毁旧实例
  • 组件卸载时清理
  1. 数据处理
  • 统一使用中文特征名
  • 控制数值精度
  • 处理好缺失值
  1. 调试方法
  • 添加详细的日志记录
  • 检查 DOM 元素状态
  • 验证数据格式

页面状态保持问题

问题描述

特征分析<EFBFBD><EFBFBD><EFBFBD>面切换到其他页面后再返回页面状态分析结果和图表会丢失需要重新分析。

调试步骤

  1. 检查路由配置
// 错误的配置
<keep-alive include="AnalysisPage">
  <router-view></router-view>
</keep-alive>

// 正确的配置
<router-view v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" :key="$route.fullPath" />
  </keep-alive>
</router-view>
  1. 检查组件定义
// 错误的组件名称定义
<script setup name="AnalysisPage">

// 正确的组件名称定义
const __name = 'AnalysisPage'

关键发现

  1. keep-alive 配置问题
  • 需要使用 v-slot API
  • 需要使用动态组件
  • 需要添加 key 属性
  1. 组件定义问题
  • setup 语法糖不支持直接添加 name
  • 需要使用 __name 或 defineOptions
  1. 缓存范围问题
  • 不需要指定 include 属性
  • 缓存所有路由组件更简单
  • 避免组件名称不匹配的问题

解决方案

  1. 修改路由视图配置
<router-view v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" :key="$route.fullPath" />
  </keep-alive>
</router-view>
  1. 修改组件定义
const __name = 'AnalysisPage'

最佳实践

  1. 路由配置
  • 使用 Vue3 的新 API
  • 保持配置简单清晰
  • 避免不必要的限制
  1. 组件定义
  • 使用推荐的方式定义组件名
  • 避免使用已废弃的语法
  • 保持代码一致性
  1. 状态管理
  • 合理使用 keep-alive
  • 正确处理组件生命周期
  • 注意清理工作
  1. 调试方法
  • 检查组件是否被缓存
  • 验证状态是否保持
  • 确认生命周期钩子的执行

代码修改最佳实践

1. 修改前的准备

  1. 检查相关文件:
前端组件修改时检查:
- 相关的路由配置
- 父子组件关系
- 共用的组件和函数
- API调用

后端接口修改时检查:
- 路由定义
- 数据库查询
- 相关的工具类和函数
- 错误处理
  1. 保持命名一致性:
- 类名ModelTrainer 而不是 ModelTraining
- 文件名train_model.py 对应 ModelTrainer
- 变量名:保持前后端一致的命名规范
  1. 添加日志记录:
# 在关键节点添加日志
logging.info(f"Starting model training for {equipment_type}")
logging.info(f"Training dataset: {train_dataset_id}")
logging.error(f"Error in model training: {str(e)}")

2. 修改过程中

  1. 错误处理:
try:
    # 主要逻辑
except Exception as e:
    logging.error(f"Error: {str(e)}")
    logging.error("Detailed traceback:", exc_info=True)
    return jsonify({'error': str(e)}), 500
  1. 数据验证:
# 验证输入
if not formData.value.type:
    throw new Error('请选择装备类型')
if not formData.value.train_dataset_id:
    throw new Error('请选择训练数据集')
  1. 状态管理:
// 重置状态
formData.value.train_dataset_id = null
formData.value.validation_dataset_id = null
trainingResult.value = null

3. 修改后的验证

  1. 功能测试:
- 验证主要功能
- 测试边界条件
- 检查错误处理
  1. 性能检查:
- 检查数据库查询性能
- 验证前端渲染性能
- 确认内存使用情况
  1. 代码质量:
- 检查代码风格
- 确保注释完整
- 验证类型定义

4. 文档更新

  1. 更新调试文档:
- 记录问题原因
- 描述解决方案
- 添加最佳实践
  1. 更新设计文档:
- 更新接口定义
- 修改数据结构
- 补充新功能说明
  1. 更新注释:
- 添加函数说明
- 说明参数用途
- 解释复杂逻辑

模型训练结果

从最新的训练结果来看:

  1. XGBoost 表现最好:
    • 训练集 R² = 0.4346,没有过拟合
    • 验证集 R² = 0.3625,表现最稳定
    • MAE = 0.60RMSE = 0.61,预测误差较小
  2. LightGBM 表现次之:
    • 训练集 R² = 0.5277,轻微过拟合
    • 验证集 R² = 0.1101,泛化能力一般
    • MAE = 0.55RMSE = 0.72,预测误差适中
  3. Random Forest
    • 训练集 R² = 0.7756,存在过拟合
    • 验证集 R² = 0.3189,泛化能力还可以
    • MAE = 0.47RMSE = 0.63,预测误差较小
  4. GBDT 过拟合严重:
    • 训练集 R² = 0.9700,严重过拟合
    • 验证集 R² = -1.3133,泛化能力很差
    • MAE = 0.96RMSE = 1.17,预测误差大

建议

  1. 使用 XGBoost 作为主要模型
  2. 可以考虑集成 XGBoost 和 Random Forest
  3. 继续调整 LightGBM 的参数
  4. 暂时不使用 GBDT