BTA-PREDICT / app.py
ranggaptr's picture
Refactor prediction logic in historis view; prepare data for model prediction with renamed columns and improved error handling
dc8c2d9
Raw
History Blame Contribute Delete
21.4 kB
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)