Toya0421 commited on
Commit
3a71817
·
verified ·
1 Parent(s): cd5e898

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -43
app.py CHANGED
@@ -5,6 +5,7 @@ import pandas as pd
5
  import os, random, tempfile, json
6
  import re
7
  import threading
 
8
 
9
  # --- API設定 ---
10
  API_KEY = os.getenv("API_KEY")
@@ -24,6 +25,38 @@ levels = [1, 2, 3, 4, 5]
24
  # ✅ ログの同時書き込みガード(ログは全ユーザーで共有)
25
  _log_lock = threading.Lock()
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  # --- AIで問題生成 ---
28
  def generate_question(text):
29
  prompt = f"""
@@ -81,7 +114,7 @@ def adaptive_test(prev_level, prev_correct):
81
  return levels[idx - 1]
82
  return prev_level
83
 
84
- # --- passage取得(used_passages は state から渡す) ---
85
  def get_passage(level, used_passages):
86
  subset = passages_df[passages_df["level"] == level]
87
  available = [pid for pid in subset["passage_id"] if pid not in used_passages]
@@ -91,30 +124,13 @@ def get_passage(level, used_passages):
91
  text = subset[subset["passage_id"] == passage_id]["text"].iloc[0]
92
  return passage_id, text
93
 
94
- # --- ログ追記(✅ CSVのみ。Dataset保存はしない) ---
95
- def log_to_csv(entry):
96
- with _log_lock:
97
- df = pd.DataFrame([entry])
98
- if os.path.exists(LOG_FILE):
99
- df.to_csv(LOG_FILE, mode="a", index=False, header=False)
100
- else:
101
- df.to_csv(LOG_FILE, index=False)
102
-
103
- # --- セッション state 初期化 ---
104
- def _new_session_state():
105
- return {
106
- "used_passages": set(),
107
- "question_count": 0,
108
- "user_id": None,
109
- "action_log": [],
110
- }
111
-
112
- # --- 開始ボタン動作 ---
113
  def start_test(student_id, state):
114
- # 新規開始時は常に初期化
115
  state = _new_session_state()
116
 
117
  if not student_id or student_id.strip() == "":
 
118
  return (
119
  state,
120
  "", "", 0, "", None,
@@ -131,31 +147,64 @@ def start_test(student_id, state):
131
  question = generate_question(text)
132
  displayed_time = datetime.utcnow() + timedelta(hours=9)
133
 
 
 
 
 
 
 
 
 
 
134
  return (
135
  state,
136
  text, question, level, passage_id, None,
137
  "", True, displayed_time.isoformat(), 1, state["user_id"]
138
  )
139
 
140
- # --- 選択肢変更イベント(操作履歴を保存) ---
141
  def log_choice_change(choice, question_number, user_id, state):
142
  if not isinstance(state, dict):
143
  state = _new_session_state()
 
144
  if choice:
 
 
 
145
  state["action_log"].append({
146
  "action": "choice",
147
  "choice": choice,
148
- "time": (datetime.utcnow() + timedelta(hours=9)).isoformat()
149
  })
 
 
 
 
 
 
 
 
150
  return state
151
 
152
- # --- 回答送信 ---
153
  def next_step(prev_level, user_answer, question_text, passage_text,
154
  displayed_time, question_number, user_id, passage_id, state):
155
 
156
  if not isinstance(state, dict):
157
  state = _new_session_state()
158
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  if not user_answer:
160
  return (
161
  state,
@@ -164,25 +213,26 @@ def next_step(prev_level, user_answer, question_text, passage_text,
164
  )
165
 
166
  submit_time = datetime.utcnow() + timedelta(hours=9)
 
 
167
  state["question_count"] += 1
168
 
169
  correct = check_answer_with_ai(passage_text, question_text, user_answer)
170
  new_level = adaptive_test(prev_level, correct)
171
 
172
- # ✅ ログ保存actionsに選択履歴入れる)
173
- entry = {
174
- "user_id": user_id,
175
- "question_number": state["question_count"],
176
- "reading_level": prev_level,
177
- "passage_id": passage_id,
178
- "question": question_text,
179
- "user_answer": user_answer,
180
- "correct": correct,
181
- "displayed_time": displayed_time,
182
- "submit_time": submit_time.strftime("%Y-%m-%d %H:%M:%S"),
183
- "actions": json.dumps(state["action_log"], ensure_ascii=False)
184
- }
185
- log_to_csv(entry)
186
 
187
  # 最終問題なら結果だけ大きく表示
188
  if state["question_count"] >= 5:
@@ -201,7 +251,7 @@ def next_step(prev_level, user_answer, question_text, passage_text,
201
  next_question = generate_question(next_text)
202
  next_display_time = datetime.utcnow() + timedelta(hours=9)
203
 
204
- # ✅ 新しい問題のために選択操作ログをリセット
205
  state["action_log"] = []
206
 
207
  feedback = "✅ Correct!" if correct else "❌ Incorrect."
@@ -251,7 +301,7 @@ with gr.Blocks() as demo:
251
  feedback_display = gr.Markdown()
252
 
253
  hidden_level = gr.Number(visible=False)
254
- hidden_passage = gr.Textbox(visible=False)
255
  hidden_display_time = gr.Textbox(visible=False)
256
  hidden_question_number = gr.Number(visible=False)
257
  hidden_user_id = gr.Textbox(visible=False)
@@ -269,14 +319,14 @@ with gr.Blocks() as demo:
269
  ]
270
  )
271
 
272
- # 選択肢変更ログ
273
  user_answer.change(
274
  fn=log_choice_change,
275
  inputs=[user_answer, hidden_question_number, hidden_user_id, session_state],
276
  outputs=[session_state]
277
  )
278
 
279
- # 回答送信
280
  submit_btn.click(
281
  fn=next_step,
282
  inputs=[hidden_level, user_answer, question_display, text_display,
@@ -289,7 +339,7 @@ with gr.Blocks() as demo:
289
  ]
290
  )
291
 
292
- # 表示のON/OFF制御
293
  def toggle_visibility(show):
294
  v = bool(show)
295
  return (
 
5
  import os, random, tempfile, json
6
  import re
7
  import threading
8
+ import uuid
9
 
10
  # --- API設定 ---
11
  API_KEY = os.getenv("API_KEY")
 
25
  # ✅ ログの同時書き込みガード(ログは全ユーザーで共有)
26
  _log_lock = threading.Lock()
27
 
28
+ # --- セッション state 初期化 ---
29
+ def _new_session_state():
30
+ return {
31
+ "session_id": str(uuid.uuid4()),
32
+ "used_passages": set(),
33
+ "question_count": 0, # ✅ submit毎に増える(=採点済みの問題数)
34
+ "user_id": None,
35
+ "action_log": [], # ✅ 1問の間の選択変更履歴(submit時に一緒に保存)
36
+ }
37
+
38
+ # --- ログ追記(CSVのみ) ---
39
+ def log_to_csv(entry: dict):
40
+ with _log_lock:
41
+ df = pd.DataFrame([entry])
42
+ if os.path.exists(LOG_FILE):
43
+ df.to_csv(LOG_FILE, mode="a", index=False, header=False)
44
+ else:
45
+ df.to_csv(LOG_FILE, index=False)
46
+
47
+ # --- イベントログ(start / select / submit / submit_result) ---
48
+ def log_event(event: str, state: dict, **kwargs):
49
+ now = (datetime.utcnow() + timedelta(hours=9)).strftime("%Y-%m-%d %H:%M:%S")
50
+ entry = {
51
+ "time": now,
52
+ "event": event,
53
+ "session_id": state.get("session_id"),
54
+ "user_id": state.get("user_id"),
55
+ "question_count_scored": state.get("question_count"),
56
+ **kwargs
57
+ }
58
+ log_to_csv(entry)
59
+
60
  # --- AIで問題生成 ---
61
  def generate_question(text):
62
  prompt = f"""
 
