Toya0421 commited on
Commit
e88a1cd
·
verified ·
1 Parent(s): a73081f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -109
app.py CHANGED
@@ -1,52 +1,33 @@
1
  import gradio as gr
2
  from openai import OpenAI
3
- from datasets import load_dataset, Dataset
4
  from datetime import datetime, timedelta
5
  import pandas as pd
6
- import time, os, random, uuid, tempfile
7
 
8
  # --- API設定 ---
9
- API_KEY = os.getenv("API_KEY") # Hugging Face Secrets
10
  BASE_URL = "https://openrouter.ai/api/v1"
11
  HF_TOKEN = os.getenv("HF_TOKEN")
12
  DATASET_REPO = "Toya0421/lexile_test_logging"
13
 
14
- # --- ローカルログファイル ---
15
  LOG_FILE = "logs.csv"
16
 
17
- # --- Lexile難易度別教材 ---
18
- texts = {
19
- 300: [
20
- "Tom has a red ball. He plays with it in the park. The sun is bright.",
21
- "Anna likes cats. She feeds them milk every morning.",
22
- "The boy sees a bird in the tree. It sings a happy song."
23
- ],
24
- 600: [
25
- "A young boy found a lost puppy near the river. He decided to take care of it.",
26
- "Lisa visits her grandmother every weekend. They bake cookies together.",
27
- "The farmer plants rice in the spring and harvests it in the autumn."
28
- ],
29
- 850: [
30
- "Sarah enjoyed reading stories about ancient civilizations and their discoveries.",
31
- "Tom built a small robot that could move and pick up light objects.",
32
- "Maria was fascinated by how airplanes could stay in the air for hours."
33
- ],
34
- 1050: [
35
- "The scientist developed a new hypothesis about the evolution of animal behavior.",
36
- "A group of engineers designed a bridge that could resist strong winds.",
37
- "Students discussed how technology changes communication in modern society."
38
- ],
39
- 1250: [
40
- "Philosophers have long debated the intricate relationship between free will and determinism.",
41
- "Economists analyzed the impact of automation on global labor markets.",
42
- "Researchers investigated how cultural identity influences moral decision-making."
43
- ]
44
- }
45
 
46
  levels = [300, 600, 850, 1050, 1250]
47
 
48
  client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
49
 
 
 
 
 
 
 
50
  # --- 問題生成 ---
51
  def generate_question(text):
52
  prompt = f"""
@@ -73,17 +54,15 @@ def generate_question(text):
73
  def check_answer_with_ai(text, question, user_answer):
74
  prompt = f"""
75
  Read the passage and question below. Decide if the user's answer is correct.
76
- Passage:
77
- {text}
78
- Question:
79
- {question}
80
- User Answer: {user_answer}
81
- Respond with only one word: "Correct" or "Incorrect".
82
  """
83
  response = client.chat.completions.create(
84
  model="google/gemma-3-27b-it:free",
85
  messages=[{"role": "user", "content": prompt}],
86
- max_tokens=10,
87
  temperature=0,
88
  )
89
  return response.choices[0].message.content.strip().lower() == "correct"
@@ -97,128 +76,130 @@ def adaptive_test(prev_level, prev_correct):
97
  return levels[idx - 1]
98
  return prev_level
99
 
100
- # --- ログ追記関数 ---
101
- def log_to_csv_and_push(user_id, level, passage, question, user_answer, correct, response_time):
102
- entry = {
103
- "user_id": user_id,
104
- "lexile_level": level,
105
- "passage": passage,
106
- "question": question,
107
- "user_answer": user_answer,
108
- "correct": correct,
109
- "response_time": response_time,
110
- "timestamp": (datetime.utcnow() + timedelta(hours=9)).strftime("%Y-%m-%d %H:%M:%S"),
111
- }
112
-
113
- # --- ローカルに追記 ---
114
  df = pd.DataFrame([entry])
115
  if os.path.exists(LOG_FILE):
116
  df.to_csv(LOG_FILE, mode="a", index=False, header=False)
117
  else:
118
  df.to_csv(LOG_FILE, index=False)
119
 
120
- # --- データセットへ反映 ---
121
  all_logs = pd.read_csv(LOG_FILE)
122
-
123
  tmp_dir = tempfile.mkdtemp()
124
  tmp_path = os.path.join(tmp_dir, "data.parquet")
125
  all_logs.to_parquet(tmp_path)
126
-
127
  dataset = Dataset.from_parquet(tmp_path)
128
  dataset.push_to_hub(DATASET_REPO, token=HF_TOKEN)
129
 
130
- # --- 状態変数 ---
131
- used_texts = set()
132
  question_count = 0
133
  MAX_QUESTIONS = 5
134
  current_user_id = None
 
135
 
136
- def start_test():
137
- global used_texts, question_count, current_user_id
138
- used_texts = set()
 
139
  question_count = 0
140
- current_user_id = str(uuid.uuid4())
 
 
 
141
 
142
  level = 850
143
- text = random.choice(texts[level])
144
- used_texts.add(text)
 
145
  question = generate_question(text)
146
- start_time = time.time()
147
- return text, question, level, None, "", "", True, start_time, current_user_id
148
 
149
- def next_step(prev_level, user_answer, question_text, passage_text, start_time, user_id):
150
- global question_count, used_texts
151
 
152
- if user_answer is None or user_answer == "":
153
- return "⚠️ Please select an answer!", passage_text, question_text, prev_level, None, "", True, start_time, user_id
154
 
155
- question_count += 1
 
 
 
 
 
156
 
157
- end_time = time.time()
158
- response_time = round(end_time - start_time, 2)
159
  correct = check_answer_with_ai(passage_text, question_text, user_answer)
160
- new_level = adaptive_test(prev_level, correct)
161
 
162
- # ログ追記
163
- log_to_csv_and_push(user_id, prev_level, passage_text, question_text, user_answer, correct, response_time)
 
 
 
 
 
 
 
 
 
 
 
 
164
 
 
165
  feedback = "✅ Correct!" if correct else "❌ Incorrect."
166
 
 
167
  if question_count >= MAX_QUESTIONS:
168
- feedback = f"🎯 **Test finished!**\n\nYour estimated reading level is **{new_level}L**.\n\nThank you for participating!"
169
- return feedback, "", "", new_level, None, "", False, 0.0, user_id
170
 
171
- available = [t for t in texts[new_level] if t not in used_texts]
 
 
172
  if not available:
173
- available = texts[new_level]
174
- next_text = random.choice(available)
175
- used_texts.add(next_text)
 
 
 
176
  next_question = generate_question(next_text)
177
- feedback += "\n➡️ Next question loading..."
178
- next_start_time = time.time()
179
- return feedback, next_text, next_question, new_level, None, "", True, next_start_time, user_id
 
 
180
 
181
  # --- Gradio UI ---
182
  with gr.Blocks() as demo:
183
- gr.Markdown("# 📘 Lexile Level Test")
184
 
 
185
  start_btn = gr.Button("▶️ Start Test")
186
 
187
- text_display = gr.Textbox(label="Reading Passage", lines=6, interactive=False, visible=True)
188
- question_display = gr.Textbox(label="Generated Question", lines=8, interactive=False, visible=True)
189
- user_answer = gr.Radio(choices=["A", "B", "C", "D"], label="Your Answer", visible=True)
190
- submit_btn = gr.Button("Submit Answer", visible=True)
191
 
192
  feedback_display = gr.Markdown()
193
  hidden_level = gr.Number(visible=False)
194
- hidden_passage = gr.Textbox(visible=False)
195
  test_visible = gr.State(True)
196
- hidden_start_time = gr.Number(visible=False)
197
  hidden_user_id = gr.Textbox(visible=False)
198
 
199
  start_btn.click(
200
  fn=start_test,
201
- inputs=[],
202
- outputs=[text_display, question_display, hidden_level, user_answer, feedback_display,
203
- hidden_passage, test_visible, hidden_start_time, hidden_user_id]
204
  )
205
 
206
  submit_btn.click(
207
  fn=next_step,
208
- inputs=[hidden_level, user_answer, question_display, text_display, hidden_start_time, hidden_user_id],
209
- outputs=[feedback_display, text_display, question_display, hidden_level, user_answer,
210
- hidden_passage, test_visible, hidden_start_time, hidden_user_id]
211
- )
212
-
213
- def toggle_visibility(show):
214
- v = bool(show)
215
- return (gr.update(visible=v), gr.update(visible=v),
216
- gr.update(visible=v), gr.update(visible=v))
217
-
218
- test_visible.change(
219
- fn=toggle_visibility,
220
- inputs=test_visible,
221
- outputs=[text_display, question_display, user_answer, submit_btn]
222
  )
223
 
224
  demo.launch()
 
1
  import gradio as gr
2
  from openai import OpenAI
3
+ from datasets import Dataset
4
  from datetime import datetime, timedelta
5
  import pandas as pd
6
+ import time, os, random, uuid, tempfile, json
7
 
8
  # --- API設定 ---
9
+ API_KEY = os.getenv("API_KEY")
10
  BASE_URL = "https://openrouter.ai/api/v1"
11
  HF_TOKEN = os.getenv("HF_TOKEN")
12
  DATASET_REPO = "Toya0421/lexile_test_logging"
13
 
14
+ # --- ログファイル ---
15
  LOG_FILE = "logs.csv"
16
 
17
+ # --- passage本文は外部ファイルから読み込み ---
18
+ PASSAGE_FILE = "passage.csv"
19
+ passages_df = pd.read_csv(PASSAGE_FILE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  levels = [300, 600, 850, 1050, 1250]
22
 
23
  client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
24
 
25
+ # --- passage取得 ---
26
+ def get_passage(level):
27
+ candidates = passages_df[passages_df["lexile_level"] == level]
28
+ row = candidates.sample(1).iloc[0]
29
+ return row["passage_id"], row["text"]
30
+
31
  # --- 問題生成 ---
32
  def generate_question(text):
33
  prompt = f"""
 
