blowbysimulation / calculator.py
SHELLAPANDIANGANHUNGING's picture
Update calculator.py
36f0575 verified
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime
if 'submitted' not in st.session_state:
st.session_state.submitted = False
st.set_page_config(page_title="Blowby Pressure & Fuel Impact Dashboard", layout="wide")
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
.main {
background: linear-gradient(135deg, #1a2a44 0%, #2c3e50 100%);
font-family: 'Poppins', sans-serif;
color: #ecf0f1;
padding: 20px;
}
.stButton>button {
background: linear-gradient(90deg, #f1c40f, #e67e22);
color: #1a2a44;
border: none;
border-radius: 25px;
padding: 12px 30px;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.stButton>button:hover {
background: linear-gradient(90deg, #e67e22, #f1c40f);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
.stRadio>div>label {
font-size: 16px;
color: #ecf0f1;
background-color: rgba(255, 255, 255, 0.1);
padding: 8px 15px;
border-radius: 15px;
transition: all 0.3s ease;
}
.stRadio>div>label:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.metric-card {
background: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
margin-bottom: 20px;
transition: transform 0.3s ease;
}
.metric-card:hover {
transform: translateY(-5px);
}
h1 {
color: #f1c40f;
font-weight: 700;
text-align: center;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
h2 {
color: #ecf0f1;
font-weight: 600;
font-size: 1.8em;
margin-bottom: 15px;
}
.stSelectbox {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 5px;
}
.stSlider>div>div>div {
background-color: #f1c40f !important;
}
.stNumberInput input {
background-color: rgba(255, 255, 255, 0.1);
color: #ecf0f1;
border-radius: 10px;
border: 1px solid #f1c40f;
}
.thank-you-message {
display: flex;
justify-content: center;
align-items: center;
height: 80vh;
font-size: 2.5em;
font-weight: 600;
color: #f1c40f;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
</style>
""", unsafe_allow_html=True)
def show_thank_you_message():
st.markdown("""
<div class="thank-you-message">
Terimakasih sudah simulasi
</div>
""", unsafe_allow_html=True)
if st.session_state.submitted:
show_thank_you_message()
else:
with st.sidebar:
st.markdown("<h2 style='color: #f1c40f;'>โš™๏ธ Dashboard Controls</h2>", unsafe_allow_html=True)
st.markdown("**Developed by: Bukit Technology (RnD)**")
st.markdown(f"**Last Updated:** {datetime.now().strftime('%Y-%m-%d')}")
st.markdown("---")
st.markdown("Use this calculator to analyze blowby pressure and fuel impact for strategic decisions.")
col_logo, col_title = st.columns([1, 4])
with col_logo:
try:
st.image("buma ina.png", width=150)
except FileNotFoundError:
st.warning("โš ๏ธ File logo 'buma_ina.PNG' tidak ditemukan. Silakan pastikan file ada di direktori yang sama dengan script.")
with col_title:
st.title("Calculator CHA for Fuel Rate Impact")
# st.markdown("**Analisis Blowby Pressure & Fuel Impact untuk Pengambilan Keputusan Strategis**")
try:
detail = pd.read_csv('1_DEV_SUMMARY_detail_data_HD785_7_ENGINE_last3month.csv')
except FileNotFoundError:
st.error("โŒ File '1_DEV_SUMMARY_detail_data_HD785_7_ENGINE_last3month.csv' tidak ditemukan.")
st.stop()
st.subheader("๐Ÿ” Pilih Unit untuk Analisis")
unit_list = sorted(detail['equipment_no'].dropna().unique())
selected_unit = st.selectbox("Pilih unit number yang akan disimulasikan:", unit_list, help="Pilih nomor peralatan untuk analisis")
unit_df = detail[detail['equipment_no'] == selected_unit].sort_values("comp_life")
if unit_df.empty:
st.warning("โš ๏ธ Data tidak tersedia untuk unit ini.")
st.stop()
current_life_df = (
detail[detail['solving_algorithm'].notna()]
.sort_values(['equipment_no', 'comp_life'])
.groupby('equipment_no')
.tail(1)
)
if selected_unit not in current_life_df['equipment_no'].values:
st.error("โŒ Data current component life tidak tersedia untuk unit ini.")
st.stop()
current_life = current_life_df.loc[current_life_df['equipment_no'] == selected_unit, 'comp_life'].iloc[0]
col1, col2 = st.columns([2, 1])
with col1:
st.subheader("๐Ÿ“Š Grafik Blowby Pressure terhadap Umur Komponen")
max_life = unit_df['comp_life'].max()
fig = px.line(unit_df, x='comp_life', y='blowby_press_max_act',
title=f"Unit: {selected_unit}",
labels={'comp_life': 'Component Life (Hours)', 'blowby_press_max_act': 'Pressure (mmH2O)'},
line_shape='linear', render_mode='svg')
fig.add_vline(x=current_life, line_dash="dash", line_color="#7f8c8d", annotation_text="Current Life", annotation_position="top left")
fig.update_traces(line_color='#1abc9c', line_width=3, marker=dict(size=8))
fig.update_layout(
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
font=dict(family="Poppins", size=12, color="#ecf0f1"),
title_font=dict(size=18, color="#f1c40f"),
showlegend=True,
xaxis=dict(showgrid=True, gridcolor='rgba(255,255,255,0.2)'),
yaxis=dict(showgrid=True, gridcolor='rgba(255,255,255,0.2)')
)
st.plotly_chart(fig, use_container_width=True)
with col2:
st.subheader("โณ Current Component Life")
st.markdown(f"""
<div class="metric-card">
<h3 style="color: #f1c40f;">Current Life</h3>
<p style="font-size: 24px; color: #1abc9c; font-weight: bold;">{current_life:,.0f} jam</p>
</div>
""", unsafe_allow_html=True)
st.subheader("โš™๏ธ Simulasi Perpanjangan Umur Komponen")
input_method = st.radio("Pilih metode input:", ["Slider", "Input Numerik"], horizontal=True)
max_deviation = int(max_life - current_life)
if input_method == "Slider":
extended_life = st.slider(
"Simulasikan Perpanjangan Umur Komponen Hingga (jam):",
min_value=int(current_life + 100),
max_value=int(max_life),
value=int(min(current_life + 1000, max_life)),
step=100,
help="Geser untuk memilih umur komponen yang diinginkan"
)
deviation = extended_life - current_life
else:
deviation = st.number_input(
"Masukkan Perpanjangan Umur Komponen Sebesar (jam):",
min_value=100,
max_value=max_deviation,
value=min(1000, max_deviation),
step=100,
help="Masukkan jumlah jam perpanjangan dari current life"
)
extended_life = current_life + deviation
st.markdown(f"**Umur Komponen Diperpanjang Hingga:** {extended_life:,.0f} jam (Perpanjangan: {deviation:,.0f} jam)", unsafe_allow_html=True)
sim_df = unit_df[(unit_df['comp_life'] > current_life) & (unit_df['comp_life'] <= extended_life)]
def hitung_avg_blowby(df, batas):
tmp = df[['comp_life', 'blowby_press_max_act']].dropna().sort_values('comp_life')
tmp = tmp[tmp['comp_life'] <= batas]
if tmp.empty:
return 0
tail = tmp.tail(10)
if len(tail) < 10:
st.warning(f"Hanya {len(tail)} titik data (<= {batas} jam).")
return tail['blowby_press_max_act'].mean()
def hitung_avg_blowbyawal(df, batas):
tmp = df[['comp_life', 'blowby_press_max_act']].dropna().sort_values('comp_life')
tmp = tmp[tmp['comp_life'] <= batas]
if tmp.empty:
return 0
tail = tmp.tail(30)
if len(tail) < 30:
st.warning(f"Hanya {len(tail)} titik data (<= {batas} jam).")
return tail['blowby_press_max_act'].mean()
def fuel_awal_data(df, batas):
tmp = df[['comp_life', 'fuel_rate_act']].dropna().sort_values('comp_life')
tmp = tmp[tmp['comp_life'] <= batas]
if tmp.empty:
return 0
tail = tmp.tail(5)
if len(tail) < 5:
st.warning(f"Hanya {len(tail)} titik data (<= {batas} jam).")
return tail['fuel_rate_act'].mean()
def fuel_awal_setelah_replacement(df, batas):
tmp = df[['comp_life', 'fuel_rate_act']].dropna().sort_values('comp_life')
tmp = tmp[tmp['comp_life'] <= batas]
if tmp.empty:
return 0
head = tmp.head(5)
if len(head) < 5:
st.warning(f"Hanya {len(head)} titik data (<= {batas} jam).")
return head['fuel_rate_act'].mean()
st.subheader("โ›ฝ Prediksi Fuel Rate")
avg_blowby_awal = fuel_awal_data(unit_df, current_life)
avg_blowby_akhir = hitung_avg_blowby(unit_df, extended_life)
fuel_setelah_replacement =fuel_awal_setelah_replacement(unit_df, extended_life)
delta_life = extended_life - current_life
fuel_rate_awal = (avg_blowby_awal * 0.004) + 71.86 # L/h
fuel_rate_akhir = (avg_blowby_akhir * 0.004) + 71.86 # L/h
hours_ext = extended_life - current_life
fuel_usage = fuel_rate_akhir * hours_ext # total L over extension
col3, col4, col5 = st.columns(3)
with col3:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Fuel Rate Terakhir</h4>
<p style="font-size: 20px; color: #3498db;">{fuel_rate_awal:.2f} L/h</p>
</div>
""", unsafe_allow_html=True)
with col4:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Fuel Akumulasi</h4>
<p style="font-size: 20px; color: #3498db;">{(fuel_rate_akhir * delta_life):,.2f} L</p>
</div>
""", unsafe_allow_html=True)
with col5:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Total Extend</h4>
<p style="font-size: 20px; color: #3498db;">{delta_life:,.2f} Hm</p>
</div>
""", unsafe_allow_html=True)
if not sim_df.empty and sim_df['trendline_blow'].notna().any():
last_f = sim_df['trendline_blow'].dropna().iloc[-1]
fr_f = last_f * 0.004 + 71.86
rpm_f = last_f * 0.034 + 1303.75
col6, col7 = st.columns(2)
with col6:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Fuel Rate Forecast</h4>
<p style="font-size: 20px; color: #e67e22;">{fr_f:.2f} L/h</p>
</div>
""", unsafe_allow_html=True)
with col7:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Engine Speed Forecast</h4>
<p style="font-size: 20px; color: #e67e22;">{rpm_f:.2f} RPM</p>
</div>
""", unsafe_allow_html=True)
st.subheader("๐Ÿ’ฐ Analisis Keuntungan dan Kerugian")
harga_engine = 110_000 # USD
harga_fuel = 1 # USD/L
cost_per_h = harga_engine / current_life
saving = ((cost_per_h * delta_life) + (fuel_setelah_replacement* delta_life))
fuel_cost = fuel_usage * harga_fuel
net_savings = fuel_cost - saving
col8, col9, col10 = st.columns(3)
with col8:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Cost Replacement</h4>
<p style="font-size: 20px; color: #2ecc71;">USD {saving:,.2f}</p>
</div>
""", unsafe_allow_html=True)
with col9:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Cost Fuel (Extend)</h4>
<p style="font-size: 20px; color: #e74c3c;">USD {fuel_cost:,.2f}</p>
</div>
""", unsafe_allow_html=True)
with col10:
st.markdown(f"""
<div class="metric-card">
<h4 style="color: #34495e;">Net Savings</h4>
<p style="font-size: 20px; color: #9b59b6;">USD {net_savings:,.2f}</p>
</div>
""", unsafe_allow_html=True)
st.subheader("Perbandingan Penghematan vs Biaya Fuel")
fig2 = go.Figure()
fig2.add_trace(go.Bar(
x=[saving, fuel_cost],
y=["Replacement Cost", "Fuel Cost if Extend"],
orientation='h',
marker=dict(color=['#2ecc71', '#e74c3c']),
text=[f"USD {saving:,.2f}", f"USD {fuel_cost:,.2f}"],
textposition='auto'
))
fig2.update_layout(
title="Perbandingan Biaya",
title_font=dict(size=18, color="#f1c40f"),
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
font=dict(family="Poppins", size=12, color="#ecf0f1"),
showlegend=False,
xaxis=dict(showgrid=True, gridcolor='rgba(255,255,255,0.2)'),
yaxis=dict(showgrid=False)
)
st.plotly_chart(fig2, use_container_width=True)
st.subheader("๐Ÿ”„ Opsi Tindakan")
choice = st.radio("Pilih tindakan:", ["replacement/service", "extend component"], horizontal=True)
if choice == "replacement/service":
st.success("โœ… Anda memilih untuk replacement component.")
else:
st.error("โŒ Anda memilih untuk extend component.")
if st.button("Submit"):
st.session_state.submitted = True
st.rerun() # Mengganti st.experimental_rerun() dengan st.rerun()
st.markdown("---")
st.markdown("<p style='text-align: center; color: #ecf0f1;'>ยฉ 2025 Your Company. All Rights Reserved.</p>", unsafe_allow_html=True)