baoshiwei
2026-03-13 6628f663b636675bcaea316f2deaddf337de480e
feat(米重分析): 新增稳态识别和预测功能页面并优化现有模型

新增米重稳态识别、深度学习预测、统一预测和预测分析功能页面
优化现有回归和高级分析页面,增加稳态数据过滤和模型保存功能
重构稳态识别逻辑为公共类,提高代码复用性
已添加6个文件
已修改3个文件
4045 ■■■■■ 文件已修改
app/pages/extruder_parameter_adjustment.py 675 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/metered_weight_advanced.py 314 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/metered_weight_deep_learning.py 832 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/metered_weight_forecast.py 716 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/metered_weight_prediction.py 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/metered_weight_regression.py 300 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/metered_weight_steady_state.py 463 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/services/parameter_adjustment_service.py 495 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dashboard.py 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/extruder_parameter_adjustment.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,675 @@
import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import joblib
import os
from datetime import datetime
from app.services.parameter_adjustment_service import ParameterAdjustmentAdvisor
# é¡µé¢å‡½æ•°å®šä¹‰
def show_extruder_parameter_adjustment():
    # é¡µé¢æ ‡é¢˜
    st.title("挤出机参数调节建议")
    # æ·»åŠ æ“ä½œæŒ‡å¼•
    with st.expander("📖 æ“ä½œæŒ‡å¼•", expanded=True):
        st.markdown("""
        æ¬¢è¿Žä½¿ç”¨æŒ¤å‡ºæœºå‚数调节建议功能!本功能可以根据您输入的米重数据和当前参数,为您提供科学合理的参数调整建议。
        **操作步骤:**
        1. é€‰æ‹©ä¸€ä¸ªå·²è®­ç»ƒå¥½çš„æ¨¡åž‹
        2. è¾“入米重标准值、上下限和当前挤出机参数
        3. è¾“入当前实际米重测量值
        4. ç‚¹å‡»"计算调节建议"按钮
        5. æŸ¥çœ‹ç³»ç»Ÿç”Ÿæˆçš„参数调整建议
        **注意事项:**
        - è¯·ç¡®ä¿è¾“入的参数值在设备允许的范围内
        - å»ºè®®æ ¹æ®å®žé™…生产情况调整模型参数
        - åŽ†å²è°ƒèŠ‚è®°å½•å¯åœ¨é¡µé¢åº•éƒ¨æŸ¥çœ‹
        """)
    # åˆå§‹åŒ–会话状态
    if 'adjustment_history' not in st.session_state:
        st.session_state['adjustment_history'] = []
    # 1. æ¨¡åž‹é€‰æ‹©åŒºåŸŸ
    with st.expander("🔍 æ¨¡åž‹é€‰æ‹©", expanded=True):
        # åˆ›å»ºæ¨¡åž‹ç›®å½•(如果不存在)
        model_dir = "saved_models"
        os.makedirs(model_dir, exist_ok=True)
        # èŽ·å–æ‰€æœ‰å·²ä¿å­˜çš„æ¨¡åž‹æ–‡ä»¶
        model_files = [f for f in os.listdir(model_dir) if f.endswith('.joblib')]
        model_files.sort(reverse=True)  # æœ€æ–°çš„æ¨¡åž‹æŽ’在前面
        if not model_files:
            st.warning("尚未保存任何模型,请先训练模型并保存。")
            return
        # æ¨¡åž‹é€‰æ‹©ä¸‹æ‹‰æ¡†
        selected_model_file = st.selectbox(
            "选择已保存的模型",
            options=model_files,
            help="选择要用于预测的模型文件"
        )
        # åŠ è½½å¹¶æ˜¾ç¤ºæ¨¡åž‹ä¿¡æ¯
        if selected_model_file:
            model_path = os.path.join(model_dir, selected_model_file)
            model_info = joblib.load(model_path)
            # æ˜¾ç¤ºæ¨¡åž‹åŸºæœ¬ä¿¡æ¯
            st.subheader("📊 æ¨¡åž‹ä¿¡æ¯")
            info_cols = st.columns(2)
            with info_cols[0]:
                st.metric("模型类型", model_info['model_type'])
                st.metric("创建时间", model_info['created_at'].strftime('%Y-%m-%d %H:%M:%S'))
                st.metric("使用稳态数据", "是" if model_info.get('use_steady_data', False) else "否")
            with info_cols[1]:
                st.metric("R² å¾—分", f"{model_info['r2_score']:.4f}")
                st.metric("均方误差 (MSE)", f"{model_info['mse']:.6f}")
                st.metric("均方根误差 (RMSE)", f"{model_info['rmse']:.6f}")
            # æ˜¾ç¤ºæ¨¡åž‹ç‰¹å¾
            st.write("🔑 æ¨¡åž‹ä½¿ç”¨çš„特征:")
            st.code(", ".join(model_info['features']))
            # å¦‚果是深度学习模型,显示序列长度
            if 'sequence_length' in model_info:
                st.metric("序列长度", model_info['sequence_length'])
            # ä¿å­˜æ¨¡åž‹ä¿¡æ¯åˆ°ä¼šè¯çŠ¶æ€
            st.session_state['selected_model'] = model_info
            st.session_state['selected_model_file'] = selected_model_file
    # 2. å‚数输入区域
    st.subheader("📝 å‚数输入")
    # 2.1 ç±³é‡æ ‡å‡†å€¼ã€ä¸Šä¸‹é™è¾“å…¥
    with st.expander("⚖️ ç±³é‡æ ‡å‡†ä¸Žä¸Šä¸‹é™", expanded=True):
        weight_cols = st.columns(3)
        with weight_cols[0]:
            standard_weight = st.number_input(
                "标准米重",
                key="standard_weight",
                value=5.20,
                min_value=0.01,
                max_value=10.0,
                step=0.0001,
                format="%.4f",
                help="输入目标米重标准值"
            )
            st.caption("单位: Kg/m")
        with weight_cols[1]:
            upper_limit = st.number_input(
                "米重上限",
                key="upper_limit",
                value=5.46,
                min_value=standard_weight,
                max_value=10.0,
                step=0.0001,
                format="%.4f",
                help="输入米重允许的上限值"
            )
            st.caption("单位: Kg/m")
        with weight_cols[2]:
            lower_limit = st.number_input(
                "米重下限",
                key="lower_limit",
                value=5.02,
                min_value=0.01,
                max_value=standard_weight,
                step=0.0001,
                format="%.4f",
                help="输入米重允许的下限值"
            )
            st.caption("单位: Kg/m")
    # 2.2 æŒ¤å‡ºæœºå½“前参数输入
    with st.expander("🔧 æŒ¤å‡ºæœºå½“前参数", expanded=True):
        param_cols = st.columns(3)
        with param_cols[0]:
            current_screw_speed = st.number_input(
                "螺杆转速",
                key="current_screw_speed",
                value=230.0,
                min_value=0.0,
                max_value=500.0,
                step=0.1,
                help="输入当前螺杆转速"
            )
            st.caption("单位: rpm")
            current_head_pressure = st.number_input(
                "机头压力",
                key="current_head_pressure",
                value=0.26,
                min_value=0.0,
                max_value=500.0,
                step=1.0,
                help="输入当前机头压力"
            )
            st.caption("单位: bar")
            current_process_speed = st.number_input(
                "流程主速",
                key="current_process_speed",
                value=6.6,
                min_value=0.0,
                max_value=300.0,
                step=0.1,
                help="输入当前流程主速"
            )
            st.caption("单位: m/min")
        with param_cols[1]:
            current_screw_temperature = st.number_input(
                "螺杆温度",
                key="current_screw_temperature",
                value=79.9,
                min_value=0.0,
                max_value=300.0,
                step=1.0,
                help="输入当前螺杆温度"
            )
            st.caption("单位: Â°C")
            current_rear_barrel_temperature = st.number_input(
                "后机筒温度",
                key="current_rear_barrel_temperature",
                value=79.9,
                min_value=0.0,
                max_value=300.0,
                step=1.0,
                help="输入当前后机筒温度"
            )
            st.caption("单位: Â°C")
        with param_cols[2]:
            current_front_barrel_temperature = st.number_input(
                "前机筒温度",
                key="current_front_barrel_temperature",
                value=80.1,
                min_value=0.0,
                max_value=300.0,
                step=1.0,
                help="输入当前前机筒温度"
            )
            st.caption("单位: Â°C")
            current_head_temperature = st.number_input(
                "机头温度",
                key="current_head_temperature",
                value=95.1,
                min_value=0.0,
                max_value=300.0,
                step=1.0,
                help="输入当前机头温度"
            )
            st.caption("单位: Â°C")
    # 2.3 å½“前实际米重测量值输入
    with st.expander("📏 å½“前实际米重", expanded=True):
        actual_weight = st.number_input(
            "当前实际米重",
            key="actual_weight",
            value=5.115,
            min_value=0.01,
            max_value=10.0,
            step=0.0001,
            format="%.4f",
            help="输入当前实际测量的米重值"
        )
        st.caption("单位: Kg/m")
    # 3. è®¡ç®—调节建议
    st.subheader("🚀 è®¡ç®—调节建议")
    # æ·»åŠ è¿­ä»£è°ƒæ•´é€‰é¡¹
    use_iterative_adjustment = st.checkbox("🔄 ä½¿ç”¨è¿­ä»£è°ƒæ•´", value=False,
                                           help="启用迭代调整,自动优化参数直到预测米重满足偏差要求")
    # è¿­ä»£è°ƒæ•´å‚数设置
    max_iterations = 5
    tolerance = 0.5
    if use_iterative_adjustment:
        st.write("### è¿­ä»£è°ƒæ•´å‚数设置")
        iter_cols = st.columns(2)
        max_iterations = iter_cols[0].number_input("最大迭代次数", min_value=1, max_value=20, value=5, step=1)
        tolerance = iter_cols[1].number_input("允许偏差百分比(%)", min_value=0.1, max_value=5.0, value=0.5, step=0.1)
    if st.button("📊 è®¡ç®—调节建议", key="calculate_adjustment"):
        # å‚数验证
        validation_errors = []
        if standard_weight <= 0:
            validation_errors.append("标准米重必须大于0")
        if upper_limit <= standard_weight:
            validation_errors.append("米重上限必须大于标准米重")
        if lower_limit >= standard_weight:
            validation_errors.append("米重下限必须小于标准米重")
        if current_screw_speed <= 0:
            validation_errors.append("螺杆转速必须大于0")
        if current_process_speed <= 0:
            validation_errors.append("流程主速必须大于0")
        if actual_weight <= 0:
            validation_errors.append("实际米重必须大于0")
        if validation_errors:
            st.error("参数输入错误:")
            for error in validation_errors:
                st.error(f"- {error}")
        else:
            with st.spinner("正在计算调节建议..."):
                # åˆå§‹åŒ–参数调节建议器
                adjustment_advisor = ParameterAdjustmentAdvisor()
                # å‡†å¤‡åˆå§‹å‚æ•°
                initial_params = {
                    'real_time_weight': actual_weight,
                    'standard_weight': standard_weight,
                    'upper_limit': upper_limit,
                    'lower_limit': lower_limit,
                    'current_screw_speed': current_screw_speed,
                    'current_process_speed': current_process_speed,
                    'current_screw_temperature': current_screw_temperature,
                    'current_rear_barrel_temperature': current_rear_barrel_temperature,
                    'current_front_barrel_temperature': current_front_barrel_temperature,
                    'current_head_temperature': current_head_temperature,
                    'current_head_pressure': current_head_pressure
                }
                # æ ¹æ®æ˜¯å¦å¯ç”¨è¿­ä»£è°ƒæ•´æ‰§è¡Œä¸åŒé€»è¾‘
                if use_iterative_adjustment and 'selected_model' in st.session_state:
                    # ä½¿ç”¨è¿­ä»£è°ƒæ•´
                    iterative_result = adjustment_advisor.iterative_adjustment(
                        initial_params=initial_params,
                        model_info=st.session_state['selected_model'],
                        max_iterations=max_iterations,
                        tolerance=tolerance
                    )
                    # ä½¿ç”¨è¿­ä»£è°ƒæ•´çš„æœ€ç»ˆç»“æžœ
                    adjustment_result = iterative_result['final_result']
                    iteration_history = iterative_result['iteration_history']
                    converged = iterative_result['converged']
                    total_iterations = iterative_result['total_iterations']
                else:
                    # æ­£å¸¸è®¡ç®—调整建议
                    adjustment_result = adjustment_advisor.calculate_adjustment(
                        real_time_weight=actual_weight,
                        standard_weight=standard_weight,
                        upper_limit=upper_limit,
                        lower_limit=lower_limit,
                        current_screw_speed=current_screw_speed,
                        current_process_speed=current_process_speed,
                        current_screw_temperature=current_screw_temperature,
                        current_rear_barrel_temperature=current_rear_barrel_temperature,
                        current_front_barrel_temperature=current_front_barrel_temperature,
                        current_head_temperature=current_head_temperature
                    )
                    # ä½¿ç”¨é€‰ä¸­çš„æ¨¡åž‹é¢„测调整后的米重
                    predicted_weight = None
                    if 'selected_model' in st.session_state:
                        selected_model_info = st.session_state['selected_model']
                        predicted_weight = adjustment_advisor.predict_weight(
                            model_info=selected_model_info,
                            screw_speed=adjustment_result['new_screw_speed'],
                            head_pressure=current_head_pressure,
                            process_speed=adjustment_result['new_process_speed'],
                            screw_temperature=current_screw_temperature,
                            rear_barrel_temperature=current_rear_barrel_temperature,
                            front_barrel_temperature=current_front_barrel_temperature,
                            head_temperature=current_head_temperature
                        )
                    # å°†é¢„测结果添加到调整结果中
                    adjustment_result['predicted_weight'] = predicted_weight
                    # åˆå§‹åŒ–迭代历史(如果未使用迭代调整)
                    iteration_history = None
                    converged = None
                    total_iterations = None
                # ä¿å­˜åˆ°åŽ†å²è®°å½•
                history_record = {
                    'timestamp': datetime.now(),
                    'model_file': st.session_state.get('selected_model_file', '未知模型'),
                    'standard_weight': standard_weight,
                    'upper_limit': upper_limit,
                    'lower_limit': lower_limit,
                    'actual_weight': actual_weight,
                    'current_screw_speed': current_screw_speed,
                    'current_process_speed': current_process_speed,
                    'current_screw_temperature': current_screw_temperature,
                    'current_rear_barrel_temperature': current_rear_barrel_temperature,
                    'current_front_barrel_temperature': current_front_barrel_temperature,
                    'current_head_temperature': current_head_temperature,
                    'adjustment_result': adjustment_result,
                    'use_iterative_adjustment': use_iterative_adjustment,
                    'iteration_history': iteration_history
                }
                # æ·»åŠ åˆ°ä¼šè¯çŠ¶æ€çš„åŽ†å²è®°å½•
                if 'adjustment_history' not in st.session_state:
                    st.session_state['adjustment_history'] = []
                st.session_state['adjustment_history'].append(history_record)
                # é™åˆ¶åŽ†å²è®°å½•æ•°é‡
                if len(st.session_state['adjustment_history']) > 100:
                    st.session_state['adjustment_history'] = st.session_state['adjustment_history'][-100:]
                # 4. ç»“果展示
                st.success("调节建议计算完成!")
                st.subheader("📋 è°ƒèŠ‚å»ºè®®ç»“æžœ")
                # 4.1 ç±³é‡çŠ¶æ€
                if adjustment_result['status'] == "正常":
                    st.success(f"米重状态: {adjustment_result['status']}")
                else:
                    st.warning(f"米重状态: {adjustment_result['status']}")
                # 4.2 åå·®ä¿¡æ¯
                info_cols = st.columns(3)
                info_cols[0].metric("实时米重", f"{adjustment_result['real_time_weight']:.4f} Kg/m")
                info_cols[1].metric("标准米重", f"{adjustment_result['standard_weight']:.4f} Kg/m")
                info_cols[2].metric("偏差百分比", f"{adjustment_result['deviation_percentage']:.2f}%")
                # 4.2.1 æ¨¡åž‹é¢„测结果
                if adjustment_result['predicted_weight'] is not None:
                    st.markdown("### ðŸ“ˆ æ¨¡åž‹é¢„测结果")
                    pred_cols = st.columns(3)
                    pred_cols[0].metric("调整后预测米重", f"{adjustment_result['predicted_weight']:.4f} Kg/m")
                    # è®¡ç®—预测偏差
                    predicted_deviation = adjustment_result['predicted_weight'] - adjustment_result['standard_weight']
                    predicted_deviation_percent = (predicted_deviation / adjustment_result['standard_weight']) * 100
                    pred_cols[1].metric("预测偏差", f"{predicted_deviation:.4f} Kg/m")
                    pred_cols[2].metric("预测偏差百分比", f"{predicted_deviation_percent:.2f}%")
                    # æ˜¾ç¤ºé¢„测效果
                    if abs(predicted_deviation_percent) < 0.5:
                        st.success("调整后米重预测值接近标准值,调整效果良好!")
                    elif abs(predicted_deviation_percent) < 1.0:
                        st.info("调整后米重预测值在可接受范围内。")
                    else:
                        st.warning("调整后米重预测值仍有较大偏差,建议进一步微调。")
                else:
                    st.warning("模型预测失败,请检查模型文件或参数。")
                # 4.3 å…³é”®è°ƒæ•´å»ºè®®
                st.markdown("### ðŸ”‘ å…³é”®è°ƒæ•´å»ºè®®")
                st.info(adjustment_result['recommendation'])
                # 4.4 å‚数调整对比
                st.markdown("### ðŸ“Š å‚æ•°è°ƒæ•´å¯¹æ¯”")
                param_compare_df = pd.DataFrame({
                    '参数名称': ['螺杆转速', '流程主速'],
                    '当前值': [adjustment_result['current_screw_speed'], adjustment_result['current_process_speed']],
                    '建议值': [adjustment_result['new_screw_speed'], adjustment_result['new_process_speed']],
                    '调整幅度': [f"{adjustment_result['screw_speed_adjust_percent']:.2f}%",
                               f"{adjustment_result['process_speed_adjust_percent']:.2f}%"]
                })
                # é«˜äº®æ˜¾ç¤ºè°ƒæ•´å¹…度
                def highlight_adjustment(val):
                    if isinstance(val, str) and '%' in val:
                        try:
                            percent = float(val.strip('%'))
                            if percent > 0:
                                return 'background-color: #90EE90'  # ç»¿è‰²è¡¨ç¤ºå¢žåŠ 
                            elif percent < 0:
                                return 'background-color: #FFB6C1'  # çº¢è‰²è¡¨ç¤ºå‡å°‘
                        except:
                            pass
                    return ''
                styled_df = param_compare_df.style.applymap(highlight_adjustment, subset=['调整幅度'])
                st.dataframe(styled_df, use_container_width=True, hide_index=True)
                # 4.5 å¯è§†åŒ–对比
                fig = go.Figure()
                fig.add_trace(go.Bar(
                    x=param_compare_df['参数名称'],
                    y=param_compare_df['当前值'],
                    name='当前值',
                    marker_color='blue'
                ))
                fig.add_trace(go.Bar(
                    x=param_compare_df['参数名称'],
                    y=param_compare_df['建议值'],
                    name='建议值',
                    marker_color='green'
                ))
                fig.update_layout(
                    barmode='group',
                    title='参数调整对比',
                    yaxis_title='数值',
                    height=400
                )
                st.plotly_chart(fig, use_container_width=True)
                # 4.6 è¿­ä»£è°ƒæ•´ç»“果展示
                if use_iterative_adjustment and iteration_history:
                    st.markdown("### ðŸ”„ è¿­ä»£è°ƒæ•´åŽ†å²")
                    # æ˜¾ç¤ºè¿­ä»£è°ƒæ•´çŠ¶æ€
                    if converged:
                        st.success(f"✅ è¿­ä»£è°ƒæ•´æˆåŠŸæ”¶æ•›ï¼ç»è¿‡ {total_iterations} æ¬¡è¿­ä»£ï¼Œé¢„测米重偏差达到 {tolerance}% ä»¥å†…。")
                    else:
                        st.warning(f"⚠️ è¿­ä»£è°ƒæ•´æœªæ”¶æ•›ï¼ç»è¿‡ {total_iterations} æ¬¡è¿­ä»£ï¼Œé¢„测米重偏差仍未达到 {tolerance}% ä»¥å†…。")
                    # æ˜¾ç¤ºè¿­ä»£åŽ†å²è¡¨æ ¼
                    iter_history_df = pd.DataFrame(iteration_history)
                    iter_history_df = iter_history_df[[
                        'iteration', 'current_screw_speed', 'current_process_speed',
                        'adjusted_screw_speed', 'adjusted_process_speed',
                        'predicted_weight', 'predicted_deviation_percent'
                    ]]
                    # æ ¼å¼åŒ–表格
                    iter_history_df = iter_history_df.rename(columns={
                        'iteration': '迭代次数',
                        'current_screw_speed': '调整前螺杆转速',
                        'current_process_speed': '调整前流程主速',
                        'adjusted_screw_speed': '调整后螺杆转速',
                        'adjusted_process_speed': '调整后流程主速',
                        'predicted_weight': '预测米重',
                        'predicted_deviation_percent': '预测偏差百分比(%)'
                    })
                    st.dataframe(iter_history_df, use_container_width=True)
                    # è¿­ä»£è°ƒæ•´å¯è§†åŒ–
                    st.markdown("### ðŸ“‰ è¿­ä»£è°ƒæ•´æ•ˆæžœ")
                    # åå·®å˜åŒ–趋势图
                    fig_deviation = go.Figure()
                    fig_deviation.add_trace(go.Scatter(
                        x=iter_history_df['迭代次数'],
                        y=iter_history_df['预测偏差百分比(%)'],
                        mode='lines+markers',
                        name='预测偏差百分比',
                        line=dict(color='blue', width=2),
                        marker=dict(size=8)
                    ))
                    # æ·»åŠ åå·®é˜ˆå€¼çº¿
                    fig_deviation.add_trace(go.Scatter(
                        x=iter_history_df['迭代次数'],
                        y=[tolerance] * len(iter_history_df),
                        mode='lines',
                        name='允许偏差上限',
                        line=dict(color='red', dash='dash', width=1)
                    ))
                    fig_deviation.add_trace(go.Scatter(
                        x=iter_history_df['迭代次数'],
                        y=[-tolerance] * len(iter_history_df),
                        mode='lines',
                        name='允许偏差下限',
                        line=dict(color='red', dash='dash', width=1)
                    ))
                    fig_deviation.update_layout(
                        title='迭代调整偏差变化趋势',
                        xaxis_title='迭代次数',
                        yaxis_title='预测偏差百分比(%)',
                        height=400,
                        legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01)
                    )
                    st.plotly_chart(fig_deviation, use_container_width=True)
                    # èžºæ†è½¬é€Ÿå’Œæµç¨‹ä¸»é€Ÿå˜åŒ–趋势
                    fig_params = go.Figure()
                    fig_params.add_trace(go.Scatter(
                        x=iter_history_df['迭代次数'],
                        y=iter_history_df['调整前螺杆转速'],
                        mode='lines+markers',
                        name='调整前螺杆转速',
                        line=dict(color='blue', width=2),
                        marker=dict(size=8)
                    ))
                    fig_params.add_trace(go.Scatter(
                        x=iter_history_df['迭代次数'],
                        y=iter_history_df['调整后螺杆转速'],
                        mode='lines+markers',
                        name='调整后螺杆转速',
                        line=dict(color='green', width=2),
                        marker=dict(size=8)
                    ))
                    fig_params.add_trace(go.Scatter(
                        x=iter_history_df['迭代次数'],
                        y=iter_history_df['调整前流程主速'],
                        mode='lines+markers',
                        name='调整前流程主速',
                        line=dict(color='orange', width=2),
                        marker=dict(size=8)
                    ))
                    fig_params.add_trace(go.Scatter(
                        x=iter_history_df['迭代次数'],
                        y=iter_history_df['调整后流程主速'],
                        mode='lines+markers',
                        name='调整后流程主速',
                        line=dict(color='purple', width=2),
                        marker=dict(size=8)
                    ))
                    fig_params.update_layout(
                        title='参数调整变化趋势',
                        xaxis_title='迭代次数',
                        yaxis_title='数值',
                        height=400,
                        legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99)
                    )
                    st.plotly_chart(fig_params, use_container_width=True)
    # 5. åŽ†å²è®°å½•å±•ç¤º
    st.subheader("📚 åŽ†å²è°ƒèŠ‚è®°å½•")
    if 'adjustment_history' in st.session_state and st.session_state['adjustment_history']:
        # æ˜¾ç¤ºåŽ†å²è®°å½•æ•°é‡
        st.write(f"共 {len(st.session_state['adjustment_history'])} æ¡åŽ†å²è®°å½•")
        # åˆ†é¡µæ˜¾ç¤º
        page_size = 10
        total_pages = (len(st.session_state['adjustment_history']) + page_size - 1) // page_size
        page = st.selectbox(
            "选择页码",
            options=range(1, total_pages + 1),
            key="history_page"
        )
        start_idx = (page - 1) * page_size
        end_idx = start_idx + page_size
        paginated_history = st.session_state['adjustment_history'][start_idx:end_idx]
        # åå‘显示,最新记录在前面
        for record in reversed(paginated_history):
            with st.expander(f"记录时间: {record['timestamp'].strftime('%Y-%m-%d %H:%M:%S')} | æ¨¡åž‹: {record['model_file']}"):
                history_cols = st.columns(3)
                with history_cols[0]:
                    st.write("**米重参数**")
                    st.write(f"- æ ‡å‡†ç±³é‡: {record['standard_weight']:.4f} Kg/m")
                    st.write(f"- ç±³é‡ä¸Šé™: {record['upper_limit']:.4f} Kg/m")
                    st.write(f"- ç±³é‡ä¸‹é™: {record['lower_limit']:.4f} Kg/m")
                    st.write(f"- å®žé™…米重: {record['actual_weight']:.4f} Kg/m")
                with history_cols[1]:
                    st.write("**速度参数**")
                    st.write(f"- èžºæ†è½¬é€Ÿ: {record['current_screw_speed']:.1f} rpm")
                    st.write(f"- æµç¨‹ä¸»é€Ÿ: {record['current_process_speed']:.1f} m/min")
                with history_cols[2]:
                    st.write("**温度参数**")
                    st.write(f"- èžºæ†æ¸©åº¦: {record['current_screw_temperature']:.1f} Â°C")
                    st.write(f"- åŽæœºç­’温度: {record['current_rear_barrel_temperature']:.1f} Â°C")
                    st.write(f"- å‰æœºç­’温度: {record['current_front_barrel_temperature']:.1f} Â°C")
                    st.write(f"- æœºå¤´æ¸©åº¦: {record['current_head_temperature']:.1f} Â°C")
                st.write("**调整建议**")
                st.write(record['adjustment_result']['recommendation'])
    else:
        st.info("暂无历史调节记录")
    # 6. å¸®åŠ©è¯´æ˜Ž
    with st.expander("❓ å¸®åŠ©è¯´æ˜Ž"):
        st.markdown("""
        ### åŠŸèƒ½è¯´æ˜Ž
        æœ¬åŠŸèƒ½æ¨¡å—ç”¨äºŽæ ¹æ®å½“å‰ç±³é‡æµ‹é‡å€¼å’ŒæŒ¤å‡ºæœºå‚æ•°ï¼Œä¸ºç”¨æˆ·æä¾›ç§‘å­¦åˆç†çš„å‚æ•°è°ƒæ•´å»ºè®®ï¼Œä»¥å¸®åŠ©ç”¨æˆ·å°†ç±³é‡æŽ§åˆ¶åœ¨æ ‡å‡†èŒƒå›´å†…ã€‚
        ### æ¨¡åž‹é€‰æ‹©
        - ç³»ç»Ÿä¼šè‡ªåŠ¨è¯»å–é¡¹ç›®ç›®å½•ä¸­è®­ç»ƒå¥½çš„æ¨¡åž‹æ–‡ä»¶
        - æ¨¡åž‹æ–‡ä»¶éœ€ç¬¦åˆç³»ç»Ÿè¦æ±‚的格式,包含模型参数和训练信息
        - å»ºè®®é€‰æ‹©R²得分较高、误差较小的模型
        ### å‚数输入
        - ç±³é‡æ ‡å‡†å€¼ï¼šæ‚¨æœŸæœ›çš„目标米重值
        - ç±³é‡ä¸Šä¸‹é™ï¼šå…è®¸çš„米重波动范围
        - æŒ¤å‡ºæœºå½“前参数:包括螺杆转速、流程主速、机头压力和挤出机电流
        - å½“前实际米重:实际测量得到的米重值
        ### ç»“果解读
        - ç±³é‡çŠ¶æ€ï¼šæ˜¾ç¤ºå½“å‰ç±³é‡æ˜¯å¦åœ¨å…è®¸èŒƒå›´å†…
        - åå·®ç™¾åˆ†æ¯”:当前米重与标准米重的偏差百分比
        - å…³é”®è°ƒæ•´å»ºè®®ï¼šç³»ç»Ÿç»™å‡ºçš„主要调整建议
        - å‚数调整对比:详细展示每个参数的当前值、建议值和调整幅度
        ### æ³¨æ„äº‹é¡¹
        1. è¯·ç¡®ä¿è¾“入的参数值准确反映设备当前状态
        2. è°ƒæ•´å»ºè®®ä»…供参考,实际操作时请结合现场经验
        3. å»ºè®®åœ¨è°ƒæ•´å‚数后密切观察米重变化
        4. å®šæœŸæ›´æ–°æ¨¡åž‹ä»¥æé«˜å»ºè®®çš„准确性
        """)