54
  def check_answer_with_ai(text, question, user_answer):
55
  prompt = f"""
56
  Read the passage and question below. Decide if the user's answer is correct.
57
+ P: {text}
58
+ Q: {question}
59
+ User: {user_answer}
60
+ Respond only: Correct or Incorrect
 
 
61
  """
62
  response = client.chat.completions.create(
63
  model="google/gemma-3-27b-it:free",
64
  messages=[{"role": "user", "content": prompt}],
65
+ max_tokens=15,
66
  temperature=0,
67
  )
68
  return response.choices[0].message.content.strip().lower() == "correct"
 
76
  return levels[idx - 1]
77
  return prev_level
78
 
79
+ # --- ログ書き込み(回答送信時のみ) ---
80
+ def log_to_csv_and_push(entry):
 
 
 
 
 
 
 
 
 
 
 
 
81
  df = pd.DataFrame([entry])
82
  if os.path.exists(LOG_FILE):
83
  df.to_csv(LOG_FILE, mode="a", index=False, header=False)
84
  else:
85
  df.to_csv(LOG_FILE, index=False)
86
 
 
87
  all_logs = pd.read_csv(LOG_FILE)
 
88
  tmp_dir = tempfile.mkdtemp()
89
  tmp_path = os.path.join(tmp_dir, "data.parquet")
90
  all_logs.to_parquet(tmp_path)
 
91
  dataset = Dataset.from_parquet(tmp_path)
92
  dataset.push_to_hub(DATASET_REPO, token=HF_TOKEN)
93
 
94
+ # --- 状態 ---
95
+ used_passages = set()
96
  question_count = 0
97
  MAX_QUESTIONS = 5
98
  current_user_id = None
99
+ action_log = []
100
 
101
+ # --- テスト開始 ---
102
+ def start_test(user_input_id):
103
+ global used_passages, question_count, current_user_id, action_log
104
+ used_passages = set()
105
  question_count = 0
106
+ action_log = []
107
+
108
+ # ユーザーIDが空 → uuid割当
109
+ current_user_id = user_input_id.strip() if user_input_id else str(uuid.uuid4())
110
 
111
  level = 850
112
+ passage_id, text = get_passage(level)
113
+ used_passages.add(passage_id)
114
+
115
  question = generate_question(text)
 
 
116
 
117
+ displayed_time = datetime.utcnow() + timedelta(hours=9)
118
+ action_log.append({"action": "displayed", "time": displayed_time.isoformat(), "question_number": 1})
119
 
120
+ return text, question, level, passage_id, "", "", True, displayed_time.isoformat(), current_user_id
 
121
 
