BTA-PREDICT / db_manager.py
ranggaptr's picture
Improve error handling and data type safety in DBManager; ensure numeric columns are converted to float and handle missing config values gracefully
52034ac
Raw
History Blame Contribute Delete
8.75 kB
import sqlite3
import pandas as pd
from datetime import datetime
from contextlib import contextmanager
class DBManager:
"""Manages SQLite database for BTA Furnace monitoring system"""
def __init__(self, db_path='bta_furnace.db'):
self.db_path = db_path
self.init_schema()
@contextmanager
def get_connection(self):
"""Context manager untuk database connection"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row # Enable column access by name
try:
yield conn
finally:
conn.close()
def init_schema(self):
"""Initialize database schema jika belum ada"""
with self.get_connection() as conn:
cursor = conn.cursor()
# Tabel data measurement
cursor.execute('''
CREATE TABLE IF NOT EXISTS data_measurement (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
cone_depan REAL,
bodi_tengah REAL,
cone_belakang REAL,
suhu_avg REAL,
ketebalan_actual REAL,
ketebalan_prediksi REAL,
status TEXT,
is_flagged INTEGER DEFAULT 0
)
''')
# Tabel maintenance log
cursor.execute('''
CREATE TABLE IF NOT EXISTS maintenance_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
maintenance_date DATETIME DEFAULT CURRENT_TIMESTAMP,
previous_thickness REAL,
reset_to_thickness REAL,
notes TEXT
)
''')
# Tabel app config
cursor.execute('''
CREATE TABLE IF NOT EXISTS app_config (
key TEXT PRIMARY KEY,
value TEXT
)
''')
# Insert default config jika belum ada
cursor.execute("SELECT COUNT(*) FROM app_config")
if cursor.fetchone()[0] == 0:
defaults = [
('min_safe_thickness', '115'),
('threshold_temp', '400'),
('slaging_interval', '300'),
('reset_thickness', '230'),
('laju_penipisan', '0.35')
]
cursor.executemany("INSERT INTO app_config (key, value) VALUES (?, ?)", defaults)
conn.commit()
def insert_measurement(self, cone_depan, bodi_tengah, cone_belakang, ketebalan_actual,
ketebalan_prediksi, status='AMAN', is_flagged=False):
"""Insert measurement data ke database"""
suhu_avg = (cone_depan + bodi_tengah + cone_belakang) / 3
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO data_measurement
(cone_depan, bodi_tengah, cone_belakang, suhu_avg, ketebalan_actual,
ketebalan_prediksi, status, is_flagged)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (cone_depan, bodi_tengah, cone_belakang, suhu_avg, ketebalan_actual,
ketebalan_prediksi, status, int(is_flagged)))
conn.commit()
return cursor.lastrowid
def get_latest_measurement(self):
"""Get latest measurement"""
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM data_measurement
ORDER BY timestamp DESC LIMIT 1
''')
row = cursor.fetchone()
if row:
result = dict(row)
# Ensure numeric columns are float
for col in ['cone_depan', 'bodi_tengah', 'cone_belakang', 'suhu_avg', 'ketebalan_actual', 'ketebalan_prediksi']:
if col in result and result[col] is not None:
result[col] = float(result[col])
return result
return None
def get_historis(self, limit=100, flagged_only=False):
"""Get historical measurements"""
with self.get_connection() as conn:
cursor = conn.cursor()
if flagged_only:
query = '''
SELECT * FROM data_measurement
WHERE is_flagged = 1
ORDER BY timestamp DESC LIMIT ?
'''
else:
query = '''
SELECT * FROM data_measurement
ORDER BY timestamp DESC LIMIT ?
'''
cursor.execute(query, (limit,))
rows = cursor.fetchall()
results = []
for row in rows:
result = dict(row)
# Ensure numeric columns are float
for col in ['cone_depan', 'bodi_tengah', 'cone_belakang', 'suhu_avg', 'ketebalan_actual', 'ketebalan_prediksi']:
if col in result and result[col] is not None:
result[col] = float(result[col])
results.append(result)
return results
def get_all_data_as_dataframe(self):
"""Get all measurement data sebagai Pandas DataFrame"""
with self.get_connection() as conn:
df = pd.read_sql_query(
"SELECT * FROM data_measurement ORDER BY timestamp ASC",
conn
)
return df
def log_maintenance(self, previous_thickness, reset_to_thickness=230, notes=''):
"""Log maintenance event"""
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO maintenance_log
(previous_thickness, reset_to_thickness, notes)
VALUES (?, ?, ?)
''', (previous_thickness, reset_to_thickness, notes))
conn.commit()
return cursor.lastrowid
def get_maintenance_history(self, limit=50):
"""Get maintenance history"""
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM maintenance_log
ORDER BY maintenance_date DESC LIMIT ?
''', (limit,))
rows = cursor.fetchall()
return [dict(row) for row in rows]
def get_config(self, key):
"""Get config value"""
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT value FROM app_config WHERE key = ?", (key,))
row = cursor.fetchone()
value = row[0] if row else None
# Ensure string type
if value is not None and isinstance(value, bytes):
value = value.decode('utf-8')
return value
def set_config(self, key, value):
"""Set config value"""
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT OR REPLACE INTO app_config (key, value) VALUES (?, ?)",
(key, str(value))
)
conn.commit()
def get_stats(self):
"""Get statistics untuk dashboard summary"""
with self.get_connection() as conn:
cursor = conn.cursor()
# Total measurements
cursor.execute("SELECT COUNT(*) FROM data_measurement")
total_measurements = cursor.fetchone()[0]
# Total flagged (slaging)
cursor.execute("SELECT COUNT(*) FROM data_measurement WHERE is_flagged = 1")
total_flagged = cursor.fetchone()[0]
# Latest measurement
cursor.execute('''
SELECT ketebalan_prediksi, timestamp FROM data_measurement
ORDER BY timestamp DESC LIMIT 1
''')
latest = cursor.fetchone()
current_thickness = float(latest[0]) if latest else 230.0
# Total maintenance events
cursor.execute("SELECT COUNT(*) FROM maintenance_log")
total_maintenance = cursor.fetchone()[0]
return {
'total_measurements': total_measurements,
'total_flagged': total_flagged,
'current_thickness': current_thickness,
'total_maintenance': total_maintenance
}