import random from datetime import timedelta, date import firebase_admin from firebase_admin import credentials, storage, firestore import streamlit as st import streamlit_authenticator as stauth import pandas as pd import plotly.express as px import plotly.graph_objects as go import json, os, dotenv from dotenv import load_dotenv load_dotenv() os.environ["FIREBASE_CREDENTIAL"] = dotenv.get_key(dotenv.find_dotenv(), "FIREBASE_CREDENTIAL") cred = credentials.Certificate(json.loads(os.environ.get("FIREBASE_CREDENTIAL"))) # Initialize Firebase (if not already initialized) if not firebase_admin._apps: firebase_admin.initialize_app(cred, {'storageBucket': 'healthhack-store.appspot.com'}) #firebase_admin.initialize_app(cred,{'storageBucket': 'healthhack-store.appspot.com'}) # connecting to firebase db = firestore.client() docs = db.collection("clinical_scores").stream() # Create a list of dictionaries from the documents data = [] for doc in docs: doc_dict = doc.to_dict() doc_dict['document_id'] = doc.id # In case you need the document ID later data.append(doc_dict) # Create a DataFrame df = pd.DataFrame(data) #print(df) # Exception handling for irregular grading, e.g. A-, B+ def standardize_grade(value): if pd.isna(value): return value value = str(value).upper().strip() # Convert to string, uppercase and remove leading/trailing spaces if value and value[0] in ['A', 'B', 'C', 'D', 'E']: return value[0] # Return the first character if it's A, B, C, D, or E return value # Return the original value if no match # Columns to check columns_to_check = ['hx_others_score', 'hx_AS_score', 'differentials_score', 'global_score'] # Apply the function to the specified columns df[columns_to_check] = df[columns_to_check].applymap(standardize_grade) login_info = { "student1": "password", "student2": "password", "student3": "password", "admin":"admin" } # Initialize username variable username = None def set_username(x): st.session_state.username = x def validate_username(username, password): if login_info.get(username) == password: set_username(username) else: st.warning("Wrong username or password") return None if not st.session_state.get("username"): ## ask to login st.title("Login") username = st.text_input("Username:") password = st.text_input("Password:", type="password") login_button = st.button("Login", on_click=validate_username, args=[username, password]) if st.session_state.get("username"): username = st.session_state.get("username") st.title(f"Hello there, {st.session_state.username}") # Display logout button if st.button('Logout'): # Remove username from session state del st.session_state.username # Rerun the app to go back to the login view st.rerun() # Convert date from string to datetime if it's not already in datetime format df['date'] = pd.to_datetime(df['date'], errors='coerce') # Streamlit page configuration #st.set_page_config(page_title="Interactive Data Dashboard", layout="wide") # Use df_selection for filtering data based on authenticated user if username != 'admin': df_selection = df[df['name'] == username] else: df_selection = df # Admin sees all data # Chart Title: Student Performance Dashboard st.title(":bar_chart: Student Performance Dashboard") st.markdown("##") # Chart 1: Total attempts if df_selection.empty: st.error("No data available to display.") else: # Total attempts by name (filtered) total_attempts_by_name = df_selection.groupby("name")['date'].count().reset_index() total_attempts_by_name.columns = ['name', 'total_attempts'] # For a single point or multiple points, use a scatter plot fig_total_attempts = px.scatter( total_attempts_by_name, x="name", y="total_attempts", title="Total Attempts", size='total_attempts', # Adjust the size of points color_discrete_sequence=["#0083B8"] * len(total_attempts_by_name), template="plotly_white", text='total_attempts' # Display total_attempts as text labels ) # Add text annotation for each point for line in range(0, total_attempts_by_name.shape[0]): fig_total_attempts.add_annotation( text=str(total_attempts_by_name['total_attempts'].iloc[line]), x=total_attempts_by_name['name'].iloc[line], y=total_attempts_by_name['total_attempts'].iloc[line], showarrow=True, font=dict(family="Courier New, monospace", size=18, color="#ffffff"), align="center", arrowhead=2, arrowsize=1, arrowwidth=2, arrowcolor="#636363", ax=20, ay=-30, bordercolor="#c7c7c7", borderwidth=2, borderpad=4, bgcolor="#ff7f0e", opacity=0.8 ) # Update traces for styling fig_total_attempts.update_traces(marker=dict(size=12), selector=dict(mode='markers+text')) # Display the scatter plot in Streamlit st.plotly_chart(fig_total_attempts, use_container_width=True) # Chart 2 (students only): Personal scores over time if username != 'admin': # Sort the DataFrame by 'date' in chronological order df_selection = df_selection.sort_values(by='date') #fig = px.bar(df_selection, x='date', y='global_score', title='Your scores!') if len(df_selection) > 1: # # If more than one point, use a bar chart # fig = px.bar(df_selection, x='date', y='global_score', title='Global Score Over Time') # # fig.update_yaxes( # # tickmode='array', # # tickvals=[1, 2, 3, 4, 5], # Reverse the order of tickvals # # ticktext=['A', 'B','C','D','E'] # Reverse the order of ticktext # # ) # Mapping dictionary grade_to_score = {'A': 100, 'B': 80, 'C': 60, 'D': 40, 'E': 20} # Apply mapping to convert letter grades to numerical scores df_selection['numeric_score'] = df_selection['global_score'].map(grade_to_score) # Sort the DataFrame by 'date' in chronological order df_selection = df_selection.sort_values(by='date') # Check if there's more than one point in the DataFrame if len(df_selection) > 1: # Create a bar chart using Plotly Express fig = px.bar(df_selection, x='date', y='numeric_score', title='Your scores over time') else: # Create a bar chart with just one point fig = px.bar(df_selection, x='date', y='numeric_score', title='Global Score') # Manually set the y-axis ticks and labels fig.update_yaxes( tickmode='array', tickvals=list(grade_to_score.values()), # Positions for the ticks ticktext=list(grade_to_score.keys()), # Text labels for the ticks range=[0, 120] # Extend the range a bit beyond 100 to accommodate 'A' ) # # Use st.plotly_chart to display the chart in Streamlit # st.plotly_chart(fig, use_container_width=True) else: # For a single point, use a scatter plot fig = px.scatter(df_selection, x='date', y='global_score', title='Global Score', text='global_score', size_max=60) # Add text annotation for line in range(0,df_selection.shape[0]): fig.add_annotation(text=df_selection['global_score'].iloc[line], x=df_selection['date'].iloc[line], y=df_selection['global_score'].iloc[line], showarrow=True, font=dict(family="Courier New, monospace", size=18, color="#ffffff"), align="center", arrowhead=2, arrowsize=1, arrowwidth=2, arrowcolor="#636363", ax=20, ay=-30, bordercolor="#c7c7c7", borderwidth=2, borderpad=4, bgcolor="#ff7f0e", opacity=0.8) fig.update_traces(marker=dict(size=12), selector=dict(mode='markers+text')) # Display the chart in Streamlit st.plotly_chart(fig, use_container_width=True) # Show students their scores over time st.dataframe(df_selection[['date', 'global_score', 'name']]) # Chart 3 (admin only): Global score chart # Define the order of categories explicitly order_of_categories = ['A', 'B', 'C', 'D', 'E'] # Convert global_score to a categorical type with the specified order df_selection['global_score'] = pd.Categorical(df_selection['global_score'], categories=order_of_categories, ordered=True) # Plot the histogram fig_score_distribution = px.histogram( df_selection, x="global_score", title="Global Score Distribution", color_discrete_sequence=["#33CFA5"], category_orders={"global_score": ["A", "B", "C", "D", "E"]} ) if username == 'admin': st.plotly_chart(fig_score_distribution, use_container_width=True) # Chart 4 (admin only): Students with <5 attempts (filtered) if username == 'admin': students_with_less_than_5_attempts = total_attempts_by_name[total_attempts_by_name['total_attempts'] < 5] fig_less_than_5_attempts = px.bar( students_with_less_than_5_attempts, x="name", y="total_attempts", title="Students with <5 Attempts", color_discrete_sequence=["#D62728"] * len(students_with_less_than_5_attempts), template="plotly_white", ) if username == 'admin': st.plotly_chart(fig_less_than_5_attempts, use_container_width=True) # Selection of a student for detailed view (<5 attempts) - based on filtered data if username == 'admin': selected_student_less_than_5 = st.selectbox("Select a student with less than 5 attempts to view details:", students_with_less_than_5_attempts['name']) if selected_student_less_than_5: st.write(df_selection[df_selection['name'] == selected_student_less_than_5]) # Chart 5 (admin only): Students with at least one global score of 'C', 'D', 'E' (filtered) if username == 'admin': students_with_cde = df_selection[df_selection['global_score'].isin(['C', 'D', 'E'])].groupby("name")['date'].count().reset_index() students_with_cde.columns = ['name', 'total_attempts'] fig_students_with_cde = px.bar( students_with_cde, x="name", y="total_attempts", title="Students with at least one global score of 'C', 'D', 'E'", color_discrete_sequence=["#FF7F0E"] * len(students_with_cde), template="plotly_white", ) st.plotly_chart(fig_students_with_cde, use_container_width=True) # Selection of a student for detailed view (score of 'C', 'D', 'E') - based on filtered data if username == 'admin': selected_student_cde = st.selectbox("Select a student with at least one score of 'C', 'D', 'E' to view details:", students_with_cde['name']) if selected_student_cde: st.write(df_selection[df_selection['name'] == selected_student_cde]) # Chart 7 (all): Radar Chart # Mapping grades to numeric values grade_to_numeric = {'A': 90, 'B': 70, 'C': 50, 'D': 30, 'E': 10} df.replace(grade_to_numeric, inplace=True) # Calculate average numeric scores for each category average_scores = df.groupby('name')[['hx_PC_score', 'hx_AS_score', 'hx_others_score', 'differentials_score']].mean().reset_index() if username == 'admin': st.title('Average Scores Radar Chart') else: st.title('Performance in each segment as compared to your friends!') # Categories for the radar chart categories = ['Presenting complaint', 'Associated symptoms', '(Others)', 'Differentials'] st.markdown(""" ### Double click on the names in the legend to include/exclude them from the plot. """) # Custom colors for better contrast colors = ['gold', 'cyan', 'magenta', 'green'] # Plotly Radar Chart fig = go.Figure() for index, row in average_scores.iterrows(): fig.add_trace(go.Scatterpolar( r=[row['hx_PC_score'], row['hx_AS_score'], row['hx_others_score'], row['differentials_score']], theta=categories, fill='toself', name=row['name'], line=dict(color=colors[index % len(colors)]) )) fig.update_layout( polar=dict( radialaxis=dict( visible=True, range=[0, 100], # Numeric range tickvals=[10, 30, 50, 70, 90], # Positions for the grade labels ticktext=['E', 'D', 'C', 'B', 'A'] # Grade labels )), showlegend=True, height=600, # Set the height of the figure width=600 # Set the width of the figure ) # Display the figure in Streamlit st.plotly_chart(fig, use_container_width=True)