# é¡µé¢å…¥å£
if __name__ == "__main__":
    show_extruder_parameter_adjustment()
app/pages/metered_weight_advanced.py
@@ -3,6 +3,8 @@
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import joblib
import os
from datetime import datetime, timedelta
from app.services.extruder_service import ExtruderService
from app.services.main_process_service import MainProcessService
@@ -13,8 +15,107 @@
from sklearn.svm import SVR
from sklearn.neural_network import MLPRegressor
# å¯¼å…¥ç¨³æ€è¯†åˆ«åŠŸèƒ½
class SteadyStateDetector:
    def __init__(self):
        pass
    def detect_steady_state(self, df, weight_col='米重', window_size=20, std_threshold=0.5, duration_threshold=60):
        """
        ç¨³æ€è¯†åˆ«é€»è¾‘:标记米重数据中的稳态段
        :param df: åŒ…含米重数据的数据框
        :param weight_col: ç±³é‡åˆ—名
        :param window_size: æ»‘动窗口大小(秒)
        :param std_threshold: æ ‡å‡†å·®é˜ˆå€¼
        :param duration_threshold: ç¨³æ€æŒç»­æ—¶é—´é˜ˆå€¼ï¼ˆç§’)
        :return: åŒ…含稳态标记的数据框和稳态信息
        """
        if df is None or df.empty:
            return df, []
        # ç¡®ä¿æ—¶é—´åˆ—是datetime类型
        df['time'] = pd.to_datetime(df['time'])
        # è®¡ç®—滚动统计量
        df['rolling_std'] = df[weight_col].rolling(window=window_size, min_periods=5).std()
        df['rolling_mean'] = df[weight_col].rolling(window=window_size, min_periods=5).mean()
        # è®¡ç®—波动范围
        df['fluctuation_range'] = (df['rolling_std'] / df['rolling_mean']) * 100
        df['fluctuation_range'] = df['fluctuation_range'].fillna(0)
        # æ ‡è®°ç¨³æ€ç‚¹
        df['is_steady'] = 0
        steady_condition = (
            (df['fluctuation_range'] < std_threshold) &
            (df[weight_col] >= 0.1)
        )
        df.loc[steady_condition, 'is_steady'] = 1
        # è¯†åˆ«è¿žç»­ç¨³æ€æ®µ
        steady_segments = []
        current_segment = {}
        for i, row in df.iterrows():
            if row['is_steady'] == 1:
                if not current_segment:
                    current_segment = {
                        'start_time': row['time'],
                        'start_idx': i,
                        'weights': [row[weight_col]]
                    }
                else:
                    current_segment['weights'].append(row[weight_col])
            else:
                if current_segment:
                    current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time']
                    current_segment['end_idx'] = i-1
                    duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
                    if duration >= duration_threshold:
                        weights_array = np.array(current_segment['weights'])
                        current_segment['duration'] = duration
                        current_segment['mean_weight'] = np.mean(weights_array)
                        current_segment['std_weight'] = np.std(weights_array)
                        current_segment['min_weight'] = np.min(weights_array)
                        current_segment['max_weight'] = np.max(weights_array)
                        current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                        # è®¡ç®—置信度
                        confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                        confidence = max(50, min(100, confidence))
                        current_segment['confidence'] = confidence
                        steady_segments.append(current_segment)
                    current_segment = {}
        # å¤„理最后一个稳态段
        if current_segment:
            current_segment['end_time'] = df['time'].iloc[-1]
            current_segment['end_idx'] = len(df) - 1
            duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
            if duration >= duration_threshold:
                weights_array = np.array(current_segment['weights'])
                current_segment['duration'] = duration
                current_segment['mean_weight'] = np.mean(weights_array)
                current_segment['std_weight'] = np.std(weights_array)
                current_segment['min_weight'] = np.min(weights_array)
                current_segment['max_weight'] = np.max(weights_array)
                current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                confidence = max(50, min(100, confidence))
                current_segment['confidence'] = confidence
                steady_segments.append(current_segment)
        # åœ¨æ•°æ®æ¡†ä¸­æ ‡è®°å®Œæ•´çš„稳态段
        for segment in steady_segments:
            df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1
        return df, steady_segments
def show_metered_weight_advanced():
    # åˆå§‹åŒ–服务
@@ -35,7 +136,13 @@
        st.session_state['ma_model_type'] = 'RandomForest'
    if 'ma_sequence_length' not in st.session_state:
        st.session_state['ma_sequence_length'] = 10
    if 'ma_use_steady_data' not in st.session_state:
        st.session_state['ma_use_steady_data'] = True
    if 'ma_steady_window' not in st.session_state:
        st.session_state['ma_steady_window'] = 20
    if 'ma_steady_threshold' not in st.session_state:
        st.session_state['ma_steady_threshold'] = 0.5
    # é»˜è®¤ç‰¹å¾åˆ—表(不再允许用户选择)
    default_features = ['螺杆转速', '机头压力', '流程主速', '螺杆温度', 
                       '后机筒温度', '前机筒温度', '机头温度']
@@ -126,6 +233,42 @@
                options=model_options,
                key="ma_model_type",
                help="选择用于预测的模型类型"
            )
        # ç¨³æ€è¯†åˆ«é…ç½®
        st.markdown("---")
        steady_cols = st.columns(3)
        with steady_cols[0]:
            st.write("⚖️ **稳态识别配置**")
            st.checkbox(
                "仅使用稳态数据进行训练",
                value=st.session_state['ma_use_steady_data'],
                key="ma_use_steady_data",
                help="启用后,只使用米重稳态时段的数据进行模型训练"
            )
        with steady_cols[1]:
            st.write("📏 **稳态参数**")
            st.slider(
                "滑动窗口大小 (秒)",
                min_value=5,
                max_value=60,
                value=st.session_state['ma_steady_window'],
                step=5,
                key="ma_steady_window",
                help="用于稳态识别的滑动窗口大小"
            )
        with steady_cols[2]:
            st.write("📊 **稳态阈值**")
            st.slider(
                "波动阈值 (%)",
                min_value=0.1,
                max_value=2.0,
                value=st.session_state['ma_steady_threshold'],
                step=0.1,
                key="ma_steady_threshold",
                help="稳态识别的波动范围阈值"
            )
@@ -249,6 +392,82 @@
            # é‡å‘½åç±³é‡åˆ—
            df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True)
            # ç¨³æ€è¯†åˆ«
            steady_detector = SteadyStateDetector()
            # èŽ·å–ç¨³æ€è¯†åˆ«å‚æ•°
            use_steady_data = st.session_state.get('ma_use_steady_data', True)
            steady_window = st.session_state.get('ma_steady_window', 20)
            steady_threshold = st.session_state.get('ma_steady_threshold', 0.5)
            # æ‰§è¡Œç¨³æ€è¯†åˆ«
            df_analysis_with_steady, steady_segments = steady_detector.detect_steady_state(
                df_analysis,
                weight_col='米重',
                window_size=steady_window,
                std_threshold=steady_threshold
            )
            # æ›´æ–°df_analysis为包含稳态标记的数据
            df_analysis = df_analysis_with_steady
            # ç¨³æ€æ•°æ®å¯è§†åŒ–
            st.subheader("📈 ç¨³æ€æ•°æ®åˆ†å¸ƒ")
            # åˆ›å»ºç¨³æ€æ•°æ®å¯è§†åŒ–图表
            fig_steady = go.Figure()
            # æ·»åŠ åŽŸå§‹ç±³é‡æ›²çº¿
            fig_steady.add_trace(go.Scatter(
                x=df_analysis['time'],
                y=df_analysis['米重'],
                name='原始米重',
                mode='lines',
                line=dict(color='lightgray', width=1)
            ))
            # æ·»åŠ ç¨³æ€æ•°æ®ç‚¹
            steady_data_points = df_analysis[df_analysis['is_steady'] == 1]
            fig_steady.add_trace(go.Scatter(
                x=steady_data_points['time'],
                y=steady_data_points['米重'],
                name='稳态米重',
                mode='markers',
                marker=dict(color='green', size=3, opacity=0.6)
            ))
            # æ·»åŠ éžç¨³æ€æ•°æ®ç‚¹
            non_steady_data_points = df_analysis[df_analysis['is_steady'] == 0]
            fig_steady.add_trace(go.Scatter(
                x=non_steady_data_points['time'],
                y=non_steady_data_points['米重'],
                name='非稳态米重',
                mode='markers',
                marker=dict(color='red', size=3, opacity=0.6)
            ))
            # é…ç½®å›¾è¡¨å¸ƒå±€
            fig_steady.update_layout(
                title="米重数据稳态分布",
                xaxis=dict(title="时间"),
                yaxis=dict(title="米重 (Kg/m)"),
                legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
                height=500
            )
            # æ˜¾ç¤ºå›¾è¡¨
            st.plotly_chart(fig_steady, use_container_width=True)
            # æ˜¾ç¤ºç¨³æ€ç»Ÿè®¡
            total_data = len(df_analysis)
            steady_data = len(df_analysis[df_analysis['is_steady'] == 1])
            steady_ratio = (steady_data / total_data * 100) if total_data > 0 else 0
            stats_cols = st.columns(3)
            stats_cols[0].metric("总数据量", total_data)
            stats_cols[1].metric("稳态数据量", steady_data)
            stats_cols[2].metric("稳态数据比例", f"{steady_ratio:.1f}%")
            # --- åŽŸå§‹æ•°æ®è¶‹åŠ¿å›¾ ---
            st.subheader("📈 åŽŸå§‹æ•°æ®è¶‹åŠ¿å›¾")
@@ -381,8 +600,16 @@
            else:
                try:
                    # å‡†å¤‡æ•°æ®
                    # æ ¹æ®é…ç½®å†³å®šæ˜¯å¦åªä½¿ç”¨ç¨³æ€æ•°æ®
                    use_steady_data = st.session_state.get('ma_use_steady_data', True)
                    if use_steady_data:
                        df_filtered = df_analysis[df_analysis['is_steady'] == 1]
                        st.info(f"已过滤非稳态数据,使用 {len(df_filtered)} æ¡ç¨³æ€æ•°æ®è¿›è¡Œè®­ç»ƒ")
                    else:
                        df_filtered = df_analysis.copy()
                    # é¦–先确保df_analysis中没有NaN值
                    df_analysis_clean = df_analysis.dropna(subset=default_features + ['米重'])
                    df_analysis_clean = df_filtered.dropna(subset=default_features + ['米重'])
                    
                    # æ£€æŸ¥æ¸…理后的数据量
                    if len(df_analysis_clean) < 30:
@@ -391,8 +618,8 @@
                        # åˆ›å»ºä¸€ä¸ªæ–°çš„DataFrame来存储所有特征和目标变量
                        all_features = df_analysis_clean[default_features + ['米重']].copy()
                        
                  
                        # æ¸…理所有NaN值
                        all_features_clean = all_features.dropna()
@@ -568,49 +795,38 @@
                                    )
                                    st.plotly_chart(fig_importance, width='stretch')
                                # --- é¢„测功能 ---
                                st.subheader("🔮 ç±³é‡é¢„测")
                                # åˆ›å»ºé¢„测表单
                                st.write("输入特征值进行米重预测:")
                                predict_cols = st.columns(2)
                                input_features = {}
                                for i, feature in enumerate(default_features):
                                    with predict_cols[i % 2]:
                                        # èŽ·å–ç‰¹å¾çš„ç»Ÿè®¡ä¿¡æ¯
                                        min_val = df_analysis_clean[feature].min()
                                        max_val = df_analysis_clean[feature].max()
                                        mean_val = df_analysis_clean[feature].mean()
                                        input_features[feature] = st.number_input(
                                            f"{feature}",
                                            key=f"ma_pred_{feature}",
                                            value=float(mean_val),
                                            min_value=float(min_val),
                                            max_value=float(max_val),
                                            step=0.1
                                        )
                                if st.button("预测米重"):
                                    # å‡†å¤‡é¢„测数据
                                    input_df = pd.DataFrame([input_features])
                                    # åˆå¹¶ç‰¹å¾
                                    input_combined = pd.concat([input_df], axis=1)
                                    # é¢„测
                                    if model_type in ['SVR', 'MLP']:
                                        input_scaled = scaler_X.transform(input_combined)
                                        prediction_scaled = model.predict(input_scaled)
                                        predicted_weight = scaler_y.inverse_transform(prediction_scaled.reshape(-1, 1)).ravel()[0]
                                    else:
                                        predicted_weight = model.predict(input_combined)[0]
                                    # æ˜¾ç¤ºé¢„测结果
                                    st.success(f"预测米重: {predicted_weight:.4f} Kg/m")
                                # --- æ¨¡åž‹ä¿å­˜ ---
                                st.subheader("� æ¨¡åž‹ä¿å­˜")
                                # åˆ›å»ºæ¨¡åž‹ç›®å½•(如果不存在)
                                model_dir = "saved_models"
                                os.makedirs(model_dir, exist_ok=True)
                                # å‡†å¤‡æ¨¡åž‹ä¿¡æ¯
                                model_info = {
                                    'model': model,
                                    'features': feature_columns,
                                    'scaler_X': scaler_X if model_type in ['SVR', 'MLP'] else None,
                                    'scaler_y': scaler_y if model_type in ['SVR', 'MLP'] else None,
                                    'model_type': model_type,
                                    'created_at': datetime.now(),
                                    'r2_score': r2,
                                    'mse': mse,
                                    'mae': mae,
                                    'rmse': rmse,
                                    'use_steady_data': use_steady_data
                                }
                                # ç”Ÿæˆæ¨¡åž‹æ–‡ä»¶å
                                model_filename = f"advanced_{model_type.lower()}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.joblib"
                                model_path = os.path.join(model_dir, model_filename)
                                # ä¿å­˜æ¨¡åž‹
                                joblib.dump(model_info, model_path)
                                st.success(f"模型已成功保存: {model_filename}")
                                st.info(f"保存路径: {model_path}")
                                # --- æ•°æ®é¢„览 ---
                                st.subheader("🔍 æ•°æ®é¢„览")
                                st.dataframe(df_analysis.head(20), width='stretch')
