seawolf2357 commited on
Commit
faaf019
·
verified ·
1 Parent(s): 86c2064

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +412 -34
app.py CHANGED
@@ -926,6 +926,156 @@ def validate_questions(questions: List[Dict], page_contents: List[PageContent],
926
 
927
  return validated
928
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
929
  # ============================================================
930
  # PDF Generation
931
  # ============================================================
@@ -1009,7 +1159,7 @@ def create_exam_pdf(questions: List[Dict], language: str) -> str:
1009
  return temp_file.name
1010
 
1011
  def create_answer_pdf(questions: List[Dict], language: str) -> str:
1012
- """Create answer key PDF with explanations"""
1013
 
1014
  lang = LANG_TEXT[language]
1015
 
@@ -1037,7 +1187,8 @@ def create_answer_pdf(questions: List[Dict], language: str) -> str:
1037
  fontSize=11,
1038
  spaceAfter=8,
1039
  textColor=colors.HexColor('#1F2937'),
1040
- fontName=font_name
 
1041
  )
1042
 
1043
  answer_style = ParagraphStyle(
@@ -1046,7 +1197,9 @@ def create_answer_pdf(questions: List[Dict], language: str) -> str:
1046
  fontSize=11,
1047
  leftIndent=15,
1048
  textColor=colors.HexColor('#059669'),
1049
- fontName=font_name
 
 
1050
  )
1051
 
1052
  explanation_style = ParagraphStyle(
@@ -1056,7 +1209,43 @@ def create_answer_pdf(questions: List[Dict], language: str) -> str:
1056
  leftIndent=15,
1057
  spaceAfter=5,
1058
  textColor=colors.HexColor('#374151'),
1059
- fontName=font_name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1060
  )
1061
 
1062
  wrong_style = ParagraphStyle(
@@ -1065,7 +1254,8 @@ def create_answer_pdf(questions: List[Dict], language: str) -> str:
1065
  fontSize=9,
1066
  leftIndent=25,
1067
  textColor=colors.HexColor('#DC2626'),
1068
- fontName=font_name
 
1069
  )
1070
 
1071
  normal_korean = ParagraphStyle(
@@ -1080,12 +1270,25 @@ def create_answer_pdf(questions: List[Dict], language: str) -> str:
1080
  fontName=font_name
1081
  )
1082
 
 
 
 
 
 
 
 
 
 
 
 
 
1083
  story = []
1084
 
1085
  story.append(Paragraph(f"{lang['pdf_answer_title']}", title_style))
1086
  story.append(Paragraph(f"{lang['pdf_date']}: {datetime.now().strftime('%Y-%m-%d')}", normal_korean))
1087
  story.append(Spacer(1, 20))
1088
 
 
1089
  story.append(Paragraph(f"<b>{lang['pdf_quick_summary']}</b>", heading_korean))
1090
 
1091
  answer_summary = []
@@ -1116,31 +1319,97 @@ def create_answer_pdf(questions: List[Dict], language: str) -> str:
1116
  story.append(Paragraph(f"<b>{lang['pdf_detailed']}</b>", heading_korean))
1117
  story.append(Spacer(1, 15))
1118
 
 
1119
  for i, q in enumerate(questions, 1):
 
1120
  q_text = f"<b>Q{i}.</b> {q['question']}"
1121
  story.append(Paragraph(q_text, question_style))
1122
 
1123
- story.append(Paragraph(f"{lang['pdf_correct_answer']}: {q['answer']}", answer_style))
 
1124
 
1125
- story.append(Paragraph(f"{lang['pdf_source']} {q.get('source_page', 'N/A')}", explanation_style))
1126
-
1127
- if q.get('source_quote'):
1128
- quote_text = q['source_quote'][:200] + "..." if len(q.get('source_quote', '')) > 200 else q.get('source_quote', '')
1129
- story.append(Paragraph(f"{lang['pdf_reference']}: \"{quote_text}\"", explanation_style))
1130
 
1131
  explanation = q.get('explanation', {})
1132
- if explanation.get('correct_reason'):
1133
- story.append(Paragraph(f"{lang['pdf_why_correct']}: {explanation['correct_reason']}", explanation_style))
1134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1135
  story.append(Spacer(1, 5))
1136
- story.append(Paragraph(f"<i>{lang['pdf_why_wrong']}</i>", explanation_style))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
 
