auto: sync OpenClaw config 2026-08-27 11:17

This commit is contained in:
2026-08-27 11:17:10 +08:00
parent 0269b99815
commit 7a5ca9f1a7
94 changed files with 4497 additions and 2088 deletions
@@ -0,0 +1,362 @@
#!/usr/bin/env python3
"""
周报数据库同步工具(SQLite 版本)
将 Markdown 周报的元数据同步到 SQLite 数据库
用法:
python3 sync_reports_db.py --all # 同步所有周报
python3 sync_reports_db.py --file xxx.md # 同步单个文件
python3 sync_reports_db.py --week 2026-W35 # 同步指定周
python3 sync_reports_db.py --stats # 查看统计信息
"""
import os
import re
import sys
import argparse
import json
import sqlite3
from pathlib import Path
from datetime import datetime, timedelta
REPORTS_DIR = Path(__file__).parent
DB_PATH = REPORTS_DIR / 'weekly_reports.db'
def init_database():
"""初始化 SQLite 数据库"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 创建周报元数据表
cursor.execute('''
CREATE TABLE IF NOT EXISTS weekly_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
year INTEGER NOT NULL,
week_number INTEGER NOT NULL,
start_date TEXT NOT NULL,
end_date TEXT NOT NULL,
project TEXT NOT NULL,
main_task TEXT,
file_path TEXT NOT NULL UNIQUE,
has_problems INTEGER DEFAULT 0,
word_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 创建每日工作明细表
cursor.execute('''
CREATE TABLE IF NOT EXISTS weekly_report_daily (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_id INTEGER NOT NULL,
day_of_week INTEGER NOT NULL,
work_date TEXT NOT NULL,
work_items TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (report_id) REFERENCES weekly_reports(id) ON DELETE CASCADE
)
''')
# 创建问题记录表
cursor.execute('''
CREATE TABLE IF NOT EXISTS weekly_report_problems (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_id INTEGER NOT NULL,
problem_description TEXT NOT NULL,
status TEXT DEFAULT 'open',
resolved_at TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (report_id) REFERENCES weekly_reports(id) ON DELETE CASCADE
)
''')
# 创建索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_date ON weekly_reports(start_date, end_date)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_project ON weekly_reports(project)')
cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS uk_year_week ON weekly_reports(year, week_number, project)')
conn.commit()
return conn
def parse_markdown_report(filepath):
"""解析 Markdown 周报文件,提取元数据"""
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
data = {
'file_path': str(filepath),
'year': None,
'week_number': None,
'start_date': None,
'end_date': None,
'project': None,
'main_task': None,
'daily_work': {},
'problems': [],
'word_count': len(content)
}
# 提取年份和周数(从文件名)
filename = filepath.name
match = re.match(r'(\d{4})-W(\d{2})', filename)
if match:
data['year'] = int(match.group(1))
data['week_number'] = int(match.group(2))
# 提取日期范围(多种格式兼容)
# 格式 1: **日期范围:** 2026-08-25 ~ 2026-08-29
date_match = re.search(r'\*\*日期范围:\*\*\s*(\d{4}-\d{2}-\d{2})\s*~\s*(\d{4}-\d{2}-\d{2})', content)
if date_match:
data['start_date'] = date_match.group(1)
data['end_date'] = date_match.group(2)
else:
# 格式 2: 2026-07-27 ~ 2026-07-31(在文件末尾)
date_match2 = re.search(r'(\d{4}-\d{2}-\d{2})\s*~\s*(\d{4}-\d{2}-\d{2})', content)
if date_match2:
data['start_date'] = date_match2.group(1)
data['end_date'] = date_match2.group(2)
else:
# 格式 3: 从文件名推算(2026-W31
if data['year'] and data['week_number']:
# 计算该周的周一和周五
jan_first = datetime(data['year'], 1, 1)
week_start = jan_first + timedelta(weeks=data['week_number'] - 1)
week_start = week_start - timedelta(days=week_start.weekday())
week_end = week_start + timedelta(days=4)
data['start_date'] = week_start.strftime('%Y-%m-%d')
data['end_date'] = week_end.strftime('%Y-%m-%d')
# 提取项目名称(多种格式兼容)
# 格式 1: 项目名称:维云智造 G5
project_match = re.search(r'项目名称:\s*(维云智造\s*(G\d+)|G\d+)', content)
if project_match:
project_text = project_match.group(1)
g_match = re.search(r'G(\d+)', project_text)
if g_match:
data['project'] = f"G{g_match.group(1)}"
else:
data['project'] = 'G5'
else:
# 格式 2: **项目:** 维云智造 G6
project_match2 = re.search(r'\*\*项目:\*\*\s*(维云智造\s*(G\d+)|G\d+)', content)
if project_match2:
project_text = project_match2.group(1)
g_match = re.search(r'G(\d+)', project_text)
if g_match:
data['project'] = f"G{g_match.group(1)}"
else:
data['project'] = 'G5'
else:
# 默认 G5
data['project'] = 'G5'
# 提取主要任务
task_match = re.search(r'主要任务:\s*(.+?)(?:\n|$)', content)
if task_match:
data['main_task'] = task_match.group(1).strip()
# 提取每日工作
daily_section = re.search(r'## 本周工作内容\s*\n([\s\S]*?)(?:\n##|\Z)', content)
if daily_section:
daily_text = daily_section.group(1)
days = re.findall(r'###?\s*(?:周 [一二三四五六日]|(\d{4}-\d{2}-\d{2}))\s*\n([\s\S]*?)(?=###?|$)', daily_text)
for date_str, work_text in days:
if date_str:
items = re.findall(r'^\s*[-•*]\s*(.+)$', work_text, re.MULTILINE)
data['daily_work'][date_str] = items
# 提取存在问题
problem_section = re.search(r'## 存在问题\s*\n([\s\S]*?)(?:\n##|\Z)', content)
if problem_section:
problem_text = problem_section.group(1).strip()
if problem_text and problem_text != '':
data['problems'] = [line.strip() for line in problem_text.split('\n') if line.strip()]
return data
def sync_report(conn, report_data):
"""同步单个周报到数据库"""
cursor = conn.cursor()
try:
# 插入或更新周报元数据
cursor.execute('''
INSERT INTO weekly_reports
(year, week_number, start_date, end_date, project, main_task, file_path, has_problems, word_count)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(year, week_number, project) DO UPDATE SET
main_task = excluded.main_task,
file_path = excluded.file_path,
has_problems = excluded.has_problems,
word_count = excluded.word_count,
updated_at = CURRENT_TIMESTAMP
''', (
report_data['year'],
report_data['week_number'],
report_data['start_date'],
report_data['end_date'],
report_data['project'],
report_data['main_task'],
report_data['file_path'],
1 if report_data['problems'] else 0,
report_data['word_count']
))
report_id = cursor.lastrowid
if report_id is None:
# 更新操作,需要获取现有 ID
cursor.execute('SELECT id FROM weekly_reports WHERE file_path = ?', (report_data['file_path'],))
row = cursor.fetchone()
if row:
report_id = row[0]
# 删除旧的每日工作记录
cursor.execute('DELETE FROM weekly_report_daily WHERE report_id = ?', (report_id,))
# 插入新的每日工作记录
for work_date, items in report_data['daily_work'].items():
day_of_week = datetime.strptime(work_date, '%Y-%m-%d').weekday() + 1
items_json = json.dumps(items, ensure_ascii=False)
cursor.execute('''
INSERT INTO weekly_report_daily
(report_id, day_of_week, work_date, work_items)
VALUES (?, ?, ?, ?)
''', (report_id, day_of_week, work_date, items_json))
# 删除旧的问题记录
cursor.execute('DELETE FROM weekly_report_problems WHERE report_id = ?', (report_id,))
# 插入新的问题记录
for problem in report_data['problems']:
cursor.execute('''
INSERT INTO weekly_report_problems
(report_id, problem_description)
VALUES (?, ?)
''', (report_id, problem))
conn.commit()
return True
except sqlite3.Error as e:
print(f"❌ 数据库错误:{e}")
conn.rollback()
return False
def show_stats(conn):
"""显示统计信息"""
cursor = conn.cursor()
print("\n📊 周报统计信息\n")
print("=" * 60)
# 总览
cursor.execute('SELECT COUNT(*) FROM weekly_reports')
total = cursor.fetchone()[0]
print(f"📁 周报总数:{total}")
# 按项目统计
cursor.execute('''
SELECT project, COUNT(*) as count,
SUM(has_problems) as problem_weeks,
AVG(word_count) as avg_words
FROM weekly_reports
GROUP BY project
''')
print("\n📋 按项目统计:")
for row in cursor.fetchall():
print(f" {row[0]}: {row[1]} 周,问题 {row[2]} 周,平均字数 {int(row[3])}")
# 最近 5 周
cursor.execute('''
SELECT year, week_number, start_date, end_date, project, main_task, has_problems
FROM weekly_reports
ORDER BY year DESC, week_number DESC
LIMIT 5
''')
print("\n📅 最近 5 周:")
for row in cursor.fetchall():
problem_flag = "⚠️" if row[6] else ""
print(f" {problem_flag} {row[0]}-W{row[1]:02d} ({row[2]}~{row[3]}) {row[4]} - {row[5][:30]}")
# 存在问题统计
cursor.execute('SELECT COUNT(*) FROM weekly_reports WHERE has_problems = 1')
problem_count = cursor.fetchone()[0]
print(f"\n⚠️ 存在问题周报:{problem_count}")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(description='周报数据库同步工具(SQLite')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--all', action='store_true', help='同步所有周报')
group.add_argument('--file', type=str, help='同步单个文件')
group.add_argument('--week', type=str, help='同步指定周,如 2026-W35')
group.add_argument('--stats', action='store_true', help='查看统计信息')
args = parser.parse_args()
# 初始化数据库
conn = init_database()
print(f"✅ 数据库已初始化:{DB_PATH}")
if args.stats:
show_stats(conn)
conn.close()
return 0
files_to_sync = []
if args.file:
files_to_sync = [Path(args.file)]
elif args.week:
match = re.match(r'(\d{4})-W(\d{2})', args.week)
if match:
year, week = match.groups()
pattern = f"{year}-W{week}-*.md"
files_to_sync = list(REPORTS_DIR.glob(f"{year}/{pattern}"))
elif args.all:
for year_dir in REPORTS_DIR.iterdir():
if year_dir.is_dir() and year_dir.name.isdigit():
files_to_sync.extend(year_dir.glob("*.md"))
if not files_to_sync:
print("⚠️ 未找到需要同步的文件")
conn.close()
return 1
print(f"📂 找到 {len(files_to_sync)} 个文件待同步\n")
success_count = 0
for filepath in sorted(files_to_sync):
if not filepath.exists():
print(f"⚠️ 文件不存在:{filepath}")
continue
print(f"📄 处理:{filepath.name}")
report_data = parse_markdown_report(filepath)
if not report_data['year'] or not report_data['week_number']:
print(f" ⚠️ 无法解析年份/周数,跳过")
continue
if sync_report(conn, report_data):
print(f" ✅ 同步成功")
success_count += 1
else:
print(f" ❌ 同步失败")
print(f"\n{'' if success_count == len(files_to_sync) else '⚠️'} 完成:{success_count}/{len(files_to_sync)} 个文件")
conn.close()
return 0 if success_count == len(files_to_sync) else 1
if __name__ == '__main__':
exit(main())