app/pages/metered_weight_deep_learning.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,832 @@
import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import joblib
import os
from datetime import datetime, timedelta
from app.services.extruder_service import ExtruderService
from app.services.main_process_service import MainProcessService
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
# å¯¼å…¥ç¨³æ€è¯†åˆ«åŠŸèƒ½
class SteadyStateDetector:
    def __init__(self):
        pass
    def detect_steady_state(self, df, weight_col='米重', window_size=20, std_threshold=0.5, duration_threshold=60):
        """
        ç¨³æ€è¯†åˆ«é€»è¾‘:标记米重数据中的稳态段
        :param df: åŒ…含米重数据的数据框
        :param weight_col: ç±³é‡åˆ—名
        :param window_size: æ»‘动窗口大小(秒)
        :param std_threshold: æ ‡å‡†å·®é˜ˆå€¼
        :param duration_threshold: ç¨³æ€æŒç»­æ—¶é—´é˜ˆå€¼ï¼ˆç§’)
        :param trend_threshold: è¶‹åŠ¿é˜ˆå€¼ï¼ˆç»å¯¹å€¼ï¼‰
        :return: åŒ…含稳态标记的数据框和稳态信息
        """
        if df is None or df.empty:
            return df, []
        # ç¡®ä¿æ—¶é—´åˆ—是datetime类型
        df['time'] = pd.to_datetime(df['time'])
        # è®¡ç®—滚动统计量
        df['rolling_std'] = df[weight_col].rolling(window=window_size, min_periods=5).std()
        df['rolling_mean'] = df[weight_col].rolling(window=window_size, min_periods=5).mean()
        # è®¡ç®—波动范围
        df['fluctuation_range'] = (df['rolling_std'] / df['rolling_mean']) * 100
        df['fluctuation_range'] = df['fluctuation_range'].fillna(0)
        # è®¡ç®—趋势
        # df['trend'] = df[weight_col].diff().rolling(window=window_size, min_periods=5).mean()
        # df['trend'] = df['trend'].fillna(0)
        # df['trend_strength'] = (abs(df['trend']) / df['rolling_mean']) * 100
        # df['trend_strength'] = df['trend_strength'].fillna(0)
        # æ ‡è®°ç¨³æ€ç‚¹
        df['is_steady'] = 0
        steady_condition = (
            (df['fluctuation_range'] < std_threshold) &
            (df[weight_col] >= 0.1)
        )
        df.loc[steady_condition, 'is_steady'] = 1
        # è¯†åˆ«è¿žç»­ç¨³æ€æ®µ
        steady_segments = []
        current_segment = {}
        for i, row in df.iterrows():
            if row['is_steady'] == 1:
                if not current_segment:
                    current_segment = {
                        'start_time': row['time'],
                        'start_idx': i,
                        'weights': [row[weight_col]]
                    }
                else:
                    current_segment['weights'].append(row[weight_col])
            else:
                if current_segment:
                    current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time']
                    current_segment['end_idx'] = i-1
                    duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
                    if duration >= duration_threshold:
                        weights_array = np.array(current_segment['weights'])
                        current_segment['duration'] = duration
                        current_segment['mean_weight'] = np.mean(weights_array)
                        current_segment['std_weight'] = np.std(weights_array)
                        current_segment['min_weight'] = np.min(weights_array)
                        current_segment['max_weight'] = np.max(weights_array)
                        current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                        # è®¡ç®—置信度
                        confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                        confidence = max(50, min(100, confidence))
                        current_segment['confidence'] = confidence
                        steady_segments.append(current_segment)
                    current_segment = {}
        # å¤„理最后一个稳态段
        if current_segment:
            current_segment['end_time'] = df['time'].iloc[-1]
            current_segment['end_idx'] = len(df) - 1
            duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
            if duration >= duration_threshold:
                weights_array = np.array(current_segment['weights'])
                current_segment['duration'] = duration
                current_segment['mean_weight'] = np.mean(weights_array)
                current_segment['std_weight'] = np.std(weights_array)
                current_segment['min_weight'] = np.min(weights_array)
                current_segment['max_weight'] = np.max(weights_array)
                current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                confidence = max(50, min(100, confidence))
                current_segment['confidence'] = confidence
                steady_segments.append(current_segment)
        # åœ¨æ•°æ®æ¡†ä¸­æ ‡è®°å®Œæ•´çš„稳态段
        for segment in steady_segments:
            df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1
        return df, steady_segments
# å°è¯•导入深度学习库
use_deep_learning = False
try:
    import torch
    import torch.nn as nn
    import torch.optim as optim
    use_deep_learning = True
    # æ£€æµ‹GPU是否可用
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print(f"使用设备: {device}")
    # PyTorch深度学习模型定义
    class LSTMModel(nn.Module):
        def __init__(self, input_dim, hidden_dim=64, num_layers=2):
            super(LSTMModel, self).__init__()
            self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True)
            self.fc1 = nn.Linear(hidden_dim, 32)
            self.dropout = nn.Dropout(0.2)
            self.fc2 = nn.Linear(32, 1)
        def forward(self, x):
            out, _ = self.lstm(x)
            out = out[:, -1, :]
            out = torch.relu(self.fc1(out))
            out = self.dropout(out)
            out = self.fc2(out)
            return out
    class GRUModel(nn.Module):
        def __init__(self, input_dim, hidden_dim=64, num_layers=2):
            super(GRUModel, self).__init__()
            self.gru = nn.GRU(input_dim, hidden_dim, num_layers, batch_first=True)
            self.fc1 = nn.Linear(hidden_dim, 32)
            self.dropout = nn.Dropout(0.2)
            self.fc2 = nn.Linear(32, 1)
        def forward(self, x):
            out, _ = self.gru(x)
            out = out[:, -1, :]
            out = torch.relu(self.fc1(out))
            out = self.dropout(out)
            out = self.fc2(out)
            return out
    class BiLSTMModel(nn.Module):
        def __init__(self, input_dim, hidden_dim=64, num_layers=2):
            super(BiLSTMModel, self).__init__()
            self.bilstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True, bidirectional=True)
            self.fc1 = nn.Linear(hidden_dim * 2, 32)
            self.dropout = nn.Dropout(0.2)
            self.fc2 = nn.Linear(32, 1)
        def forward(self, x):
            out, _ = self.bilstm(x)
            out = out[:, -1, :]
            out = torch.relu(self.fc1(out))
            out = self.dropout(out)
            out = self.fc2(out)
            return out
    st.success(f"使用设备: {device}")
except ImportError:
    st.warning("未检测到PyTorch,深度学习模型将不可用。请安装pytorch以使用LSTM/GRU模型。")
def show_metered_weight_deep_learning():
    # åˆå§‹åŒ–服务
    extruder_service = ExtruderService()
    main_process_service = MainProcessService()
    # é¡µé¢æ ‡é¢˜
    st.title("米重深度学习预测")
    # åˆå§‹åŒ–会话状态
    if 'mdl_start_date' not in st.session_state:
        st.session_state['mdl_start_date'] = datetime.now().date() - timedelta(days=7)
    if 'mdl_end_date' not in st.session_state:
        st.session_state['mdl_end_date'] = datetime.now().date()
    if 'mdl_quick_select' not in st.session_state:
        st.session_state['mdl_quick_select'] = "最近7天"
    if 'mdl_model_type' not in st.session_state:
        st.session_state['mdl_model_type'] = 'LSTM'
    if 'mdl_sequence_length' not in st.session_state:
        st.session_state['mdl_sequence_length'] = 10
    if 'mdl_time_offset' not in st.session_state:
        st.session_state['mdl_time_offset'] = 0
    if 'mdl_product_variety' not in st.session_state:
        st.session_state['mdl_product_variety'] = 'all'
    if 'mdl_filter_transient' not in st.session_state:
        st.session_state['mdl_filter_transient'] = True
    # é»˜è®¤ç‰¹å¾åˆ—表
    default_features = ['螺杆转速', '机头压力', '流程主速', '螺杆温度',
                       '后机筒温度', '前机筒温度', '机头温度']
    # å®šä¹‰å›žè°ƒå‡½æ•°
    def update_dates(qs):
        st.session_state['mdl_quick_select'] = qs
        today = datetime.now().date()
        if qs == "今天":
            st.session_state['mdl_start_date'] = today
            st.session_state['mdl_end_date'] = today
        elif qs == "最近3天":
            st.session_state['mdl_start_date'] = today - timedelta(days=3)
            st.session_state['mdl_end_date'] = today
        elif qs == "最近7天":
            st.session_state['mdl_start_date'] = today - timedelta(days=7)
            st.session_state['mdl_end_date'] = today
        elif qs == "最近30天":
            st.session_state['mdl_start_date'] = today - timedelta(days=30)
            st.session_state['mdl_end_date'] = today
    def on_date_change():
        st.session_state['mdl_quick_select'] = "自定义"
    # æŸ¥è¯¢æ¡ä»¶åŒºåŸŸ
    with st.expander("🔍 æŸ¥è¯¢é…ç½®", expanded=True):
        # æ·»åŠ è‡ªå®šä¹‰ CSS å®žçŽ°å“åº”å¼æ¢è¡Œ
        st.markdown("""
            <style>
            /* å¼ºåˆ¶åˆ—容器换行 */
            [data-testid="stExpander"] [data-testid="column"] {
                flex: 1 1 120px !important;
                min-width: 120px !important;
            }
            /* é’ˆå¯¹æ—¥æœŸè¾“入框列稍微加宽一点 */
            @media (min-width: 768px) {
                [data-testid="stExpander"] [data-testid="column"]:nth-child(6),
                [data-testid="stExpander"] [data-testid="column"]:nth-child(7) {
                    flex: 2 1 180px !important;
                    min-width: 180px !important;
                }
            }
            </style>
            """, unsafe_allow_html=True)
        # åˆ›å»ºå¸ƒå±€
        cols = st.columns([1, 1, 1, 1, 1, 1.5, 1.5, 1])
        options = ["今天", "最近3天", "最近7天", "最近30天", "自定义"]
        for i, option in enumerate(options):
            with cols[i]:
                # æ ¹æ®å½“前选择状态决定按钮类型
                button_type = "primary" if st.session_state['mdl_quick_select'] == option else "secondary"
                if st.button(option, key=f"btn_mdl_{option}", width='stretch', type=button_type):
                    update_dates(option)
                    st.rerun()
        with cols[5]:
            start_date = st.date_input(
                "开始日期",
                label_visibility="collapsed",
                key="mdl_start_date",
                on_change=on_date_change
            )
        with cols[6]:
            end_date = st.date_input(
                "结束日期",
                label_visibility="collapsed",
                key="mdl_end_date",
                on_change=on_date_change
            )
        with cols[7]:
            query_button = st.button("🚀 å¼€å§‹åˆ†æž", key="mdl_query", width='stretch')
        # é«˜çº§é…ç½®
        st.markdown("---")
        advanced_cols = st.columns(2)
        with advanced_cols[0]:
            st.write("🤖 **模型配置**")
            # æ¨¡åž‹ç±»åž‹é€‰æ‹©
            if use_deep_learning:
                model_options = ['LSTM', 'GRU', 'BiLSTM']
                model_type = st.selectbox(
                    "模型类型",
                    options=model_options,
                    key="mdl_model_type",
                    help="选择用于预测的深度学习模型类型"
                )
                # åºåˆ—长度
                sequence_length = st.slider(
                    "序列长度",
                    min_value=5,
                    max_value=30,
                    value=st.session_state['mdl_sequence_length'],
                    step=1,
                    help="用于深度学习模型的时间序列长度",
                    key="mdl_sequence_length"
                )
            else:
                st.warning("未检测到PyTorch,无法使用深度学习模型")
        with advanced_cols[1]:
            st.write("⏱️ **时间延迟配置**")
            # åŠ¨æ€æ—¶é—´åç§»ï¼ˆåŸºäºŽæµç¨‹ä¸»é€Ÿï¼‰
            time_offset = st.slider(
                "挤出数据向后偏移 (分钟)",
                min_value=0,
                max_value=60,
                value=st.session_state['mdl_time_offset'],
                step=1,
                help="由于胎面从挤出到称重需要时间,将挤出机数据向后移动,使其与米重数据在时间轴上对齐。偏移量会影响预测准确性。",
                key="mdl_time_offset"
            )
        # ç¨³æ€è¯†åˆ«é…ç½®
        st.markdown("---")
        steady_cols = st.columns(3)
        with steady_cols[0]:
            st.write("⚖️ **稳态识别配置**")
            use_steady_data = st.checkbox(
                "仅使用稳态数据进行训练",
                value=True,
                key="mdl_use_steady_data",
                help="启用后,只使用米重稳态时段的数据进行模型训练和预测"
            )
        with steady_cols[1]:
            st.write("📏 **稳态参数**")
            steady_window = st.slider(
                "滑动窗口大小 (秒)",
                min_value=5,
                max_value=60,
                value=20,
                step=5,
                key="mdl_steady_window",
                help="用于稳态识别的滑动窗口大小"
            )
        with steady_cols[2]:
            st.write("📊 **稳态阈值**")
            steady_threshold = st.slider(
                "波动阈值 (%)",
                min_value=0.1,
                max_value=2.0,
                value=0.5,
                step=0.1,
                key="mdl_steady_threshold",
                help="稳态识别的波动范围阈值"
            )
    # è½¬æ¢ä¸ºdatetime对象
    start_dt = datetime.combine(start_date, datetime.min.time())
    end_dt = datetime.combine(end_date, datetime.max.time())
    # æŸ¥è¯¢å¤„理
    if query_button:
        with st.spinner("正在获取数据..."):
            # 1. èŽ·å–å®Œæ•´çš„æŒ¤å‡ºæœºæ•°æ®
            df_extruder_full = extruder_service.get_extruder_data(start_dt, end_dt)
            # 2. èŽ·å–ä¸»æµç¨‹æŽ§åˆ¶æ•°æ®
            df_main_speed = main_process_service.get_cutting_setting_data(start_dt, end_dt)
            df_temp = main_process_service.get_temperature_control_data(start_dt, end_dt)
            # æ£€æŸ¥æ˜¯å¦æœ‰æ•°æ®
            has_data = any([
                df_extruder_full is not None and not df_extruder_full.empty,
                df_main_speed is not None and not df_main_speed.empty,
                df_temp is not None and not df_temp.empty
            ])
            if not has_data:
                st.warning("所选时间段内未找到任何数据,请尝试调整查询条件。")
                return
            # ç¼“存数据到会话状态
            st.session_state['cached_extruder_full'] = df_extruder_full
            st.session_state['cached_main_speed'] = df_main_speed
            st.session_state['cached_temp'] = df_temp
            st.session_state['last_query_start'] = start_dt
            st.session_state['last_query_end'] = end_dt
    # æ•°æ®å¤„理和分析
    if all(key in st.session_state for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp']):
        with st.spinner("正在分析数据..."):
            # èŽ·å–ç¼“å­˜æ•°æ®
            df_extruder_full = st.session_state['cached_extruder_full']
            df_main_speed = st.session_state['cached_main_speed']
            df_temp = st.session_state['cached_temp']
            # æ£€æŸ¥æ˜¯å¦æœ‰æ•°æ®
            has_data = any([
                df_extruder_full is not None and not df_extruder_full.empty,
                df_main_speed is not None and not df_main_speed.empty,
                df_temp is not None and not df_temp.empty
            ])
            if not has_data:
                st.warning("所选时间段内未找到任何数据,请尝试调整查询条件。")
                return
            # æ•°æ®æ•´åˆä¸Žé¢„处理
            def integrate_data(df_extruder_full, df_main_speed, df_temp, time_offset):
                # ç¡®ä¿æŒ¤å‡ºæœºæ•°æ®å­˜åœ¨
                if df_extruder_full is None or df_extruder_full.empty:
                    return None
                # åº”用时间偏移
                offset_delta = timedelta(minutes=time_offset)
                df_extruder_shifted = df_extruder_full.copy()
                df_extruder_shifted['time'] = df_extruder_shifted['time'] + offset_delta
                # åˆ›å»ºåªåŒ…含米重和时间的主数据集
                df_merged = df_extruder_shifted[['time', 'metered_weight', 'screw_speed_actual', 'head_pressure']].copy()
                # æ•´åˆä¸»æµç¨‹æ•°æ®
                if df_main_speed is not None and not df_main_speed.empty:
                    df_main_speed_shifted = df_main_speed.copy()
                    df_main_speed_shifted['time'] = df_main_speed_shifted['time'] + offset_delta
                    df_main_speed_shifted = df_main_speed_shifted[['time', 'process_main_speed']]
                    df_merged = pd.merge_asof(
                        df_merged.sort_values('time'),
                        df_main_speed_shifted.sort_values('time'),
                        on='time',
                        direction='nearest',
                        tolerance=pd.Timedelta('1min')
                    )
                # æ•´åˆæ¸©åº¦æ•°æ®
                if df_temp is not None and not df_temp.empty:
                    df_temp_shifted = df_temp.copy()
                    df_temp_shifted['time'] = df_temp_shifted['time'] + offset_delta
                    temp_cols = ['time', 'nakata_extruder_screw_display_temp',
                               'nakata_extruder_rear_barrel_display_temp',
                               'nakata_extruder_front_barrel_display_temp',
                               'nakata_extruder_head_display_temp']
                    df_temp_subset = df_temp_shifted[temp_cols].copy()
                    df_merged = pd.merge_asof(
                        df_merged.sort_values('time'),
                        df_temp_subset.sort_values('time'),
                        on='time',
                        direction='nearest',
                        tolerance=pd.Timedelta('1min')
                    )
                # é‡å‘½ååˆ—以提高可读性
                df_merged.rename(columns={
                    'screw_speed_actual': '螺杆转速',
                    'head_pressure': '机头压力',
                    'process_main_speed': '流程主速',
                    'nakata_extruder_screw_display_temp': '螺杆温度',
                    'nakata_extruder_rear_barrel_display_temp': '后机筒温度',
                    'nakata_extruder_front_barrel_display_temp': '前机筒温度',
                    'nakata_extruder_head_display_temp': '机头温度'
                }, inplace=True)
                # æ¸…理数据
                df_merged.dropna(subset=['metered_weight'], inplace=True)
                return df_merged
            # æ‰§è¡Œæ•°æ®æ•´åˆ
            df_analysis = integrate_data(df_extruder_full, df_main_speed, df_temp, st.session_state['mdl_time_offset'])
            if df_analysis is None or df_analysis.empty:
                st.warning("数据整合失败,请检查数据质量或调整时间范围。")
                return
            # é‡å‘½åç±³é‡åˆ—
            df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True)
            # ç¨³æ€è¯†åˆ«
            steady_detector = SteadyStateDetector()
            # èŽ·å–ç¨³æ€è¯†åˆ«å‚æ•°
            use_steady_data = st.session_state.get('mdl_use_steady_data', True)
            steady_window = st.session_state.get('mdl_steady_window', 20)
            steady_threshold = st.session_state.get('mdl_steady_threshold', 0.5)
            # æ‰§è¡Œç¨³æ€è¯†åˆ«
            df_analysis_with_steady, steady_segments = steady_detector.detect_steady_state(
                df_analysis,
                weight_col='米重',
                window_size=steady_window,
                std_threshold=steady_threshold
            )
            # æ›´æ–°df_analysis为包含稳态标记的数据
            df_analysis = df_analysis_with_steady
            # é«˜çº§é¢„测分析
            st.subheader("📊 æ·±åº¦å­¦ä¹ é¢„测分析")
            if use_deep_learning:
                # æ£€æŸ¥æ‰€æœ‰é»˜è®¤ç‰¹å¾æ˜¯å¦åœ¨æ•°æ®ä¸­
                missing_features = [f for f in default_features if f not in df_analysis.columns]
                if missing_features:
                    st.warning(f"数据中缺少以下特征: {', '.join(missing_features)}")
                else:
                    # å‡†å¤‡æ•°æ®
                    required_cols = default_features + ['米重', 'is_steady']
                    combined = df_analysis[required_cols].copy()
                    # å¦‚果启用了稳态数据,过滤掉非稳态数据
                    use_steady_data = st.session_state.get('mdl_use_steady_data', True)
                    if use_steady_data:
                        combined = combined[combined['is_steady'] == 1]
                        st.info(f"已过滤非稳态数据,使用 {len(combined)} æ¡ç¨³æ€æ•°æ®è¿›è¡Œè®­ç»ƒ")
                    # æ¸…理数据中的NaN值
                    combined_clean = combined.dropna()
                    # æ£€æŸ¥æ¸…理后的数据量
                    if len(combined_clean) < 30:
                        st.warning("数据量不足,无法进行有效的预测分析")
                        if use_steady_data:
                            st.info("建议:尝试调整稳态识别参数或禁用'仅使用稳态数据'选项")
                    else:
                        # æ˜¾ç¤ºç¨³æ€ç»Ÿè®¡
                        total_data = len(df_analysis)
                        steady_data = len(combined_clean)
                        steady_ratio = (steady_data / total_data * 100) if total_data > 0 else 0
                        metrics_cols = st.columns(3)
                        with metrics_cols[0]:
                            st.metric("总数据量", total_data)
                        with metrics_cols[1]:
                            st.metric("稳态数据量", steady_data)
                        with metrics_cols[2]:
                            st.metric("稳态数据比例", f"{steady_ratio:.1f}%")
                        # ç¨³æ€æ•°æ®å¯è§†åŒ–
                        st.markdown("---")
                        st.subheader("📈 ç¨³æ€æ•°æ®åˆ†å¸ƒ")
                        # åˆ›å»ºç¨³æ€æ•°æ®å¯è§†åŒ–图表
                        fig_steady = go.Figure()
                        # æ·»åŠ åŽŸå§‹ç±³é‡æ›²çº¿
                        fig_steady.add_trace(go.Scatter(
                            x=df_analysis['time'],
                            y=df_analysis['米重'],
                            name='原始米重',
                            mode='lines',
                            line=dict(color='lightgray', width=1)
                        ))
                        # æ·»åŠ ç¨³æ€æ•°æ®ç‚¹
                        steady_data_points = df_analysis[df_analysis['is_steady'] == 1]
                        fig_steady.add_trace(go.Scatter(
                            x=steady_data_points['time'],
                            y=steady_data_points['米重'],
                            name='稳态米重',
                            mode='markers',
                            marker=dict(color='green', size=3, opacity=0.6)
                        ))
                        # æ·»åŠ éžç¨³æ€æ•°æ®ç‚¹
                        non_steady_data_points = df_analysis[df_analysis['is_steady'] == 0]
                        fig_steady.add_trace(go.Scatter(
                            x=non_steady_data_points['time'],
                            y=non_steady_data_points['米重'],
                            name='非稳态米重',
                            mode='markers',
                            marker=dict(color='red', size=3, opacity=0.6)
                        ))
                        # é…ç½®å›¾è¡¨å¸ƒå±€
                        fig_steady.update_layout(
                            title="米重数据稳态分布",
                            xaxis=dict(title="时间"),
                            yaxis=dict(title="米重 (Kg/m)"),
                            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
                            height=500
                        )
                        # æ˜¾ç¤ºå›¾è¡¨
                        st.plotly_chart(fig_steady, use_container_width=True)
                        # åˆ†ç¦»X和y
                        X_clean = combined_clean[default_features]
                        y_clean = combined_clean['米重']
                        # ä¸ºæ—¶é—´åºåˆ—模型准备数据
                        def create_sequences(X, y, sequence_length):
                            X_seq = []
                            y_seq = []
                            for i in range(len(X) - sequence_length):
                                X_seq.append(X[i:i+sequence_length])
                                y_seq.append(y[i+sequence_length])
                            return np.array(X_seq), np.array(y_seq)
                        # æ•°æ®æ ‡å‡†åŒ–
                        scaler_X = StandardScaler()
                        scaler_y = MinMaxScaler()
                        X_scaled = scaler_X.fit_transform(X_clean)
                        y_scaled = scaler_y.fit_transform(y_clean.values.reshape(-1, 1)).ravel()
                        # åˆ›å»ºåºåˆ—数据
                        sequence_length = st.session_state['mdl_sequence_length']
                        X_seq, y_seq = create_sequences(X_scaled, y_scaled, sequence_length)
                        # æ£€æŸ¥åºåˆ—数据量
                        if len(X_seq) < 20:
                            st.warning("序列数据量不足,无法进行有效的深度学习训练")
                        else:
                            # åˆ†å‰²è®­ç»ƒé›†å’Œæµ‹è¯•集
                            train_size = int(len(X_seq) * 0.8)
                            X_train_seq, X_test_seq = X_seq[:train_size], X_seq[train_size:]
                            y_train_seq, y_test_seq = y_seq[:train_size], y_seq[train_size:]
                            # è½¬æ¢ä¸ºPyTorch张量
                            X_train_tensor = torch.tensor(X_train_seq, dtype=torch.float32).to(device)
                            y_train_tensor = torch.tensor(y_train_seq, dtype=torch.float32).unsqueeze(1).to(device)
                            X_test_tensor = torch.tensor(X_test_seq, dtype=torch.float32).to(device)
                            y_test_tensor = torch.tensor(y_test_seq, dtype=torch.float32).unsqueeze(1).to(device)
                            # æž„建模型
                            input_dim = X_scaled.shape[1]
                            if st.session_state['mdl_model_type'] == 'LSTM':
                                model = LSTMModel(input_dim).to(device)
                            elif st.session_state['mdl_model_type'] == 'GRU':
                                model = GRUModel(input_dim).to(device)
                            elif st.session_state['mdl_model_type'] == 'BiLSTM':
                                model = BiLSTMModel(input_dim).to(device)
                            # å®šä¹‰æŸå¤±å‡½æ•°å’Œä¼˜åŒ–器
                            criterion = nn.MSELoss()
                            optimizer = optim.Adam(model.parameters(), lr=0.001)
                            # è®­ç»ƒæ¨¡åž‹
                            num_epochs = 50
                            batch_size = 32
                            # æ˜¾ç¤ºè®­ç»ƒè¿›åº¦
                            progress_bar = st.progress(0)
                            status_text = st.empty()
                            for epoch in range(num_epochs):
                                model.train()
                                optimizer.zero_grad()
                                # å‰å‘ä¼ æ’­
                                outputs = model(X_train_tensor)
                                loss = criterion(outputs, y_train_tensor)
                                # åå‘传播和优化
                                loss.backward()
                                optimizer.step()
                                # æ›´æ–°è¿›åº¦
                                progress_bar.progress((epoch + 1) / num_epochs)
                                status_text.text(f"训练中: ç¬¬ {epoch + 1}/{num_epochs} è½®, æŸå¤±: {loss.item():.6f}")
                            # é¢„测
                            model.eval()
                            with torch.no_grad():
                                y_pred_scaled_tensor = model(X_test_tensor)
                                y_pred_scaled = y_pred_scaled_tensor.cpu().numpy().ravel()
                                # åå½’一化
                                y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
                                y_test_actual = scaler_y.inverse_transform(y_test_seq.reshape(-1, 1)).ravel()
                            # è®¡ç®—评估指标
                            r2 = r2_score(y_test_actual, y_pred)
                            mse = mean_squared_error(y_test_actual, y_pred)
                            mae = mean_absolute_error(y_test_actual, y_pred)
                            rmse = np.sqrt(mse)
                            # æ˜¾ç¤ºæ¨¡åž‹æ€§èƒ½
                            metrics_cols = st.columns(2)
                            with metrics_cols[0]:
                                st.metric("R² å¾—分", f"{r2:.4f}")
                                st.metric("均方误差 (MSE)", f"{mse:.6f}")
                            with metrics_cols[1]:
                                st.metric("平均绝对误差 (MAE)", f"{mae:.6f}")
                                st.metric("均方根误差 (RMSE)", f"{rmse:.6f}")
                            # æ·»åŠ ç¨³æ€ç›¸å…³çš„è¯„ä¼°è¯´æ˜Ž
                            use_steady_data = st.session_state.get('mdl_use_steady_data', True)
                            if use_steady_data:
                                st.info("⚠️ æ¨¡åž‹ä»…使用稳态数据进行训练,在非稳态工况下预测结果可能不准确")
                            # --- å®žé™…值与预测值对比 ---
                            # --- å®žé™…值与预测值对比 ---
                            st.subheader("🔄 å®žé™…值与预测值对比")
                            # åˆ›å»ºå¯¹æ¯”数据
                            compare_df = pd.DataFrame({
                                '实际值': y_test_actual,
                                '预测值': y_pred
                            })
                            compare_df = compare_df.sort_index()
                            # åˆ›å»ºå¯¹æ¯”图
                            fig_compare = go.Figure()
                            fig_compare.add_trace(go.Scatter(
                                x=compare_df.index,
                                y=compare_df['实际值'],
                                name='实际值',
                                mode='lines+markers',
                                line=dict(color='blue', width=2)
                            ))
                            fig_compare.add_trace(go.Scatter(
                                x=compare_df.index,
                                y=compare_df['预测值'],
                                name='预测值',
                                mode='lines+markers',
                                line=dict(color='red', width=2, dash='dash')
                            ))
                            fig_compare.update_layout(
                                title=f'测试集: å®žé™…米重 vs é¢„测米重 ({st.session_state["mdl_model_type"]})',
                                xaxis=dict(title='样本索引'),
                                yaxis=dict(title='米重 (Kg/m)'),
                                legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
                                height=400
                            )
                            st.plotly_chart(fig_compare, width='stretch')
                            # --- æ®‹å·®åˆ†æž ---
                            st.subheader("📉 æ®‹å·®åˆ†æž")
                            # è®¡ç®—残差
                            residuals = y_test_actual - y_pred
                            # åˆ›å»ºæ®‹å·®å›¾
                            fig_residual = go.Figure()
                            fig_residual.add_trace(go.Scatter(
                                x=y_pred,
                                y=residuals,
                                mode='markers',
                                marker=dict(color='green', size=8, opacity=0.6)
                            ))
                            fig_residual.add_shape(
                                type="line",
                                x0=y_pred.min(),
                                y0=0,
                                x1=y_pred.max(),
                                y1=0,
                                line=dict(color="red", width=2, dash="dash")
                            )
                            fig_residual.update_layout(
                                title='残差图',
                                xaxis=dict(title='预测值'),
                                yaxis=dict(title='残差'),
                                height=400
                            )
                            st.plotly_chart(fig_residual, width='stretch')
                            # --- æ¨¡åž‹ä¿å­˜ ---
                            st.subheader("💾 æ¨¡åž‹ä¿å­˜")
                            # åˆ›å»ºæ¨¡åž‹ç›®å½•(如果不存在)
                            model_dir = "saved_models"
                            os.makedirs(model_dir, exist_ok=True)
                            # å‡†å¤‡æ¨¡åž‹ä¿¡æ¯
                            model_info = {
                                'model': model,
                                'features': default_features,
                                'scaler_X': scaler_X,
                                'scaler_y': scaler_y,
                                'model_type': st.session_state['mdl_model_type'],
                                'sequence_length': sequence_length,
                                'created_at': datetime.now(),
                                'r2_score': r2,
                                'mse': mse,
                                'mae': mae,
                                'rmse': rmse,
                                'use_steady_data': use_steady_data
                            }
                            # ç”Ÿæˆæ¨¡åž‹æ–‡ä»¶å
                            model_filename = f"deep_{st.session_state['mdl_model_type'].lower()}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.joblib"
                            model_path = os.path.join(model_dir, model_filename)
                            # ä¿å­˜æ¨¡åž‹
                            joblib.dump(model_info, model_path)
                            st.success(f"模型已成功保存: {model_filename}")
                            st.info(f"保存路径: {model_path}")
            else:
                st.warning("未检测到PyTorch,无法使用深度学习预测功能。请确保已正确安装PyTorch库。")
            # --- æ•°æ®é¢„览 ---
            st.subheader("🔍 æ•°æ®é¢„览")
            st.dataframe(df_analysis.head(20), width='stretch')
            # --- å¯¼å‡ºæ•°æ® ---
            st.subheader("💾 å¯¼å‡ºæ•°æ®")
            # å°†æ•°æ®è½¬æ¢ä¸ºCSV格式
            csv = df_analysis.to_csv(index=False)
            # åˆ›å»ºä¸‹è½½æŒ‰é’®
            st.download_button(
                label="导出整合后的数据 (CSV)",
                data=csv,
                file_name=f"metered_weight_deep_learning_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
                mime="text/csv",
                help="点击按钮导出整合后的米重分析数据"
            )
    else:
        # æç¤ºç”¨æˆ·ç‚¹å‡»å¼€å§‹åˆ†æžæŒ‰é’®
        st.info("请选择时间范围并点击'开始分析'按钮获取数据。")