1138
- for key in ['wrong_A', 'wrong_B', 'wrong_C', 'wrong_D', 'wrong_E']:
1139
- if explanation.get(key) and explanation[key].strip():
1140
- letter = key.split('_')[1]
1141
- story.append(Paragraph(f"X {letter}: {explanation[key]}", wrong_style))
1142
 
1143
- story.append(Spacer(1, 20))
 
 
1144
 
1145
  doc.build(story)
1146
  return temp_file.name
@@ -1195,14 +1464,18 @@ def process_exam(
1195
  if not validated:
1196
  return lang["error_no_questions"], "", None, None, "", []
1197
 
1198
- progress(0.85, desc=lang["phase_pdf"])
1199
- exam_pdf = create_exam_pdf(validated, language)
1200
- answer_pdf = create_answer_pdf(validated, language)
 
 
 
 
1201
 
1202
  progress(0.95, desc=lang["phase_final"])
1203
 
1204
  answer_distribution = {}
1205
- for q in validated:
1206
  label = q.get('correct_label', 'A')
1207
  answer_distribution[label] = answer_distribution.get(label, 0) + 1
1208
 
@@ -1213,11 +1486,12 @@ def process_exam(
1213
  📁 File Type: {file_type_display}
1214
  {'=' * 50}
1215
  {lang['stats_title']}
1216
- • {lang['total_questions']}: {len(validated)}
1217
  • {lang['choices_per_q']}: {num_choices}
1218
  • {lang['difficulty']}: {difficulty}
1219
  • {lang['doc_pages']}: {len(page_contents)}
1220
  • Total Characters: {total_chars:,}
 
1221
  {'=' * 50}
1222
  {lang['answer_dist']}
1223
  {dist_text}
@@ -1229,11 +1503,11 @@ def process_exam(
1229
 
1230
  🎯 Now go to "Take Exam" tab to start your interactive exam!"""
1231
 
1232
- display_text = format_questions_display(validated, language)
1233
 
1234
  progress(1.0, desc=lang["phase_complete"])
1235
 
1236
- return info_log, display_text, exam_pdf, answer_pdf, "", validated
1237
 
1238
  except Exception as e:
1239
  import traceback
@@ -1241,7 +1515,7 @@ def process_exam(
1241
  return f"❌ Error: {str(e)}", "", None, None, "", []
1242
 
1243
  def format_questions_display(questions: List[Dict], language: str) -> str:
1244
- """Format questions for text display"""
1245
 
1246
  lang = LANG_TEXT[language]
1247
 
@@ -1261,7 +1535,22 @@ def format_questions_display(questions: List[Dict], language: str) -> str:
1261
  else:
1262
  lines.append(f" ○ {opt}")
1263
 
1264
- lines.append(f"\n 💡 {q.get('explanation', {}).get('correct_reason', 'N/A')[:150]}...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1265
  lines.append("-" * 60)
1266
 
1267
  return "\n".join(lines)
@@ -1939,19 +2228,77 @@ with gr.Blocks(fill_height=True, css=css, title="PDF/HWP TO EXAM | SOMA") as dem
1939
  user_answers[current_idx] = selected_answer
1940
 
1941
  is_correct = selected_answer == q['answer']
 
 
 
 
 
 
 
1942
 
1943
  if is_correct:
1944
  feedback_text = f"""## {lang['correct_feedback']}
1945
 
1946
- **{lang['explanation_label']}:** {q.get('explanation', {}).get('correct_reason', 'N/A')}"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1947
  else:
 
 
 
 
1948
  feedback_text = f"""## {lang['incorrect_feedback']}
1949
 
1950
  **{lang['your_answer']}:** {selected_answer}
1951
 
1952
  **{lang['correct_answer_is']}:** {q['answer']}
1953
 
1954
- **{lang['explanation_label']}:** {q.get('explanation', {}).get('correct_reason', 'N/A')}"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1955
 
1956
  return user_answers, gr.update(value=feedback_text, visible=True)
1957
 
@@ -2018,6 +2365,7 @@ with gr.Blocks(fill_height=True, css=css, title="PDF/HWP TO EXAM | SOMA") as dem
2018
  for i, q in enumerate(questions):
2019
  user_answer = user_answers.get(i, None)
2020
  is_correct = user_answer == q['answer']
 
2021
 
2022
  if user_answer is None:
2023
  skipped_count += 1
@@ -2029,15 +2377,45 @@ with gr.Blocks(fill_height=True, css=css, title="PDF/HWP TO EXAM | SOMA") as dem
2029
  incorrect_count += 1
2030
  status = f"❌ {lang['incorrect_feedback']}"
2031
 
2032
- review_lines.append(f"""
 
 
 
 
 
2033
  ---
2034
- **{lang['question_review'].format(num=i+1)}:** {q['question'][:100]}{'...' if len(q['question']) > 100 else ''}
2035
 
2036
  - **{lang['your_answer']}:** {user_answer or lang['not_answered']}
2037
  - **{lang['correct_answer_is']}:** {q['answer']}
2038
  - **Status:** {status}
2039
- - **{lang['explanation_label']}:** {q.get('explanation', {}).get('correct_reason', 'N/A')[:200]}{'...' if len(q.get('explanation', {}).get('correct_reason', '')) > 200 else ''}
2040
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2041
 
2042
  percentage = (correct_count / total) * 100 if total > 0 else 0
2043
  grade = calculate_grade(percentage)
 
926
 
927
  return validated
928
 
929
+ # ============================================================
930
+ # Enhanced Explanation Generation using GROQ API
931
+ # ============================================================
932
+ def enhance_explanations(
933
+ questions: List[Dict],
934
+ page_contents: List[PageContent],
935
+ language: str
936
+ ) -> List[Dict]:
937
+ """
938
+ GROQ API를 사용하여 각 문제에 대한 상세한 논리적 설명을 생성
939
+ - 정답의 논리적 근거
940
+ - 관련 핵심 개념 및 팩트
941
+ - 오답별 구체적인 오류 설명
942
+ - 학습 포인트
943
+ """
944
+
945
+ print(f"\n🧠 [설명 강화] {len(questions)}개 문제에 대한 상세 설명 생성 중...")
946
+
947
+ # 문서 컨텍스트 준비
948
+ full_text = "\n\n".join([f"[Page {p.page_num}]\n{p.text}" for p in page_contents])
949
+ doc_context = full_text[:8000] # 컨텍스트 제한
950
+
951
+ enhanced_questions = []
952
+
953
+ for i, q in enumerate(questions):
954
+ try:
955
+ print(f" 📝 문제 {i+1}/{len(questions)} 설명 강화 중...")
956
+
957
+ # 해당 문제의 소스 페이지 컨텍스트 찾기
958
+ source_page = q.get('source_page', 1)
959
+ relevant_context = ""
960
+ for p in page_contents:
961
+ if p.page_num == source_page:
962
+ relevant_context = p.text[:2000]
963
+ break
964
+
965
+ if not relevant_context:
966
+ relevant_context = doc_context[:2000]
967
+
968
+ if language == "한국어":
969
+ prompt = f"""당신은 교육 전문가입니다. 다음 시험 문제에 대해 상세하고 논리적인 설명을 작성하세요.
970
+
971
+ 📚 관련 문서 내용 (페이지 {source_page}):
972
+ {relevant_context}
973
+
974
+ 📋 문제:
975
+ {q['question']}
976
+
977
+ 📝 선택지:
978
+ {chr(10).join(q['options'])}
979
+
980
+ ✅ 정답: {q['answer']}
981
+
982
+ 다음 JSON 형식으로만 응답하세요:
983
+ {{
984
+ "detailed_explanation": "정답이 맞는 이유에 대한 3-5문장의 상세한 논리적 설명. 문서의 구체적인 내용을 인용하여 설명.",
985
+ "key_facts": ["관련 핵심 팩트 1", "관련 핵심 팩트 2", "관련 핵심 팩트 3"],
986
+ "wrong_explanations": {{
987
+ "A": "A가 오답인 구체적 이유 (정답이면 빈 문자열)",
988
+ "B": "B가 오답인 구체적 이유 (정답이면 빈 문자열)",
989
+ "C": "C가 오답인 구체적 이유 (정답이면 빈 문자열)",
990
+ "D": "D가 오답인 구체적 이유 (정답이면 빈 문자열)",
991
+ "E": "E가 오답인 구체적 이유 (정답이면 빈 문자열)"
992
+ }},
993
+ "learning_point": "이 문제를 통해 학습해야 할 핵심 포인트 1-2문장",
994
+ "related_concepts": ["관련 개념 1", "관련 개념 2"]
995
+ }}
996
+
997
+ 중요: 추측하지 말고 문서 내용을 기반으로 팩트에 근거한 설명을 작성하세요."""
998
+ else:
999
+ prompt = f"""You are an education expert. Write detailed, logical explanations for the following exam question.
1000
+
1001
+ 📚 Relevant Document Content (Page {source_page}):
1002
+ {relevant_context}
1003
+
1004
+ 📋 Question:
1005
+ {q['question']}
1006
+
1007
+ 📝 Options:
1008
+ {chr(10).join(q['options'])}
1009
+
1010
+ ✅ Correct Answer: {q['answer']}
1011
+
1012
+ Respond ONLY in JSON format:
1013
+ {{
1014
+ "detailed_explanation": "3-5 sentence detailed logical explanation of why the answer is correct. Quote specific content from the document.",
1015
+ "key_facts": ["Key fact 1", "Key fact 2", "Key fact 3"],
1016
+ "wrong_explanations": {{
1017
+ "A": "Specific reason why A is wrong (empty string if correct)",
1018
+ "B": "Specific reason why B is wrong (empty string if correct)",
1019
+ "C": "Specific reason why C is wrong (empty string if correct)",
1020
+ "D": "Specific reason why D is wrong (empty string if correct)",
1021
+ "E": "Specific reason why E is wrong (empty string if correct)"
1022
+ }},
1023
+ "learning_point": "1-2 sentence key learning point from this question",
1024
+ "related_concepts": ["Related concept 1", "Related concept 2"]
1025
+ }}
1026
+
1027
+ IMPORTANT: Do not speculate. Base explanations on facts from the document."""
1028
+
1029
+ completion = client.chat.completions.create(
1030
+ model="openai/gpt-oss-120b",
1031
+ messages=[{"role": "user", "content": prompt}],
1032
+ temperature=0.3,
1033
+ max_completion_tokens=2048,
1034
+ top_p=1,
1035
+ )
1036
+
1037
+ response = completion.choices[0].message.content
1038
+ if response is None:
1039
+ response = ""
1040
+ response = response.strip()
1041
+
1042
+ # JSON 파싱
1043
+ if "```json" in response:
1044
+ response = response.split("```json")[1].split("```")[0]
1045
+ elif "```" in response:
1046
+ response = response.split("```")[1].split("```")[0]
1047
+
1048
+ enhanced_data = json.loads(response.strip())
1049
+
1050
+ # 기존 explanation 업데이트
1051
+ if 'explanation' not in q:
1052
+ q['explanation'] = {}
1053
+
1054
+ q['explanation']['detailed_explanation'] = enhanced_data.get('detailed_explanation', '')
1055
+ q['explanation']['key_facts'] = enhanced_data.get('key_facts', [])
1056
+ q['explanation']['learning_point'] = enhanced_data.get('learning_point', '')
1057
+ q['explanation']['related_concepts'] = enhanced_data.get('related_concepts', [])
1058
+
1059
+ # 오답 설명 업데이트
1060
+ wrong_expl = enhanced_data.get('wrong_explanations', {})
1061
+ for letter in ['A', 'B', 'C', 'D', 'E']:
1062
+ if wrong_expl.get(letter):
1063
+ q['explanation'][f'wrong_{letter}'] = wrong_expl[letter]
1064
+
1065
+ # 기존 correct_reason이 없거나 짧으면 업데이트
1066
+ if not q['explanation'].get('correct_reason') or len(q['explanation'].get('correct_reason', '')) < 50:
1067
+ q['explanation']['correct_reason'] = enhanced_data.get('detailed_explanation', q['explanation'].get('correct_reason', ''))
1068
+
1069
+ enhanced_questions.append(q)
1070
+
1071
+ except Exception as e:
1072
+ print(f" ⚠️ 문제 {i+1} 설명 강화 실패: {e}")
1073
+ enhanced_questions.append(q) # 원본 유지
1074
+
1075
+ print(f"✅ 설명 강화 완료: {len(enhanced_questions)}개 문제")
1076
+ return enhanced_questions
1077
+
1078
+
1079
  # ============================================================
1080
  # PDF Generation
1081
  # ============================================================
 
1159
  return temp_file.name
1160
 
1161
  def create_answer_pdf(questions: List[Dict], language: str) -> str:
1162
+ """Create answer key PDF with enhanced explanations"""
1163
 
1164
  lang = LANG_TEXT[language]
1165
 
 
1187
  fontSize=11,
1188
  spaceAfter=8,
1189
  textColor=colors.HexColor('#1F2937'),
1190
+ fontName=font_name,
1191
+ leading=14
1192
  )
1193
 
1194
  answer_style = ParagraphStyle(
 
1197
  fontSize=11,
1198
  leftIndent=15,
1199
  textColor=colors.HexColor('#059669'),
1200
+ fontName=font_name,
1201
+ spaceBefore=5,
1202
+ spaceAfter=5
1203
  )
1204
 
1205
  explanation_style = ParagraphStyle(
 
1209
  leftIndent=15,
1210
  spaceAfter=5,
1211
  textColor=colors.HexColor('#374151'),
1212
+ fontName=font_name,
1213
+ leading=13
1214
+ )
1215
+
1216
+ detailed_style = ParagraphStyle(
1217
+ 'DetailedExplanation',
1218
+ parent=styles['Normal'],
1219
+ fontSize=10,
1220
+ leftIndent=15,
1221
+ spaceAfter=8,
1222
+ textColor=colors.HexColor('#1E40AF'),
1223
+ fontName=font_name,
1224
+ leading=14,
1225
+ backColor=colors.HexColor('#EFF6FF'),
1226
+ borderPadding=8
1227
+ )
1228
+
1229
+ fact_style = ParagraphStyle(
1230
+ 'KeyFact',
1231
+ parent=styles['Normal'],
1232
+ fontSize=9,
1233
+ leftIndent=25,
1234
+ spaceAfter=3,
1235
+ textColor=colors.HexColor('#065F46'),
1236
+ fontName=font_name,
1237
+ bulletIndent=15
1238
+ )
1239
+
1240
+ learning_style = ParagraphStyle(
1241
+ 'LearningPoint',
1242
+ parent=styles['Normal'],
1243
+ fontSize=10,
1244
+ leftIndent=15,
1245
+ spaceAfter=5,
1246
+ textColor=colors.HexColor('#7C3AED'),
1247
+ fontName=font_name,
1248
+ leading=13
1249
  )
1250
 
1251
  wrong_style = ParagraphStyle(
 
1254
  fontSize=9,
1255
  leftIndent=25,
1256
  textColor=colors.HexColor('#DC2626'),
1257
+ fontName=font_name,
1258
+ leading=12
1259
  )
1260
 
1261
  normal_korean = ParagraphStyle(
 
1270
  fontName=font_name
1271
  )
1272
 
1273
+ subheading_style = ParagraphStyle(
1274
+ 'SubHeading',
1275
+ parent=styles['Normal'],
1276
+ fontSize=10,
1277
+ leftIndent=15,
1278
+ spaceBefore=8,
1279
+ spaceAfter=5,
1280
+ textColor=colors.HexColor('#1F2937'),
1281
+ fontName=font_name,
1282
+ fontWeight='bold'
1283
+ )
1284
+
1285
  story = []
1286
 
1287
  story.append(Paragraph(f"{lang['pdf_answer_title']}", title_style))
1288
  story.append(Paragraph(f"{lang['pdf_date']}: {datetime.now().strftime('%Y-%m-%d')}", normal_korean))
1289
  story.append(Spacer(1, 20))
1290
 
1291
+ # Quick Answer Summary
1292
  story.append(Paragraph(f"<b>{lang['pdf_quick_summary']}</b>", heading_korean))
1293
 
1294
  answer_summary = []
 
1319
  story.append(Paragraph(f"<b>{lang['pdf_detailed']}</b>", heading_korean))
1320
  story.append(Spacer(1, 15))
1321
 
1322
+ # Detailed explanations for each question
1323
  for i, q in enumerate(questions, 1):
1324
+ # Question text
1325
  q_text = f"<b>Q{i}.</b> {q['question']}"
1326
  story.append(Paragraph(q_text, question_style))
1327
 
1328
+ # Correct answer
1329
+ story.append(Paragraph(f"<b>{lang['pdf_correct_answer']}:</b> {q['answer']}", answer_style))
1330
 
1331
+ # Source page
1332
+ story.append(Paragraph(f"<b>{lang['pdf_source']}:</b> {q.get('source_page', 'N/A')}", explanation_style))
 
 
 
1333
 
1334
  explanation = q.get('explanation', {})
 
 
1335
 
1336
+ # Detailed Explanation (NEW - Enhanced)
1337
+ detailed_expl = explanation.get('detailed_explanation', '')
1338
+ if detailed_expl:
1339
+ if language == "한국어":
1340
+ story.append(Paragraph("<b>📖 상세 설명:</b>", subheading_style))
1341
+ else:
1342
+ story.append(Paragraph("<b>📖 Detailed Explanation:</b>", subheading_style))
1343
+ story.append(Paragraph(detailed_expl, detailed_style))
1344
+ elif explanation.get('correct_reason'):
1345
+ # Fallback to correct_reason if no detailed explanation
1346
+ if language == "한국어":
1347
+ story.append(Paragraph("<b>📖 정답 근거:</b>", subheading_style))
1348
+ else:
1349
+ story.append(Paragraph("<b>📖 Why Correct:</b>", subheading_style))
1350
+ story.append(Paragraph(explanation['correct_reason'], detailed_style))
1351
+
1352
+ # Key Facts (NEW)
1353
+ key_facts = explanation.get('key_facts', [])
1354
+ if key_facts:
1355
+ if language == "한국어":
1356
+ story.append(Paragraph("<b>📌 핵심 팩트:</b>", subheading_style))
1357
+ else:
1358
+ story.append(Paragraph("<b>📌 Key Facts:</b>", subheading_style))
1359
+ for fact in key_facts[:5]: # Limit to 5 facts
1360
+ story.append(Paragraph(f"• {fact}", fact_style))
1361
+
1362
+ # Learning Point (NEW)
1363
+ learning_point = explanation.get('learning_point', '')
1364
+ if learning_point:
1365
+ if language == "한국어":
1366
+ story.append(Paragraph("<b>💡 학습 포인트:</b>", subheading_style))
1367
+ else:
1368
+ story.append(Paragraph("<b>💡 Learning Point:</b>", subheading_style))
1369
+ story.append(Paragraph(learning_point, learning_style))
1370
+
1371
+ # Related Concepts (NEW)
1372
+ related_concepts = explanation.get('related_concepts', [])
1373
+ if related_concepts:
1374
+ concepts_text = ", ".join(related_concepts[:5])
1375
+ if language == "한국어":
1376
+ story.append(Paragraph(f"<b>🔗 관련 개념:</b> {concepts_text}", explanation_style))
1377
+ else:
1378
+ story.append(Paragraph(f"<b>🔗 Related Concepts:</b> {concepts_text}", explanation_style))
1379
+
1380
+ # Source Quote
1381
+ if q.get('source_quote'):
1382
+ quote_text = q['source_quote'][:300] + "..." if len(q.get('source_quote', '')) > 300 else q.get('source_quote', '')
1383
+ if language == "한국어":
1384
+ story.append(Paragraph(f"<b>📝 문서 인용:</b> \"{quote_text}\"", explanation_style))
1385
+ else:
1386
+ story.append(Paragraph(f"<b>📝 Document Quote:</b> \"{quote_text}\"", explanation_style))
1387
+
1388
+ # Why Wrong Options (Enhanced)
1389
  story.append(Spacer(1, 5))
1390
+ if language == "한국어":
1391
+ story.append(Paragraph(f"<b><i>❌ 오답 분석:</i></b>", subheading_style))
1392
+ else:
1393
+ story.append(Paragraph(f"<b><i>❌ Why Other Options Are Wrong:</i></b>", subheading_style))
1394
+
1395
+ has_wrong_explanations = False
1396
+ for letter in ['A', 'B', 'C', 'D', 'E']:
1397
+ wrong_key = f'wrong_{letter}'
1398
+ if explanation.get(wrong_key) and explanation[wrong_key].strip():
1399
+ has_wrong_explanations = True
1400
+ story.append(Paragraph(f"<b>{letter}:</b> {explanation[wrong_key]}", wrong_style))
1401
+
1402
+ if not has_wrong_explanations:
1403
+ if language == "한국어":
1404
+ story.append(Paragraph("(오답 설명 없음)", wrong_style))
1405
+ else:
1406
+ story.append(Paragraph("(No wrong answer explanations available)", wrong_style))
1407
 
1408
+ story.append(Spacer(1, 25))
 
 
 
1409
 
1410
+ # Add page break every 3 questions to avoid cramped layout
1411
+ if i % 3 == 0 and i < len(questions):
1412
+ story.append(PageBreak())
1413
 
1414
  doc.build(story)
1415
  return temp_file.name
 
1464
  if not validated:
1465
  return lang["error_no_questions"], "", None, None, "", []
1466
 
1467
+ # NEW: Enhance explanations using GROQ API
1468
+ progress(0.8, desc="🧠 Enhancing explanations..." if language == "English" else "🧠 설명 강화 중...")
1469
+ enhanced = enhance_explanations(validated, page_contents, language)
1470
+
1471
+ progress(0.9, desc=lang["phase_pdf"])
1472
+ exam_pdf = create_exam_pdf(enhanced, language)
1473
+ answer_pdf = create_answer_pdf(enhanced, language)
1474
 
1475
  progress(0.95, desc=lang["phase_final"])
1476
 
1477
  answer_distribution = {}
1478
+ for q in enhanced:
1479
  label = q.get('correct_label', 'A')
1480
  answer_distribution[label] = answer_distribution.get(label, 0) + 1
1481
 
 
1486
  📁 File Type: {file_type_display}
1487
  {'=' * 50}
1488
  {lang['stats_title']}
1489
+ • {lang['total_questions']}: {len(enhanced)}
1490
  • {lang['choices_per_q']}: {num_choices}
1491
  • {lang['difficulty']}: {difficulty}
1492
  • {lang['doc_pages']}: {len(page_contents)}
1493
  • Total Characters: {total_chars:,}
1494
+ • 🧠 Enhanced Explanations: ✅
1495
  {'=' * 50}
1496
  {lang['answer_dist']}
1497
  {dist_text}
 
1503
 
1504
  🎯 Now go to "Take Exam" tab to start your interactive exam!"""
1505
 
1506
+ display_text = format_questions_display(enhanced, language)
1507
 
1508
  progress(1.0, desc=lang["phase_complete"])
1509
 
1510
+ return info_log, display_text, exam_pdf, answer_pdf, "", enhanced
1511
 
1512
  except Exception as e:
1513
  import traceback
 
1515
  return f"❌ Error: {str(e)}", "", None, None, "", []
1516
 
1517
  def format_questions_display(questions: List[Dict], language: str) -> str:
1518
+ """Format questions for text display with enhanced explanations"""
1519
 
1520
  lang = LANG_TEXT[language]
1521
 
 
1535
  else:
1536
  lines.append(f" ○ {opt}")
1537
 
1538
+ explanation = q.get('explanation', {})
1539
+
1540
+ # Detailed explanation
1541
+ detailed = explanation.get('detailed_explanation', explanation.get('correct_reason', 'N/A'))
1542
+ lines.append(f"\n 📖 {detailed[:200]}{'...' if len(detailed) > 200 else ''}")
1543
+
1544
+ # Key facts
1545
+ key_facts = explanation.get('key_facts', [])
1546
+ if key_facts:
1547
+ lines.append(f" 📌 핵심 팩트: {', '.join(key_facts[:3])}" if language == "한국어" else f" 📌 Key Facts: {', '.join(key_facts[:3])}")
1548
+
1549
+ # Learning point
1550
+ learning_point = explanation.get('learning_point', '')
1551
+ if learning_point:
1552
+ lines.append(f" 💡 {learning_point[:150]}{'...' if len(learning_point) > 150 else ''}")
1553
+
1554
  lines.append("-" * 60)
1555
 
1556
  return "\n".join(lines)
 
2228
  user_answers[current_idx] = selected_answer
2229
 
2230
  is_correct = selected_answer == q['answer']
2231
+ explanation = q.get('explanation', {})
2232
+
2233
+ # Get enhanced explanation content
2234
+ detailed_expl = explanation.get('detailed_explanation', explanation.get('correct_reason', 'N/A'))
2235
+ key_facts = explanation.get('key_facts', [])
2236
+ learning_point = explanation.get('learning_point', '')
2237
+ related_concepts = explanation.get('related_concepts', [])
2238
 
2239
  if is_correct:
2240
  feedback_text = f"""## {lang['correct_feedback']}
2241
 
2242
+ **{lang['explanation_label']}:** {detailed_expl}"""
2243
+
2244
+ # Add key facts if available
2245
+ if key_facts:
2246
+ if language == "한국어":
2247
+ feedback_text += "\n\n**📌 핵심 팩트:**"
2248
+ else:
2249
+ feedback_text += "\n\n**📌 Key Facts:**"
2250
+ for fact in key_facts[:3]:
2251
+ feedback_text += f"\n• {fact}"
2252
+
2253
+ # Add learning point if available
2254
+ if learning_point:
2255
+ if language == "한국어":
2256
+ feedback_text += f"\n\n**💡 학습 포인트:** {learning_point}"
2257
+ else:
2258
+ feedback_text += f"\n\n**💡 Learning Point:** {learning_point}"
2259
  else:
2260
+ # Get wrong answer explanation
2261
+ selected_letter = selected_answer[0] if selected_answer else 'A'
2262
+ wrong_reason = explanation.get(f'wrong_{selected_letter}', '')
2263
+
2264
  feedback_text = f"""## {lang['incorrect_feedback']}
2265
 
2266
  **{lang['your_answer']}:** {selected_answer}
2267
 
2268
  **{lang['correct_answer_is']}:** {q['answer']}
2269
 
2270
+ **{lang['explanation_label']}:** {detailed_expl}"""
2271
+
2272
+ # Add why the selected answer was wrong
2273
+ if wrong_reason:
2274
+ if language == "한국어":
2275
+ feedback_text += f"\n\n**❌ 선택한 답이 틀린 이유:** {wrong_reason}"
2276
+ else:
2277
+ feedback_text += f"\n\n**❌ Why your answer was wrong:** {wrong_reason}"
2278
+
2279
+ # Add key facts if available
2280
+ if key_facts:
2281
+ if language == "한국어":
2282
+ feedback_text += "\n\n**📌 핵심 팩트:**"
2283
+ else:
2284
+ feedback_text += "\n\n**📌 Key Facts:**"
2285
+ for fact in key_facts[:3]:
2286
+ feedback_text += f"\n• {fact}"
2287
+
2288
+ # Add learning point if available
2289
+ if learning_point:
2290
+ if language == "한국어":
2291
+ feedback_text += f"\n\n**💡 학습 포인트:** {learning_point}"
2292
+ else:
2293
+ feedback_text += f"\n\n**💡 Learning Point:** {learning_point}"
2294
+
2295
+ # Add related concepts
2296
+ if related_concepts:
2297
+ concepts_text = ", ".join(related_concepts[:5])
2298
+ if language == "한국어":
2299
+ feedback_text += f"\n\n**🔗 관련 개념:** {concepts_text}"
2300
+ else:
2301
+ feedback_text += f"\n\n**🔗 Related Concepts:** {concepts_text}"
2302
 
2303
  return user_answers, gr.update(value=feedback_text, visible=True)
2304
 
 
2365
  for i, q in enumerate(questions):
2366
  user_answer = user_answers.get(i, None)
2367
  is_correct = user_answer == q['answer']
2368
+ explanation = q.get('explanation', {})
2369
 
2370
  if user_answer is None:
2371
  skipped_count += 1
 
2377
  incorrect_count += 1
2378
  status = f"❌ {lang['incorrect_feedback']}"
2379
 
2380
+ # Get enhanced explanation content
2381
+ detailed_expl = explanation.get('detailed_explanation', explanation.get('correct_reason', 'N/A'))
2382
+ key_facts = explanation.get('key_facts', [])
2383
+ learning_point = explanation.get('learning_point', '')
2384
+
2385
+ review_entry = f"""
2386
  ---
2387
+ **{lang['question_review'].format(num=i+1)}:** {q['question'][:150]}{'...' if len(q['question']) > 150 else ''}
2388
 
2389
  - **{lang['your_answer']}:** {user_answer or lang['not_answered']}
2390
  - **{lang['correct_answer_is']}:** {q['answer']}
2391
  - **Status:** {status}
2392
+ - **{lang['explanation_label']}:** {detailed_expl[:300]}{'...' if len(detailed_expl) > 300 else ''}"""
2393
+
2394
+ # Add why user's answer was wrong (if incorrect)
2395
+ if user_answer and not is_correct:
2396
+ selected_letter = user_answer[0] if user_answer else 'A'
2397
+ wrong_reason = explanation.get(f'wrong_{selected_letter}', '')
2398
+ if wrong_reason:
2399
+ if language == "한국어":
2400
+ review_entry += f"\n- **오답 이유:** {wrong_reason[:200]}{'...' if len(wrong_reason) > 200 else ''}"
2401
+ else:
2402
+ review_entry += f"\n- **Why Wrong:** {wrong_reason[:200]}{'...' if len(wrong_reason) > 200 else ''}"
2403
+
2404
+ # Add key facts
2405
+ if key_facts:
2406
+ if language == "한국어":
2407
+ review_entry += f"\n- **핵심 팩트:** {', '.join(key_facts[:2])}"
2408
+ else:
2409
+ review_entry += f"\n- **Key Facts:** {', '.join(key_facts[:2])}"
2410
+
2411
+ # Add learning point
2412
+ if learning_point:
2413
+ if language == "한국어":
2414
+ review_entry += f"\n- **💡 학습 포인트:** {learning_point[:150]}{'...' if len(learning_point) > 150 else ''}"
2415
+ else:
2416
+ review_entry += f"\n- **💡 Learning Point:** {learning_point[:150]}{'...' if len(learning_point) > 150 else ''}"
2417
+
2418
+ review_lines.append(review_entry)
2419
 
2420
  percentage = (correct_count / total) * 100 if total > 0 else 0
2421
  grade = calculate_grade(percentage)