import gradio as gr import pandas as pd import plotly.graph_objects as go from datetime import datetime from db_manager import DBManager from model_manager import ModelManager # ==================== INITIALIZATION ==================== db = DBManager('bta_furnace.db') model_mgr = ModelManager('model_ai_bta.pkl', 'laju_penipisan.pkl') # ==================== UTILITY FUNCTIONS ==================== def get_min_safe_thickness(): try: val = db.get_config('min_safe_thickness') return float(val) if val else 115.0 except: return 115.0 def get_initial_empty_state(): """Return empty state untuk initial load""" return "—", "—", "—", "—", "—", None def submit_measurement(cone_depan, bodi_tengah, cone_belakang, is_flagged): """Tab 1: Submit measurement data""" try: # Validate inputs if not all([cone_depan is not None, bodi_tengah is not None, cone_belakang is not None]): return "❌ Error: Semua field suhu harus diisi!", "", "", "", "" # Predict ketebalan ketebalan_prediksi = model_mgr.predict(cone_depan, bodi_tengah, cone_belakang) suhu_avg = (cone_depan + bodi_tengah + cone_belakang) / 3 # Determine status min_safe = get_min_safe_thickness() if suhu_avg > float(db.get_config('threshold_temp')) or ketebalan_prediksi < min_safe: status = "⚠️ CRITICAL" else: status = "✅ AMAN" # Calculate RUL rul_info = model_mgr.calculate_rul(ketebalan_prediksi, min_safe) # Insert to database db.insert_measurement( cone_depan, bodi_tengah, cone_belakang, ketebalan_actual=ketebalan_prediksi, ketebalan_prediksi=ketebalan_prediksi, status=status, is_flagged=is_flagged ) result_text = f""" ✅ Data tersimpan! 📊 Hasil Prediksi: • Ketebalan BTA: {ketebalan_prediksi:.2f} mm • Suhu Rata-rata: {suhu_avg:.2f} °C • Status: {status} • Sisa RUL: {rul_info['sisa_hari_str']} • Tanggal Target Maintenance: {rul_info['tgl_target']} • Flag Slaging: {'✓ Ya' if is_flagged else '✗ Tidak'} """ return result_text, f"{ketebalan_prediksi:.2f}", f"{suhu_avg:.2f}", status, rul_info['sisa_hari_str'] except Exception as e: return f"❌ Error: {str(e)}", "", "", "", "" def get_latest_display(): """Tab 2: Get latest measurement for real-time display""" try: latest = db.get_latest_measurement() if latest is None: # Return default empty state dengan chart kosong fig = go.Figure().update_layout( title="📭 Belum ada data - Silakan submit measurement dari Tab 1 terlebih dahulu", plot_bgcolor='white', height=300 ) return "—", "—", "—", "—", "—", fig min_safe = get_min_safe_thickness() rul_info = model_mgr.calculate_rul(latest['ketebalan_prediksi'], min_safe) # Get last 10 measurements untuk chart historis = list(reversed(db.get_historis(limit=10, flagged_only=False))) if len(historis) > 0: df_hist = pd.DataFrame(historis) df_hist['timestamp'] = pd.to_datetime(df_hist['timestamp']) fig = go.Figure() fig.add_trace(go.Scatter( x=df_hist['timestamp'], y=df_hist['ketebalan_prediksi'], mode='lines+markers', name='Ketebalan Prediksi', line=dict(color='#00bd7e', width=2) )) fig.add_hline(y=min_safe, line_dash="dash", line_color="red", annotation_text=f"Min Safe: {min_safe}mm") fig.update_layout( title="10 Pengukuran Terakhir", xaxis_title="Waktu", yaxis_title="Ketebalan (mm)", hovermode='x unified', plot_bgcolor='white', height=300 ) else: fig = go.Figure().update_layout(title="Tidak ada data untuk trend") status_color = "🟢" if latest['status'] == "✅ AMAN" else "🔴" return ( f"{status_color} {latest['status']}", f"{latest['ketebalan_prediksi']:.2f} mm", f"{latest['suhu_avg']:.2f} °C", rul_info['sisa_hari_str'], rul_info['tgl_target'], fig ) except Exception as e: fig = go.Figure().update_layout( title=f"❌ Error: {str(e)}", plot_bgcolor='white', height=300 ) return f"❌ Error: {str(e)}", "—", "—", "—", "—", fig def get_summary_dashboard(): """Tab 3: Get dashboard summary KPIs""" try: stats = db.get_stats() # Get trend info df_all = db.get_all_data_as_dataframe() if len(df_all) > 0: # Thickness reduction trend first_thickness = df_all['ketebalan_prediksi'].iloc[0] current_thickness = df_all['ketebalan_prediksi'].iloc[-1] thickness_reduced = first_thickness - current_thickness thickness_pct = (current_thickness / 230 * 100) if first_thickness > 0 else 100 else: thickness_pct = 100 first_thickness = 230 current_thickness = 230 thickness_reduced = 0 # Gauge chart untuk persentase thickness fig_gauge = go.Figure(go.Indicator( mode="gauge+number+delta", value=thickness_pct, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': "Sisa Ketebalan (%)"}, delta={'reference': 100}, gauge={ 'axis': {'range': [0, 100]}, 'bar': {'color': "darkblue"}, 'steps': [ {'range': [0, 50], 'color': "lightgray"}, {'range': [50, 100], 'color': "lightgreen"} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 50 } } )) fig_gauge.update_layout(height=300) summary_text = f""" 📊 DASHBOARD SUMMARY 📈 KPI Utama: • Total Measurements: {stats['total_measurements']} • Flagged Slaging: {stats['total_flagged']} • Current Thickness: {stats['current_thickness']:.2f} mm • Total Maintenance: {stats['total_maintenance']} 📉 Trend: • Initial Thickness: {first_thickness:.2f} mm • Thickness Reduced: {thickness_reduced:.2f} mm • Remaining: {thickness_pct:.1f}% 🔧 Last Maintenance: {datetime.now().strftime('%d %B %Y')} """ return summary_text, fig_gauge except Exception as e: fig_gauge = go.Figure().update_layout( title=f"❌ Error: {str(e)}", plot_bgcolor='white', height=300 ) error_text = f"❌ Error saat load summary:\n{str(e)}\n\n💡 Silakan submit measurement dari Tab 1 terlebih dahulu." return error_text, fig_gauge def get_historis_view(): """Tab 4: Get historical data with chart - Load from CSV temporarily""" try: # Try to load from CSV file (temporary, for demo) df = pd.read_csv('data-temp-clean.csv', sep=';', skiprows=1) df = df.dropna(subset=['Tanggal', 'Cone Depan (°C)']) # Convert numeric columns safely cols_numeric = ['Cone Depan (°C)', 'Bodi Tengah (°C)', 'Cone Belakang (°C)', 'average', 'Ketebalan BTA (mm)'] for col in cols_numeric: df[col] = pd.to_numeric(df[col].astype(str).str.replace(',', '.'), errors='coerce') df = df.dropna(subset=cols_numeric) # Convert date df['Tanggal'] = pd.to_datetime(df['Tanggal'], format='%d/%m/%Y', errors='coerce') df = df.dropna(subset=['Tanggal']).sort_values('Tanggal') # Prepare data for model prediction # Rename columns to match model expectations df_model = pd.DataFrame({ 'cone_depan': df['Cone Depan (°C)'], 'bodi_tengah': df['Bodi Tengah (°C)'], 'cone_belakang': df['Cone Belakang (°C)'], 'suhu_avg': df['average'] }) # Get predictions from actual model predictions = model_mgr.predict_batch(df_model) df['ketebalan_prediksi'] = predictions # Create display table df_display = df[['Tanggal', 'average', 'ketebalan_prediksi', 'Ketebalan BTA (mm)']].copy() df_display['Tanggal'] = df_display['Tanggal'].dt.strftime('%Y-%m-%d') df_display.columns = ['Timestamp', 'Suhu Avg (°C)', 'Ketebalan Prediksi (mm)', 'Ketebalan Aktual (mm)'] df_display = df_display.round(2) # Create chart: Aktual vs Prediksi fig = go.Figure() fig.add_trace(go.Scatter( x=df['Tanggal'], y=df['Ketebalan BTA (mm)'], mode='lines+markers', name='Ketebalan Aktual', line=dict(color='#5d5dff', width=2), marker=dict(size=5) )) fig.add_trace(go.Scatter( x=df['Tanggal'], y=df['ketebalan_prediksi'], mode='lines+markers', name='Prediksi AI', line=dict(color='#00bd7e', width=2, dash='dash'), marker=dict(size=5) )) min_safe = get_min_safe_thickness() fig.add_hline(y=min_safe, line_dash="dash", line_color="red", annotation_text=f"Min Safe: {min_safe}mm") fig.update_layout( title="Historis Ketebalan: Aktual vs Prediksi AI", xaxis_title="Tanggal", yaxis_title="Ketebalan (mm)", hovermode='x unified', plot_bgcolor='white', height=400 ) fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='LightGray') fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='LightGray') return df_display, fig except FileNotFoundError: empty_df = pd.DataFrame(columns=['Timestamp', 'Suhu Avg (°C)', 'Ketebalan Prediksi (mm)', 'Ketebalan Aktual (mm)']) fig = go.Figure().update_layout( title="❌ File CSV tidak ditemukan. Silakan upload data-temp-clean.csv", plot_bgcolor='white' ) return empty_df, fig except Exception as e: empty_df = pd.DataFrame(columns=['Timestamp', 'Suhu Avg (°C)', 'Ketebalan Prediksi (mm)', 'Ketebalan Aktual (mm)']) fig = go.Figure().update_layout( title=f"❌ Error: {str(e)}", plot_bgcolor='white' ) return empty_df, fig def export_csv(): """Tab 4: Export to CSV""" df = db.get_all_data_as_dataframe() if len(df) == 0: return None filename = f"bta_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" df.to_csv(filename, index=False) return filename def update_settings(min_thick, temp_threshold, slaging_int): """Tab 5: Update settings""" try: db.set_config('min_safe_thickness', min_thick) db.set_config('threshold_temp', temp_threshold) db.set_config('slaging_interval', slaging_int) return f"✅ Pengaturan berhasil disimpan!\n\nMin Safe Thickness: {min_thick} mm\nTemp Threshold: {temp_threshold}°C\nSlaging Interval: {slaging_int} detik" except Exception as e: return f"❌ Error: {str(e)}" def get_maintenance_history_view(): """Tab 6: Get maintenance history""" maint_list = db.get_maintenance_history(limit=50) if len(maint_list) == 0: return pd.DataFrame(columns=['Tanggal', 'Thickness Before', 'Reset To', 'Notes']) df_maint = pd.DataFrame([{ 'Tanggal': m['maintenance_date'], 'Thickness Before (mm)': f"{m['previous_thickness']:.2f}", 'Reset To (mm)': m['reset_to_thickness'], 'Notes': m['notes'] if m['notes'] else '-' } for m in maint_list]) return df_maint def record_maintenance(notes_input): """Tab 6: Record new maintenance event""" try: latest = db.get_latest_measurement() if latest is None: return "❌ Tidak ada data pengukuran sebelumnya!" prev_thickness = latest['ketebalan_prediksi'] db.log_maintenance(prev_thickness, reset_to_thickness=230, notes=notes_input) return f"""✅ Maintenance Event Recorded! 🔧 Detail: • Previous Thickness: {prev_thickness:.2f} mm • Reset To: 230 mm • Maintenance Date: {datetime.now().strftime('%d %B %Y %H:%M:%S')} • Notes: {notes_input if notes_input else '(no notes)'} ⚠️ Sistem siap untuk monitoring furnace yang baru! """ except Exception as e: return f"❌ Error: {str(e)}" # ==================== GRADIO INTERFACE ==================== with gr.Blocks(title="🏭 Industrial AI: Rotary Furnace BTA Monitoring", theme=gr.themes.Soft()) as demo: gr.Markdown("# 🏭 Industrial AI: Rotary Furnace BTA Predictive Maintenance") gr.Markdown("Dashboard monitoring furnace dengan prediksi ketebalan BTA real-time & historis lengkap") with gr.Tabs(): # ============ TAB 1: INPUT DATA MANUAL ============ with gr.TabItem("📥 Input Data Manual", id="tab_input"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📥 Input Parameter Suhu") t_depan = gr.Number(label="Suhu Cone Depan (°C)", value=360) t_tengah = gr.Number(label="Suhu Bodi Tengah (°C)", value=340) t_belakang = gr.Number(label="Suhu Cone Belakang (°C)", value=370) is_slaging = gr.Checkbox(label="🚩 Flag as Slaging (Measurement yang diperhitungkan)?", value=False) btn_submit = gr.Button("Submit Data", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown("### 📊 Hasil Prediksi Sistem AI") result_text = gr.Textbox(label="Hasil Prediksi", lines=8, interactive=False) out_tebal = gr.Textbox(label="Ketebalan BTA (mm)", interactive=False) out_avg = gr.Textbox(label="Suhu Rata-rata (°C)", interactive=False) out_status = gr.Textbox(label="Status Furnace", interactive=False) out_rul = gr.Textbox(label="Sisa Umur Pemakaian (RUL)", interactive=False) btn_submit.click( fn=submit_measurement, inputs=[t_depan, t_tengah, t_belakang, is_slaging], outputs=[result_text, out_tebal, out_avg, out_status, out_rul] ) # ============ TAB 2: REAL-TIME PREDICTION ============ with gr.TabItem("📊 Real-time Prediction", id="tab_realtime"): gr.Markdown("### Status Furnace Terkini") gr.Markdown("*Click tombol di bawah untuk load data terbaru*") with gr.Row(): with gr.Column(scale=1): kpi_status = gr.Textbox(label="Status", interactive=False, value="—") with gr.Column(scale=1): kpi_thickness = gr.Textbox(label="Ketebalan Prediksi (mm)", interactive=False, value="—") with gr.Column(scale=1): kpi_temp = gr.Textbox(label="Suhu Rata-rata (°C)", interactive=False, value="—") with gr.Column(scale=1): kpi_rul = gr.Textbox(label="Sisa RUL (Hari)", interactive=False, value="—") with gr.Column(scale=1): kpi_target = gr.Textbox(label="Target Maintenance", interactive=False, value="—") chart_realtime = gr.Plot(label="Trend 10 Pengukuran Terakhir") btn_refresh_realtime = gr.Button("🔄 Load Data Terbaru", variant="primary", size="lg") btn_refresh_realtime.click( fn=get_latest_display, inputs=[], outputs=[kpi_status, kpi_thickness, kpi_temp, kpi_rul, kpi_target, chart_realtime] ) # ============ TAB 3: DASHBOARD SUMMARY ============ with gr.TabItem("📈 Dashboard Summary", id="tab_summary"): gr.Markdown("### Overview Keseluruhan Sistem") gr.Markdown("*Click tombol di bawah untuk load summary terbaru*") summary_text = gr.Textbox(label="KPI Summary", lines=12, interactive=False, value="Belum ada data. Klik tombol 'Load Summary' untuk memuat.") gauge_chart = gr.Plot(label="Remaining Thickness Percentage") btn_refresh_summary = gr.Button("📊 Load Summary", variant="primary", size="lg") btn_refresh_summary.click( fn=get_summary_dashboard, inputs=[], outputs=[summary_text, gauge_chart] ) # ============ TAB 4: HISTORIS & ANALISIS ============ with gr.TabItem("📉 Historis & Analisis", id="tab_historis"): gr.Markdown("### Analisis Data Historis: Regular vs Slaging") gr.Markdown("*Click 'Refresh Historis' untuk load semua data measurement*") chart_historis = gr.Plot(label="Historis Ketebalan") table_historis = gr.Dataframe(label="Data Table", interactive=False) with gr.Row(): btn_refresh_historis = gr.Button("📉 Load Historis", variant="primary") btn_export_csv = gr.Button("📥 Export to CSV", variant="secondary") download_file = gr.File(label="CSV File", visible=False) btn_refresh_historis.click( fn=get_historis_view, inputs=[], outputs=[table_historis, chart_historis] ) btn_export_csv.click( fn=export_csv, inputs=[], outputs=[download_file] ) # ============ TAB 5: SETTINGS ============ with gr.TabItem("⚙️ Settings", id="tab_settings"): gr.Markdown("### Konfigurasi Sistem") with gr.Row(): with gr.Column(scale=1): setting_min_thick = gr.Number( label="Min Safe Thickness (mm)", value=float(db.get_config('min_safe_thickness')) ) with gr.Column(scale=1): setting_temp = gr.Number( label="Temperature Threshold (°C)", value=float(db.get_config('threshold_temp')) ) with gr.Column(scale=1): setting_interval = gr.Number( label="Slaging Interval (seconds)", value=float(db.get_config('slaging_interval')) ) settings_output = gr.Textbox(label="Status", lines=4, interactive=False) btn_save_settings = gr.Button("💾 Save Settings", variant="primary") btn_save_settings.click( fn=update_settings, inputs=[setting_min_thick, setting_temp, setting_interval], outputs=[settings_output] ) db_info = gr.Textbox( value=f"Database Location: bta_furnace.db\nStatus: Connected ✓", label="Database Info", interactive=False ) # ============ TAB 6: MAINTENANCE LOG ============ with gr.TabItem("🔧 Maintenance Log", id="tab_maintenance"): gr.Markdown("### Riwayat & Pencatatan Maintenance") table_maint = gr.Dataframe(label="Maintenance History", interactive=False) with gr.Row(): btn_refresh_maint = gr.Button("🔄 Load Log", variant="secondary") gr.Markdown("### Record New Maintenance Event") with gr.Row(): with gr.Column(scale=3): notes_input = gr.Textbox(label="Notes (opsional)", placeholder="Catatan maintenance...") with gr.Column(scale=1): btn_record_maint = gr.Button("✅ Record Maintenance", variant="primary", size="lg") maint_output = gr.Textbox(label="Status", lines=6, interactive=False) btn_refresh_maint.click( fn=get_maintenance_history_view, inputs=[], outputs=[table_maint] ) btn_record_maint.click( fn=record_maintenance, inputs=[notes_input], outputs=[maint_output] ) # Launch aplikasi if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860, share=False)