app/pages/metered_weight_forecast.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,716 @@
import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import joblib
import os
from datetime import datetime, timedelta
from app.services.extruder_service import ExtruderService
from app.services.main_process_service import MainProcessService
# å°è¯•导入torch,如果失败则禁用深度学习模型支持
try:
    import torch
    TORCH_AVAILABLE = True
except ImportError:
    TORCH_AVAILABLE = False
# ç¨³æ€è¯†åˆ«ç±»
class SteadyStateDetector:
    def __init__(self):
        pass
    def detect_steady_state(self, df, weight_col='米重', window_size=20, std_threshold=0.5, duration_threshold=60):
        """
        ç¨³æ€è¯†åˆ«é€»è¾‘:标记米重数据中的稳态段
        :param df: åŒ…含米重数据的数据框
        :param weight_col: ç±³é‡åˆ—名
        :param window_size: æ»‘动窗口大小(秒)
        :param std_threshold: æ ‡å‡†å·®é˜ˆå€¼
        :param duration_threshold: ç¨³æ€æŒç»­æ—¶é—´é˜ˆå€¼ï¼ˆç§’)
        :return: åŒ…含稳态标记的数据框和稳态信息
        """
        if df is None or df.empty:
            return df, []
        # ç¡®ä¿æ—¶é—´åˆ—是datetime类型
        df['time'] = pd.to_datetime(df['time'])
        # è®¡ç®—滚动统计量
        df['rolling_std'] = df[weight_col].rolling(window=window_size, min_periods=5).std()
        df['rolling_mean'] = df[weight_col].rolling(window=window_size, min_periods=5).mean()
        # è®¡ç®—波动范围
        df['fluctuation_range'] = (df['rolling_std'] / df['rolling_mean']) * 100
        df['fluctuation_range'] = df['fluctuation_range'].fillna(0)
        # æ ‡è®°ç¨³æ€ç‚¹
        df['is_steady'] = 0
        steady_condition = (
            (df['fluctuation_range'] < std_threshold) &
            (df[weight_col] >= 0.1)
        )
        df.loc[steady_condition, 'is_steady'] = 1
        # è¯†åˆ«è¿žç»­ç¨³æ€æ®µ
        steady_segments = []
        current_segment = {}
        for i, row in df.iterrows():
            if row['is_steady'] == 1:
                if not current_segment:
                    current_segment = {
                        'start_time': row['time'],
                        'start_idx': i,
                        'weights': [row[weight_col]]
                    }
                else:
                    current_segment['weights'].append(row[weight_col])
            else:
                if current_segment:
                    current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time']
                    current_segment['end_idx'] = i-1
                    duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
                    if duration >= duration_threshold:
                        weights_array = np.array(current_segment['weights'])
                        current_segment['duration'] = duration
                        current_segment['mean_weight'] = np.mean(weights_array)
                        current_segment['std_weight'] = np.std(weights_array)
                        current_segment['min_weight'] = np.min(weights_array)
                        current_segment['max_weight'] = np.max(weights_array)
                        current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                        # è®¡ç®—置信度
                        confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                        confidence = max(50, min(100, confidence))
                        current_segment['confidence'] = confidence
                        steady_segments.append(current_segment)
                    current_segment = {}
        # å¤„理最后一个稳态段
        if current_segment:
            current_segment['end_time'] = df['time'].iloc[-1]
            current_segment['end_idx'] = len(df) - 1
            duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
            if duration >= duration_threshold:
                weights_array = np.array(current_segment['weights'])
                current_segment['duration'] = duration
                current_segment['mean_weight'] = np.mean(weights_array)
                current_segment['std_weight'] = np.std(weights_array)
                current_segment['min_weight'] = np.min(weights_array)
                current_segment['max_weight'] = np.max(weights_array)
                current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                confidence = max(50, min(100, confidence))
                current_segment['confidence'] = confidence
                steady_segments.append(current_segment)
        # åœ¨æ•°æ®æ¡†ä¸­æ ‡è®°å®Œæ•´çš„稳态段
        for segment in steady_segments:
            df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1
        return df, steady_segments
