""" 历史知识问答引擎 — 七年级中国历史背诵题库 从 data/历史背诵题库.json 加载题目,支持随机出题、答题判断、进度追踪 """ import json import random import os from pathlib import Path from datetime import datetime QUIZ_FILE = Path(os.environ.get( "HISTORY_QUIZ_FILE", os.path.join(os.path.dirname(__file__), "..", "data", "历史背诵题库.json") )) # 单元别名映射(用于前端显示) UNIT_ALIASES = { "U1": "七上·第一单元 史前时期", "U2": "七上·第二单元 夏商周时期", "U3": "七上·第三单元 秦汉时期", "U4": "七上·第四单元 三国两晋南北朝", "U5": "七下·第一单元 隋唐时期", "U6": "七下·第二单元 辽宋夏金元", "U7": "七下·第三单元 明清时期", } class HistoryQuiz: """历史题库引擎""" def __init__(self): self.data = self._load_quiz() self.units = {u["unit_id"]: u for u in self.data.get("units", [])} self.all_questions = [] for unit in self.data.get("units", []): for q in unit.get("questions", []): q["unit_id"] = unit["unit_id"] q["unit_title"] = unit.get("title", "") self.all_questions.append(q) def _load_quiz(self) -> dict: if QUIZ_FILE.exists(): return json.loads(QUIZ_FILE.read_text(encoding="utf-8")) # 降级:空题库 return {"metadata": {}, "units": []} def get_units(self) -> list: """获取所有单元信息(含题目数量)""" result = [] for unit in self.data.get("units", []): uid = unit["unit_id"] result.append({ "unit_id": uid, "title": unit.get("title", ""), "grade": unit.get("grade", ""), "alias": UNIT_ALIASES.get(uid, uid), "question_count": len(unit.get("questions", [])), "lessons": unit.get("lessons", []), }) return result def get_random_question(self, unit_id: str = None, question_type: str = None) -> dict: """随机获取一道题,可选按单元/题型过滤""" pool = self.all_questions if unit_id: pool = [q for q in pool if q.get("unit_id") == unit_id] if question_type: pool = [q for q in pool if q.get("type") == question_type] if not pool: return {"error": "该条件下没有题目"} q = random.choice(pool) return { "id": q["id"], "type": q["type"], "question": q["question"], "answer": q["answer"], "unit_id": q["unit_id"], "unit_title": q.get("unit_title", ""), "unit_alias": UNIT_ALIASES.get(q["unit_id"], q["unit_id"]), "source": q.get("source", ""), } def get_batch(self, count: int = 10, unit_id: str = None, question_type: str = None) -> list: """批量获取题目(不重复)""" pool = list(self.all_questions) if unit_id: pool = [q for q in pool if q.get("unit_id") == unit_id] if question_type: pool = [q for q in pool if q.get("type") == question_type] random.shuffle(pool) selected = pool[:count] return [{ "id": q["id"], "type": q["type"], "question": q["question"], "answer": q["answer"], "unit_id": q["unit_id"], "unit_title": q.get("unit_title", ""), "unit_alias": UNIT_ALIASES.get(q["unit_id"], q["unit_id"]), } for q in selected] def check_answer(self, question_id: str, student_answer: str) -> dict: """检查答案 — 模糊匹配(关键词匹配)""" q = next((q for q in self.all_questions if q["id"] == question_id), None) if not q: return {"correct": False, "error": "题目不存在"} correct = q["answer"].strip() student = student_answer.strip() # 精确匹配 if student == correct: return { "correct": True, "question_id": question_id, "student_answer": student, "correct_answer": correct, "match_type": "exact" } # 包含匹配(学生答案包含在标准答案中,或反之) if len(student) >= 2 and (student in correct or correct in student): return { "correct": True, "question_id": question_id, "student_answer": student, "correct_answer": correct, "match_type": "partial" } # 关键词匹配(答案拆分为关键词,匹配度>60%算对) keywords = set(correct.replace(",", " ").replace("、", " ").replace(";", " ").split()) student_words = set(student.replace(",", " ").replace("、", " ").replace(";", " ").split()) if keywords and student_words: overlap = len(keywords & student_words) / len(keywords) if overlap >= 0.6: return { "correct": True, "question_id": question_id, "student_answer": student, "correct_answer": correct, "match_type": "keyword", "keyword_match": f"{overlap:.0%}" } return { "correct": False, "question_id": question_id, "student_answer": student, "correct_answer": correct, "hint": self._generate_hint(correct) } def _generate_hint(self, answer: str) -> str: """生成提示(显示答案首字+长度)""" if len(answer) <= 1: return "答案很短,再想想?" first_char = answer[0] return f"提示:答案以「{first_char}」开头,共{len(answer)}个字" def get_stats(self) -> dict: """题库统计""" type_counts = {} for q in self.all_questions: t = q.get("type", "unknown") type_counts[t] = type_counts.get(t, 0) + 1 return { "total_questions": len(self.all_questions), "total_units": len(self.units), "by_type": type_counts, "by_unit": {uid: len(u.get("questions", [])) for uid, u in self.units.items()}, } # 全局单例 _quiz_instance = None def get_quiz() -> HistoryQuiz: global _quiz_instance if _quiz_instance is None: _quiz_instance = HistoryQuiz() return _quiz_instance