114
  return levels[idx - 1]
115
  return prev_level
116
 
117
+ # --- passage取得(used_passages は state から渡す) ---
118
  def get_passage(level, used_passages):
119
  subset = passages_df[passages_df["level"] == level]
120
  available = [pid for pid in subset["passage_id"] if pid not in used_passages]
 
124
  text = subset[subset["passage_id"] == passage_id]["text"].iloc[0]
125
  return passage_id, text
126
 
127
+ # --- 開始ボタン動作(startログを残す) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  def start_test(student_id, state):
129
+ # 新規開始時は常に初期化(session_idも更新)
130
  state = _new_session_state()
131
 
132
  if not student_id or student_id.strip() == "":
133
+ # (必要なら invalid start も残せるが、今回は残さない)
134
  return (
135
  state,
136
  "", "", 0, "", None,
 
147
  question = generate_question(text)
148
  displayed_time = datetime.utcnow() + timedelta(hours=9)
149
 
150
+ # ✅ startイベントをCSVに記録
151
+ log_event(
152
+ "start",
153
+ state,
154
+ initial_level=level,
155
+ passage_id=passage_id,
156
+ displayed_time=displayed_time.isoformat()
157
+ )
158
+
159
  return (
160
  state,
161
  text, question, level, passage_id, None,
162
  "", True, displayed_time.isoformat(), 1, state["user_id"]
163
  )
164
 
165
+ # --- 選択肢変更イベント(変更も全部CSVに残す) ---
166
  def log_choice_change(choice, question_number, user_id, state):
167
  if not isinstance(state, dict):
168
  state = _new_session_state()
169
+
170
  if choice:
171
+ t_iso = (datetime.utcnow() + timedelta(hours=9)).isoformat()
172
+
173
+ # ✅ メモリ上の操作履歴(submit_resultにまとめて入れる)
174
  state["action_log"].append({
175
  "action": "choice",
176
  "choice": choice,
177
+ "time": t_iso
178
  })
179
+
180
+ # ✅ selectイベントをCSVに都度記録(変更も全部残る)
181
+ log_event(
182
+ "select",
183
+ state,
184
+ question_number=question_number,
185
+ choice=choice
186
+ )
187
  return state
188
 
189
+ # --- 回答送信(submitログ + 結果ログ) ---
190
  def next_step(prev_level, user_answer, question_text, passage_text,
191
  displayed_time, question_number, user_id, passage_id, state):
192
 
193
  if not isinstance(state, dict):
194
  state = _new_session_state()
195
 
196
+ # ✅ submitイベントを「ボタン押下ごとに」必ずCSVに記録(未選択でも残す)
197
+ log_event(
198
+ "submit",
199
+ state,
200
+ question_number=question_number,
201
+ reading_level=prev_level,
202
+ passage_id=passage_id,
203
+ choice=user_answer,
204
+ displayed_time=displayed_time,
205
+ submit_status="ok" if user_answer else "no_answer"
206
+ )
207
+
208
  if not user_answer:
209
  return (
210
  state,
 
213
  )
214
 
215
  submit_time = datetime.utcnow() + timedelta(hours=9)
216
+
217
+ # ✅ 採点済み数を増やす(=この問題は確定)
218
  state["question_count"] += 1
219
 
220
  correct = check_answer_with_ai(passage_text, question_text, user_answer)
221
  new_level = adaptive_test(prev_level, correct)
222
 
223
+ # ✅ submit_result採点結果)CSVに記録(actionsも含める)
224
+ log_event(
225
+ "submit_result",
226
+ state,
227
+ question_number=state["question_count"],
228
+ reading_level=prev_level,
229
+ passage_id=passage_id,
230
+ question=question_text,
231
+ user_answer=user_answer,
232
+ correct=correct,
233
+ submit_time=submit_time.strftime("%Y-%m-%d %H:%M:%S"),
234
+ actions=json.dumps(state["action_log"], ensure_ascii=False)
235
+ )
 
236
 
237
  # 最終問題なら結果だけ大きく表示
238
  if state["question_count"] >= 5:
 
251
  next_question = generate_question(next_text)
252
  next_display_time = datetime.utcnow() + timedelta(hours=9)
253
 
254
+ # ✅ 次の問題のために選択操作ログをリセット
255
  state["action_log"] = []
256
 
257
  feedback = "✅ Correct!" if correct else "❌ Incorrect."
 
301
  feedback_display = gr.Markdown()
302
 
303
  hidden_level = gr.Number(visible=False)
304
+ hidden_passage = gr.Textbox(visible=False) # ※使っていないが互換で残す
305
  hidden_display_time = gr.Textbox(visible=False)
306
  hidden_question_number = gr.Number(visible=False)
307
  hidden_user_id = gr.Textbox(visible=False)
 
319
  ]
320
  )
321
 
322
+ # 選択肢変更ログ(変更も全部残す)
323
  user_answer.change(
324
  fn=log_choice_change,
325
  inputs=[user_answer, hidden_question_number, hidden_user_id, session_state],
326
  outputs=[session_state]
327
  )
328
 
329
+ # 回答送信(submitログ + resultログ)
330
  submit_btn.click(
331
  fn=next_step,
332
  inputs=[hidden_level, user_answer, question_display, text_display,
 
339
  ]
340
  )
341
 
342
+ # 表示のON/OFF制御
343
  def toggle_visibility(show):
344
  v = bool(show)
345
  return (