def show_metered_weight_forecast():
    # åˆå§‹åŒ–服务
    extruder_service = ExtruderService()
    main_process_service = MainProcessService()
    # é¡µé¢æ ‡é¢˜
    st.title("米重预测分析")
    # åˆå§‹åŒ–会话状态
    if 'forecast_start_date' not in st.session_state:
        st.session_state['forecast_start_date'] = datetime.now().date() - timedelta(days=7)
    if 'forecast_end_date' not in st.session_state:
        st.session_state['forecast_end_date'] = datetime.now().date()
    if 'forecast_quick_select' not in st.session_state:
        st.session_state['forecast_quick_select'] = "最近7天"
    if 'selected_model' not in st.session_state:
        st.session_state['selected_model'] = None
    if 'selected_model_file' not in st.session_state:
        st.session_state['selected_model_file'] = None
    if 'forecast_use_steady_only' not in st.session_state:
        st.session_state['forecast_use_steady_only'] = True
    if 'forecast_steady_window' not in st.session_state:
        st.session_state['forecast_steady_window'] = 20
    if 'forecast_steady_threshold' not in st.session_state:
        st.session_state['forecast_steady_threshold'] = 1.5
    # å®šä¹‰å›žè°ƒå‡½æ•°
    def update_dates(qs):
        st.session_state['forecast_quick_select'] = qs
        today = datetime.now().date()
        if qs == "今天":
            st.session_state['forecast_start_date'] = today
            st.session_state['forecast_end_date'] = today
        elif qs == "最近3天":
            st.session_state['forecast_start_date'] = today - timedelta(days=3)
            st.session_state['forecast_end_date'] = today
        elif qs == "最近7天":
            st.session_state['forecast_start_date'] = today - timedelta(days=7)
            st.session_state['forecast_end_date'] = today
        elif qs == "最近30天":
            st.session_state['forecast_start_date'] = today - timedelta(days=30)
            st.session_state['forecast_end_date'] = today
    def on_date_change():
        st.session_state['forecast_quick_select'] = "自定义"
    # æŸ¥è¯¢æ¡ä»¶åŒºåŸŸ
    with st.expander("🔍 æ•°æ®é€‰æ‹©", expanded=True):
        # æ·»åŠ è‡ªå®šä¹‰ CSS å®žçŽ°å“åº”å¼æ¢è¡Œ
        st.markdown("""
            <style>
            /* å¼ºåˆ¶åˆ—容器换行 */
            [data-testid="stExpander"] [data-testid="column"] {
                flex: 1 1 120px !important;
                min-width: 120px !important;
            }
            /* é’ˆå¯¹æ—¥æœŸè¾“入框列稍微加宽一点 */
            @media (min-width: 768px) {
                [data-testid="stExpander"] [data-testid="column"]:nth-child(6),
                [data-testid="stExpander"] [data-testid="column"]:nth-child(7) {
                    flex: 2 1 180px !important;
                    min-width: 180px !important;
                }
            }
            </style>
            """, unsafe_allow_html=True)
        # åˆ›å»ºå¸ƒå±€
        cols = st.columns([1, 1, 1, 1, 1, 1.5, 1.5, 1])
        options = ["今天", "最近3天", "最近7天", "最近30天", "自定义"]
        for i, option in enumerate(options):
            with cols[i]:
                # æ ¹æ®å½“前选择状态决定按钮类型
                button_type = "primary" if st.session_state['forecast_quick_select'] == option else "secondary"
                if st.button(option, key=f"btn_forecast_{option}", width='stretch', type=button_type):
                    update_dates(option)
                    st.rerun()
        with cols[5]:
            start_date = st.date_input(
                "开始日期",
                label_visibility="collapsed",
                key="forecast_start_date",
                on_change=on_date_change
            )
        with cols[6]:
            end_date = st.date_input(
                "结束日期",
                label_visibility="collapsed",
                key="forecast_end_date",
                on_change=on_date_change
            )
        with cols[7]:
            query_button = st.button("🚀 æŸ¥è¯¢æ•°æ®", key="forecast_query", width='stretch')
    # è½¬æ¢ä¸ºdatetime对象
    start_dt = datetime.combine(start_date, datetime.min.time())
    end_dt = datetime.combine(end_date, datetime.max.time())
    # æ¨¡åž‹é€‰æ‹©åŒºåŸŸ
    with st.expander("📁 æ¨¡åž‹é€‰æ‹©", expanded=True):
        # åˆ›å»ºæ¨¡åž‹ç›®å½•(如果不存在)
        model_dir = "saved_models"
        os.makedirs(model_dir, exist_ok=True)
        # èŽ·å–æ‰€æœ‰å·²ä¿å­˜çš„æ¨¡åž‹æ–‡ä»¶
        model_files = [f for f in os.listdir(model_dir) if f.endswith('.joblib')]
        model_files.sort(reverse=True)  # æœ€æ–°çš„æ¨¡åž‹æŽ’在前面
        if not model_files:
            st.warning("尚未保存任何模型,请先训练模型并保存。")
        else:
            # æ¨¡åž‹é€‰æ‹©ä¸‹æ‹‰æ¡†
            selected_model_file = st.selectbox(
                "选择已保存的模型",
                options=model_files,
                help="选择要用于预测的模型文件",
                key="forecast_selected_model"
            )
            # åŠ è½½å¹¶æ˜¾ç¤ºæ¨¡åž‹ä¿¡æ¯
            if selected_model_file:
                model_path = os.path.join(model_dir, selected_model_file)
                model_info = joblib.load(model_path)
                # æ˜¾ç¤ºæ¨¡åž‹åŸºæœ¬ä¿¡æ¯
                st.subheader("📊 æ¨¡åž‹ä¿¡æ¯")
                info_cols = st.columns(2)
                with info_cols[0]:
                    st.metric("模型类型", model_info['model_type'])
                    st.metric("创建时间", model_info['created_at'].strftime('%Y-%m-%d %H:%M:%S'))
                    st.metric("使用稳态数据", "是" if model_info.get('use_steady_data', False) else "否")
                with info_cols[1]:
                    st.metric("R² å¾—分", f"{model_info['r2_score']:.4f}")
                    st.metric("均方误差 (MSE)", f"{model_info['mse']:.6f}")
                    st.metric("均方根误差 (RMSE)", f"{model_info['rmse']:.6f}")
                # æ˜¾ç¤ºæ¨¡åž‹ç‰¹å¾
                st.write("🔑 æ¨¡åž‹ä½¿ç”¨çš„特征:")
                st.code(", ".join(model_info['features']))
                # å¦‚果是深度学习模型,显示序列长度
                if 'sequence_length' in model_info:
                    st.metric("序列长度", model_info['sequence_length'])
                # ä¿å­˜æ¨¡åž‹ä¿¡æ¯åˆ°ä¼šè¯çŠ¶æ€
                st.session_state['selected_model'] = model_info
                st.session_state['selected_model_file'] = selected_model_file
        # ç¨³æ€è¯†åˆ«é…ç½®
        st.markdown("---")
        st.write("⚖️ **稳态识别配置**")
        steady_cols = st.columns(3)
        with steady_cols[0]:
            st.checkbox(
                "仅预测稳态数据",
                value=st.session_state['forecast_use_steady_only'],
                key="forecast_use_steady_only",
                help="启用后,只对处于稳态时段的数据进行米重预测"
            )
        with steady_cols[1]:
            st.slider(
                "滑动窗口大小 (秒)",
                min_value=5,
                max_value=60,
                value=st.session_state['forecast_steady_window'],
                step=5,
                key="forecast_steady_window",
                help="用于稳态识别的滑动窗口大小"
            )
        with steady_cols[2]:
            st.slider(
                "波动阈值 (%)",
                min_value=0.1,
                max_value=2.0,
                value=st.session_state['forecast_steady_threshold'],
                step=0.1,
                key="forecast_steady_threshold",
                help="稳态识别的波动范围阈值"
            )
    # é¢„测功能区域
    st.subheader("🔮 ç±³é‡é¢„测")
    if query_button and st.session_state['selected_model']:
        with st.spinner("正在获取数据并进行预测..."):
            # 1. èŽ·å–å®Œæ•´çš„æŒ¤å‡ºæœºæ•°æ®
            df_extruder_full = extruder_service.get_extruder_data(start_dt, end_dt)
            # 2. èŽ·å–ä¸»æµç¨‹æŽ§åˆ¶æ•°æ®
            df_main_speed = main_process_service.get_cutting_setting_data(start_dt, end_dt)
            df_temp = main_process_service.get_temperature_control_data(start_dt, end_dt)
            # æ£€æŸ¥æ˜¯å¦æœ‰æ•°æ®
            has_data = any([
                df_extruder_full is not None and not df_extruder_full.empty,
                df_main_speed is not None and not df_main_speed.empty,
                df_temp is not None and not df_temp.empty
            ])
            if not has_data:
                st.warning("所选时间段内未找到任何数据,请尝试调整查询条件。")
            else:
                # æ•°æ®æ•´åˆä¸Žé¢„处理
                def integrate_data(df_extruder_full, df_main_speed, df_temp):
                    # ç¡®ä¿æŒ¤å‡ºæœºæ•°æ®å­˜åœ¨
                    if df_extruder_full is None or df_extruder_full.empty:
                        return None
                    # åˆ›å»ºåªåŒ…含米重和时间的主数据集
                    df_merged = df_extruder_full[['time', 'metered_weight', 'screw_speed_actual', 'head_pressure']].copy()
                    # æ•´åˆä¸»æµç¨‹æ•°æ®
                    if df_main_speed is not None and not df_main_speed.empty:
                        df_main_speed = df_main_speed[['time', 'process_main_speed']]
                        df_merged = pd.merge_asof(
                            df_merged.sort_values('time'),
                            df_main_speed.sort_values('time'),
                            on='time',
                            direction='nearest',
                            tolerance=pd.Timedelta('1min')
                        )
                    # æ•´åˆæ¸©åº¦æ•°æ®
                    if df_temp is not None and not df_temp.empty:
                        temp_cols = ['time', 'nakata_extruder_screw_display_temp',
                                   'nakata_extruder_rear_barrel_display_temp',
                                   'nakata_extruder_front_barrel_display_temp',
                                   'nakata_extruder_head_display_temp']
                        df_temp_subset = df_temp[temp_cols].copy()
                        df_merged = pd.merge_asof(
                            df_merged.sort_values('time'),
                            df_temp_subset.sort_values('time'),
                            on='time',
                            direction='nearest',
                            tolerance=pd.Timedelta('1min')
                        )
                    # é‡å‘½ååˆ—以提高可读性
                    df_merged.rename(columns={
                        'screw_speed_actual': '螺杆转速',
                        'head_pressure': '机头压力',
                        'process_main_speed': '流程主速',
                        'nakata_extruder_screw_display_temp': '螺杆温度',
                        'nakata_extruder_rear_barrel_display_temp': '后机筒温度',
                        'nakata_extruder_front_barrel_display_temp': '前机筒温度',
                        'nakata_extruder_head_display_temp': '机头温度'
                    }, inplace=True)
                    # æ¸…理数据
                    df_merged.dropna(subset=['metered_weight'], inplace=True)
                    return df_merged
                # æ‰§è¡Œæ•°æ®æ•´åˆ
                df_analysis = integrate_data(df_extruder_full, df_main_speed, df_temp)
                if df_analysis is None or df_analysis.empty:
                    st.warning("数据整合失败,请检查数据质量或调整时间范围。")
                else:
                    # é‡å‘½åç±³é‡åˆ—
                    df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True)
                    # ç¨³æ€è¯†åˆ«
                    steady_detector = SteadyStateDetector()
                    # èŽ·å–ç¨³æ€è¯†åˆ«å‚æ•°
                    use_steady_only = st.session_state.get('forecast_use_steady_only', True)
                    steady_window = st.session_state.get('forecast_steady_window', 20)
                    steady_threshold = st.session_state.get('forecast_steady_threshold', 0.5)
                    # æ‰§è¡Œç¨³æ€è¯†åˆ«
                    df_analysis_with_steady, steady_segments = steady_detector.detect_steady_state(
                        df_analysis,
                        weight_col='米重',
                        window_size=steady_window,
                        std_threshold=steady_threshold
                    )
                    # æ›´æ–°df_analysis为包含稳态标记的数据
                    df_analysis = df_analysis_with_steady
                    # æ˜¾ç¤ºç¨³æ€ç»Ÿè®¡ä¿¡æ¯
                    total_data = len(df_analysis)
                    steady_data = len(df_analysis[df_analysis['is_steady'] == 1])
                    steady_ratio = (steady_data / total_data * 100) if total_data > 0 else 0
                    st.subheader("📊 ç¨³æ€æ•°æ®ç»Ÿè®¡")
                    stats_cols = st.columns(4)
                    stats_cols[0].metric("总数据量", total_data)
                    stats_cols[1].metric("稳态数据量", steady_data)
                    stats_cols[2].metric("稳态数据比例", f"{steady_ratio:.1f}%")
                    stats_cols[3].metric("稳态段数量", len(steady_segments))
                    # èŽ·å–æ¨¡åž‹ä¿¡æ¯
                    model_info = st.session_state['selected_model']
                    required_features = model_info['features']
                    # æ£€æŸ¥æ‰€æœ‰å¿…需的特征是否在数据中
                    missing_features = [f for f in required_features if f not in df_analysis.columns]
                    if missing_features:
                        st.warning(f"数据中缺少以下特征: {', '.join(missing_features)}")
                    else:
                        # å‡†å¤‡æ‰€æœ‰æ•°æ®ç”¨äºŽæ˜¾ç¤º
                        df_all = df_analysis.dropna(subset=required_features + ['米重']).copy()
                        if len(df_all) == 0:
                            st.warning("没有足够的有效数据进行预测,请调整时间范围或检查数据质量。")
                        else:
                            # æ ¹æ®é…ç½®å†³å®šæ˜¯å¦åªä½¿ç”¨ç¨³æ€æ•°æ®è¿›è¡Œé¢„测
                            if use_steady_only:
                                df_pred_steady = df_all[df_all['is_steady'] == 1].copy()
                                if len(df_pred_steady) > 0:
                                    df_pred = df_pred_steady
                                    st.info(f"已启用稳态过滤,使用 {len(df_pred)} æ¡ç¨³æ€æ•°æ®è¿›è¡Œé¢„测")
                                else:
                                    df_pred = df_all.copy()
                                    st.warning("未找到稳态数据,将使用所有数据进行预测")
                            else:
                                df_pred = df_all.copy()
                            # æ‰§è¡Œé¢„测 - åªå¯¹é€‰å®šçš„æ•°æ®ï¼ˆç¨³æ€æˆ–全部)进行预测
                            X_pred = df_pred[required_features]
                            predicted_weights = []
                            # èŽ·å–æ¨¡åž‹
                            model = model_info['model']
                            # æ£€æŸ¥æ¨¡åž‹ç±»åž‹å¹¶æ‰§è¡Œé¢„测
                            if model_info['model_type'] in ['LSTM', 'GRU', 'BiLSTM']:
                                # æ·±åº¦å­¦ä¹ æ¨¡åž‹é¢„测
                                if not TORCH_AVAILABLE:
                                    st.error("PyTorch æœªå®‰è£…,无法使用深度学习模型进行预测。")
                                    st.stop()
                                # æ•°æ®æ ‡å‡†åŒ–
                                scaler_X = model_info['scaler_X']
                                scaler_y = model_info['scaler_y']
                                X_scaled = scaler_X.transform(X_pred)
                                # èŽ·å–åºåˆ—é•¿åº¦
                                sequence_length = model_info['sequence_length']
                                # ä¸ºæ·±åº¦å­¦ä¹ æ¨¡åž‹åˆ›å»ºåºåˆ—
                                def create_sequences(data, seq_length):
                                    sequences = []
                                    for i in range(len(data) - seq_length + 1):
                                        seq = data[i:i+seq_length]
                                        sequences.append(seq)
                                    return np.array(sequences)
                                X_sequences = create_sequences(X_scaled, sequence_length)
                                # è½¬æ¢ä¸ºPyTorch张量
                                import torch
                                device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
                                X_tensor = torch.tensor(X_sequences, dtype=torch.float32).to(device)
                                # é¢„测
                                model.eval()
                                with torch.no_grad():
                                    y_pred_scaled_tensor = model(X_tensor)
                                    y_pred_scaled = y_pred_scaled_tensor.cpu().numpy().ravel()
                                    # åå½’一化
                                    predicted = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
                                    # ç”±äºŽåºåˆ—预测,我们需要填充前面的缺失值
                                    predicted_weights = [np.nan] * (sequence_length - 1) + list(predicted)
                            elif model_info['model_type'] in ['SVR', 'MLP']:
                                # æ”¯æŒå‘量机或多层感知器预测
                                # æ•°æ®æ ‡å‡†åŒ–
                                scaler_X = model_info['scaler_X']
                                scaler_y = model_info['scaler_y']
                                X_scaled = scaler_X.transform(X_pred)
                                # é¢„测
                                y_pred_scaled = model.predict(X_scaled)
                                # åå½’一化
                                predicted_weights = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
                            else:
                                # å…¶ä»–模型(如随机森林、梯度提升、线性回归等)
                                predicted_weights = model.predict(X_pred)
                            # å°†é¢„测结果添加到数据框中
                            df_pred['预测米重'] = predicted_weights
                            # ç¡®ä¿æ—¶é—´åˆ—是datetime类型
                            df_pred['time'] = pd.to_datetime(df_pred['time'])
                            # æ•°æ®å¯¹æ¯”功能
                            st.subheader("📊 é¢„测结果对比分析")
                            # è®¡ç®—预测误差
                            df_pred['误差'] = df_pred['预测米重'] - df_pred['米重']
                            df_pred['绝对误差'] = abs(df_pred['误差'])
                            df_pred['相对误差'] = (df_pred['绝对误差'] / df_pred['米重']) * 100
                            # æ˜¾ç¤ºè¯¯å·®ç»Ÿè®¡ä¿¡æ¯
                            error_stats = df_pred.dropna(subset=['预测米重']).describe()
                            stats_cols = st.columns(3)
                            with stats_cols[0]:
                                st.metric("平均实际米重", f"{error_stats['米重']['mean']:.4f} Kg/m")
                                st.metric("平均预测米重", f"{error_stats['预测米重']['mean']:.4f} Kg/m")
                            with stats_cols[1]:
                                st.metric("平均绝对误差", f"{error_stats['绝对误差']['mean']:.4f} Kg/m")
                                st.metric("最大绝对误差", f"{error_stats['绝对误差']['max']:.4f} Kg/m")
                            with stats_cols[2]:
                                st.metric("平均相对误差", f"{error_stats['相对误差']['mean']:.2f}%")
                                st.metric("最大相对误差", f"{error_stats['相对误差']['max']:.2f}%")
                            # å¯è§†åŒ–展示
                            st.subheader("📈 ç±³é‡è¶‹åŠ¿å¯¹æ¯”")
                            # åˆ›å»ºè¶‹åŠ¿å›¾ - ä½¿ç”¨æ‰€æœ‰æ•°æ®df_all进行显示
                            fig = go.Figure()
                            # ç¡®ä¿æ—¶é—´åˆ—是datetime类型
                            df_all['time'] = pd.to_datetime(df_all['time'])
                            # # æ·»åŠ å®žæ—¶ç±³é‡æ•°æ®ç‚¹ï¼ˆç¨³æ€æ•°æ®ç”¨è“è‰²ï¼Œéžç¨³æ€æ•°æ®ç”¨ç°è‰²ï¼‰
                            # if 'is_steady' in df_all.columns:
                            #     # ç¨³æ€æ•°æ® - ä½¿ç”¨ç‚¹æ˜¾ç¤º
                            #     steady_data = df_all[df_all['is_steady'] == 1]
                            #     non_steady_data = df_all[df_all['is_steady'] == 0]
                            #     if len(steady_data) > 0:
                            #         fig.add_trace(go.Scatter(
                            #             x=steady_data['time'],
                            #             y=steady_data['米重'],
                            #             name='实时米重(稳态)',
                            #             mode='markers',
                            #             marker=dict(color='blue', size=3),
                            #             hovertemplate='时间: %{x}<br>实时米重(稳态): %{y:.4f} Kg/m<extra></extra>'
                            #         ))
                            #     # éžç¨³æ€æ•°æ®ä¹Ÿæ˜¾ç¤ºï¼Œä½†ä¸è¿›è¡Œé¢„测
                            #     if len(non_steady_data) > 0:
                            #         fig.add_trace(go.Scatter(
                            #             x=non_steady_data['time'],
                            #             y=non_steady_data['米重'],
                            #             name='实时米重(非稳态)',
                            #             mode='markers',
                            #             marker=dict(color='lightgray', size=3),
                            #             hovertemplate='时间: %{x}<br>实时米重(非稳态): %{y:.4f} Kg/m<extra></extra>'
                            #         ))
                            # else:
                            # å¦‚果没有稳态标记,显示所有数据点
                            fig.add_trace(go.Scatter(
                                x=df_all['time'],
                                y=df_all['米重'],
                                name='实时米重',
                                mode='lines',
                                line=dict(color='blue', width=1.5),
                                # hovertemplate='时间: %{x}<br>实时米重: %{y:.4f} Kg/m<extra></extra>'
                            ))
                            # æ·»åŠ é¢„æµ‹ç±³é‡æ›²çº¿ - åªå¯¹é¢„测的数据(稳态或全部)显示
                            fig.add_trace(go.Scatter(
                                x=df_pred['time'],
                                y=df_pred['预测米重'],
                                name='预测米重',
                                mode='lines',
                                line=dict(color='red', width=2, dash='dash'),
                                marker=dict(size=3),
                                # hovertemplate='时间: %{x}<br>预测米重: %{y:.4f} Kg/m<extra></extra>'
                            ))
                            # æ·»åŠ æ‰€æœ‰æŒ¤å‡ºæœºå‚æ•°æ›²çº¿ - ä½¿ç”¨æ‰€æœ‰æ•°æ®
                            colors = ['green', 'orange', 'purple', 'brown', 'pink', 'gray', 'olive', 'cyan', 'magenta', 'yellow', 'lime', 'teal']
                            for i, feature in enumerate(required_features):
                                # ä¸ºæ¯ä¸ªç‰¹å¾åˆ†é…ä¸åŒçš„颜色
                                color = colors[i % len(colors)]
                                # ç¡®ä¿ç‰¹å¾å­˜åœ¨äºŽæ‰€æœ‰æ•°æ®ä¸­
                                if feature in df_all.columns:
                                    fig.add_trace(go.Scatter(
                                        x=df_all['time'],
                                        y=df_all[feature],
                                        name=feature,
                                        mode='lines',
                                        line=dict(color=color, width=1.5),
                                        yaxis=f'y{i+2}',
                                        # hovertemplate=f'时间: %{{x}}<br>{feature}: %{{y}}<extra></extra>'
                                    ))
                            # é…ç½®å›¾è¡¨å¸ƒå±€
                            layout = {
                                'title': '米重预测与实时数据对比',
                                'xaxis': {
                                    'title': '时间',
                                    'rangeslider': {'visible': True},
                                    'type': 'date',
                                    'tickformat': '%Y-%m-%d %H:%M'
                                },
                                'yaxis': {
                                    'title': '米重 (Kg/m)',
                                    'title_font': {'color': 'blue'},
                                    'tickfont': {'color': 'blue'},
                                    'side': 'left',
                                    'fixedrange': False  # å…è®¸y轴缩放
                                },
                                'legend': {
                                    'orientation': 'h',
                                    'yanchor': 'bottom',
                                    'y': 1.02,
                                    'xanchor': 'right',
                                    'x': 1
                                },
                                'height': 600,
                                'margin': {'l': 100, 'r': 200, 't': 100, 'b': 100},
                                'hovermode': 'x unified'
                            }
                            # æ·»åŠ é¢å¤–çš„y轴配置 - ä¸ºæ‰€æœ‰ç‰¹å¾åˆ›å»ºyè½´
                            for i, feature in enumerate(required_features):
                                layout[f'yaxis{i+2}'] = {
                                    'title': feature,
                                    'title_font': {'color': colors[i % len(colors)]},
                                    'tickfont': {'color': colors[i % len(colors)]},
                                    'overlaying': 'y',
                                    'side': 'right',
                                    'anchor': 'free',
                                    'position': 1 - (i+1)*0.08,
                                    'fixedrange': False  # å…è®¸y轴缩放
                                }
                            fig.update_layout(layout)
                            # æ˜¾ç¤ºè¶‹åŠ¿å›¾ - å¯ç”¨å®Œæ•´çš„交互功能
                            st.plotly_chart(fig, use_container_width=True, config={
                                'scrollZoom': True,
                                'displayModeBar': True,
                                'modeBarButtonsToAdd': ['pan2d', 'select2d', 'lasso2d', 'resetScale2d'],
                                'displaylogo': False
                            })
                            # è¯¯å·®åˆ†æžå›¾
                            st.subheader("📉 é¢„测误差分析")
                            # åˆ›å»ºè¯¯å·®åˆ†å¸ƒç›´æ–¹å›¾
                            fig_error = px.histogram(df_pred.dropna(subset=['相对误差']), x='相对误差', nbins=50,
                                                   title='预测相对误差分布',
                                                   labels={'相对误差': '相对误差 (%)'})
                            fig_error.update_layout(
                                xaxis_title='相对误差 (%)',
                                yaxis_title='频次',
                                height=400
                            )
                            st.plotly_chart(fig_error, use_container_width=True)
                            # æ•°æ®é¢„览
                            st.subheader("🔍 æ•°æ®é¢„览")
                            preview_columns = ['time', '米重', '预测米重', '误差', '绝对误差', '相对误差']
                            if 'is_steady' in df_pred.columns:
                                preview_columns.append('is_steady')
                            preview_columns.extend(required_features)
                            st.dataframe(df_pred[preview_columns].head(20),
                                        use_container_width=True)
                            # å¯¼å‡ºæ•°æ®
                            st.subheader("💾 å¯¼å‡ºæ•°æ®")
                            # å°†æ•°æ®è½¬æ¢ä¸ºCSV格式
                            csv = df_pred.to_csv(index=False)
                            # åˆ›å»ºä¸‹è½½æŒ‰é’®
                            st.download_button(
                                label="导出预测结果数据 (CSV)",
                                data=csv,
                                file_name=f"metered_weight_forecast_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
                                mime="text/csv",
                                help="点击按钮导出预测结果数据"
                            )
    elif query_button:
        st.warning("请先选择一个模型。")
    else:
        st.info("请选择时间范围和模型,然后点击'查询数据'按钮开始预测分析。")
# é¡µé¢å…¥å£
if __name__ == "__main__":
    show_metered_weight_forecast()
app/pages/metered_weight_prediction.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,208 @@
import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import joblib
import os
from datetime import datetime
# å°è¯•导入torch,如果失败则禁用深度学习模型支持
try:
    import torch
    TORCH_AVAILABLE = True
except ImportError:
    TORCH_AVAILABLE = False
