codewithharsha commited on
Commit
f7e2c08
Β·
verified Β·
1 Parent(s): f146eaf

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +204 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,206 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import pandas as pd
3
+ import io
4
+
5
+ def parse_quiz_file(file_content):
6
+ """
7
+ Parses the custom MD format into a list of question dictionaries.
8
+ """
9
+ questions = []
10
+ raw_blocks = file_content.split("-END-")
11
+
12
+ for block in raw_blocks:
13
+ if not block.strip():
14
+ continue
15
+
16
+ q_data = {}
17
+ current_key = None
18
+
19
+ known_keys = [
20
+ "TOPIC", "SUB_TOPIC", "QUESTION_KEY", "BASE_QUESTION_KEYS",
21
+ "QUESTION_TEXT", "CONTENT_TYPE", "QUESTION_TYPE", "LEARNING_OUTCOME",
22
+ "CODE", "CODE_LANGUAGE", "OPTION_1", "OPTION_2", "OPTION_3",
23
+ "OPTION_4", "CORRECT_OPTION", "EXPLANATION", "BLOOM_LEVEL"
24
+ ]
25
+
26
+ lines = block.strip().split('\n')
27
+
28
+ for line in lines:
29
+ match = None
30
+ for key in known_keys:
31
+ if line.startswith(f"{key}:"):
32
+ match = key
33
+ break
34
+
35
+ if match:
36
+ current_key = match
37
+ val = line.split(':', 1)[1].strip()
38
+ q_data[current_key] = val
39
+ else:
40
+ if current_key:
41
+ q_data[current_key] += "\n" + line
42
+
43
+ if "QUESTION_TEXT" in q_data and "OPTION_1" in q_data:
44
+ questions.append(q_data)
45
+
46
+ return questions
47
+
48
+ def generate_excel_report(questions):
49
+ """
50
+ Gathers all review notes and creates an Excel byte stream.
51
+ Now simplified to only include: Question Key, Question, and Review.
52
+ """
53
+ report_data = []
54
+
55
+ # Iterate through session state to find notes
56
+ for idx, q in enumerate(questions):
57
+ note_key = f"note_{idx}"
58
+ # Get note from session state (if it exists)
59
+ note = st.session_state.get(note_key, "").strip()
60
+
61
+ if note:
62
+ # Add to report with specific columns requested
63
+ report_data.append({
64
+ "Question Key": q.get('QUESTION_KEY', 'N/A'),
65
+ "Question": q['QUESTION_TEXT'],
66
+ "Review": note
67
+ })
68
+
69
+ if not report_data:
70
+ return None
71
+
72
+ df = pd.DataFrame(report_data)
73
+
74
+ # Write to Excel in memory
75
+ output = io.BytesIO()
76
+ with pd.ExcelWriter(output, engine='openpyxl') as writer:
77
+ df.to_excel(writer, index=False, sheet_name='Review Notes')
78
+
79
+ return output.getvalue()
80
+
81
+ def main():
82
+ st.set_page_config(page_title="Quiz Review Tool", layout="wide")
83
+
84
+ st.title("πŸ› οΈ Quiz Content Reviewer")
85
+ st.markdown("Test questions and flag issues for the content team.")
86
+
87
+ # 1. File Uploader
88
+ uploaded_file = st.file_uploader("Choose a Quiz MD file", type=['md'])
89
+
90
+ if uploaded_file is not None:
91
+ string_data = uploaded_file.getvalue().decode("utf-8")
92
+ questions = parse_quiz_file(string_data)
93
+
94
+ if "answered_questions" not in st.session_state:
95
+ st.session_state["answered_questions"] = set()
96
+
97
+ st.success(f"Loaded {len(questions)} questions for review.")
98
+ st.divider()
99
+
100
+ score = 0
101
+ total_answered = 0
102
+
103
+ # 2. Questions Loop
104
+ for idx, q in enumerate(questions):
105
+ with st.container():
106
+ # Header with ID
107
+ c1, c2 = st.columns([0.8, 0.2])
108
+ with c1:
109
+ st.subheader(f"Q{idx+1}: {q.get('TOPIC', 'General')}")
110
+ with c2:
111
+ st.caption(f"ID: {q.get('QUESTION_KEY', 'N/A')}")
112
+
113
+ st.markdown(q['QUESTION_TEXT'])
114
+
115
+ options = [
116
+ q.get('OPTION_1'),
117
+ q.get('OPTION_2'),
118
+ q.get('OPTION_3'),
119
+ q.get('OPTION_4')
120
+ ]
121
+
122
+ radio_key = f"radio_{idx}"
123
+ is_answered = idx in st.session_state["answered_questions"]
124
+
125
+ user_choice = st.radio(
126
+ "Select Answer:",
127
+ options,
128
+ index=None,
129
+ key=radio_key,
130
+ disabled=is_answered
131
+ )
132
+
133
+ # --- Actions Row (Submit & Review) ---
134
+ col_submit, col_review = st.columns([0.2, 0.8])
135
+
136
+ # A. Submit Button Logic
137
+ with col_submit:
138
+ if not is_answered:
139
+ if st.button(f"Check Q{idx+1}", key=f"btn_{idx}"):
140
+ if user_choice:
141
+ st.session_state["answered_questions"].add(idx)
142
+ st.rerun()
143
+ else:
144
+ st.warning("Select an option.")
145
+
146
+ # B. Result Display (If Answered)
147
+ if is_answered:
148
+ correct_key = q['CORRECT_OPTION']
149
+ correct_text = q.get(correct_key)
150
+
151
+ if user_choice == correct_text:
152
+ st.success("βœ… Correct")
153
+ score += 1
154
+ else:
155
+ st.error("❌ Incorrect")
156
+ st.markdown(f"**Correct:** {correct_text}")
157
+
158
+ st.info(f"**Explanation:** {q.get('EXPLANATION', 'None')}")
159
+ total_answered += 1
160
+
161
+ # C. Review Note Feature (Always Visible)
162
+ with st.expander("🚩 Flag Issue / Add Note"):
163
+ st.text_area(
164
+ "Describe the issue:",
165
+ key=f"note_{idx}",
166
+ placeholder="Type review here..."
167
+ )
168
+
169
+ st.markdown("---")
170
+
171
+ # 3. Sidebar - Stats & Export
172
+ with st.sidebar:
173
+ st.header("πŸ“Š Review Status")
174
+ if len(questions) > 0:
175
+ st.progress(total_answered / len(questions))
176
+ st.metric("Score", f"{score} / {total_answered}")
177
+
178
+ st.markdown("---")
179
+ st.header("πŸ“₯ Export Review")
180
+ st.write("When you are done reviewing, download the report.")
181
+
182
+ # Count how many notes exist
183
+ notes_count = 0
184
+ for k in st.session_state:
185
+ if k.startswith("note_") and st.session_state[k].strip():
186
+ notes_count += 1
187
+
188
+ st.info(f"Questions Flagged: **{notes_count}**")
189
+
190
+ if notes_count > 0:
191
+ excel_data = generate_excel_report(questions)
192
+ if excel_data:
193
+ st.download_button(
194
+ label="πŸ“„ Download Excel Report",
195
+ data=excel_data,
196
+ file_name="quiz_review_notes.xlsx",
197
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
198
+ )
199
+
200
+ # Reset
201
+ if st.button("Reset All"):
202
+ st.session_state.clear()
203
+ st.rerun()
204
 
205
+ if __name__ == "__main__":
206
+ main()