122
+ # --- 回答送信 ---
123
+ def next_step(prev_level, user_answer, question_text, passage_text, displayed_time, user_id, passage_id):
124
+ global question_count, used_passages, action_log
125
+
126
+ if not user_answer:
127
+ return "⚠️ Please select an answer!", passage_text, question_text, prev_level, None, "", True, displayed_time, user_id, passage_id
128
 
129
+ question_count += 1
130
+ submit_time = datetime.utcnow() + timedelta(hours=9)
131
  correct = check_answer_with_ai(passage_text, question_text, user_answer)
 
132
 
133
+ # ログ書き込み(回答送信時のみ)
134
+ entry = {
135
+ "user_id": user_id,
136
+ "question_number": question_count,
137
+ "lexile_level": prev_level,
138
+ "passage_id": passage_id,
139
+ "question": question_text,
140
+ "user_answer": user_answer,
141
+ "correct": correct,
142
+ "displayed_time": displayed_time,
143
+ "submit_time": submit_time.strftime("%Y-%m-%d %H:%M:%S"),
144
+ "actions": json.dumps(action_log, ensure_ascii=False),
145
+ }
146
+ log_to_csv_and_push(entry)
147
 
148
+ new_level = adaptive_test(prev_level, correct)
149
  feedback = "✅ Correct!" if correct else "❌ Incorrect."
150
 
151
+ # 終了
152
  if question_count >= MAX_QUESTIONS:
153
+ return f"🎯 Test finished!\nYour reading level: **{new_level}L**", "", "", new_level, None, "", False, "", user_id, passage_id
 
154
 
155
+ # 次の問題へ
156
+ candidates = passages_df[passages_df["lexile_level"] == new_level]
157
+ available = [pid for pid in candidates["passage_id"] if pid not in used_passages]
158
  if not available:
159
+ available = list(candidates["passage_id"])
160
+
161
+ next_passage_id = random.choice(available)
162
+ next_text = passages_df[passages_df["passage_id"] == next_passage_id]["text"].iloc[0]
163
+ used_passages.add(next_passage_id)
164
+
165
  next_question = generate_question(next_text)
166
+ next_display_time = datetime.utcnow() + timedelta(hours=9)
167
+
168
+ action_log = [{"action": "displayed", "time": next_display_time.isoformat(), "question_number": question_count+1}]
169
+
170
+ return feedback + "\n➡️ Loading next question…", next_text, next_question, new_level, None, "", True, next_display_time.isoformat(), user_id, next_passage_id
171
 
172
  # --- Gradio UI ---
173
  with gr.Blocks() as demo:
174
+ gr.Markdown("# 📘 Lexile Adaptive Reading Test")
175
 
176
+ user_input_id = gr.Textbox(label="Enter your Student ID", placeholder="e.g. 2025012")
177
  start_btn = gr.Button("▶️ Start Test")
178
 
179
+ text_display = gr.Textbox(label="Reading Passage", lines=6, interactive=False)
180
+ question_display = gr.Textbox(label="Question", lines=7, interactive=False)
181
+ answer_box = gr.Radio(choices=["A", "B", "C", "D"], label="Select Answer")
182
+ submit_btn = gr.Button("Submit Answer")
183
 
184
  feedback_display = gr.Markdown()
185
  hidden_level = gr.Number(visible=False)
186
+ hidden_passage_id = gr.Textbox(visible=False)
187
  test_visible = gr.State(True)
188
+ hidden_display_time = gr.Textbox(visible=False)
189
  hidden_user_id = gr.Textbox(visible=False)
190
 
191
  start_btn.click(
192
  fn=start_test,
193
+ inputs=[user_input_id],
194
+ outputs=[text_display, question_display, hidden_level, hidden_passage_id, answer_box,
195
+ feedback_display, test_visible, hidden_display_time, hidden_user_id]
196
  )
197
 
198
  submit_btn.click(
199
  fn=next_step,
200
+ inputs=[hidden_level, answer_box, question_display, text_display, hidden_display_time, hidden_user_id, hidden_passage_id],
201
+ outputs=[feedback_display, text_display, question_display, hidden_level, answer_box,
202
+ hidden_passage_id, test_visible, hidden_display_time, hidden_user_id, hidden_passage_id]
 
 
 
 
 
 
 
 
 
 
 
203
  )
204
 
205
  demo.launch()