# é¡µé¢å‡½æ•°å®šä¹‰
def show_metered_weight_prediction():
    # é¡µé¢æ ‡é¢˜
    st.title("米重统一预测")
    # åˆå§‹åŒ–会话状态
    if 'selected_model' not in st.session_state:
        st.session_state['selected_model'] = None
    # åˆ›å»ºæ¨¡åž‹ç›®å½•(如果不存在)
    model_dir = "saved_models"
    os.makedirs(model_dir, exist_ok=True)
    # èŽ·å–æ‰€æœ‰å·²ä¿å­˜çš„æ¨¡åž‹æ–‡ä»¶
    model_files = [f for f in os.listdir(model_dir) if f.endswith('.joblib')]
    model_files.sort(reverse=True)  # æœ€æ–°çš„æ¨¡åž‹æŽ’在前面
    # æ¨¡åž‹é€‰æ‹©åŒºåŸŸ
    with st.expander("📁 é€‰æ‹©æ¨¡åž‹", expanded=True):
        if not model_files:
            st.warning("尚未保存任何模型,请先训练模型并保存。")
        else:
            # æ¨¡åž‹é€‰æ‹©ä¸‹æ‹‰æ¡†
            selected_model_file = st.selectbox(
                "选择已保存的模型",
                options=model_files,
                help="选择要用于预测的模型文件"
            )
            # åŠ è½½å¹¶æ˜¾ç¤ºæ¨¡åž‹ä¿¡æ¯
            if selected_model_file:
                model_path = os.path.join(model_dir, selected_model_file)
                model_info = joblib.load(model_path)
                # æ˜¾ç¤ºæ¨¡åž‹åŸºæœ¬ä¿¡æ¯
                st.subheader("📊 æ¨¡åž‹ä¿¡æ¯")
                info_cols = st.columns(2)
                with info_cols[0]:
                    st.metric("模型类型", model_info['model_type'])
                    st.metric("创建时间", model_info['created_at'].strftime('%Y-%m-%d %H:%M:%S'))
                    st.metric("使用稳态数据", "是" if model_info.get('use_steady_data', False) else "否")
                with info_cols[1]:
                    st.metric("R² å¾—分", f"{model_info['r2_score']:.4f}")
                    st.metric("均方误差 (MSE)", f"{model_info['mse']:.6f}")
                    st.metric("均方根误差 (RMSE)", f"{model_info['rmse']:.6f}")
                # æ˜¾ç¤ºæ¨¡åž‹ç‰¹å¾
                st.write("🔑 æ¨¡åž‹ä½¿ç”¨çš„特征:")
                st.code(", ".join(model_info['features']))
                # å¦‚果是深度学习模型,显示序列长度
                if 'sequence_length' in model_info:
                    st.metric("序列长度", model_info['sequence_length'])
                # ä¿å­˜æ¨¡åž‹ä¿¡æ¯åˆ°ä¼šè¯çŠ¶æ€
                st.session_state['selected_model'] = model_info
                st.session_state['selected_model_file'] = selected_model_file
    # é¢„测功能区域
    st.subheader("🔮 ç±³é‡é¢„测")
    if st.session_state['selected_model']:
        model_info = st.session_state['selected_model']
        # èŽ·å–æ¨¡åž‹éœ€è¦çš„ç‰¹å¾
        required_features = model_info['features']
        # åˆ›å»ºé¢„测表单
        st.write("输入特征值进行米重预测:")
        predict_cols = st.columns(2)
        input_features = {}
        # æ˜¾ç¤ºè¾“入表单
        for i, feature in enumerate(required_features):
            with predict_cols[i % 2]:
                input_features[feature] = st.number_input(
                    f"{feature}",
                    key=f"pred_{feature}",
                    value=0.0,
                    step=0.0001,
                    format="%.4f"
                )
        # é¢„测按钮
        if st.button("🚀 å¼€å§‹é¢„测"):
            try:
                # å‡†å¤‡é¢„测数据
                input_df = pd.DataFrame([input_features])
                # æ ¹æ®æ¨¡åž‹ç±»åž‹æ‰§è¡Œä¸åŒçš„预测逻辑
                predicted_weight = None
                # èŽ·å–æ¨¡åž‹
                model = model_info['model']
                # æ£€æŸ¥æ¨¡åž‹ç±»åž‹å¹¶æ‰§è¡Œé¢„测
                if model_info['model_type'] in ['LSTM', 'GRU', 'BiLSTM']:
                    # æ·±åº¦å­¦ä¹ æ¨¡åž‹é¢„测
                    if not TORCH_AVAILABLE:
                        st.error("PyTorch æœªå®‰è£…,无法使用深度学习模型进行预测。")
                        return
                    # æ•°æ®æ ‡å‡†åŒ–
                    scaler_X = model_info['scaler_X']
                    scaler_y = model_info['scaler_y']
                    input_scaled = scaler_X.transform(input_df)
                    # èŽ·å–åºåˆ—é•¿åº¦
                    sequence_length = model_info['sequence_length']
                    # ä¸ºæ·±åº¦å­¦ä¹ æ¨¡åž‹åˆ›å»ºåºåˆ—
                    input_seq = np.tile(input_scaled, (sequence_length, 1)).reshape(1, sequence_length, -1)
                    # è½¬æ¢ä¸ºPyTorch张量
                    import torch
                    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
                    input_tensor = torch.tensor(input_seq, dtype=torch.float32).to(device)
                    # é¢„测
                    model.eval()
                    with torch.no_grad():
                        y_pred_scaled_tensor = model(input_tensor)
                        y_pred_scaled = y_pred_scaled_tensor.cpu().numpy().ravel()[0]
                        # åå½’一化
                        predicted_weight = scaler_y.inverse_transform(np.array([[y_pred_scaled]]))[0][0]
                elif model_info['model_type'] in ['SVR', 'MLP']:
                    # æ”¯æŒå‘量机或多层感知器预测
                    # æ•°æ®æ ‡å‡†åŒ–
                    scaler_X = model_info['scaler_X']
                    scaler_y = model_info['scaler_y']
                    input_scaled = scaler_X.transform(input_df)
                    # é¢„测
                    y_pred_scaled = model.predict(input_scaled)[0]
                    # åå½’一化
                    predicted_weight = scaler_y.inverse_transform(np.array([[y_pred_scaled]]))[0][0]
                else:
                    # å…¶ä»–模型(如随机森林、梯度提升、线性回归等)
                    predicted_weight = model.predict(input_df)[0]
                # æ˜¾ç¤ºé¢„测结果
                st.success(f"预测米重: {predicted_weight:.4f} Kg/m")
            except Exception as e:
                st.error(f"预测失败: {str(e)}")
    else:
        st.warning("请先选择一个模型。")
    # æ¨¡åž‹ç®¡ç†åŒºåŸŸ
    if model_files:
        with st.expander("🗑️ æ¨¡åž‹ç®¡ç†", expanded=False):
            st.write("管理已保存的模型文件:")
            # æ˜¾ç¤ºæ‰€æœ‰æ¨¡åž‹æ–‡ä»¶
            for model_file in model_files:
                cols = st.columns([3, 1, 1])
                cols[0].write(model_file)
                # æŸ¥çœ‹æ¨¡åž‹ä¿¡æ¯æŒ‰é’®
                if cols[1].button("查看", key=f"view_{model_file}", help="查看模型信息"):
                    model_path = os.path.join(model_dir, model_file)
                    model_info = joblib.load(model_path)
                    st.write("模型详细信息:")
                    st.json({
                        'model_type': model_info['model_type'],
                        'created_at': model_info['created_at'].strftime('%Y-%m-%d %H:%M:%S'),
                        'r2_score': f"{model_info['r2_score']:.4f}",
                        'mse': f"{model_info['mse']:.6f}",
                        'mae': f"{model_info['mae']:.6f}",
                        'rmse': f"{model_info['rmse']:.6f}",
                        'features': model_info['features'],
                        'use_steady_data': model_info.get('use_steady_data', False)
                    })
                # åˆ é™¤æ¨¡åž‹æŒ‰é’®
                if cols[2].button("删除", key=f"delete_{model_file}", help="删除模型文件", type="primary"):
                    model_path = os.path.join(model_dir, model_file)
                    os.remove(model_path)
                    st.success(f"已删除模型: {model_file}")
                    st.rerun()
# é¡µé¢å…¥å£
if __name__ == "__main__":
    show_metered_weight_prediction()
app/pages/metered_weight_regression.py
@@ -3,12 +3,117 @@
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import joblib
import os
from datetime import datetime, timedelta
from app.services.extruder_service import ExtruderService
from app.services.main_process_service import MainProcessService
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
# å¯¼å…¥ç¨³æ€è¯†åˆ«åŠŸèƒ½
class SteadyStateDetector:
    def __init__(self):
        pass
    def detect_steady_state(self, df, weight_col='米重', window_size=20, std_threshold=0.5, duration_threshold=60):
        """
        ç¨³æ€è¯†åˆ«é€»è¾‘:标记米重数据中的稳态段
        :param df: åŒ…含米重数据的数据框
        :param weight_col: ç±³é‡åˆ—名
        :param window_size: æ»‘动窗口大小(秒)
        :param std_threshold: æ ‡å‡†å·®é˜ˆå€¼
        :param duration_threshold: ç¨³æ€æŒç»­æ—¶é—´é˜ˆå€¼ï¼ˆç§’)
        :return: åŒ…含稳态标记的数据框和稳态信息
        """
        if df is None or df.empty:
            return df, []
        # ç¡®ä¿æ—¶é—´åˆ—是datetime类型
        df['time'] = pd.to_datetime(df['time'])
        # è®¡ç®—滚动统计量
        df['rolling_std'] = df[weight_col].rolling(window=window_size, min_periods=5).std()
        df['rolling_mean'] = df[weight_col].rolling(window=window_size, min_periods=5).mean()
        # è®¡ç®—波动范围
        df['fluctuation_range'] = (df['rolling_std'] / df['rolling_mean']) * 100
        df['fluctuation_range'] = df['fluctuation_range'].fillna(0)
        # æ ‡è®°ç¨³æ€ç‚¹
        df['is_steady'] = 0
        steady_condition = (
            (df['fluctuation_range'] < std_threshold) &
            (df[weight_col] >= 0.1)
        )
        df.loc[steady_condition, 'is_steady'] = 1
        # è¯†åˆ«è¿žç»­ç¨³æ€æ®µ
        steady_segments = []
        current_segment = {}
        for i, row in df.iterrows():
            if row['is_steady'] == 1:
                if not current_segment:
                    current_segment = {
                        'start_time': row['time'],
                        'start_idx': i,
                        'weights': [row[weight_col]]
                    }
                else:
                    current_segment['weights'].append(row[weight_col])
            else:
                if current_segment:
                    current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time']
                    current_segment['end_idx'] = i-1
                    duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
                    if duration >= duration_threshold:
                        weights_array = np.array(current_segment['weights'])
                        current_segment['duration'] = duration
                        current_segment['mean_weight'] = np.mean(weights_array)
                        current_segment['std_weight'] = np.std(weights_array)
                        current_segment['min_weight'] = np.min(weights_array)
                        current_segment['max_weight'] = np.max(weights_array)
                        current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                        # è®¡ç®—置信度
                        confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                        confidence = max(50, min(100, confidence))
                        current_segment['confidence'] = confidence
                        steady_segments.append(current_segment)
                    current_segment = {}
        # å¤„理最后一个稳态段
        if current_segment:
            current_segment['end_time'] = df['time'].iloc[-1]
            current_segment['end_idx'] = len(df) - 1
            duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
            if duration >= duration_threshold:
                weights_array = np.array(current_segment['weights'])
                current_segment['duration'] = duration
                current_segment['mean_weight'] = np.mean(weights_array)
                current_segment['std_weight'] = np.std(weights_array)
                current_segment['min_weight'] = np.min(weights_array)
                current_segment['max_weight'] = np.max(weights_array)
                current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                confidence = max(50, min(100, confidence))
                current_segment['confidence'] = confidence
                steady_segments.append(current_segment)
        # åœ¨æ•°æ®æ¡†ä¸­æ ‡è®°å®Œæ•´çš„稳态段
        for segment in steady_segments:
            df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1
        return df, steady_segments
def show_metered_weight_regression():
@@ -33,6 +138,12 @@
            '螺杆转速', '机头压力', '流程主速', '螺杆温度', 
            '后机筒温度', '前机筒温度', '机头温度'
        ]
    if 'mr_use_steady_data' not in st.session_state:
        st.session_state['mr_use_steady_data'] = True
    if 'mr_steady_window' not in st.session_state:
        st.session_state['mr_steady_window'] = 20
    if 'mr_steady_threshold' not in st.session_state:
        st.session_state['mr_steady_threshold'] = 0.5
    # å®šä¹‰å›žè°ƒå‡½æ•°
    def update_dates(qs):
@@ -123,6 +234,42 @@
            st.session_state['mr_time_offset'] = time_offset
        with offset_cols[2]:
            st.write(f"当前偏移: {time_offset} åˆ†é’Ÿ")
        # ç¨³æ€è¯†åˆ«é…ç½®
        st.markdown("---")
        steady_cols = st.columns(3)
        with steady_cols[0]:
            st.write("⚖️ **稳态识别配置**")
            st.checkbox(
                "仅使用稳态数据进行训练",
                value=st.session_state['mr_use_steady_data'],
                key="mr_use_steady_data",
                help="启用后,只使用米重稳态时段的数据进行模型训练"
            )
        with steady_cols[1]:
            st.write("📏 **稳态参数**")
            st.slider(
                "滑动窗口大小 (秒)",
                min_value=5,
                max_value=60,
                value=st.session_state['mr_steady_window'],
                step=5,
                key="mr_steady_window",
                help="用于稳态识别的滑动窗口大小"
            )
        with steady_cols[2]:
            st.write("📊 **稳态阈值**")
            st.slider(
                "波动阈值 (%)",
                min_value=0.1,
                max_value=2.0,
                value=st.session_state['mr_steady_threshold'],
                step=0.1,
                key="mr_steady_threshold",
                help="稳态识别的波动范围阈值"
            )
        # ç‰¹å¾é€‰æ‹©
        st.markdown("---")
@@ -305,6 +452,82 @@
            # é‡å‘½åç±³é‡åˆ—
            df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True)
            # ç¨³æ€è¯†åˆ«
            steady_detector = SteadyStateDetector()
            # èŽ·å–ç¨³æ€è¯†åˆ«å‚æ•°
            use_steady_data = st.session_state.get('mr_use_steady_data', True)
            steady_window = st.session_state.get('mr_steady_window', 20)
            steady_threshold = st.session_state.get('mr_steady_threshold', 0.5)
            # æ‰§è¡Œç¨³æ€è¯†åˆ«
            df_analysis_with_steady, steady_segments = steady_detector.detect_steady_state(
                df_analysis,
                weight_col='米重',
                window_size=steady_window,
                std_threshold=steady_threshold
            )
            # æ›´æ–°df_analysis为包含稳态标记的数据
            df_analysis = df_analysis_with_steady
            # ç¨³æ€æ•°æ®å¯è§†åŒ–
            st.subheader("📈 ç¨³æ€æ•°æ®åˆ†å¸ƒ")
            # åˆ›å»ºç¨³æ€æ•°æ®å¯è§†åŒ–图表
            fig_steady = go.Figure()
            # æ·»åŠ åŽŸå§‹ç±³é‡æ›²çº¿
            fig_steady.add_trace(go.Scatter(
                x=df_analysis['time'],
                y=df_analysis['米重'],
                name='原始米重',
                mode='lines',
                line=dict(color='lightgray', width=1)
            ))
            # æ·»åŠ ç¨³æ€æ•°æ®ç‚¹
            steady_data_points = df_analysis[df_analysis['is_steady'] == 1]
            fig_steady.add_trace(go.Scatter(
                x=steady_data_points['time'],
                y=steady_data_points['米重'],
                name='稳态米重',
                mode='markers',
                marker=dict(color='green', size=3, opacity=0.6)
            ))
            # æ·»åŠ éžç¨³æ€æ•°æ®ç‚¹
            non_steady_data_points = df_analysis[df_analysis['is_steady'] == 0]
            fig_steady.add_trace(go.Scatter(
                x=non_steady_data_points['time'],
                y=non_steady_data_points['米重'],
                name='非稳态米重',
                mode='markers',
                marker=dict(color='red', size=3, opacity=0.6)
            ))
            # é…ç½®å›¾è¡¨å¸ƒå±€
            fig_steady.update_layout(
                title="米重数据稳态分布",
                xaxis=dict(title="时间"),
                yaxis=dict(title="米重 (Kg/m)"),
                legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
                height=500
            )
            # æ˜¾ç¤ºå›¾è¡¨
            st.plotly_chart(fig_steady, use_container_width=True)
            # æ˜¾ç¤ºç¨³æ€ç»Ÿè®¡
            total_data = len(df_analysis)
            steady_data = len(df_analysis[df_analysis['is_steady'] == 1])
            steady_ratio = (steady_data / total_data * 100) if total_data > 0 else 0
            stats_cols = st.columns(3)
            stats_cols[0].metric("总数据量", total_data)
            stats_cols[1].metric("稳态数据量", steady_data)
            stats_cols[2].metric("稳态数据比例", f"{steady_ratio:.1f}%")
            # --- åŽŸå§‹æ•°æ®è¶‹åŠ¿å›¾ ---
            st.subheader("📈 åŽŸå§‹æ•°æ®è¶‹åŠ¿å›¾")
@@ -440,8 +663,16 @@
                    st.warning(f"数据中缺少以下特征: {', '.join(missing_features)}")
                else:
                    # å‡†å¤‡æ•°æ®
                    X = df_analysis[st.session_state['mr_selected_features']]
                    y = df_analysis['米重']
                    # æ ¹æ®é…ç½®å†³å®šæ˜¯å¦åªä½¿ç”¨ç¨³æ€æ•°æ®
                    use_steady_data = st.session_state.get('mr_use_steady_data', True)
                    if use_steady_data:
                        df_filtered = df_analysis[df_analysis['is_steady'] == 1]
                        st.info(f"已过滤非稳态数据,使用 {len(df_filtered)} æ¡ç¨³æ€æ•°æ®è¿›è¡Œè®­ç»ƒ")
                    else:
                        df_filtered = df_analysis.copy()
                    X = df_filtered[st.session_state['mr_selected_features']]
                    y = df_filtered['米重']
                    # æ¸…理数据中的NaN值
                    combined = pd.concat([X, y], axis=1)
@@ -454,7 +685,7 @@
                        # é‡æ–°åˆ†ç¦»X和y
                        X_clean = combined_clean[st.session_state['mr_selected_features']]
                        y_clean = combined_clean['米重']
                        # åˆ†å‰²è®­ç»ƒé›†å’Œæµ‹è¯•集
                        X_train, X_test, y_train, y_test = train_test_split(X_clean, y_clean, test_size=0.2, random_state=42)
@@ -580,37 +811,42 @@
                        })
                        st.dataframe(coef_df, use_container_width=True)
                        # --- é¢„测功能 ---
                        st.subheader("🔮 ç±³é‡é¢„测")
                        # --- æ¨¡åž‹ä¿å­˜åŠŸèƒ½ ---
                        st.subheader("💾 æ¨¡åž‹ä¿å­˜")
                        # åˆ›å»ºé¢„测表单
                        st.write("输入特征值进行米重预测:")
                        predict_cols = st.columns(2)
                        input_features = {}
                        # åˆ›å»ºæ¨¡åž‹ä¿å­˜è¡¨å•
                        st.write("保存训练好的模型权重:")
                        model_name = st.text_input(
                            "模型名称",
                            value=f"linear_regression_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
                            help="请输入模型名称,模型将保存为该名称的.joblib文件"
                        )
                        for i, feature in enumerate(st.session_state['mr_selected_features']):
                            with predict_cols[i % 2]:
                                # èŽ·å–ç‰¹å¾çš„ç»Ÿè®¡ä¿¡æ¯
                                min_val = df_analysis[feature].min()
                                max_val = df_analysis[feature].max()
                                mean_val = df_analysis[feature].mean()
                                input_features[feature] = st.number_input(
                                    f"{feature}",
                                    key=f"pred_{feature}",
                                    value=float(mean_val),
                                    min_value=float(min_val),
                                    max_value=float(max_val),
                                    step=0.1
                                )
                        if st.button("预测米重"):
                            # å‡†å¤‡é¢„测数据
                            input_data = [[input_features[feature] for feature in st.session_state['mr_selected_features']]]
                            # é¢„测
                            predicted_weight = model.predict(input_data)[0]
                            # æ˜¾ç¤ºé¢„测结果
                            st.success(f"预测米重: {predicted_weight:.4f} Kg/m")
                        if st.button("保存模型"):
                            # ç¡®ä¿æ¨¡åž‹ç›®å½•存在
                            model_dir = "saved_models"
                            os.makedirs(model_dir, exist_ok=True)
                            # ä¿å­˜æ¨¡åž‹
                            model_path = os.path.join(model_dir, f"{model_name}.joblib")
                            try:
                                # ä¿å­˜æ¨¡åž‹æƒé‡å’Œç›¸å…³ä¿¡æ¯
                                model_info = {
                                    'model': model,
                                    'features': st.session_state['mr_selected_features'],
                                    'scaler': None,  # çº¿æ€§å›žå½’不需要标化器
                                    'model_type': 'linear_regression',
                                    'created_at': datetime.now(),
                                    'r2_score': r2,
                                    'mse': mse,
                                    'mae': mae,
                                    'rmse': rmse,
                                    'use_steady_data': use_steady_data
                                }
                                joblib.dump(model_info, model_path)
                                st.success(f"模型已成功保存到: {model_path}")
                            except Exception as e:
                                st.error(f"模型保存失败: {e}")
                        # --- æ•°æ®é¢„览 ---
                        st.subheader("🔍 æ•°æ®é¢„览")
app/pages/metered_weight_steady_state.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,463 @@
import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from app.services.extruder_service import ExtruderService
from app.services.data_processing_service import DataProcessingService
class SteadyStateDetector:
    def __init__(self):
        self.data_processor = DataProcessingService()
    def preprocess_data(self, df, weight_col='metered_weight', window_size=20):
        """
        æ•°æ®é¢„处理:仅处理缺失值
        :param df: åŽŸå§‹æ•°æ®æ¡†
        :param weight_col: ç±³é‡åˆ—名
        :param window_size: æ»‘动窗口大小
        :return: é¢„处理后的数据框
        """
        if df is None or df.empty:
            return df
        # å¤åˆ¶æ•°æ®é¿å…ä¿®æ”¹åŽŸå§‹æ•°æ®
        df_processed = df.copy()
        # å¤„理缺失值
        df_processed[weight_col] = df_processed[weight_col].ffill().bfill()
        # ç›´æŽ¥ä½¿ç”¨åŽŸå§‹æ•°æ®ï¼Œä¸è¿›è¡Œå¼‚å¸¸å€¼æ›¿æ¢å’Œå¹³æ»‘å¤„ç†
        df_processed['smoothed_weight'] = df_processed[weight_col]
        # è®¡ç®—移动标准差
        df_processed['rolling_std'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).std()
        df_processed['rolling_mean'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).mean()
        return df_processed
    def detect_steady_state(self, df, weight_col='smoothed_weight', window_size=20, std_threshold=0.5, duration_threshold=60):
        """
        ç¨³æ€è¯†åˆ«é€»è¾‘
        :param df: é¢„处理后的数据框
        :param weight_col: ç±³é‡åˆ—名(已平滑)
        :param window_size: æ»‘动窗口大小(秒)
        :param std_threshold: æ ‡å‡†å·®é˜ˆå€¼
        :param duration_threshold: ç¨³æ€æŒç»­æ—¶é—´é˜ˆå€¼ï¼ˆç§’)
        :return: åŒ…含稳态标记的数据框和稳态信息
        """
        if df is None or df.empty:
            return df, []
        # ç¡®ä¿æ—¶é—´åˆ—是datetime类型
        df['time'] = pd.to_datetime(df['time'])
        # è®¡ç®—时间差(秒)
        df['time_diff'] = df['time'].diff().dt.total_seconds().fillna(0)
        # åˆå§‹åŒ–稳态标记
        df['is_steady'] = 0
        # è®¡ç®—每个窗口的统计特征
        df['window_std'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).std()
        df['window_mean'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).mean()
        # è®¡ç®—波动范围(相对于均值的百分比)
        df['fluctuation_range'] = (df['window_std'] / df['window_mean']) * 100
        df['fluctuation_range'] = df['fluctuation_range'].fillna(0)
        # åˆæ­¥æ ‡è®°ç¨³æ€ç‚¹ - æŽ’除米重小于0.1kg/m的数据
        df.loc[(df['fluctuation_range'] < std_threshold) & (df['smoothed_weight'] >= 0.1), 'is_steady'] = 1
        # ç»Ÿè®¡è¿žç»­ç¨³æ€æ®µ
        steady_segments = []
        current_segment = {}
        for i, row in df.iterrows():
            if row['is_steady'] == 1:
                if not current_segment:
                    # æ–°çš„稳态段开始
                    current_segment = {
                        'start_time': row['time'],
                        'start_idx': i,
                        'weights': [row['smoothed_weight']]
                    }
                else:
                    # ç»§ç»­å½“前稳态段
                    current_segment['weights'].append(row['smoothed_weight'])
            else:
                if current_segment:
                    # ç¨³æ€æ®µç»“束,计算持续时间
                    current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time']
                    current_segment['end_idx'] = i-1
                    duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
                    if duration >= duration_threshold:
                        # è®¡ç®—稳态段的统计指标
                        weights_array = np.array(current_segment['weights'])
                        current_segment['duration'] = duration
                        current_segment['mean_weight'] = np.mean(weights_array)
                        current_segment['std_weight'] = np.std(weights_array)
                        current_segment['min_weight'] = np.min(weights_array)
                        current_segment['max_weight'] = np.max(weights_array)
                        current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                        # è®¡ç®—置信度(基于波动范围和持续时间)
                        confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                        confidence = max(50, min(100, confidence))  # ç½®ä¿¡åº¦èŒƒå›´50-100
                        current_segment['confidence'] = confidence
                        steady_segments.append(current_segment)
                    # é‡ç½®å½“前稳态段
                    current_segment = {}
        # å¤„理最后一个稳态段
        if current_segment:
            current_segment['end_time'] = df['time'].iloc[-1]
            current_segment['end_idx'] = len(df) - 1
            duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
            if duration >= duration_threshold:
                weights_array = np.array(current_segment['weights'])
                current_segment['duration'] = duration
                current_segment['mean_weight'] = np.mean(weights_array)
                current_segment['std_weight'] = np.std(weights_array)
                current_segment['min_weight'] = np.min(weights_array)
                current_segment['max_weight'] = np.max(weights_array)
                current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                confidence = max(50, min(100, confidence))
                current_segment['confidence'] = confidence
                steady_segments.append(current_segment)
        # åœ¨æ•°æ®æ¡†ä¸­æ ‡è®°ç¨³æ€æ®µ
        for segment in steady_segments:
            df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1
        return df, steady_segments
    def get_steady_state_metrics(self, steady_segments):
        """
        è®¡ç®—稳态识别的量化指标
        :param steady_segments: ç¨³æ€æ®µåˆ—表
        :return: ç¨³æ€ç»Ÿè®¡æŒ‡æ ‡å­—å…¸
        """
        if not steady_segments:
            return {}
        # è®¡ç®—平均稳态持续时间
        avg_duration = np.mean([seg['duration'] for seg in steady_segments])
        # è®¡ç®—平均波动范围
        avg_fluctuation = np.mean([seg['fluctuation_range'] for seg in steady_segments])
        # è®¡ç®—平均置信度
        avg_confidence = np.mean([seg['confidence'] for seg in steady_segments])
        # è®¡ç®—稳态总时长
        total_steady_duration = sum([seg['duration'] for seg in steady_segments])
        return {
            'total_steady_segments': len(steady_segments),
            'average_steady_duration': avg_duration,
            'average_fluctuation_range': avg_fluctuation,
            'average_confidence': avg_confidence,
            'total_steady_duration': total_steady_duration
        }
