Emanrashid7 commited on
Commit
ee301f1
Β·
verified Β·
1 Parent(s): a412db2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +181 -0
app.py CHANGED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import seaborn as sns
6
+ from io import BytesIO
7
+ from docx import Document
8
+
9
+ # ==========================================
10
+ # 1. PREMIUM UI & BRANDING
11
+ # ==========================================
12
+ st.set_page_config(page_title="Shaking Table Optimization | Emaan Rashid", layout="wide")
13
+
14
+ st.markdown("""
15
+ <style>
16
+ .stApp { background: #f8f9fa; }
17
+ [data-testid="stSidebar"] { background-color: #f0f2f6 !important; border-right: 2px solid #0e314d; }
18
+ [data-testid="stSidebar"] .stMarkdown p, [data-testid="stSidebar"] label {
19
+ color: #0e314d !important; font-weight: bold !important;
20
+ }
21
+ div[data-testid="stMetric"] {
22
+ background: white; padding: 25px !important; border-radius: 15px !important;
23
+ box-shadow: 0 10px 25px rgba(0,0,0,0.05) !important; border-top: 5px solid #0e314d !important;
24
+ }
25
+ h1, h2, h3 { color: #0e314d !important; font-weight: 800 !important; }
26
+ </style>
27
+ """, unsafe_allow_html=True)
28
+
29
+ # ==========================================
30
+ # 2. ACADEMIC HEADER
31
+ # ==========================================
32
+ st.markdown("""
33
+ <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;">
34
+ <h1 style="margin:0; font-size: 28px;">🟫 Advanced Simulation of Gravity Separation Processes</h1>
35
+ <p style="margin:5px 0; color: #666;">Project Focus: Digital Twin for Shaking Table Optimization</p>
36
+ <hr style="border: 0.5px solid #eee;">
37
+ <div style="display: flex; justify-content: space-between; align-items: center;">
38
+ <span><strong>Submitted To:</strong> Dr. Muhammad Badar Hayat</span>
39
+ <span><strong>Submitted By:</strong> Emaan Rashid (Reg No: 2022-min-13)</span>
40
+ <span style="background: #0e314d; color: white; padding: 5px 15px; border-radius: 20px; font-size: 12px;">Mining Engineering | UET Lahore</span>
41
+ </div>
42
+ </div>
43
+ """, unsafe_allow_html=True)
44
+
45
+ # ==========================================
46
+ # 3. GLOBAL ENGINE & FIXED SIDEBAR
47
+ # ==========================================
48
+ FEED_GRADES = {"Gold": 0.80, "Ilmenite": 1.5, "Rutile": 0.30, "Monazite": 0.15}
49
+ DEFAULT_PRICES = {"Gold": 80.0, "Ilmenite": 250.0, "Rutile": 800.0, "Monazite": 1500.0}
50
+
51
+ # Default plant data dictionary
52
+ current_plant_rec = {"Gold": 75.0, "Ilmenite": 60.0, "Rutile": 65.0, "Monazite": 70.0}
53
+
54
+ st.sidebar.title("πŸŽ›οΈ Control Panel")
55
+
56
+ # --- πŸ“₯ NEW ROBUST IMPORT & TEMPLATE SECTION ---
57
+ st.sidebar.markdown("### πŸ“₯ Calibration Data")
58
+
59
+ # 1. Provide a Template to avoid errors
60
+ template_df = pd.DataFrame({"Mineral": ["Gold", "Ilmenite", "Rutile", "Monazite"], "Recovery": [75.0, 60.0, 65.0, 70.0]})
61
+ csv_template = template_df.to_csv(index=False).encode('utf-8')
62
+ st.sidebar.download_button("1. Download Sample File", data=csv_template, file_name="plant_data_template.csv", mime='text/csv')
63
+
64
+ # 2. Upload with better error handling
65
+ uploaded_file = st.sidebar.file_uploader("2. Upload Your File", type=['csv', 'xlsx'])
66
+
67
+ if uploaded_file is not None:
68
+ try:
69
+ if uploaded_file.name.endswith('.csv'):
70
+ df_up = pd.read_csv(uploaded_file)
71
+ else:
72
+ df_up = pd.read_excel(uploaded_file)
73
+
74
+ # Standardize column names
75
+ df_up.columns = [str(c).strip().title() for c in df_up.columns]
76
+
77
+ # Check if required columns exist
78
+ if 'Mineral' in df_up.columns and 'Recovery' in df_up.columns:
79
+ # Map values
80
+ for _, row in df_up.iterrows():
81
+ m_name = str(row['Mineral']).strip().capitalize()
82
+ if m_name in current_plant_rec:
83
+ current_plant_rec[m_name] = float(row['Recovery'])
84
+ st.sidebar.success("βœ… Plant Data Calibrated!")
85
+ else:
86
+ st.sidebar.error("❌ Need 'Mineral' & 'Recovery' columns")
87
+ except Exception as e:
88
+ st.sidebar.error("❌ Invalid File Format")
89
+
90
+ # --- MECHANICAL CONTROLS ---
91
+ with st.sidebar:
92
+ st.divider()
93
+ st.subheader("βš™οΈ Machine Parameters")
94
+ feed_rate = st.slider("Feed Rate (TPH)", 20, 250, 85)
95
+ stroke = st.slider("Stroke Length (mm)", 10.0, 35.0, 22.0)
96
+ slope = st.slider("Deck Inclination (Β°)", 2.0, 8.0, 4.5)
97
+ water = st.slider("Wash Water (mΒ³/h)", 5.0, 40.0, 18.0)
98
+ st.divider()
99
+ st.subheader("πŸ’° Economics")
100
+ mining_cost = st.number_input("Mining Cost ($/t)", 5.0, 50.0, 12.5)
101
+ op_cost = st.number_input("Fixed OPEX ($/h)", 50.0, 500.0, 210.0)
102
+
103
+ # ==========================================
104
+ # 4. CALCULATIONS
105
+ # ==========================================
106
+ def run_model(rate, s, sl, w, target_dict):
107
+ eff = max(0.45, (s/22) * (1 - abs(sl-4.5)/10) * (1 - abs(w-18)/40))
108
+ conc_mass = rate * 0.25
109
+ tail_mass = rate - conc_mass
110
+ res = []
111
+ total_rev = 0
112
+ for m, p_rec in target_dict.items():
113
+ m_rec = min(p_rec * eff, 99.0)
114
+ feed_val = rate * (FEED_GRADES[m] if m == "Gold" else FEED_GRADES[m]/100)
115
+ rev = (feed_val * (m_rec/100)) * DEFAULT_PRICES[m]
116
+ total_rev += rev
117
+ res.append({"Mineral": m, "Plant Rec%": p_rec, "Model Rec%": round(m_rec, 2), "Revenue ($/h)": round(rev, 2)})
118
+ return pd.DataFrame(res), total_rev, eff, conc_mass, tail_mass
119
+
120
+ df_results, hourly_rev, sys_eff, c_mass, t_mass = run_model(feed_rate, stroke, slope, water, current_plant_rec)
121
+ profit = hourly_rev - ((feed_rate * mining_cost) + op_cost)
122
+
123
+ # ==========================================
124
+ # 5. DASHBOARD TABS
125
+ # ==========================================
126
+ tab1, tab2, tab3, tab4 = st.tabs(["πŸš€ Executive Dashboard", "πŸ“Š Validation & Analytics", "πŸ“‰ Quality Curves", "πŸ“‘ Export Center"])
127
+
128
+ with tab1:
129
+ st.subheader("Mass Balance & Plant Performance")
130
+ m1, m2, m3 = st.columns(3)
131
+ m1.metric("Total Feed", f"{feed_rate} TPH")
132
+ m2.metric("Concentrate", f"{c_mass:.2f} TPH")
133
+ m3.metric("Tailings", f"{t_mass:.2f} TPH")
134
+
135
+ st.divider()
136
+ l, r = st.columns(2)
137
+ with l:
138
+ fig_pie, ax_pie = plt.subplots()
139
+ ax_pie.pie([c_mass, t_mass], labels=['Concentrate', 'Tailings'], autopct='%1.1f%%', colors=['#d4af37', '#0e314d'])
140
+ st.pyplot(fig_pie)
141
+ with r:
142
+ st.metric("Net Profit", f"${profit:,.0f}/h")
143
+ st.metric("Efficiency", f"{sys_eff*100:.1f}%")
144
+
145
+ st.markdown("#### Performance Matrix")
146
+ st.dataframe(df_results.style.background_gradient(cmap='Blues'), use_container_width=True)
147
+
148
+ with tab2:
149
+ st.subheader("Calibration Analysis")
150
+ df_results['Error %'] = abs(df_results['Model Rec%'] - df_results['Plant Rec%'])
151
+ st.info(f"Mean Calibration Deviation: {df_results['Error %'].mean():.2f}%")
152
+
153
+ fig_bar, ax_bar = plt.subplots(figsize=(10, 4))
154
+ x = np.arange(len(df_results))
155
+ ax_bar.bar(x-0.2, df_results['Plant Rec%'], 0.4, label='Plant (Input)', color='#adb5bd')
156
+ ax_bar.bar(x+0.2, df_results['Model Rec%'], 0.4, label='Twin (Model)', color='#d4af37')
157
+ ax_bar.set_xticks(x); ax_bar.set_xticklabels(df_results['Mineral']); ax_bar.legend()
158
+ st.pyplot(fig_bar)
159
+
160
+ with tab3:
161
+ st.subheader("Grade-Recovery Engineering")
162
+
163
+ m_choice = st.selectbox("Select Mineral", list(FEED_GRADES.keys()))
164
+ recs = np.linspace(30, 98, 50)
165
+ f_mass = feed_rate * (FEED_GRADES[m_choice] if m_choice=="Gold" else FEED_GRADES[m_choice]/100)
166
+ grades = [(f_mass * (r/100))/c_mass * (1 if m_choice=="Gold" else 100) for r in recs]
167
+ fig_gr, ax_gr = plt.subplots(figsize=(10, 4))
168
+ ax_gr.plot(recs, grades, color='#0e314d', lw=2)
169
+ st.pyplot(fig_gr)
170
+
171
+ with tab4:
172
+ st.subheader("Official Report")
173
+ if st.button("Generate Final Word Report"):
174
+ doc = Document()
175
+ doc.add_heading(f"Digital Twin Calibration Report", 0)
176
+ doc.add_paragraph(f"Student: Emaan Rashid | 2022-min-13")
177
+ bio = BytesIO(); doc.save(bio)
178
+ st.download_button("Download .docx", bio.getvalue(), "Emaan_Rashid_Final.docx")
179
+
180
+ st.divider()
181
+ st.caption("Developed by Gemini AI | Senior Digital Twin Engine | UET Lahore")