File size: 13,540 Bytes
3fe6513
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
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="<b>Total Attempts</b>",
            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="<b>Global Score Distribution</b>",
        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="<b>Students with <5 Attempts</b>",
            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="<b>Students with at least one global score of 'C', 'D', 'E'</b>",
            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)