shaking_table / app.py
Emanrashid7's picture
Update app.py
ee301f1 verified
import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from io import BytesIO
from docx import Document
# ==========================================
# 1. PREMIUM UI & BRANDING
# ==========================================
st.set_page_config(page_title="Shaking Table Optimization | Emaan Rashid", layout="wide")
st.markdown("""
<style>
.stApp { background: #f8f9fa; }
[data-testid="stSidebar"] { background-color: #f0f2f6 !important; border-right: 2px solid #0e314d; }
[data-testid="stSidebar"] .stMarkdown p, [data-testid="stSidebar"] label {
color: #0e314d !important; font-weight: bold !important;
}
div[data-testid="stMetric"] {
background: white; padding: 25px !important; border-radius: 15px !important;
box-shadow: 0 10px 25px rgba(0,0,0,0.05) !important; border-top: 5px solid #0e314d !important;
}
h1, h2, h3 { color: #0e314d !important; font-weight: 800 !important; }
</style>
""", unsafe_allow_html=True)
# ==========================================
# 2. ACADEMIC HEADER
# ==========================================
st.markdown("""
<div style="background: white; padding: 30px; border-radius: 15px; border-left: 10px solid #d4af37; box-shadow: 0 4px 15px rgba(0,0,0,0.1); margin-bottom: 30px;">
<h1 style="margin:0; font-size: 28px;">🟫 Advanced Simulation of Gravity Separation Processes</h1>
<p style="margin:5px 0; color: #666;">Project Focus: Digital Twin for Shaking Table Optimization</p>
<hr style="border: 0.5px solid #eee;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span><strong>Submitted To:</strong> Dr. Muhammad Badar Hayat</span>
<span><strong>Submitted By:</strong> Emaan Rashid (Reg No: 2022-min-13)</span>
<span style="background: #0e314d; color: white; padding: 5px 15px; border-radius: 20px; font-size: 12px;">Mining Engineering | UET Lahore</span>
</div>
</div>
""", unsafe_allow_html=True)
# ==========================================
# 3. GLOBAL ENGINE & FIXED SIDEBAR
# ==========================================
FEED_GRADES = {"Gold": 0.80, "Ilmenite": 1.5, "Rutile": 0.30, "Monazite": 0.15}
DEFAULT_PRICES = {"Gold": 80.0, "Ilmenite": 250.0, "Rutile": 800.0, "Monazite": 1500.0}
# Default plant data dictionary
current_plant_rec = {"Gold": 75.0, "Ilmenite": 60.0, "Rutile": 65.0, "Monazite": 70.0}
st.sidebar.title("πŸŽ›οΈ Control Panel")
# --- πŸ“₯ NEW ROBUST IMPORT & TEMPLATE SECTION ---
st.sidebar.markdown("### πŸ“₯ Calibration Data")
# 1. Provide a Template to avoid errors
template_df = pd.DataFrame({"Mineral": ["Gold", "Ilmenite", "Rutile", "Monazite"], "Recovery": [75.0, 60.0, 65.0, 70.0]})
csv_template = template_df.to_csv(index=False).encode('utf-8')
st.sidebar.download_button("1. Download Sample File", data=csv_template, file_name="plant_data_template.csv", mime='text/csv')
# 2. Upload with better error handling
uploaded_file = st.sidebar.file_uploader("2. Upload Your File", type=['csv', 'xlsx'])
if uploaded_file is not None:
try:
if uploaded_file.name.endswith('.csv'):
df_up = pd.read_csv(uploaded_file)
else:
df_up = pd.read_excel(uploaded_file)
# Standardize column names
df_up.columns = [str(c).strip().title() for c in df_up.columns]
# Check if required columns exist
if 'Mineral' in df_up.columns and 'Recovery' in df_up.columns:
# Map values
for _, row in df_up.iterrows():
m_name = str(row['Mineral']).strip().capitalize()
if m_name in current_plant_rec:
current_plant_rec[m_name] = float(row['Recovery'])
st.sidebar.success("βœ… Plant Data Calibrated!")
else:
st.sidebar.error("❌ Need 'Mineral' & 'Recovery' columns")
except Exception as e:
st.sidebar.error("❌ Invalid File Format")
# --- MECHANICAL CONTROLS ---
with st.sidebar:
st.divider()
st.subheader("βš™οΈ Machine Parameters")
feed_rate = st.slider("Feed Rate (TPH)", 20, 250, 85)
stroke = st.slider("Stroke Length (mm)", 10.0, 35.0, 22.0)
slope = st.slider("Deck Inclination (Β°)", 2.0, 8.0, 4.5)
water = st.slider("Wash Water (mΒ³/h)", 5.0, 40.0, 18.0)
st.divider()
st.subheader("πŸ’° Economics")
mining_cost = st.number_input("Mining Cost ($/t)", 5.0, 50.0, 12.5)
op_cost = st.number_input("Fixed OPEX ($/h)", 50.0, 500.0, 210.0)
# ==========================================
# 4. CALCULATIONS
# ==========================================
def run_model(rate, s, sl, w, target_dict):
eff = max(0.45, (s/22) * (1 - abs(sl-4.5)/10) * (1 - abs(w-18)/40))
conc_mass = rate * 0.25
tail_mass = rate - conc_mass
res = []
total_rev = 0
for m, p_rec in target_dict.items():
m_rec = min(p_rec * eff, 99.0)
feed_val = rate * (FEED_GRADES[m] if m == "Gold" else FEED_GRADES[m]/100)
rev = (feed_val * (m_rec/100)) * DEFAULT_PRICES[m]
total_rev += rev
res.append({"Mineral": m, "Plant Rec%": p_rec, "Model Rec%": round(m_rec, 2), "Revenue ($/h)": round(rev, 2)})
return pd.DataFrame(res), total_rev, eff, conc_mass, tail_mass
df_results, hourly_rev, sys_eff, c_mass, t_mass = run_model(feed_rate, stroke, slope, water, current_plant_rec)
profit = hourly_rev - ((feed_rate * mining_cost) + op_cost)
# ==========================================
# 5. DASHBOARD TABS
# ==========================================
tab1, tab2, tab3, tab4 = st.tabs(["πŸš€ Executive Dashboard", "πŸ“Š Validation & Analytics", "πŸ“‰ Quality Curves", "πŸ“‘ Export Center"])
with tab1:
st.subheader("Mass Balance & Plant Performance")
m1, m2, m3 = st.columns(3)
m1.metric("Total Feed", f"{feed_rate} TPH")
m2.metric("Concentrate", f"{c_mass:.2f} TPH")
m3.metric("Tailings", f"{t_mass:.2f} TPH")
st.divider()
l, r = st.columns(2)
with l:
fig_pie, ax_pie = plt.subplots()
ax_pie.pie([c_mass, t_mass], labels=['Concentrate', 'Tailings'], autopct='%1.1f%%', colors=['#d4af37', '#0e314d'])
st.pyplot(fig_pie)
with r:
st.metric("Net Profit", f"${profit:,.0f}/h")
st.metric("Efficiency", f"{sys_eff*100:.1f}%")
st.markdown("#### Performance Matrix")
st.dataframe(df_results.style.background_gradient(cmap='Blues'), use_container_width=True)
with tab2:
st.subheader("Calibration Analysis")
df_results['Error %'] = abs(df_results['Model Rec%'] - df_results['Plant Rec%'])
st.info(f"Mean Calibration Deviation: {df_results['Error %'].mean():.2f}%")
fig_bar, ax_bar = plt.subplots(figsize=(10, 4))
x = np.arange(len(df_results))
ax_bar.bar(x-0.2, df_results['Plant Rec%'], 0.4, label='Plant (Input)', color='#adb5bd')
ax_bar.bar(x+0.2, df_results['Model Rec%'], 0.4, label='Twin (Model)', color='#d4af37')
ax_bar.set_xticks(x); ax_bar.set_xticklabels(df_results['Mineral']); ax_bar.legend()
st.pyplot(fig_bar)
with tab3:
st.subheader("Grade-Recovery Engineering")
m_choice = st.selectbox("Select Mineral", list(FEED_GRADES.keys()))
recs = np.linspace(30, 98, 50)
f_mass = feed_rate * (FEED_GRADES[m_choice] if m_choice=="Gold" else FEED_GRADES[m_choice]/100)
grades = [(f_mass * (r/100))/c_mass * (1 if m_choice=="Gold" else 100) for r in recs]
fig_gr, ax_gr = plt.subplots(figsize=(10, 4))
ax_gr.plot(recs, grades, color='#0e314d', lw=2)
st.pyplot(fig_gr)
with tab4:
st.subheader("Official Report")
if st.button("Generate Final Word Report"):
doc = Document()
doc.add_heading(f"Digital Twin Calibration Report", 0)
doc.add_paragraph(f"Student: Emaan Rashid | 2022-min-13")
bio = BytesIO(); doc.save(bio)
st.download_button("Download .docx", bio.getvalue(), "Emaan_Rashid_Final.docx")
st.divider()
st.caption("Developed by Gemini AI | Senior Digital Twin Engine | UET Lahore")