[AC-SCRIPTS] chore: 新增临时工具脚本
- 新增 tmp_fix_metadata_cn.py 用于修复元数据中文编码 - 新增 tmp_kb_transform.py 用于知识库数据转换 - 新增 tmp_pack_kb_for_import.py 用于打包知识库导入数据
This commit is contained in:
parent
60e16d65c9
commit
a61fb72d2b
|
|
@ -0,0 +1,29 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
|
||||||
|
ROOT = Path(r"Q:/agentProject/ai-robot-core/docs/kb/result/课程知识库_入库包")
|
||||||
|
|
||||||
|
INFO_TYPE_CN = {
|
||||||
|
"schedule": "课表",
|
||||||
|
"objective": "课程目标",
|
||||||
|
"benefit": "课程收获",
|
||||||
|
"feature": "课程特色",
|
||||||
|
"overview": "课程概述",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
count = 0
|
||||||
|
for fp in ROOT.rglob("metadata.json"):
|
||||||
|
data = json.loads(fp.read_text(encoding="utf-8"))
|
||||||
|
data.pop("source_markdown", None)
|
||||||
|
it = data.get("info_type")
|
||||||
|
if isinstance(it, str):
|
||||||
|
data["info_type"] = INFO_TYPE_CN.get(it, it)
|
||||||
|
fp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
count += 1
|
||||||
|
print(f"updated: {count} files")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import csv
|
||||||
|
|
||||||
|
SRC = Path(r"D:/wxChatData/xwechat_files/wxid_j9wciaq7pbxo22_667d/msg/file/2026-03/知识库_课程知识库")
|
||||||
|
OUT_ROOT = Path(r"Q:/agentProject/ai-robot-core/docs/kb/result")
|
||||||
|
KB_DIR = OUT_ROOT / "课程知识库_原子化_单类目"
|
||||||
|
KB_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
INFO_TYPE_MAP = {
|
||||||
|
"课表": "schedule",
|
||||||
|
"课程收获": "benefit",
|
||||||
|
"主讲": "feature",
|
||||||
|
"赠礼": "feature",
|
||||||
|
"课程": "overview",
|
||||||
|
"知识点": "objective",
|
||||||
|
"案例": "feature",
|
||||||
|
"方法": "objective",
|
||||||
|
"海报": "overview",
|
||||||
|
}
|
||||||
|
|
||||||
|
DROP_KEYWORDS = [
|
||||||
|
"高途是一个在线教育平台",
|
||||||
|
"我的动态功能",
|
||||||
|
"我的订单功能",
|
||||||
|
"学币商城",
|
||||||
|
"购物车功能",
|
||||||
|
"意见反馈功能",
|
||||||
|
"中奖记录",
|
||||||
|
"帮助中心",
|
||||||
|
"社区公约",
|
||||||
|
"专题中心",
|
||||||
|
"赚现金",
|
||||||
|
"周周分享",
|
||||||
|
"得钻石",
|
||||||
|
"100+高校vlog",
|
||||||
|
"邀请有礼",
|
||||||
|
"推荐有礼",
|
||||||
|
"课程评价功能",
|
||||||
|
"我的预约功能",
|
||||||
|
"我的关注功能",
|
||||||
|
"平台内",
|
||||||
|
]
|
||||||
|
|
||||||
|
LOW_VALUE_PATTERNS = [
|
||||||
|
r"^以下是.+详细安排$",
|
||||||
|
r"^课程安排包含.+$",
|
||||||
|
r"^课程状态显示为已过期$",
|
||||||
|
r"^用户可以查看全部课程列表$",
|
||||||
|
r"^平台内.+板块按科目分类展示课程$",
|
||||||
|
r"^该训练营课程共包含\d+节内容.*$",
|
||||||
|
r"^课程针对中考必考的最复杂最值问题$",
|
||||||
|
r"^课程从生活出发$",
|
||||||
|
]
|
||||||
|
|
||||||
|
GENERIC_SUBJECTS = {"课程", "该课程", "该语文课", "该英语课", "该数学课", "该物理课", "该化学课"}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_file_meta(name: str):
|
||||||
|
m = re.match(r"([^_]+)_([^_]+)_([^\.]+)\.txt", name)
|
||||||
|
if not m:
|
||||||
|
return "通用", "通用", "通用"
|
||||||
|
return m.group(1), m.group(2), m.group(3)
|
||||||
|
|
||||||
|
|
||||||
|
def infer_info_type(text: str, default_type: str) -> str:
|
||||||
|
if re.search(r"Day\d+|周[一二三四五六日天]|\d{1,2}:\d{2}-\d{1,2}:\d{2}|开课", text):
|
||||||
|
return "schedule"
|
||||||
|
if any(k in text for k in ["目标", "旨在", "掌握", "培养", "帮助学生", "学会"]):
|
||||||
|
return "objective"
|
||||||
|
if any(k in text for k in ["收获", "提分", "提高", "打下基础", "建立"]):
|
||||||
|
return "benefit"
|
||||||
|
if any(k in text for k in ["老师", "主讲", "教龄", "学位", "博士", "硕士", "冠军", "称号"]):
|
||||||
|
return "feature"
|
||||||
|
if any(k in text for k in ["课程名称", "训练营", "课程计划", "教育项目", "课程是", "计划是"]):
|
||||||
|
return "overview"
|
||||||
|
return default_type
|
||||||
|
|
||||||
|
|
||||||
|
def clean_text(t: str) -> str:
|
||||||
|
t = re.sub(r"^【[^】]+】", "", t).strip().strip("。")
|
||||||
|
t = t.replace("该课程", "课程").replace("本课程", "课程")
|
||||||
|
t = t.replace("该项目", "飞跃领航计划").replace("该计划", "飞跃领航计划")
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
def split_text(t: str):
|
||||||
|
segs = [t]
|
||||||
|
for token in [",旨在", ",目标是", ",帮助", ",通过"]:
|
||||||
|
tmp = []
|
||||||
|
for s in segs:
|
||||||
|
if token in s and len(s) > 24:
|
||||||
|
a, b = s.split(token, 1)
|
||||||
|
tmp.append(a.strip(",。 "))
|
||||||
|
b = b.strip(",。 ")
|
||||||
|
if b:
|
||||||
|
if not b.startswith("课程"):
|
||||||
|
b = "课程目标" + b
|
||||||
|
tmp.append(b)
|
||||||
|
else:
|
||||||
|
tmp.append(s)
|
||||||
|
segs = tmp
|
||||||
|
return [x.strip(",。 ") for x in segs if len(x.strip(",。 ")) >= 6]
|
||||||
|
|
||||||
|
|
||||||
|
def is_low_value(seg: str) -> bool:
|
||||||
|
if any(re.match(pat, seg) for pat in LOW_VALUE_PATTERNS):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 去掉无关键实体且信息泛化的句子
|
||||||
|
has_time = bool(re.search(r"Day\d+|周[一二三四五六日天]|\d{1,2}:\d{2}-\d{1,2}:\d{2}", seg))
|
||||||
|
has_teacher = "老师" in seg or "主讲" in seg
|
||||||
|
has_specific_course = any(k in seg for k in ["课程名称", "课节名称", "主题是", "科目为", "中考", "真题", "文言文", "勾股定理", "酸碱", "凸透镜"])
|
||||||
|
|
||||||
|
if not (has_time or has_teacher or has_specific_course):
|
||||||
|
if len(seg) <= 18:
|
||||||
|
return True
|
||||||
|
if seg in GENERIC_SUBJECTS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# OCR噪声或结构残句
|
||||||
|
if any(k in seg for k in ["课程目标动态图形展示", "课程目标实验让学生", "课程目标文本与真题解析"]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_course_reference(seg: str, last_course_name: str, last_subject: str) -> str:
|
||||||
|
explicit_name = last_course_name
|
||||||
|
if not explicit_name and last_subject:
|
||||||
|
explicit_name = f"{last_subject}"
|
||||||
|
|
||||||
|
if not explicit_name:
|
||||||
|
return seg
|
||||||
|
|
||||||
|
# 代词课程名显式化
|
||||||
|
seg = re.sub(r"^该语法课程", f"{explicit_name}课程", seg)
|
||||||
|
seg = re.sub(r"^该英语课", f"{explicit_name}课程", seg)
|
||||||
|
seg = re.sub(r"^该语文课", f"{explicit_name}课程", seg)
|
||||||
|
seg = re.sub(r"^该数学课", f"{explicit_name}课程", seg)
|
||||||
|
seg = re.sub(r"^该物理课", f"{explicit_name}课程", seg)
|
||||||
|
seg = re.sub(r"^该化学课", f"{explicit_name}课程", seg)
|
||||||
|
seg = re.sub(r"^该课程", f"{explicit_name}课程", seg)
|
||||||
|
|
||||||
|
# 泛化“课程”在可用上下文下显式化
|
||||||
|
seg = re.sub(r"^课程(旨在|目标|强调|还|将|内容|从|通过|运用|帮助|解决|涵盖)", f"{explicit_name}课程\\1", seg)
|
||||||
|
seg = re.sub(r"^课程使学生", f"{explicit_name}课程使学生", seg)
|
||||||
|
seg = re.sub(r"^课程学习", f"{explicit_name}课程学习", seg)
|
||||||
|
seg = re.sub(r"^课程时间表", f"{explicit_name}课程时间表", seg)
|
||||||
|
seg = re.sub(r"^课程安排", f"{explicit_name}课程安排", seg)
|
||||||
|
|
||||||
|
return seg
|
||||||
|
|
||||||
|
|
||||||
|
def process_one(fp: Path):
|
||||||
|
grade, subject, course_plan = parse_file_meta(fp.name)
|
||||||
|
default_type = INFO_TYPE_MAP.get(course_plan, "overview")
|
||||||
|
|
||||||
|
raw_lines = fp.read_text(encoding="utf-8", errors="ignore").splitlines()
|
||||||
|
part_a = []
|
||||||
|
rows = []
|
||||||
|
ambiguities = []
|
||||||
|
last_course_name = ""
|
||||||
|
last_subject = ""
|
||||||
|
|
||||||
|
for line in raw_lines:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
text = clean_text(line)
|
||||||
|
if len(text) < 6:
|
||||||
|
continue
|
||||||
|
if any(k in text for k in DROP_KEYWORDS):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for seg in split_text(text):
|
||||||
|
# 更新上下文课程名
|
||||||
|
m_name1 = re.search(r"课节名称为[“\"]([^”\"]+)[”\"]", seg)
|
||||||
|
m_name2 = re.search(r"课程名称是[“\"]([^”\"]+)[”\"]", seg)
|
||||||
|
m_name3 = re.search(r"主题是[“\"]([^”\"]+)[”\"]", seg)
|
||||||
|
if m_name1:
|
||||||
|
last_course_name = m_name1.group(1).strip()
|
||||||
|
elif m_name2:
|
||||||
|
last_course_name = m_name2.group(1).strip()
|
||||||
|
elif m_name3:
|
||||||
|
last_course_name = m_name3.group(1).strip()
|
||||||
|
|
||||||
|
# 兼容“课程的名称为【xx】”模式
|
||||||
|
m_name4 = re.search(r"课程的名称为[【\"]([^】\"]+)[】\"]", seg)
|
||||||
|
if m_name4:
|
||||||
|
last_course_name = m_name4.group(1).strip()
|
||||||
|
|
||||||
|
# 提取科目上下文
|
||||||
|
m_subject = re.search(r"科目为([^,。]+)", seg)
|
||||||
|
if m_subject:
|
||||||
|
last_subject = m_subject.group(1).strip()
|
||||||
|
|
||||||
|
seg = resolve_course_reference(seg, last_course_name, last_subject)
|
||||||
|
|
||||||
|
# 仍以“课程”开头且无显式课程名的句子视为低价值
|
||||||
|
if seg.startswith("课程") and not last_course_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if any(k in seg for k in DROP_KEYWORDS):
|
||||||
|
continue
|
||||||
|
if is_low_value(seg):
|
||||||
|
continue
|
||||||
|
|
||||||
|
info_type = infer_info_type(seg, default_type)
|
||||||
|
type_label = {
|
||||||
|
"schedule": "课表",
|
||||||
|
"objective": "课程目标",
|
||||||
|
"benefit": "课程收获",
|
||||||
|
"feature": "课程特色",
|
||||||
|
"overview": "课程概述",
|
||||||
|
}[info_type]
|
||||||
|
|
||||||
|
a_line = f"【{grade}-课程咨询-{type_label}】{seg}。"
|
||||||
|
part_a.append(a_line)
|
||||||
|
|
||||||
|
day_index = ""
|
||||||
|
m_day = re.search(r"(Day\d+)", seg)
|
||||||
|
if m_day:
|
||||||
|
day_index = m_day.group(1)
|
||||||
|
|
||||||
|
time_range = ""
|
||||||
|
m_time = re.search(r"(\d{1,2}:\d{2}-\d{1,2}:\d{2})", seg)
|
||||||
|
if m_time:
|
||||||
|
time_range = m_time.group(1)
|
||||||
|
|
||||||
|
teacher = ""
|
||||||
|
m_teacher = re.search(r"([\u4e00-\u9fa5]{2,4})老师", seg)
|
||||||
|
if m_teacher:
|
||||||
|
teacher = m_teacher.group(1)
|
||||||
|
|
||||||
|
rows.append([
|
||||||
|
a_line,
|
||||||
|
grade,
|
||||||
|
subject if subject else "通用",
|
||||||
|
"课程咨询",
|
||||||
|
course_plan,
|
||||||
|
day_index,
|
||||||
|
time_range,
|
||||||
|
teacher,
|
||||||
|
info_type,
|
||||||
|
])
|
||||||
|
|
||||||
|
if any(x in seg for x in ["思想道德(数)", "一文会云题", "1+1方法", "高考阅读提分"]):
|
||||||
|
ambiguities.append(seg)
|
||||||
|
|
||||||
|
# 去重
|
||||||
|
uniq = []
|
||||||
|
seen = set()
|
||||||
|
for x in part_a:
|
||||||
|
if x not in seen:
|
||||||
|
uniq.append(x)
|
||||||
|
seen.add(x)
|
||||||
|
part_a = uniq
|
||||||
|
|
||||||
|
uniq_rows = []
|
||||||
|
seen2 = set()
|
||||||
|
for r in rows:
|
||||||
|
if r[0] in seen2:
|
||||||
|
continue
|
||||||
|
seen2.add(r[0])
|
||||||
|
uniq_rows.append(r)
|
||||||
|
rows = uniq_rows
|
||||||
|
|
||||||
|
info_type_to_label = {
|
||||||
|
"schedule": "课表",
|
||||||
|
"objective": "课程目标",
|
||||||
|
"benefit": "课程收获",
|
||||||
|
"feature": "课程特色",
|
||||||
|
"overview": "课程概述",
|
||||||
|
}
|
||||||
|
|
||||||
|
grouped_rows: dict[str, list[list[str]]] = {
|
||||||
|
"schedule": [],
|
||||||
|
"objective": [],
|
||||||
|
"benefit": [],
|
||||||
|
"feature": [],
|
||||||
|
"overview": [],
|
||||||
|
}
|
||||||
|
for r in rows:
|
||||||
|
grouped_rows[r[8]].append(r)
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
|
||||||
|
for info_type, g_rows in grouped_rows.items():
|
||||||
|
if not g_rows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
part_a_group = [r[0] for r in g_rows]
|
||||||
|
type_cn = info_type_to_label[info_type]
|
||||||
|
out_file = KB_DIR / f"{fp.stem}_{info_type}_原子化.md"
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
lines.append("Part A:原子知识行(用于知识库正文)")
|
||||||
|
lines.append("")
|
||||||
|
lines.extend(part_a_group)
|
||||||
|
lines.append("")
|
||||||
|
lines.append("Part B:文件级元数据建议(用于按文件打标)")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"- grade: {grade}")
|
||||||
|
lines.append(f"- subject: {subject if subject else '通用'}")
|
||||||
|
lines.append("- kb_scene: 课程咨询")
|
||||||
|
lines.append(f"- course_plan: {course_plan}")
|
||||||
|
lines.append(f"- info_type: {info_type}")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("自检结果")
|
||||||
|
lines.append(f"- 总行数:{len(part_a_group)}")
|
||||||
|
lines.append(f"- 可打标行数:{len(part_a_group)}")
|
||||||
|
lines.append("- 拆分前后知识点完整性说明:已拆分混合句并保留时间、老师、课程名、目标等关键实体。")
|
||||||
|
lines.append(f"- 文档类目一致性:本文件仅包含 info_type={info_type}({type_cn})。")
|
||||||
|
if ambiguities:
|
||||||
|
lines.append("- 发现的歧义项:" + ";".join(ambiguities) + "(需人工确认)")
|
||||||
|
else:
|
||||||
|
lines.append("- 发现的歧义项:无明显歧义项。")
|
||||||
|
|
||||||
|
out_file.write_text("\n".join(lines), encoding="utf-8")
|
||||||
|
outputs.append((info_type, len(part_a_group), str(out_file)))
|
||||||
|
|
||||||
|
total_lines = sum(x[1] for x in outputs)
|
||||||
|
return fp.name, total_lines, outputs
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
files = sorted([p for p in SRC.glob("*.txt") if p.name != "metadata_config.json"])
|
||||||
|
summary = []
|
||||||
|
for fp in files:
|
||||||
|
source_file, total_lines, outputs = process_one(fp)
|
||||||
|
for info_type, atomic_lines, output_file in outputs:
|
||||||
|
summary.append([source_file, info_type, atomic_lines, output_file])
|
||||||
|
|
||||||
|
index_file = KB_DIR / "_处理结果索引.csv"
|
||||||
|
with index_file.open("w", encoding="utf-8-sig", newline="") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["source_file", "info_type", "atomic_lines", "output_file"])
|
||||||
|
writer.writerows(summary)
|
||||||
|
|
||||||
|
print(f"done: {len(files)} source files, {len(summary)} output files")
|
||||||
|
print(index_file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
|
||||||
|
SRC_DIR = Path(r"Q:/agentProject/ai-robot-core/docs/kb/result/课程知识库_原子化_单类目")
|
||||||
|
OUT_DIR = Path(r"Q:/agentProject/ai-robot-core/docs/kb/result/课程知识库_入库包")
|
||||||
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_md(md_path: Path):
|
||||||
|
lines = md_path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
||||||
|
|
||||||
|
content_lines = []
|
||||||
|
metadata = {}
|
||||||
|
|
||||||
|
in_part_a = False
|
||||||
|
in_part_b = False
|
||||||
|
|
||||||
|
for raw in lines:
|
||||||
|
line = raw.strip()
|
||||||
|
|
||||||
|
if line == "Part A:原子知识行(用于知识库正文)":
|
||||||
|
in_part_a = True
|
||||||
|
in_part_b = False
|
||||||
|
continue
|
||||||
|
if line == "Part B:文件级元数据建议(用于按文件打标)":
|
||||||
|
in_part_a = False
|
||||||
|
in_part_b = True
|
||||||
|
continue
|
||||||
|
if line.startswith("自检结果"):
|
||||||
|
in_part_a = False
|
||||||
|
in_part_b = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_part_a:
|
||||||
|
if line.startswith("【") and "】" in line:
|
||||||
|
content_lines.append(line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_part_b and line.startswith("- ") and ":" in line:
|
||||||
|
key, value = line[2:].split(":", 1)
|
||||||
|
metadata[key.strip()] = value.strip()
|
||||||
|
|
||||||
|
return content_lines, metadata
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
files = sorted(SRC_DIR.glob("*_原子化.md"))
|
||||||
|
index = []
|
||||||
|
|
||||||
|
for md_file in files:
|
||||||
|
content_lines, metadata = parse_md(md_file)
|
||||||
|
if not content_lines:
|
||||||
|
continue
|
||||||
|
|
||||||
|
folder_name = md_file.stem.replace("_原子化", "")
|
||||||
|
target_dir = OUT_DIR / folder_name
|
||||||
|
target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
content_file = target_dir / "content.txt"
|
||||||
|
metadata_file = target_dir / "metadata.json"
|
||||||
|
|
||||||
|
content_file.write_text("\n".join(content_lines), encoding="utf-8")
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"grade": metadata.get("grade", "通用"),
|
||||||
|
"subject": metadata.get("subject", "通用"),
|
||||||
|
"kb_scene": metadata.get("kb_scene", "课程咨询"),
|
||||||
|
"course_plan": metadata.get("course_plan", "通用"),
|
||||||
|
"info_type": metadata.get("info_type", "overview"),
|
||||||
|
"source_markdown": str(md_file),
|
||||||
|
}
|
||||||
|
metadata_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
index.append({
|
||||||
|
"folder": folder_name,
|
||||||
|
"content_lines": len(content_lines),
|
||||||
|
"content_file": str(content_file),
|
||||||
|
"metadata_file": str(metadata_file),
|
||||||
|
})
|
||||||
|
|
||||||
|
(OUT_DIR / "_index.json").write_text(json.dumps(index, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
print(f"done: {len(index)} packages")
|
||||||
|
print(str(OUT_DIR))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue