import os import sqlite3 import datetime import streamlit as st import pandas as pd from huggingface_hub import hf_hub_download from xgboost import XGBRegressor # --- KONFIGURASI HALAMAN --- st.set_page_config(page_title="Sistem Monitoring BTA v2.1", page_icon="🏗️", layout="wide") # --- KONEKSI DATABASE --- def init_db(): conn = sqlite3.connect('furnace_data.db', check_same_thread=False) c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS cycles (id INTEGER PRIMARY KEY, start_date TEXT, initial_thickness REAL, active INTEGER)''') c.execute('''CREATE TABLE IF NOT EXISTS daily_logs (id INTEGER PRIMARY KEY, cycle_id INTEGER, log_date TEXT, raw_temp REAL)''') conn.commit() return conn # --- LOAD MODEL --- HF_TOKEN = os.environ.get("HF_TOKEN") REPO_MODEL = "Rendhaputra/BTA_predictive" FILE_MODEL = "xgboost_bta.json" @st.cache_resource def load_model(): try: path = hf_hub_download(repo_id=REPO_MODEL, filename=FILE_MODEL, token=HF_TOKEN) m = XGBRegressor() m.load_model(path) return m except Exception as e: st.error(f"Gagal muat model: {e}") return None model = load_model() db = init_db() # --- FUNGSI HELPER --- def get_active_cycle(): c = db.cursor() c.execute("SELECT id, start_date FROM cycles WHERE active = 1 ORDER BY id DESC LIMIT 1") return c.fetchone() # (Fungsi ini tetap disimpan sebagai cadangan/helper, meski logika utamanya sekarang menggunakan Pandas) def get_rolling_30(cycle_id, current_temp): c = db.cursor() c.execute("SELECT raw_temp FROM daily_logs WHERE cycle_id = ? ORDER BY log_date DESC LIMIT 29", (cycle_id,)) past_temps = [] for row in c.fetchall(): try: if row[0] is not None: past_temps.append(float(row[0])) except ValueError: pass all_temps = past_temps + [float(current_temp)] if len(all_temps) == 0: return 0.0 return sum(all_temps) / len(all_temps) # --- ANTARMUKA (UI) --- st.title("🏗️ Smart Monitoring BTA Furnace (v2.1 - Data Migration Enabled)") # SIDEBAR with st.sidebar: st.header("⚙️ Pengaturan & Import") active_cycle = get_active_cycle() if active_cycle: st.success(f"Siklus Aktif: {active_cycle[1]}") # --- FITUR IMPORT CSV (Versi Diperbarui) --- st.markdown("---") st.subheader("📤 Bulk Import Data Historis") st.caption("Gunakan CSV dengan kolom: 'Tanggal' dan 'Bodi Tengah (°C)'") uploaded_file = st.file_uploader("Pilih file CSV", type="csv") if uploaded_file is not None: try: # 1. Melewati baris judul (skiprows=1) & Penanganan Encoding try: data_import = pd.read_csv(uploaded_file, encoding='utf-8-sig', skiprows=1) except UnicodeDecodeError: uploaded_file.seek(0) data_import = pd.read_csv(uploaded_file, encoding='latin1', skiprows=1) # 2. Cek apakah pemisahnya ternyata titik koma (;) if len(data_import.columns) == 1: uploaded_file.seek(0) data_import = pd.read_csv(uploaded_file, encoding='utf-8-sig', sep=';', skiprows=1) # 3. Bersihkan nama kolom dari spasi data_import.columns = data_import.columns.str.strip() # 4. Samakan nama kolom di CSV ("Bodi Tengah (°C)") ke "Suhu" if 'Bodi Tengah (°C)' in data_import.columns: data_import.rename(columns={'Bodi Tengah (°C)': 'Suhu'}, inplace=True) st.info("Preview Data yang akan diimpor:") st.dataframe(data_import[['Tanggal', 'Suhu']].head(3)) if st.button("Konfirmasi Import Data"): count = 0 # Abaikan data kosong pada kolom vital for index, row in data_import.dropna(subset=['Tanggal', 'Suhu']).iterrows(): db.execute("INSERT INTO daily_logs (cycle_id, log_date, raw_temp) VALUES (?, ?, ?)", (active_cycle[0], str(row['Tanggal']), float(row['Suhu']))) count += 1 db.commit() st.success(f"✅ Berhasil mengimpor {count} data historis!") st.rerun() except KeyError as e: st.error(f"Kolom tidak ditemukan: {e}. Pastikan file memiliki kolom 'Tanggal' dan 'Bodi Tengah (°C)'.") except Exception as e: st.error(f"Gagal memproses CSV: {e}") st.markdown("---") if st.button("Reset / Pasang BTA Baru"): db.execute("UPDATE cycles SET active = 0 WHERE active = 1") db.commit() st.rerun() else: st.warning("Buat siklus dulu sebelum import!") new_date = st.date_input("Tanggal Pemasangan BTA Baru", datetime.date.today()) if st.button("Mulai Siklus Baru"): db.execute("INSERT INTO cycles (start_date, initial_thickness, active) VALUES (?, ?, ?)", (new_date.isoformat(), 230.0, 1)) db.commit() st.rerun() # --- MAIN AREA (Versi Diperbarui: Otomatis Update) --- if active_cycle: cycle_id, start_date_str = active_cycle # Pastikan start_date dikonversi dengan aman menggunakan Pandas try: start_date = pd.to_datetime(start_date_str).date() except Exception: start_date = datetime.date.fromisoformat(start_date_str) col1, col2 = st.columns([1, 2]) with col1: st.subheader("📥 Input Harian Tunggal") tgl_skrg = st.date_input("Tanggal Hari Ini", datetime.date.today()) temp_raw = st.number_input("Suhu Bodi Tengah (°C)", 200.0, 500.0, 350.0) if st.button("Simpan & Prediksi", type="primary"): # Hanya menyimpan data ke DB, perhitungannya ditangani di Col2 db.execute("INSERT INTO daily_logs (cycle_id, log_date, raw_temp) VALUES (?, ?, ?)", (cycle_id, tgl_skrg.isoformat(), temp_raw)) db.commit() st.rerun() with col2: st.subheader("📊 Visualisasi & Analisis") # Tarik semua data dari DB menggunakan Pandas df_hist = pd.read_sql_query(f"SELECT log_date, raw_temp FROM daily_logs WHERE cycle_id={cycle_id}", db) if not df_hist.empty and model is not None: # Rapikan dan urutkan tanggal (antisipasi beda format CSV "MM/DD/YYYY" vs Streamlit "YYYY-MM-DD") df_hist['log_date'] = pd.to_datetime(df_hist['log_date'], format="mixed", dayfirst=False) df_hist = df_hist.sort_values('log_date').reset_index(drop=True) # Ambil data hari terakhir yang ada di database latest_row = df_hist.iloc[-1] latest_date = latest_row['log_date'].date() # Perhitungan Hari Operasi hari_ops = (latest_date - start_date).days # Hitung Rolling 30 dari Pandas tail_30 = pd.to_numeric(df_hist['raw_temp'].tail(30), errors='coerce').dropna() roll30 = tail_30.mean() if not tail_30.empty else 0.0 # Lakukan Prediksi Model pred_mm = float(model.predict([[hari_ops, roll30]])[0]) sisa_hari = max(0, int((pred_mm - 100) / 0.191)) # Tampilkan ke Layar secara Otomatis m1, m2, m3 = st.columns(3) m1.metric("Ketebalan BTA", f"{pred_mm:.1f} mm") m2.metric("Umur Operasi", f"{hari_ops} Hari") m3.metric("Est. Sisa Umur", f"{sisa_hari} Hari") # Kembalikan tipe data ke string untuk chart agar rapi df_hist['log_date'] = df_hist['log_date'].dt.strftime('%Y-%m-%d') st.line_chart(df_hist.set_index('log_date')['raw_temp']) st.write(f"Total data dalam database: **{len(df_hist)} baris**") else: st.info("Belum ada data untuk dianalisis. Silakan input harian atau import CSV.")