Spaces:
Sleeping
Sleeping
| 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) |