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