chih.yikuan Claude Opus 4.6 (1M context) commited on
Commit
3efc9d0
·
1 Parent(s): 5722fbf

Fix: Handle missing teacher answers — include all student answers instead

Browse files

When 標準答案 is not uploaded, filter_student_wrong_answers now returns
ALL student answers + ALL questions (instead of empty arrays), with a
separate prompt template that asks the LLM to infer correctness.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

chatkit/backend/app/report_generator.py CHANGED
@@ -51,10 +51,11 @@ def filter_student_wrong_answers(
51
  student: dict,
52
  questions_data: dict,
53
  teacher_answers_data: dict,
54
- ) -> tuple[list[dict], list[dict], list[dict]]:
55
  """Compare a student's answers with teacher answers, return wrong items.
56
 
57
- Returns (wrong_questions, wrong_student_answers, relevant_teacher_answers).
 
58
  """
59
  teacher_map: dict[int, dict] = {}
60
  for ta in teacher_answers_data.get("answers", []):
@@ -68,6 +69,12 @@ def filter_student_wrong_answers(
68
  if num is not None:
69
  question_map[int(num)] = q
70
 
 
 
 
 
 
 
71
  wrong_questions: list[dict] = []
72
  wrong_answers: list[dict] = []
73
  relevant_teacher: list[dict] = []
@@ -91,7 +98,7 @@ def filter_student_wrong_answers(
91
  if q:
92
  wrong_questions.append(q)
93
 
94
- return wrong_questions, wrong_answers, relevant_teacher
95
 
96
 
97
  STUDENT_REPORT_SYSTEM_PROMPT = """You are ClassLens, an AI teaching assistant that creates a focused, personalized exam analysis report for a single student.
@@ -173,10 +180,53 @@ STUDENT_REPORT_PROMPT = """請為以下學生生成個人化的錯題分析報
173
  請針對每一道錯題提供詳細分析,找出錯誤模式,並給出具體的學習建議。"""
174
 
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  def get_default_prompt() -> str:
177
  return STUDENT_REPORT_PROMPT
178
 
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  def build_student_prompt(
181
  student: dict,
182
  questions_data: dict,
@@ -184,21 +234,17 @@ def build_student_prompt(
184
  ) -> dict:
185
  """Build the filled prompt for a student (for preview).
186
 
187
- Returns dict with filled_prompt, student_name, total_questions, wrong_count.
188
  """
189
  total_questions = len(questions_data.get("questions", []))
190
- wrong_questions, wrong_answers, relevant_teacher = filter_student_wrong_answers(
191
  student, questions_data, teacher_answers_data,
192
  )
193
  student_name = student.get("name", "Unknown")
194
 
195
- filled = STUDENT_REPORT_PROMPT.format(
196
- student_name=student_name,
197
- total_questions=total_questions,
198
- wrong_count=len(wrong_answers),
199
- wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
200
- wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
201
- teacher_answers=json.dumps(relevant_teacher, ensure_ascii=False, indent=2),
202
  )
203
 
204
  return {
@@ -206,6 +252,7 @@ def build_student_prompt(
206
  "student_name": student_name,
207
  "total_questions": total_questions,
208
  "wrong_count": len(wrong_answers),
 
209
  }
210
 
211
 
@@ -233,13 +280,13 @@ async def generate_student_report(
233
  client = AsyncOpenAI(api_key=settings.openai_api_key)
234
 
235
  total_questions = len(questions_data.get("questions", []))
236
- wrong_questions, wrong_answers, relevant_teacher = filter_student_wrong_answers(
237
  student, questions_data, teacher_answers_data,
238
  )
239
 
240
  student_name = student.get("name", "Unknown")
241
 
242
- if not wrong_answers:
243
  # All correct — return a simple congratulations page
244
  return f"""<!DOCTYPE html>
245
  <html lang="zh-TW"><head><meta charset="UTF-8"><title>{student_name} - 分析報告</title>
@@ -248,13 +295,9 @@ async def generate_student_report(
248
  h1{{color:#4ecdc4;margin-bottom:1rem}}p{{color:#94a3b8;font-size:1.1rem}}</style></head>
249
  <body><div class="card"><h1>🎉 全部答對!</h1><p><strong>{student_name}</strong> 在本次考試中答對了全部 {total_questions} 題,表現優異!</p></div></body></html>"""
250
 
251
- filled_prompt = STUDENT_REPORT_PROMPT.format(
252
- student_name=student_name,
253
- total_questions=total_questions,
254
- wrong_count=len(wrong_answers),
255
- wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
256
- wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
257
- teacher_answers=json.dumps(relevant_teacher, ensure_ascii=False, indent=2),
258
  )
259
 
260
  prompt_tokens = _estimate_tokens(STUDENT_REPORT_SYSTEM_PROMPT) + _estimate_tokens(filled_prompt)
 
51
  student: dict,
52
  questions_data: dict,
53
  teacher_answers_data: dict,
54
+ ) -> tuple[list[dict], list[dict], list[dict], bool]:
55
  """Compare a student's answers with teacher answers, return wrong items.
56
 
57
+ Returns (wrong_questions, wrong_student_answers, relevant_teacher_answers, has_teacher_answers).
58
+ When teacher answers are missing, returns ALL student answers & questions instead.
59
  """
60
  teacher_map: dict[int, dict] = {}
61
  for ta in teacher_answers_data.get("answers", []):
 
69
  if num is not None:
70
  question_map[int(num)] = q
71
 
72
+ # No teacher answers → return ALL student answers for LLM to analyze
73
+ if not teacher_map:
74
+ all_questions = questions_data.get("questions", [])
75
+ all_answers = student.get("answers", [])
76
+ return all_questions, all_answers, [], False
77
+
78
  wrong_questions: list[dict] = []
79
  wrong_answers: list[dict] = []
80
  relevant_teacher: list[dict] = []
 
98
  if q:
99
  wrong_questions.append(q)
100
 
101
+ return wrong_questions, wrong_answers, relevant_teacher, True
102
 
103
 
104
  STUDENT_REPORT_SYSTEM_PROMPT = """You are ClassLens, an AI teaching assistant that creates a focused, personalized exam analysis report for a single student.
 
180
  請針對每一道錯題提供詳細分析,找出錯誤模式,並給出具體的學習建議。"""
181
 
182
 
183
+ STUDENT_REPORT_PROMPT_NO_KEY = """請為以下學生生成個人化的考試分析報告。
184
+
185
+ ## 學生姓名:{student_name}
186
+
187
+ ## 考試總題數:{total_questions}
188
+
189
+ ## 考試題目:
190
+ {wrong_questions}
191
+
192
+ ## 該學生的全部答案:
193
+ {wrong_answers}
194
+
195
+ ⚠️ 注意:未提供標準答案,請根據題目內容與學生答案進行分析。
196
+ 請嘗試判斷答案的正確性,找出可能的錯誤,並給出具體的學習建議。"""
197
+
198
+
199
  def get_default_prompt() -> str:
200
  return STUDENT_REPORT_PROMPT
201
 
202
 
203
+ def _fill_student_prompt(
204
+ student_name: str,
205
+ total_questions: int,
206
+ wrong_questions: list[dict],
207
+ wrong_answers: list[dict],
208
+ relevant_teacher: list[dict],
209
+ has_teacher_answers: bool,
210
+ ) -> str:
211
+ """Fill the appropriate prompt template for a student."""
212
+ if has_teacher_answers:
213
+ return STUDENT_REPORT_PROMPT.format(
214
+ student_name=student_name,
215
+ total_questions=total_questions,
216
+ wrong_count=len(wrong_answers),
217
+ wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
218
+ wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
219
+ teacher_answers=json.dumps(relevant_teacher, ensure_ascii=False, indent=2),
220
+ )
221
+ else:
222
+ return STUDENT_REPORT_PROMPT_NO_KEY.format(
223
+ student_name=student_name,
224
+ total_questions=total_questions,
225
+ wrong_questions=json.dumps({"questions": wrong_questions}, ensure_ascii=False, indent=2),
226
+ wrong_answers=json.dumps(wrong_answers, ensure_ascii=False, indent=2),
227
+ )
228
+
229
+
230
  def build_student_prompt(
231
  student: dict,
232
  questions_data: dict,
 
234
  ) -> dict:
235
  """Build the filled prompt for a student (for preview).
236
 
237
+ Returns dict with filled_prompt, student_name, total_questions, wrong_count, has_teacher_answers.
238
  """
239
  total_questions = len(questions_data.get("questions", []))
240
+ wrong_questions, wrong_answers, relevant_teacher, has_teacher = filter_student_wrong_answers(
241
  student, questions_data, teacher_answers_data,
242
  )
243
  student_name = student.get("name", "Unknown")
244
 
245
+ filled = _fill_student_prompt(
246
+ student_name, total_questions,
247
+ wrong_questions, wrong_answers, relevant_teacher, has_teacher,
 
 
 
 
248
  )
249
 
250
  return {
 
252
  "student_name": student_name,
253
  "total_questions": total_questions,
254
  "wrong_count": len(wrong_answers),
255
+ "has_teacher_answers": has_teacher,
256
  }
257
 
258
 
 
280
  client = AsyncOpenAI(api_key=settings.openai_api_key)
281
 
282
  total_questions = len(questions_data.get("questions", []))
283
+ wrong_questions, wrong_answers, relevant_teacher, has_teacher = filter_student_wrong_answers(
284
  student, questions_data, teacher_answers_data,
285
  )
286
 
287
  student_name = student.get("name", "Unknown")
288
 
289
+ if has_teacher and not wrong_answers:
290
  # All correct — return a simple congratulations page
291
  return f"""<!DOCTYPE html>
292
  <html lang="zh-TW"><head><meta charset="UTF-8"><title>{student_name} - 分析報告</title>
 
295
  h1{{color:#4ecdc4;margin-bottom:1rem}}p{{color:#94a3b8;font-size:1.1rem}}</style></head>
296
  <body><div class="card"><h1>🎉 全部答對!</h1><p><strong>{student_name}</strong> 在本次考試中答對了全部 {total_questions} 題,表現優異!</p></div></body></html>"""
297
 
298
+ filled_prompt = _fill_student_prompt(
299
+ student_name, total_questions,
300
+ wrong_questions, wrong_answers, relevant_teacher, has_teacher,
 
 
 
 
301
  )
302
 
303
  prompt_tokens = _estimate_tokens(STUDENT_REPORT_SYSTEM_PROMPT) + _estimate_tokens(filled_prompt)