#!/usr/bin/env python3 """ 批量为教育AI研究项目笔记添加Obsidian frontmatter属性 使用说明:python 批量添加笔记属性.py """ import os import re from pathlib import Path from datetime import datetime # 项目根目录 PROJECT_ROOT = Path(r"d:\TC_UP\2023card\projects\openclaw\教育AI研究") # 分类规则 CATEGORY_PATTERNS = { "机构档案": {"tags": ["机构档案", "研究机构"], "pattern": r"机构档案"}, "深度研究报告": {"tags": ["深度报告", "研究"], "pattern": r"深度研究报告"}, "每周报告": {"tags": ["周报", "项目进展"], "pattern": r"每周报告|W\d+"}, "案例分析": {"tags": ["案例分析", "对比研究"], "pattern": r"案例分析"}, "国际比较": {"tags": ["国际比较", "区域研究"], "pattern": r"国际比较"}, "知识卡片": {"tags": ["知识卡片", "卡片"], "pattern": r"知识卡片"}, "outputs": {"tags": ["输出", "核查", "质量"], "pattern": r"outputs|核查|质量"}, "templates": {"tags": ["模板", "文档模板"], "pattern": r"templates|模板"}, "归档": {"tags": ["归档", "历史"], "pattern": r"归档"}, "文献库": {"tags": ["文献", "论文"], "pattern": r"文献库|论文"}, "高等教育AI专题": {"tags": ["高等教育", "大学", "AI教学"], "pattern": r"高等教育|哈佛|大学"}, } # 国家/机构关键词 COUNTRY_KEYWORDS = { "哈佛": ["哈佛", "Harvard"], "MIT": ["MIT", "Massachusetts"], "斯坦福": ["斯坦福", "Stanford"], "CMU": ["CMU", "Carnegie"], "牛津": ["牛津", "Oxford"], "清华": ["清华"], "北大": ["北大", "北京大学"], "中国": ["中国", "北京", "上海", "复旦"], "印度": ["India", "upGrad", "PhysicsWallah", "Byju"], "新加坡": ["NUS", "新加坡", "Singapore"], } def extract_date_from_filename(filepath): """从文件名提取日期""" patterns = [ r"(\d{4})-(\d{2})-(\d{2})", # 2026-04-14 r"(\d{4})(\d{2})(\d{2})", # 20260414 r"(\d{4})-W(\d+)", # 2026-W14 ] for pattern in patterns: match = re.search(pattern, str(filepath)) if match: if "W" in pattern: return f"{match.group(1)}-W{match.group(2)}" return f"{match.group(1)}-{match.group(2)}-{match.group(3)}" return datetime.now().strftime("%Y-%m-%d") def extract_title_from_content(filepath): """从文件内容提取标题""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() # 跳过frontmatter lines = content.split('\n') start = 0 if lines and lines[0].strip() == '---': for i in range(1, len(lines)): if lines[i].strip() == '---': start = i + 1 break # 找第一个# 标题 for line in lines[start:]: match = re.match(r'^#\s+(.+)$', line.strip()) if match: return match.group(1).strip() # 用文件名 return filepath.stem except Exception as e: return filepath.stem def get_category_and_tags(filepath): """根据文件路径确定分类和标签""" path_str = str(filepath) for category, config in CATEGORY_PATTERNS.items(): if re.search(config["pattern"], path_str): tags = config["tags"].copy() # 检查国家/机构 for country, keywords in COUNTRY_KEYWORDS.items(): for kw in keywords: if kw in path_str: if country not in tags: tags.append(country) return category, tags return "其他", ["教育AI", "研究"] def has_frontmatter(filepath): """检查文件是否有frontmatter""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() return content.startswith('---') except: return False def generate_frontmatter(filepath): """生成frontmatter""" date = extract_date_from_filename(filepath) title = extract_title_from_content(filepath) category, tags = get_category_and_tags(filepath) # 构建YAML frontmatter fm = f"""--- created: {date} title: {title} tags: [{", ".join(tags)}] category: {category} --- """ return fm def add_frontmatter_to_file(filepath): """为文件添加frontmatter""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() # 检查是否已有frontmatter if has_frontmatter(filepath): # 检查frontmatter是否完整 lines = content.split('\n') if len(lines) >= 3 and lines[0].strip() == '---': # 找到第二个--- end_idx = -1 for i in range(1, len(lines)): if lines[i].strip() == '---': end_idx = i break if end_idx > 0: # 检查是否有created字段 fm_content = '\n'.join(lines[1:end_idx]) if 'created:' not in fm_content: # 需要添加created new_fm_lines = [] for line in lines[1:end_idx]: new_fm_lines.append(line) if 'title:' in line: date = extract_date_from_filename(filepath) new_fm_lines.append(f'created: {date}') content = '---\n' + '\n'.join(new_fm_lines) + '\n' + '\n'.join(lines[end_idx:]) with open(filepath, 'w', encoding='utf-8') as f: f.write(content) return "updated_created" return "ok" return "ok" # 无frontmatter,需要添加 fm = generate_frontmatter(filepath) new_content = fm + content with open(filepath, 'w', encoding='utf-8') as f: f.write(new_content) return "added" except Exception as e: return f"error: {e}" def main(): print("=" * 60) print("教育AI研究项目 - 批量添加笔记属性") print("=" * 60) md_files = list(PROJECT_ROOT.rglob("*.md")) stats = { "total": 0, "has_fm": 0, "added": 0, "updated": 0, "errors": 0 } results = [] for md_file in md_files: # 跳过隐藏文件和系统文件 if ".workbuddy" in str(md_file) or ".git" in str(md_file): continue stats["total"] += 1 result = add_frontmatter_to_file(md_file) if result == "added": stats["added"] += 1 results.append(("+NEW", md_file.relative_to(PROJECT_ROOT))) elif result == "updated_created": stats["updated"] += 1 results.append(("~UP", md_file.relative_to(PROJECT_ROOT))) elif result == "ok": stats["has_fm"] += 1 elif result.startswith("error"): stats["errors"] += 1 results.append(("!ERR", f"{md_file.name}: {result}")) # 输出结果 print(f"\n[处理统计]") print(f" 总文件数:{stats['total']}") print(f" 已有完整属性:{stats['has_fm']}") print(f" 新增属性:{stats['added']}") print(f" 更新属性:{stats['updated']}") print(f" 处理失败:{stats['errors']}") if results: print(f"\n[变更详情](前20项)") for status, path in results[:20]: print(f" {status}: {path}") if len(results) > 20: print(f" ... 还有 {len(results) - 20} 项变更") if __name__ == "__main__": main()