Phase 0-2: Schema cleanup, typed relations, event-driven automation
- Phase 0: AGENTS.md cleanup (dedup quotes, renumber sections, merge qmd) - Phase 1: typed relations (manage-relations.py, graph-search.py, check-staleness.py, detect-conflicts.py) - Phase 2: frontmatter validator, weekly lint, knowledge promotion, git hooks - Fix .gitignore to track tools/ and .githooks/ - Fix git remote URL (remove plaintext token) - New wiki pages: 504 pages, 34 raw sources
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user