def show_metered_weight_steady_state():
    # åˆå§‹åŒ–服务和检测器
    extruder_service = ExtruderService()
    steady_state_detector = SteadyStateDetector()
    # é¡µé¢æ ‡é¢˜
    st.title("米重稳态识别分析")
    # åˆå§‹åŒ–会话状态
    if 'ss_start_date' not in st.session_state:
        st.session_state['ss_start_date'] = datetime.now().date() - timedelta(days=1)
    if 'ss_end_date' not in st.session_state:
        st.session_state['ss_end_date'] = datetime.now().date()
    if 'ss_quick_select' not in st.session_state:
        st.session_state['ss_quick_select'] = "最近24小时"
    if 'ss_window_size' not in st.session_state:
        st.session_state['ss_window_size'] = 20
    if 'ss_std_threshold' not in st.session_state:
        st.session_state['ss_std_threshold'] = 1.5
    if 'ss_duration_threshold' not in st.session_state:
        st.session_state['ss_duration_threshold'] = 60
    # å®šä¹‰å›žè°ƒå‡½æ•°
    def update_dates(qs):
        st.session_state['ss_quick_select'] = qs
        today = datetime.now().date()
        if qs == "今天":
            st.session_state['ss_start_date'] = today
            st.session_state['ss_end_date'] = today
        elif qs == "最近24小时":
            st.session_state['ss_start_date'] = today - timedelta(days=1)
            st.session_state['ss_end_date'] = today
        elif qs == "最近7天":
            st.session_state['ss_start_date'] = today - timedelta(days=7)
            st.session_state['ss_end_date'] = today
        elif qs == "最近30天":
            st.session_state['ss_start_date'] = today - timedelta(days=30)
            st.session_state['ss_end_date'] = today
    def on_date_change():
        st.session_state['ss_quick_select'] = "自定义"
    # æŸ¥è¯¢æ¡ä»¶åŒºåŸŸ
    with st.expander("🔍 æŸ¥è¯¢é…ç½®", expanded=True):
        # åˆ›å»ºå¸ƒå±€
        cols = st.columns([1, 1, 1, 1, 1, 1.5, 1.5, 1])
        options = ["今天", "最近24小时", "最近7天", "最近30天", "自定义"]
        for i, option in enumerate(options):
            with cols[i]:
                button_type = "primary" if st.session_state['ss_quick_select'] == option else "secondary"
                if st.button(option, key=f"btn_ss_{option}", width='stretch', type=button_type):
                    update_dates(option)
                    st.rerun()
        with cols[5]:
            start_date = st.date_input(
                "开始日期",
                label_visibility="collapsed",
                key="ss_start_date",
                on_change=on_date_change
            )
        with cols[6]:
            end_date = st.date_input(
                "结束日期",
                label_visibility="collapsed",
                key="ss_end_date",
                on_change=on_date_change
            )
        with cols[7]:
            query_button = st.button("🚀 å¼€å§‹åˆ†æž", key="ss_query", width='stretch')
        # ç¨³æ€å‚数配置
        st.markdown("---")
        param_cols = st.columns(3)
        with param_cols[0]:
            st.write("⚙️ **稳态参数配置**")
            window_size = st.slider(
                "滑动窗口大小 (秒)",
                min_value=5,
                max_value=60,
                value=st.session_state['ss_window_size'],
                step=5,
                key="ss_window_size",
                help="用于平滑数据和计算统计特征的滑动窗口大小"
            )
        with param_cols[1]:
            st.write("📏 **波动阈值配置**")
            std_threshold = st.slider(
                "标准差阈值",
                min_value=0.1,
                max_value=2.0,
                value=st.session_state['ss_std_threshold'],
                step=0.1,
                key="ss_std_threshold",
                help="米重波动的标准差阈值,低于此值视为稳态"
            )
        with param_cols[2]:
            st.write("⏱️ **持续时间配置**")
            duration_threshold = st.slider(
                "稳态持续时间 (秒)",
                min_value=30,
                max_value=300,
                value=st.session_state['ss_duration_threshold'],
                step=10,
                key="ss_duration_threshold",
                help="稳态持续的最小时间,低于此值不视为稳态段"
            )
    # è½¬æ¢ä¸ºdatetime对象
    start_dt = datetime.combine(start_date, datetime.min.time())
    end_dt = datetime.combine(end_date, datetime.max.time())
    # æŸ¥è¯¢å¤„理
    if query_button:
        with st.spinner("正在获取数据..."):
            # èŽ·å–æŒ¤å‡ºæœºæ•°æ®
            df_extruder = extruder_service.get_extruder_data(start_dt, end_dt)
            if df_extruder is None or df_extruder.empty:
                st.warning("所选时间段内未找到任何数据,请尝试调整查询条件。")
                return
            # ç¼“存数据到会话状态
            st.session_state['cached_extruder_ss'] = df_extruder
            st.session_state['last_query_start_ss'] = start_dt
            st.session_state['last_query_end_ss'] = end_dt
    # æ•°æ®å¤„理和分析
    if 'cached_extruder_ss' in st.session_state:
        with st.spinner("正在分析数据..."):
            # èŽ·å–ç¼“å­˜æ•°æ®
            df_extruder = st.session_state['cached_extruder_ss']
            # æ•°æ®é¢„处理
            df_processed = steady_state_detector.preprocess_data(df_extruder, window_size=st.session_state['ss_window_size'])
            # ç¨³æ€è¯†åˆ«
            df_with_steady, steady_segments = steady_state_detector.detect_steady_state(
                df_processed,
                window_size=st.session_state['ss_window_size'],
                std_threshold=st.session_state['ss_std_threshold'],
                duration_threshold=st.session_state['ss_duration_threshold']
            )
            # è®¡ç®—稳态指标
            steady_metrics = steady_state_detector.get_steady_state_metrics(steady_segments)
            # æ•°æ®ç±»åž‹æ£€æŸ¥å’Œè½¬æ¢
            df_with_steady['time'] = pd.to_datetime(df_with_steady['time'])
            df_with_steady['metered_weight'] = pd.to_numeric(df_with_steady['metered_weight'], errors='coerce')
            df_with_steady['smoothed_weight'] = pd.to_numeric(df_with_steady['smoothed_weight'], errors='coerce')
            # åŽ»é™¤å¯èƒ½å­˜åœ¨çš„NaN值
            df_with_steady = df_with_steady.dropna(subset=['time', 'metered_weight', 'smoothed_weight'])
            # æ•°æ®å¯è§†åŒ–区域
            st.subheader("📊 ç±³é‡ç¨³æ€è¯†åˆ«ç»“æžœ")
            # åˆ›å»ºå›¾è¡¨
            fig = go.Figure()
            # æ·»åŠ åŽŸå§‹ç±³é‡æ›²çº¿
            fig.add_trace(go.Scatter(
                x=df_with_steady['time'],
                y=df_with_steady['metered_weight'],
                name='原始米重',
                mode='lines',
                opacity=0.6,
                line=dict(color='lightgray', width=1)
            ))
            # æ·»åŠ å¹³æ»‘ç±³é‡æ›²çº¿
            fig.add_trace(go.Scatter(
                x=df_with_steady['time'],
                y=df_with_steady['smoothed_weight'],
                name='平滑米重',
                mode='lines',
                line=dict(color='blue', width=2)
            ))
            # æ ‡è®°ç¨³æ€åŒºåŸŸ
            for segment in steady_segments:
                fig.add_shape(
                    type="rect",
                    x0=segment['start_time'],
                    y0=segment['min_weight'] * 0.95,
                    x1=segment['end_time'],
                    y1=segment['max_weight'] * 1.05,
                    fillcolor="rgba(0, 255, 0, 0.2)",
                    line=dict(color="rgba(0, 200, 0, 0.5)", width=1),
                    name="稳态区域"
                )
            # é…ç½®å›¾è¡¨å¸ƒå±€
            fig.update_layout(
                title="米重稳态识别结果",
                xaxis=dict(title="时间"),
                yaxis=dict(title="米重 (Kg/m)"),
                legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
                height=600
            )
            # æ˜¾ç¤ºå›¾è¡¨
            st.plotly_chart(fig, use_container_width=True)
            # ç¨³æ€ç»Ÿè®¡æŒ‡æ ‡
            st.subheader("📈 ç¨³æ€ç»Ÿè®¡æŒ‡æ ‡")
            metrics_cols = st.columns(5)
            with metrics_cols[0]:
                st.metric(
                    "稳态段总数",
                    steady_metrics.get('total_steady_segments', 0),
                    help="识别到的稳态段数量"
                )
            with metrics_cols[1]:
                st.metric(
                    "平均稳态时长",
                    f"{steady_metrics.get('average_steady_duration', 0):.2f} ç§’",
                    help="所有稳态段的平均持续时间"
                )
            with metrics_cols[2]:
                st.metric(
                    "平均波动范围",
                    f"{steady_metrics.get('average_fluctuation_range', 0):.2f}%",
                    help="稳态段内米重的平均波动范围(相对于均值的百分比)"
                )
            with metrics_cols[3]:
                st.metric(
                    "平均置信度",
                    f"{steady_metrics.get('average_confidence', 0):.1f}%",
                    help="稳态识别结果的平均置信度"
                )
            with metrics_cols[4]:
                st.metric(
                    "总稳态时长",
                    f"{steady_metrics.get('total_steady_duration', 0)/60:.2f} åˆ†é’Ÿ",
                    help="所有稳态段的总持续时间"
                )
            # ç¨³æ€æ®µè¯¦æƒ…表格
            st.subheader("📋 ç¨³æ€æ®µè¯¦æƒ…")
            if steady_segments:
                steady_df = pd.DataFrame(steady_segments)
                # é€‰æ‹©è¦æ˜¾ç¤ºçš„列
                display_cols = ['start_time', 'end_time', 'duration', 'mean_weight', 'std_weight', 'fluctuation_range', 'confidence']
                steady_df_display = steady_df[display_cols].copy()
                # æ ¼å¼åŒ–显示
                steady_df_display['duration'] = steady_df_display['duration'].apply(lambda x: f"{x:.1f} ç§’")
                steady_df_display['mean_weight'] = steady_df_display['mean_weight'].apply(lambda x: f"{x:.4f} Kg/m")
                steady_df_display['std_weight'] = steady_df_display['std_weight'].apply(lambda x: f"{x:.4f} Kg/m")
                steady_df_display['fluctuation_range'] = steady_df_display['fluctuation_range'].apply(lambda x: f"{x:.2f}%")
                steady_df_display['confidence'] = steady_df_display['confidence'].apply(lambda x: f"{x:.1f}%")
                st.dataframe(steady_df_display, use_container_width=True)
                # å¯¼å‡ºç¨³æ€è¯†åˆ«ç»“æžœ
                st.subheader("💾 å¯¼å‡ºæ•°æ®")
                # å‡†å¤‡å¯¼å‡ºæ•°æ®
                export_df = df_with_steady[['time', 'metered_weight', 'smoothed_weight', 'is_steady']].copy()
                export_csv = export_df.to_csv(index=False)
                # åˆ›å»ºä¸‹è½½æŒ‰é’®
                st.download_button(
                    label="导出稳态识别结果 (CSV)",
                    data=export_csv,
                    file_name=f"metered_weight_steady_state_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
                    mime="text/csv",
                    help="点击按钮导出米重稳态识别结果数据"
                )
            else:
                st.info("未识别到任何稳态段,请尝试调整稳态参数配置。")
            # æ•°æ®é¢„览
            st.subheader("🔍 æ•°æ®é¢„览")
            st.dataframe(df_with_steady[['time', 'metered_weight', 'smoothed_weight', 'is_steady', 'fluctuation_range']].head(20), use_container_width=True)
    else:
        # æç¤ºç”¨æˆ·ç‚¹å‡»å¼€å§‹åˆ†æžæŒ‰é’®
        st.info("请选择时间范围并点击'开始分析'按钮获取数据。")
app/services/parameter_adjustment_service.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,495 @@
import pandas as pd
import numpy as np
# å°è¯•导入torch,如果失败则标记为不可用
try:
    import torch
except ImportError:
    torch = None
