specialization_allocation / src /streamlit_app.py
Deevyankar's picture
Update src/streamlit_app.py
28747d4 verified
Raw
History Blame Contribute Delete
16.6 kB
import streamlit as st
import pandas as pd
import os
from io import BytesIO
from datetime import datetime
import plotly.express as px
st.set_page_config(page_title="Specialization Allocation System", layout="wide")
st.markdown("""
<style>
.stApp {
background-color: #f3f8fc;
}
[data-testid="stSidebar"] {
background-color: #eaf3ff;
}
[data-testid="stSidebar"] h1,
[data-testid="stSidebar"] h2,
[data-testid="stSidebar"] h3,
[data-testid="stSidebar"] label,
[data-testid="stSidebar"] p,
[data-testid="stSidebar"] span {
color: #003b7a !important;
font-weight: 700 !important;
}
h1, h2, h3 {
color: #003b7a !important;
font-weight: 800 !important;
}
p, li, label {
color: #1a1a1a !important;
font-size: 16px !important;
}
.stTextInput input {
background-color: white !important;
color: black !important;
border: 1px solid #8aa7c7 !important;
border-radius: 8px !important;
}
div[data-baseweb="select"] > div {
background-color: white !important;
color: black !important;
border: 1px solid #8aa7c7 !important;
border-radius: 8px !important;
}
ul[role="listbox"], li[role="option"] {
background-color: white !important;
color: black !important;
}
.stButton>button,
div[data-testid="stDownloadButton"] button {
background-color: #f58220 !important;
color: white !important;
border-radius: 10px !important;
height: 46px !important;
font-weight: bold !important;
border: none !important;
}
.stButton>button:hover,
div[data-testid="stDownloadButton"] button:hover {
background-color: #003b7a !important;
color: white !important;
}
.portal-header {
background: linear-gradient(90deg, #003b7a 0%, #005baa 70%, #f58220 100%);
padding: 28px;
border-radius: 18px;
color: white;
margin-bottom: 25px;
}
.portal-title {
font-size: 38px;
font-weight: 850;
color: white;
}
.portal-subtitle {
font-size: 19px;
color: white;
margin-top: 8px;
}
.card {
background-color: white;
padding: 22px;
border-radius: 16px;
border-left: 7px solid #f58220;
box-shadow: 0px 3px 12px rgba(0,0,0,0.08);
margin-bottom: 20px;
}
.admin-card {
background-color: white;
padding: 22px;
border-radius: 16px;
border-left: 7px solid #003b7a;
box-shadow: 0px 3px 12px rgba(0,0,0,0.08);
margin-bottom: 15px;
}
.admin-note {
background-color: #eaf3ff;
color: #003b7a !important;
padding: 14px;
border-radius: 10px;
font-weight: 700;
font-size: 17px;
}
.download-box {
background-color: #eaf3ff;
color: #003b7a;
padding: 16px;
border-radius: 12px;
border-left: 6px solid #f58220;
margin-top: 15px;
margin-bottom: 15px;
font-weight: 700;
}
</style>
""", unsafe_allow_html=True)
BASE_DIR = os.path.dirname(__file__)
excel_path = os.path.join(BASE_DIR, "For Specialisation.xlsx")
csv_file = os.path.join(BASE_DIR, "submissions.csv")
logo_path = os.path.join(BASE_DIR, "logo.png")
if not os.path.exists(excel_path):
st.error("Excel file not found. Please upload For Specialisation.xlsx")
st.stop()
df_students = pd.read_excel(excel_path)
all_choices = [
"Civil Engineering (Baccalaureate level)",
"Architectural Engineering (Baccalaureate level)",
"Quantity Surveying & Cost Estimation (Baccalaureate level)",
"Mechanical Engineering (Baccalaureate level)",
"Chemical Engineering (Baccalaureate level)",
"Heating Ventilation & Air Conditioning (Diploma level)",
"Oil and Gas (Diploma level)",
"Electrical Power Engineering (Baccalaureate level)",
"Electronics and Telecommunication Engineering (Baccalaureate level)",
"Computer Engineering (Baccalaureate level)"
]
female_choices = [c for c in all_choices if "Heating Ventilation" not in c]
eligibility_df = pd.DataFrame({
"S.No": range(1, 11),
"Specialization": all_choices,
"Male": ["Yes"] * 10,
"Female": ["Yes", "Yes", "Yes", "Yes", "Yes", "Not Applicable", "Yes", "Yes", "Yes", "Yes"]
})
def reset_student_form():
for key in list(st.session_state.keys()):
if key.startswith("choice_") or key in ["student_id_input", "phone_input", "gender_input"]:
del st.session_state[key]
def convert_df_to_excel(df):
output = BytesIO()
with pd.ExcelWriter(output, engine="openpyxl") as writer:
df.to_excel(writer, index=False, sheet_name="Student Choices")
output.seek(0)
return output
def create_ack_html(student_id, name, advisor, gender, phone, choices):
rows = "".join([
f"<tr><td>Choice {i+1}</td><td>{c}</td></tr>"
for i, c in enumerate(choices)
])
return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Specialization Choice Acknowledgement</title>
<style>
html, body {{
background-color: #ffffff !important;
color: #000000 !important;
font-family: Arial, sans-serif;
padding: 30px;
}}
.container {{
background-color: #ffffff !important;
color: #000000 !important;
padding: 30px;
border-radius: 12px;
border-top: 10px solid #003b7a;
max-width: 900px;
margin: auto;
}}
h1 {{
color: #003b7a;
text-align: center;
}}
h3 {{
color: #f58220;
}}
p {{
color: #000000;
font-size: 15px;
}}
table {{
width: 100%;
border-collapse: collapse;
margin-top: 20px;
color: #000000;
}}
th {{
background-color: #003b7a;
color: white;
padding: 10px;
border: 1px solid #999;
}}
td {{
background-color: #ffffff;
color: #000000;
padding: 10px;
border: 1px solid #999;
}}
.note {{
margin-top: 20px;
background-color: #fff3e6;
color: #000000;
padding: 15px;
border-left: 6px solid #f58220;
}}
</style>
</head>
<body>
<div class="container">
<h1>Specialization Choice Acknowledgement</h1>
<h3>Engineering Specialization Selection Portal</h3>
<p><b>Submission Date/Time:</b> {datetime.now().strftime("%d-%m-%Y %H:%M:%S")}</p>
<p><b>Student ID:</b> {student_id}</p>
<p><b>Student Name:</b> {name}</p>
<p><b>Advisor Name:</b> {advisor}</p>
<p><b>Gender:</b> {gender}</p>
<p><b>Phone Number:</b> {phone}</p>
<table>
<tr>
<th>Priority</th>
<th>Selected Specialization</th>
</tr>
{rows}
</table>
<div class="note">
This acknowledgement confirms that your specialization preferences have been submitted successfully.
If the same student submits again, only the latest submission will be considered.
Final allocation is subject to Specialization Committee approval.
</div>
</div>
</body>
</html>
""".encode("utf-8")
def show_header():
top1, top2 = st.columns([1, 5])
with top1:
if os.path.exists(logo_path):
st.image(logo_path, width=150)
with top2:
st.markdown("""
<div class="portal-header">
<div class="portal-title">Specialization Priority Selection System</div>
<div class="portal-subtitle">Engineering Specialization Selection Portal</div>
</div>
""", unsafe_allow_html=True)
def student_portal():
show_header()
st.markdown("""
<div class="card">
<h3>Student Steps and Rules</h3>
<ol>
<li>Enter your Student ID.</li>
<li>If your ID is found, your name and advisor name will appear.</li>
<li>Select your gender and enter your phone number.</li>
<li>After entering phone number, specialization choices will appear.</li>
<li>Male students must prioritize all 10 specializations.</li>
<li>Female students must prioritize 9 specializations only, as HVAC is not applicable.</li>
<li>After submission, download your acknowledgement report.</li>
<li>If you submit more than once using the same Student ID, only your latest submission will be stored and considered.</li>
<li>Final allocation is subject to Specialization Committee approval.</li>
</ol>
</div>
""", unsafe_allow_html=True)
st.header("Available Specializations")
st.table(eligibility_df)
st.header("Step 1: Student Login")
student_id = st.text_input("Enter your Student ID", key="student_id_input")
if student_id:
matched = df_students[df_students["Student No"].astype(str) == student_id]
if matched.empty:
st.error(
"Student ID not found in the system. Please contact Mazoon Mohamed Mubarak Al Araimi "
"(mazoon.alaraimi@utas.edu.om) or Dr. Deevyankar Agarwal "
"(deevyankar.agarwal@utas.edu.om) through Microsoft Teams."
)
else:
row = matched.iloc[0]
st.success("Student ID found successfully.")
st.info(
f"**Student Name:** {row['Student Name']} \n"
f"**Advisor Name:** {row['Advisor Name']}"
)
st.header("Step 2: Basic Information")
gender = st.radio(
"Select Gender",
["Male", "Female"],
horizontal=True,
key="gender_input"
)
phone = st.text_input("Enter Phone Number", key="phone_input")
if phone.strip():
choices_available = all_choices if gender == "Male" else female_choices
required_choices = 10 if gender == "Male" else 9
st.header(f"Step 3: Prioritize Your {required_choices} Choices")
selected_choices = []
for i in range(required_choices):
remaining = [c for c in choices_available if c not in selected_choices]
choice = st.selectbox(f"Choice {i+1}", remaining, key=f"choice_{i}")
selected_choices.append(choice)
if st.button("Submit Choices"):
submission = {
"Student ID": student_id,
"Student Name": row["Student Name"],
"Advisor Name": row["Advisor Name"],
"Gender": gender,
"Phone Number": phone
}
for i, c in enumerate(selected_choices):
submission[f"Choice {i+1}"] = c
submit_df = pd.DataFrame([submission])
if os.path.exists(csv_file):
old_df = pd.read_csv(csv_file)
old_df = old_df[old_df["Student ID"].astype(str) != student_id]
final_df = pd.concat([old_df, submit_df], ignore_index=True)
else:
final_df = submit_df
final_df.to_csv(csv_file, index=False)
st.success("Your choices have been submitted successfully.")
st.markdown("""
<div class="download-box">
Your acknowledgement report is ready. Please click the orange button below to download it.
After downloading, the form will reset for the next student.
</div>
""", unsafe_allow_html=True)
ack_html = create_ack_html(
student_id,
row["Student Name"],
row["Advisor Name"],
gender,
phone,
selected_choices
)
st.download_button(
"📥 Download Acknowledgement Report",
data=ack_html,
file_name=f"acknowledgement_{student_id}.html",
mime="text/html",
on_click=reset_student_form
)
else:
st.warning("Please enter your phone number to continue.")
def admin_dashboard():
show_header()
st.markdown("""
<div class="admin-card">
<h3>Admin Dashboard</h3>
<div class="admin-note">
This section is only for Specialization Committee Members.
Please enter the authorized password to view reports and analytics.
</div>
</div>
""", unsafe_allow_html=True)
admin_password = st.text_input("Enter Admin Password", type="password")
if admin_password != "admin123":
st.info("Please enter the correct admin password to open the dashboard.")
return
st.success("Admin access granted.")
if not os.path.exists(csv_file):
st.warning("No student submissions are available yet.")
return
report_df = pd.read_csv(csv_file)
if report_df.empty:
st.warning("No student submissions are available yet.")
return
st.subheader("Summary")
m1, m2, m3 = st.columns(3)
with m1:
st.metric("Total Submissions", len(report_df))
with m2:
st.metric("Male Students", len(report_df[report_df["Gender"] == "Male"]))
with m3:
st.metric("Female Students", len(report_df[report_df["Gender"] == "Female"]))
excel_data = convert_df_to_excel(report_df)
st.markdown("""
<div class="download-box">
The full Excel report is ready. Please click the orange button below to download the student choice report.
</div>
""", unsafe_allow_html=True)
st.download_button(
"📥 Download Excel Report",
data=excel_data,
file_name="specialization_report.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
st.divider()
st.header("Analytics Dashboard")
first_choice_counts = report_df["Choice 1"].value_counts().reset_index()
first_choice_counts.columns = ["Specialization", "Required Seats"]
st.subheader("Seats Required to Fulfil Student First Choice")
st.dataframe(first_choice_counts, use_container_width=True)
st.info(
"This table shows how many seats are required in each specialization "
"to fulfil students' first-choice preferences."
)
color_sequence = [
"#003b7a", "#f58220", "#00a6a6", "#7a4cc2", "#2ca02c",
"#d62728", "#9467bd", "#8c564b", "#e377c2", "#17becf"
]
st.subheader("First Choice Demand by Specialization")
fig_first = px.bar(
first_choice_counts,
x="Specialization",
y="Required Seats",
color="Specialization",
text="Required Seats",
title="Number of Students Interested in Each Specialization as First Choice",
template="plotly_white",
color_discrete_sequence=color_sequence
)
fig_first.update_traces(textposition="outside")
fig_first.update_layout(
xaxis_tickangle=-35,
plot_bgcolor="white",
paper_bgcolor="white",
font=dict(color="black"),
height=550,
showlegend=False
)
st.plotly_chart(fig_first, use_container_width=True)
st.subheader("First Choice Preference by Gender")
gender_choice = (
report_df.groupby(["Choice 1", "Gender"])
.size()
.reset_index(name="Students")
)
fig_gender = px.bar(
gender_choice,
x="Choice 1",
y="Students",
color="Gender",
barmode="group",
text="Students",
title="Male and Female Student First Choice Preferences",
template="plotly_white",
color_discrete_map={
"Male": "#003b7a",
"Female": "#f58220"
}
)
fig_gender.update_traces(textposition="outside")
fig_gender.update_layout(
xaxis_tickangle=-35,
plot_bgcolor="white",
paper_bgcolor="white",
font=dict(color="black"),
height=600
)
st.plotly_chart(fig_gender, use_container_width=True)
st.subheader("Submitted Student Choices")
st.dataframe(report_df, use_container_width=True)
st.sidebar.title("Navigation")
page = st.sidebar.radio(
"Select Page",
["Student Portal", "Admin Dashboard"]
)
if page == "Student Portal":
student_portal()
else:
admin_dashboard()