knowledge.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. """知识点树解析 + 编码生成模块"""
  2. import json
  3. import re
  4. from pathlib import Path
  5. from app.database import get_connection, sync_knowledge_points
  6. def parse_knowledge_tree(tree_path: str | Path) -> dict:
  7. """解析 5 层 Markdown 知识点树,返回 code → {path, leaf_name, chapter_code, chapter_name} 映射
  8. 层级: # (章) → ## (节) → ### (小节) → #### (项) → - (叶子)
  9. 编码: 每层两位数字,各层独立,用 . 连接
  10. 示例: 01.01.03.02.01
  11. """
  12. tree_path = Path(tree_path)
  13. if not tree_path.exists():
  14. print(f"⚠ 知识点树文件不存在: {tree_path}")
  15. return {}
  16. with open(tree_path, "r", encoding="utf-8") as f:
  17. content = f.read()
  18. lines = content.split("\n")
  19. knowledge_map: dict[str, dict] = {}
  20. # 层级计数器,每层独立(5 层编码)
  21. counters = [0, 0, 0, 0, 0] # 章, 节, 小节, 项, 叶子
  22. chapter_code = ""
  23. chapter_name = ""
  24. current_path_parts = ["", "", "", "", ""]
  25. # 标题行: # → ## → ### → #### (1-4 级)
  26. heading_re = re.compile(r"^(#{1,4})\s+(.+)$")
  27. # 叶子行: - xxx(先 strip 后匹配)
  28. leaf_re = re.compile(r"^-\s+(.+)$")
  29. for line in lines:
  30. stripped = line.strip()
  31. if not stripped:
  32. continue
  33. match = heading_re.match(stripped)
  34. leaf_match = leaf_re.match(stripped)
  35. if match:
  36. level = len(match.group(1))
  37. title = match.group(2).strip()
  38. idx = level - 1 # 0-based
  39. if idx < 4:
  40. counters[idx] += 1
  41. # 重置下层计数器(包括叶子层)
  42. for i in range(idx + 1, 5):
  43. counters[i] = 0
  44. current_path_parts[idx] = title
  45. for i in range(idx + 1, 5):
  46. current_path_parts[i] = ""
  47. if idx == 0:
  48. chapter_code = f"{counters[0]:02d}"
  49. chapter_name = title
  50. elif leaf_match:
  51. leaf_name = leaf_match.group(1).strip()
  52. counters[4] += 1 # 叶子层独立计数
  53. code_parts = [f"{counters[i]:02d}" for i in range(5)]
  54. code = ".".join(code_parts)
  55. current_path_parts[4] = leaf_name
  56. path_parts = [p for p in current_path_parts if p]
  57. path = " > ".join(path_parts)
  58. knowledge_map[code] = {
  59. "path": path,
  60. "leaf_name": leaf_name,
  61. "chapter_code": chapter_code,
  62. "chapter_name": chapter_name,
  63. }
  64. return knowledge_map
  65. def get_chapter_from_code(code: str, knowledge_map: dict) -> str:
  66. """从编码反推章节名,如 '01.01.03.02.01' → '01-集合与常用逻辑用语'"""
  67. # 取前两位作为章编码
  68. chapter_code = ".".join(code.split(".")[:2]) # 前两层编码
  69. for c, info in knowledge_map.items():
  70. if c.startswith(chapter_code[:2] + "."):
  71. ch_code = info.get("chapter_code", "")
  72. ch_name = info.get("chapter_name", "")
  73. if ch_code and ch_name:
  74. return f"{ch_code}-{ch_name}"
  75. return ""
  76. def find_code_by_name(name: str, knowledge_map: dict) -> str | None:
  77. """根据知识点名称查找编码"""
  78. name = name.strip()
  79. for code, info in knowledge_map.items():
  80. if info["leaf_name"] == name:
  81. return code
  82. return None
  83. def find_code_by_name_fuzzy(name: str, knowledge_map: dict) -> str | None:
  84. """模糊匹配知识点名称"""
  85. name = name.strip()
  86. # 精确匹配
  87. for code, info in knowledge_map.items():
  88. if info["leaf_name"] == name:
  89. return code
  90. # 包含匹配
  91. for code, info in knowledge_map.items():
  92. if name in info["leaf_name"] or info["leaf_name"] in name:
  93. return code
  94. return None
  95. def build_knowledge(config):
  96. """全流程:解析知识点树 → 存 JSON → 同步数据库"""
  97. from app.config import Config
  98. cfg = config if isinstance(config, Config) else config
  99. print("解析知识点树...")
  100. knowledge_map = parse_knowledge_tree(cfg.knowledge_tree_path)
  101. if not knowledge_map:
  102. print("❌ 知识点树解析失败")
  103. return
  104. # 存 JSON
  105. import json
  106. cfg.knowledge_map_path.parent.mkdir(parents=True, exist_ok=True)
  107. with open(cfg.knowledge_map_path, "w", encoding="utf-8") as f:
  108. json.dump(knowledge_map, f, ensure_ascii=False, indent=2)
  109. print(f"✓ knowledge_map.json 已生成 ({len(knowledge_map)} 个叶子)")
  110. # 同步数据库(先确保数据库已初始化)
  111. from app.database import init_db
  112. init_db(cfg.db_path)
  113. conn = get_connection(cfg.db_path)
  114. try:
  115. sync_knowledge_points(conn, knowledge_map)
  116. print(f"✓ 知识点已同步到数据库")
  117. finally:
  118. conn.close()
  119. def load_knowledge_map(config) -> dict:
  120. """加载已有的 knowledge_map.json"""
  121. import json
  122. map_path = config.knowledge_map_path
  123. if map_path.exists():
  124. with open(map_path, "r", encoding="utf-8") as f:
  125. return json.load(f)
  126. return {}