| 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() |