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