class ParameterAdjustmentAdvisor:
    """
    æŒ¤å‡ºæœºå‚数调节建议器
    æ ¹æ®å®žæ—¶ç±³é‡ä¸Žæ ‡å‡†ç±³é‡çš„偏差,给出螺杆转速和流程主速的调整建议
    """
    def __init__(self):
        # åˆå§‹åŒ–默认参数关系系数
        # è¿™äº›ç³»æ•°è¡¨ç¤ºç±³é‡åå·®1%时,参数需要调整的百分比
        # å¯ä»¥æ ¹æ®å®žé™…生产数据进行优化
        self.default_coefficients = {
            'screw_speed': 0.1,  # ç±³é‡åå·®1%,螺杆转速调整0.3%(降低调整幅度)
            'process_main_speed': -0.1  # ç±³é‡åå·®1%,流程主速调整-0.2%(降低调整幅度)
        }
        # é»˜è®¤å‚数上下限(可根据实际设备情况调整)
        self.default_limits = {
            'screw_speed': {'min': 30, 'max': 500},
            'process_main_speed': {'min': 0, 'max': 200}
        }
        # æœ€å¤§è°ƒæ•´å¹…度限制(百分比)
        self.max_adjustment_percentage = {
            'screw_speed': 15.0,  # èžºæ†è½¬é€Ÿå•次最大调整15%
            'process_main_speed': 10.0  # æµç¨‹ä¸»é€Ÿå•次最大调整10%
        }
    def calculate_adjustment(self, real_time_weight, standard_weight, upper_limit, lower_limit,
                           current_screw_speed, current_process_speed,
                           current_screw_temperature=None, current_rear_barrel_temperature=None,
                           current_front_barrel_temperature=None, current_head_temperature=None,
                           coefficients=None, limits=None):
        """
        è®¡ç®—参数调整建议
        :param real_time_weight: å®žæ—¶ç±³é‡ (Kg/m)
        :param standard_weight: æ ‡å‡†ç±³é‡ (Kg/m)
        :param upper_limit: ç±³é‡ä¸Šé™ (Kg/m)
        :param lower_limit: ç±³é‡ä¸‹é™ (Kg/m)
        :param current_screw_speed: å½“前螺杆转速 (rpm)
        :param current_process_speed: å½“前流程主速 (m/min)
        :param current_screw_temperature: å½“前螺杆温度 (°C)
        :param current_rear_barrel_temperature: å½“前后机筒温度 (°C)
        :param current_front_barrel_temperature: å½“前前机筒温度 (°C)
        :param current_head_temperature: å½“前机头温度 (°C)
        :param coefficients: è‡ªå®šä¹‰å‚数关系系数 (可选)
        :param limits: è‡ªå®šä¹‰å‚数上下限 (可选)
        :return: è°ƒæ•´å»ºè®®å­—å…¸
        """
        # ä½¿ç”¨é»˜è®¤ç³»æ•°æˆ–自定义系数
        coeffs = coefficients if coefficients else self.default_coefficients
        # ä½¿ç”¨é»˜è®¤ä¸Šä¸‹é™æˆ–自定义上下限
        param_limits = limits if limits else self.default_limits
        # è®¡ç®—米重偏差
        weight_deviation = real_time_weight - standard_weight
        deviation_percentage = (weight_deviation / standard_weight) * 100 if standard_weight != 0 else 0
        # ç¡®å®šç±³é‡çŠ¶æ€
        if real_time_weight > upper_limit:
            status = "超上限"
        elif real_time_weight < lower_limit:
            status = "超下限"
        else:
            status = "正常范围"
        # è®¡ç®—温度影响因子 - è€ƒè™‘温度对挤出过程的影响
        # æ¸©åº¦è¶Šé«˜ï¼Œç‰©æ–™æµåŠ¨æ€§è¶Šå¥½ï¼Œç›¸åŒèžºæ†è½¬é€Ÿä¸‹æŒ¤å‡ºé‡è¶Šå¤§
        temperature_factor = 1.0
        # å¦‚果提供了温度参数,计算温度影响因子
        if current_screw_temperature is not None and current_head_temperature is not None:
            # è®¡ç®—平均温度与参考温度的偏差
            reference_temperature = 80.0  # å‚考温度,可根据实际工艺调整
            avg_temperature = (current_screw_temperature + current_head_temperature) / 2
            temperature_deviation = avg_temperature - reference_temperature
            # æ¸©åº¦åå·®æ¯å˜åŒ–10°C,影响因子变化5%
            temperature_factor = 1.0 + (temperature_deviation / 10.0) * 0.05
            # é™åˆ¶æ¸©åº¦å½±å“å› å­çš„范围
            temperature_factor = max(0.8, min(1.2, temperature_factor))
        # è®¡ç®—调整量 - è€ƒè™‘温度影响因子
        # ç±³é‡åå·® * ç³»æ•° * æ¸©åº¦å› å­ = è°ƒæ•´ç™¾åˆ†æ¯”
        screw_speed_adjustment_percent = -deviation_percentage * coeffs['screw_speed'] * temperature_factor
        process_speed_adjustment_percent = -deviation_percentage * coeffs['process_main_speed'] * temperature_factor
        # é™åˆ¶è°ƒæ•´å¹…度,避免调整量过大
        screw_speed_adjustment_percent = max(
            -self.max_adjustment_percentage['screw_speed'],
            min(self.max_adjustment_percentage['screw_speed'],
                screw_speed_adjustment_percent)
        )
        process_speed_adjustment_percent = max(
            -self.max_adjustment_percentage['process_main_speed'],
            min(self.max_adjustment_percentage['process_main_speed'],
                process_speed_adjustment_percent)
        )
        # è®¡ç®—实际调整量
        screw_speed_adjustment = (screw_speed_adjustment_percent / 100) * current_screw_speed
        process_speed_adjustment = (process_speed_adjustment_percent / 100) * current_process_speed
        # è®¡ç®—调整后的参数值
        new_screw_speed = current_screw_speed + screw_speed_adjustment
        new_process_speed = current_process_speed + process_speed_adjustment
        # ç¡®ä¿å‚数在合理范围内
        new_screw_speed = max(param_limits['screw_speed']['min'],
                            min(param_limits['screw_speed']['max'], new_screw_speed))
        new_process_speed = max(param_limits['process_main_speed']['min'],
                              min(param_limits['process_main_speed']['max'], new_process_speed))
        # é‡æ–°è®¡ç®—实际调整百分比(考虑了最大调整幅度和参数上下限)
        screw_speed_adjust_percent = ((new_screw_speed - current_screw_speed) / current_screw_speed) * 100 if current_screw_speed != 0 else 0
        process_speed_adjust_percent = ((new_process_speed - current_process_speed) / current_process_speed) * 100 if current_process_speed != 0 else 0
        # ç”Ÿæˆè°ƒæ•´å»ºè®® - æ— è®ºç±³é‡æ˜¯å¦åœ¨èŒƒå›´å†…,都提供调整建议,目标是让实时米重尽可能接近标准米重
        recommendation = self._generate_recommendation(
            deviation_percentage,
            screw_speed_adjust_percent,
            new_screw_speed,
            process_speed_adjust_percent,
            new_process_speed
        )
        return {
            'status': status,
            'real_time_weight': real_time_weight,
            'standard_weight': standard_weight,
            'upper_limit': upper_limit,
            'lower_limit': lower_limit,
            'deviation': weight_deviation,
            'deviation_percentage': deviation_percentage,
            'current_screw_speed': current_screw_speed,
            'current_process_speed': current_process_speed,
            'new_screw_speed': new_screw_speed,
            'new_process_speed': new_process_speed,
            'screw_speed_adjustment': screw_speed_adjustment,
            'process_speed_adjustment': process_speed_adjustment,
            'screw_speed_adjust_percent': screw_speed_adjust_percent,
            'process_speed_adjust_percent': process_speed_adjust_percent,
            'recommendation': recommendation
        }
    def _generate_recommendation(self, deviation_percentage, screw_speed_adjust_percent,
                                new_screw_speed, process_speed_adjust_percent, new_process_speed):
        """
        ç”Ÿæˆè°ƒæ•´å»ºè®®æ–‡æœ¬
        :param deviation_percentage: ç±³é‡åå·®ç™¾åˆ†æ¯”
        :param screw_speed_adjust_percent: èžºæ†è½¬é€Ÿè°ƒæ•´ç™¾åˆ†æ¯”
        :param new_screw_speed: è°ƒæ•´åŽçš„螺杆转速
        :param process_speed_adjust_percent: æµç¨‹ä¸»é€Ÿè°ƒæ•´ç™¾åˆ†æ¯”
        :param new_process_speed: è°ƒæ•´åŽçš„æµç¨‹ä¸»é€Ÿ
        :return: è°ƒæ•´å»ºè®®æ–‡æœ¬
        """
        # æ ¹æ®åå·®æ–¹å‘和大小生成建议
        abs_deviation = abs(deviation_percentage)
        if abs_deviation < 0.5:
            # åå·®å¾ˆå°ï¼ŒæŽ¥è¿‘标准值
            return f"米重偏差很小 ({abs_deviation:.2f}%),接近标准值。\n" \
                   f"建议微调以进一步接近标准值:\n" \
                   f"1. å°†èžºæ†è½¬é€Ÿè°ƒæ•´è‡³ {new_screw_speed:.1f} rpm " \
                   f"( {'降低' if screw_speed_adjust_percent < 0 else '提高'} {abs(screw_speed_adjust_percent):.2f}% )\n" \
                   f"2. å°†æµç¨‹ä¸»é€Ÿè°ƒæ•´è‡³ {new_process_speed:.1f} m/min " \
                   f"( {'降低' if process_speed_adjust_percent < 0 else '提高'} {abs(process_speed_adjust_percent):.2f}% )"
        elif deviation_percentage > 0:
            # ç±³é‡åé«˜ï¼Œéœ€è¦é™ä½Žç±³é‡
            return f"米重偏差 {abs_deviation:.2f}%(偏高),建议调整:\n" \
                   f"1. å°†èžºæ†è½¬é€Ÿä»Žå½“前值调整至 {new_screw_speed:.1f} rpm " \
                   f"( {'降低' if screw_speed_adjust_percent < 0 else '提高'} {abs(screw_speed_adjust_percent):.2f}% )\n" \
                   f"2. å°†æµç¨‹ä¸»é€Ÿä»Žå½“前值调整至 {new_process_speed:.1f} m/min " \
                   f"( {'降低' if process_speed_adjust_percent < 0 else '提高'} {abs(process_speed_adjust_percent):.2f}% )"
        else:
            # ç±³é‡åä½Žï¼Œéœ€è¦æé«˜ç±³é‡
            return f"米重偏差 {abs_deviation:.2f}%(偏低),建议调整:\n" \
                   f"1. å°†èžºæ†è½¬é€Ÿä»Žå½“前值调整至 {new_screw_speed:.1f} rpm " \
                   f"( {'降低' if screw_speed_adjust_percent < 0 else '提高'} {abs(screw_speed_adjust_percent):.2f}% )\n" \
                   f"2. å°†æµç¨‹ä¸»é€Ÿä»Žå½“前值调整至 {new_process_speed:.1f} m/min " \
                   f"( {'降低' if process_speed_adjust_percent < 0 else '提高'} {abs(process_speed_adjust_percent):.2f}% )"
    def predict_weight(self, model_info, screw_speed, head_pressure, process_speed,
                     screw_temperature, rear_barrel_temperature,
                     front_barrel_temperature, head_temperature):
        """
        ä½¿ç”¨æ¨¡åž‹é¢„测米重
        :param model_info: åŒ…含模型和缩放器的模型信息字典
        :param screw_speed: èžºæ†è½¬é€Ÿ (rpm)
        :param head_pressure: æœºå¤´åŽ‹åŠ› (bar)
        :param process_speed: æµç¨‹ä¸»é€Ÿ (m/min)
        :param screw_temperature: èžºæ†æ¸©åº¦ (°C)
        :param rear_barrel_temperature: åŽæœºç­’温度 (°C)
        :param front_barrel_temperature: å‰æœºç­’温度 (°C)
        :param head_temperature: æœºå¤´æ¸©åº¦ (°C)
        :return: é¢„测的米重值 (Kg/m)
        """
        try:
            # èŽ·å–æ¨¡åž‹éœ€è¦çš„ç‰¹å¾
            required_features = model_info['features']
            # å‡†å¤‡è¾“入数据,保持与模型特征顺序一致
            input_features = {
                '螺杆转速': screw_speed,
                '机头压力': head_pressure,
                '流程主速': process_speed,
                '螺杆温度': screw_temperature,
                '后机筒温度': rear_barrel_temperature,
                '前机筒温度': front_barrel_temperature,
                '机头温度': head_temperature
            }
            # æ ¹æ®æ¨¡åž‹ç‰¹å¾åˆ›å»ºè¾“å…¥DataFrame
            input_df = pd.DataFrame([input_features])[required_features]
            # åˆå§‹åŒ–预测结果
            predicted_weight = None
            # èŽ·å–æ¨¡åž‹
            model = model_info['model']
            # æ ¹æ®æ¨¡åž‹ç±»åž‹æ‰§è¡Œä¸åŒçš„预测逻辑
            if model_info['model_type'] in ['LSTM', 'GRU', 'BiLSTM']:
                # æ·±åº¦å­¦ä¹ æ¨¡åž‹é¢„测
                if torch is None:
                    print("PyTorch not available, cannot predict with deep learning models")
                    return None
                # æ•°æ®æ ‡å‡†åŒ–
                scaler_X = model_info['scaler_X']
                scaler_y = model_info['scaler_y']
                input_scaled = scaler_X.transform(input_df)
                # èŽ·å–åºåˆ—é•¿åº¦
                sequence_length = model_info['sequence_length']
                # ä¸ºæ·±åº¦å­¦ä¹ æ¨¡åž‹åˆ›å»ºåºåˆ—
                input_seq = np.tile(input_scaled, (sequence_length, 1)).reshape(1, sequence_length, -1)
                # è½¬æ¢ä¸ºPyTorch张量
                device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
                input_tensor = torch.tensor(input_seq, dtype=torch.float32).to(device)
                # é¢„测
                model.eval()
                with torch.no_grad():
                    y_pred_scaled_tensor = model(input_tensor)
                    y_pred_scaled = y_pred_scaled_tensor.cpu().numpy().ravel()[0]
                    # åå½’一化
                    predicted_weight = scaler_y.inverse_transform(np.array([[y_pred_scaled]]))[0][0]
            elif model_info['model_type'] in ['SVR', 'MLP', 'GradientBoosting']:
                # éœ€è¦ç‰¹å¾ç¼©æ”¾çš„æ¨¡åž‹
                scaler_X = model_info['scaler_X']
                scaler_y = model_info['scaler_y']
                input_scaled = scaler_X.transform(input_df)
                # é¢„测
                y_pred_scaled = model.predict(input_scaled)[0]
                # åå½’一化
                predicted_weight = scaler_y.inverse_transform(np.array([[y_pred_scaled]]))[0][0]
            else:
                # å…¶ä»–模型(如随机森林、线性回归等)
                predicted_weight = model.predict(input_df)[0]
            return predicted_weight
        except Exception as e:
            print(f"模型预测失败: {e}")
            import traceback
            traceback.print_exc()
            return None
    def iterative_adjustment(self, initial_params, model_info, max_iterations=5, tolerance=0.5):
        """
        è¿­ä»£è°ƒæ•´å‚数,直到预测米重满足偏差要求
        :param initial_params: åˆå§‹å‚数字典,包含:
                              - real_time_weight: å®žæ—¶ç±³é‡
                              - standard_weight: æ ‡å‡†ç±³é‡
                              - upper_limit: ç±³é‡ä¸Šé™
                              - lower_limit: ç±³é‡ä¸‹é™
                              - current_screw_speed: å½“前螺杆转速
                              - current_process_speed: å½“前流程主速
                              - current_screw_temperature: èžºæ†æ¸©åº¦
                              - current_rear_barrel_temperature: åŽæœºç­’温度
                              - current_front_barrel_temperature: å‰æœºç­’温度
                              - current_head_temperature: æœºå¤´æ¸©åº¦
                              - current_head_pressure: æœºå¤´åŽ‹åŠ›
        :param model_info: æ¨¡åž‹ä¿¡æ¯å­—å…¸
        :param max_iterations: æœ€å¤§è¿­ä»£æ¬¡æ•°
        :param tolerance: å…è®¸çš„米重偏差百分比阈值
        :return: è¿­ä»£è°ƒæ•´ç»“果字典,包含:
                - final_result: æœ€ç»ˆè°ƒæ•´ç»“æžœ
                - iteration_history: æ¯æ¬¡è¿­ä»£çš„历史记录
                - converged: æ˜¯å¦æ”¶æ•›
        """
        iteration_history = []
        converged = False
        # ä¿å­˜åˆå§‹å‚数,用于最终结果计算
        original_params = initial_params.copy()
        # åˆå§‹åŒ–当前参数为初始参数
        current_params = initial_params.copy()
        for i in range(max_iterations):
            # è®¡ç®—当前迭代的调整建议
            adjustment_result = self.calculate_adjustment(
                real_time_weight=current_params['real_time_weight'],
                standard_weight=current_params['standard_weight'],
                upper_limit=current_params['upper_limit'],
                lower_limit=current_params['lower_limit'],
                current_screw_speed=current_params['current_screw_speed'],
                current_process_speed=current_params['current_process_speed'],
                current_screw_temperature=current_params['current_screw_temperature'],
                current_rear_barrel_temperature=current_params['current_rear_barrel_temperature'],
                current_front_barrel_temperature=current_params['current_front_barrel_temperature'],
                current_head_temperature=current_params['current_head_temperature']
            )
            # ä½¿ç”¨æ¨¡åž‹é¢„测调整后的米重
            predicted_weight = self.predict_weight(
                model_info=model_info,
                screw_speed=adjustment_result['new_screw_speed'],
                head_pressure=current_params['current_head_pressure'],
                process_speed=adjustment_result['new_process_speed'],
                screw_temperature=current_params['current_screw_temperature'],
                rear_barrel_temperature=current_params['current_rear_barrel_temperature'],
                front_barrel_temperature=current_params['current_front_barrel_temperature'],
                head_temperature=current_params['current_head_temperature']
            )
            # æ£€æŸ¥æ¨¡åž‹é¢„测是否成功
            if predicted_weight is None:
                print(f"模型预测失败,终止迭代调整")
                iteration_history.append({
                    'iteration': i + 1,
                    'current_screw_speed': current_params['current_screw_speed'],
                    'current_process_speed': current_params['current_process_speed'],
                    'adjusted_screw_speed': adjustment_result['new_screw_speed'],
                    'adjusted_process_speed': adjustment_result['new_process_speed'],
                    'predicted_weight': None,
                    'predicted_deviation': None,
                    'predicted_deviation_percent': None,
                    'screw_speed_adjustment': adjustment_result['screw_speed_adjustment'],
                    'process_speed_adjustment': adjustment_result['process_speed_adjustment']
                })
                break
            # è®¡ç®—预测偏差
            predicted_deviation = predicted_weight - current_params['standard_weight']
            predicted_deviation_percent = (predicted_deviation / current_params['standard_weight']) * 100
            # ä¿å­˜è¿­ä»£åŽ†å²
            iteration_history.append({
                'iteration': i + 1,
                'current_screw_speed': current_params['current_screw_speed'],
                'current_process_speed': current_params['current_process_speed'],
                'adjusted_screw_speed': adjustment_result['new_screw_speed'],
                'adjusted_process_speed': adjustment_result['new_process_speed'],
                'predicted_weight': predicted_weight,
                'predicted_deviation': predicted_deviation,
                'predicted_deviation_percent': predicted_deviation_percent,
                'screw_speed_adjustment': adjustment_result['screw_speed_adjustment'],
                'process_speed_adjustment': adjustment_result['process_speed_adjustment']
            })
            # æ£€æŸ¥æ˜¯å¦æ”¶æ•›
            if abs(predicted_deviation_percent) <= tolerance:
                converged = True
                break
            # æ›´æ–°å½“前参数,准备下一次迭代
            current_params.update({
                'real_time_weight': predicted_weight,
                'current_screw_speed': adjustment_result['new_screw_speed'],
                'current_process_speed': adjustment_result['new_process_speed']
            })
        # èŽ·å–æœ€ç»ˆè°ƒæ•´åŽçš„å‚æ•°
        final_screw_speed = iteration_history[-1]['adjusted_screw_speed']
        final_process_speed = iteration_history[-1]['adjusted_process_speed']
        final_predicted_weight = iteration_history[-1]['predicted_weight']
        # è®¡ç®—从初始参数到最终参数的总调整
        # åˆ›å»ºä¸€ä¸ªåŒ…含初始参数的调整结果
        final_result = {
            'status': '正常范围' if abs((final_predicted_weight - original_params['standard_weight']) / original_params['standard_weight'] * 100) <= tolerance else '超上限' if final_predicted_weight > original_params['upper_limit'] else '超下限',
            'real_time_weight': original_params['real_time_weight'],  # åˆå§‹å®žæ—¶ç±³é‡
            'standard_weight': original_params['standard_weight'],
            'upper_limit': original_params['upper_limit'],
            'lower_limit': original_params['lower_limit'],
            'deviation': original_params['real_time_weight'] - original_params['standard_weight'],  # åˆå§‹åå·®
            'deviation_percentage': (original_params['real_time_weight'] - original_params['standard_weight']) / original_params['standard_weight'] * 100,  # åˆå§‹åå·®ç™¾åˆ†æ¯”
            'current_screw_speed': original_params['current_screw_speed'],  # åˆå§‹èžºæ†è½¬é€Ÿ
            'current_process_speed': original_params['current_process_speed'],  # åˆå§‹æµç¨‹ä¸»é€Ÿ
            'new_screw_speed': final_screw_speed,  # æœ€ç»ˆè°ƒæ•´åŽçš„螺杆转速
            'new_process_speed': final_process_speed,  # æœ€ç»ˆè°ƒæ•´åŽçš„æµç¨‹ä¸»é€Ÿ
            'screw_speed_adjustment': final_screw_speed - original_params['current_screw_speed'],  # æ€»è°ƒæ•´é‡
            'process_speed_adjustment': final_process_speed - original_params['current_process_speed'],  # æ€»è°ƒæ•´é‡
            'screw_speed_adjust_percent': ((final_screw_speed - original_params['current_screw_speed']) / original_params['current_screw_speed']) * 100 if original_params['current_screw_speed'] != 0 else 0,  # æ€»è°ƒæ•´ç™¾åˆ†æ¯”
            'process_speed_adjust_percent': ((final_process_speed - original_params['current_process_speed']) / original_params['current_process_speed']) * 100 if original_params['current_process_speed'] != 0 else 0,  # æ€»è°ƒæ•´ç™¾åˆ†æ¯”
            'predicted_weight': final_predicted_weight  # æœ€ç»ˆé¢„测米重
        }
        # ç”Ÿæˆè°ƒæ•´å»ºè®®æ–‡æœ¬
        final_deviation_percent = (final_predicted_weight - original_params['standard_weight']) / original_params['standard_weight'] * 100
        final_result['recommendation'] = self._generate_recommendation(
            final_deviation_percent,
            final_result['screw_speed_adjust_percent'],
            final_screw_speed,
            final_result['process_speed_adjust_percent'],
            final_process_speed
        )
        # æ·»åŠ æ¸©åº¦ç›¸å…³å‚æ•°ï¼ˆç”¨äºŽé¢„æµ‹ï¼‰
        final_result['current_screw_temperature'] = original_params['current_screw_temperature']
        final_result['current_rear_barrel_temperature'] = original_params['current_rear_barrel_temperature']
        final_result['current_front_barrel_temperature'] = original_params['current_front_barrel_temperature']
        final_result['current_head_temperature'] = original_params['current_head_temperature']
        # æ·»åŠ æœ€ç»ˆé¢„æµ‹ç›¸å…³ä¿¡æ¯
        final_predicted_deviation = final_predicted_weight - original_params['standard_weight']
        final_predicted_deviation_percent = (final_predicted_deviation / original_params['standard_weight']) * 100
        final_result['final_predicted_deviation'] = final_predicted_deviation
        final_result['final_predicted_deviation_percent'] = final_predicted_deviation_percent
        return {
            'final_result': final_result,
            'iteration_history': iteration_history,
            'converged': converged,
            'total_iterations': len(iteration_history),
            'initial_params': original_params  # ä¿å­˜åˆå§‹å‚数,用于结果展示
        }
    def analyze_historical_adjustments(self, df):
        """
        åˆ†æžåŽ†å²è°ƒæ•´æ•°æ®ï¼Œä¼˜åŒ–è°ƒæ•´ç³»æ•°
        :param df: åŒ…含历史调整数据的DataFrame,需要包含以下列:
                  - real_time_weight: å®žæ—¶ç±³é‡
                  - standard_weight: æ ‡å‡†ç±³é‡
                  - current_screw_speed: å½“前螺杆转速
                  - current_process_speed: å½“前流程主速
                  - adjusted_screw_speed: è°ƒæ•´åŽçš„螺杆转速
                  - adjusted_process_speed: è°ƒæ•´åŽçš„æµç¨‹ä¸»é€Ÿ
                  - result_weight: è°ƒæ•´åŽçš„米重
        :return: ä¼˜åŒ–后的系数
        """
        if df is None or df.empty:
            return self.default_coefficients
        try:
            # è®¡ç®—米重偏差
            df['weight_deviation'] = df['real_time_weight'] - df['standard_weight']
            df['deviation_percentage'] = (df['weight_deviation'] / df['standard_weight']) * 100
            # è®¡ç®—参数调整百分比
            df['screw_speed_adjust_percent'] = ((df['adjusted_screw_speed'] - df['current_screw_speed']) / df['current_screw_speed']) * 100
            df['process_speed_adjust_percent'] = ((df['adjusted_process_speed'] - df['current_process_speed']) / df['current_process_speed']) * 100
            # è®¡ç®—调整效果
            df['adjustment_effect'] = df['result_weight'] - df['real_time_weight']
            # ä½¿ç”¨çº¿æ€§å›žå½’计算优化系数
            # ç³»æ•° = å‚数调整百分比 / ç±³é‡åå·®ç™¾åˆ†æ¯”
            screw_speed_coeff = df['screw_speed_adjust_percent'].sum() / df['deviation_percentage'].sum() if df['deviation_percentage'].sum() != 0 else self.default_coefficients['screw_speed']
            process_speed_coeff = df['process_speed_adjust_percent'].sum() / df['deviation_percentage'].sum() if df['deviation_percentage'].sum() != 0 else self.default_coefficients['process_main_speed']
            # å–绝对值,确保方向正确
            optimized_coeffs = {
                'screw_speed': abs(screw_speed_coeff),
                'process_main_speed': -abs(process_speed_coeff)  # æµç¨‹ä¸»é€Ÿä¸Žç±³é‡è´Ÿç›¸å…³
            }
            return optimized_coeffs
        except Exception as e:
            print(f"分析历史调整数据失败: {e}")
            return self.default_coefficients
dashboard.py
@@ -7,6 +7,11 @@
from app.pages.metered_weight_correlation import show_metered_weight_correlation
from app.pages.metered_weight_regression import show_metered_weight_regression
from app.pages.metered_weight_advanced import show_metered_weight_advanced
from app.pages.metered_weight_deep_learning import show_metered_weight_deep_learning
from app.pages.metered_weight_steady_state import show_metered_weight_steady_state
from app.pages.metered_weight_prediction import show_metered_weight_prediction
from app.pages.metered_weight_forecast import show_metered_weight_forecast
from app.pages.extruder_parameter_adjustment import show_extruder_parameter_adjustment
# è®¾ç½®é¡µé¢é…ç½®
st.set_page_config(
@@ -72,6 +77,41 @@
    url_path="metered_weight_advanced"
)
metered_weight_deep_learning_page = st.Page(
    show_metered_weight_deep_learning,
    title="米重深度学习预测",
    icon="🧠",
    url_path="metered_weight_deep_learning"
)
metered_weight_steady_state_page = st.Page(
    show_metered_weight_steady_state,
    title="米重稳态识别",
    icon="⚖️",
    url_path="metered_weight_steady_state"
)
metered_weight_prediction_page = st.Page(
    show_metered_weight_prediction,
    title="米重统一预测",
    icon="🔮",
    url_path="metered_weight_prediction"
)
metered_weight_forecast_page = st.Page(
    show_metered_weight_forecast,
    title="米重预测分析",
    icon="📈",
    url_path="metered_weight_forecast"
)
extruder_parameter_adjustment_page = st.Page(
    show_extruder_parameter_adjustment,
    title="挤出机参数调节",
    icon="⚙️",
    url_path="extruder_parameter_adjustment"
)
# ä¾§è¾¹æ é¡µè„šä¿¡æ¯
def show_footer():
    st.sidebar.markdown("---")
@@ -79,7 +119,7 @@
# å¯¼èˆªé…ç½®
pg = st.navigation({
    "综合分析": [comprehensive_page, metered_weight_page, metered_weight_correlation_page, metered_weight_regression_page, metered_weight_advanced_page],
    "综合分析": [comprehensive_page, metered_weight_page, metered_weight_correlation_page, metered_weight_regression_page, metered_weight_advanced_page, metered_weight_deep_learning_page, metered_weight_steady_state_page, metered_weight_prediction_page, metered_weight_forecast_page, extruder_parameter_adjustment_page],
    "分项分析": [sorting_page, extruder_page, main_process_page]
})