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
+268
View File
@@ -0,0 +1,268 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""解析研究院-维云智造 OKR 绩效 Excel(杨轩),归档到 MySQL resume 库。
支持新旧两种模板:
- 旧模板(202601 等,多 sheet):表头行4,目标行5起
- 新模板(8月起,单 sheet OKR考核):表头行5-6,目标行8起(跳过示例行)
用法:
python3 parse_okr_to_mysql.py # 解析 inbound 最新 OKR xlsx
python3 parse_okr_to_mysql.py --month 202601 # 指定考核月
python3 parse_okr_to_mysql.py --file <xlsx> # 指定文件
python3 parse_okr_to_mysql.py --dry-run # 预览不写入
"""
import glob
import os
import re
import sys
import openpyxl
MYSQL_DB = "resume"
MYSQL_HOST = "127.0.0.1"
MYSQL_PORT = 3306
MYSQL_USER = "root"
def get_mysql_pwd():
env_path = "/home/yangxuan/.openclaw/.env"
for line in open(env_path):
line = line.strip()
if line.startswith("MYSQL_PWD="):
return line.split("=", 1)[1].strip()
return ""
def parse_info(text):
"""从人员信息行解析 被考核人/岗位/上级/周期/时间(新旧模板通用)"""
info = {}
m = re.search(r"(?:被考核人|姓名)[:]\s*(\S+)", text)
info["full_name"] = m.group(1) if m else ""
m = re.search(r"岗位[:]\s*(\S+)", text)
info["position"] = m.group(1) if m else ""
m = re.search(r"(?:直属上级|直接上级)[:]\s*(\S+)", text)
info["supervisor"] = m.group(1) if m else ""
m = re.search(r"考核周期[:]\s*([^\s]+)", text)
info["period"] = m.group(1).strip() if m else ""
m = re.search(r"考核时间[:]\s*(\S+)", text)
info["assess_date"] = m.group(1).strip() if m else ""
return info
def detect_template(ws):
"""识别模板。'new'=8月新模板,'old'=202601旧模板。"""
# 新模板:行5-6 含"员工本人填报"或"关键结果"
for r in (5, 6):
for c in range(3, 12):
v = ws.cell(row=r, column=c).value
if v and ("员工本人填报" in str(v) or "关键结果" in str(v)):
return "new"
# 旧模板:行4 含"目标O"或"关键绩效指标"
for c in range(3, 12):
v = ws.cell(row=4, column=c).value
if v and ("目标O" in str(v) or "关键绩效指标" in str(v)):
return "old"
return "new"
def parse_sheet_new(ws):
"""解析 8 月新模板(OKR考核 sheet)"""
b3 = ws["B3"].value or ""
info = parse_info(b3)
# 月度评价:F15 是“月度评价”标签,分数在其右侧(G15 等),上级可能未填
monthly_rating = None
for coord in ("G15", "H15", "I15"):
v = ws[coord].value
if isinstance(v, (int, float)):
monthly_rating = float(v)
break
objectives = []
for row in range(8, 40): # 目标行8起
seq = ws.cell(row=row, column=2).value # B 序号
obj = ws.cell(row=row, column=3).value # C 任务工作
krs = ws.cell(row=row, column=4).value # D 关键结果
compl = ws.cell(row=row, column=5).value # E 完成情况
score = ws.cell(row=row, column=7).value # G 评分
weight = ws.cell(row=row, column=9).value # I 权重
wscore = ws.cell(row=row, column=10).value # J 得分
if isinstance(obj, str) and "月度工作补充" in obj:
break
if not isinstance(seq, (int, float)):
continue
objectives.append({
"seq": int(seq),
"objective": (obj or "").strip(),
"krs": (krs or "").strip(),
"completion": (compl or "").strip(),
"score": float(score) if isinstance(score, (int, float)) else None,
"weight": float(weight) if isinstance(weight, (int, float)) else None,
"weighted_score": float(wscore) if isinstance(wscore, (int, float)) else None,
})
return {**info, "monthly_rating": monthly_rating, "objectives": objectives}
def parse_sheet_old(ws):
"""解析 202601 旧模板"""
b2 = ws["B2"].value or ""
info = parse_info(b2)
monthly_rating = None
for coord in ("J12", "B12"):
v = ws[coord].value
if isinstance(v, (int, float)):
monthly_rating = float(v)
break
objectives = []
for row in range(5, 11): # 行5-10 对应序号1-6
seq = ws.cell(row=row, column=2).value # B 序号
obj = ws.cell(row=row, column=3).value # C 目标O
krs = ws.cell(row=row, column=5).value # E KR
compl = ws.cell(row=row, column=6).value # F 完成情况
score = ws.cell(row=row, column=8).value # H 评分
weight = ws.cell(row=row, column=9).value # I 权重
wscore = ws.cell(row=row, column=10).value # J 加权
if seq is None and obj is None:
continue
objectives.append({
"seq": seq if isinstance(seq, (int, float)) else row - 4,
"objective": (obj or "").strip(),
"krs": (krs or "").strip(),
"completion": (compl or "").strip(),
"score": float(score) if isinstance(score, (int, float)) else None,
"weight": float(weight) if isinstance(weight, (int, float)) else None,
"weighted_score": float(wscore) if isinstance(wscore, (int, float)) else None,
})
return {**info, "monthly_rating": monthly_rating, "objectives": objectives}
def month_from_period(period, sheet_name, fallback):
"""从考核周期/sheet名 确定 assess_monthYYYYMM
跨月周期(如“7月27日-8月27日”)取后段月份(考核期末)
"""
# 末段月份优先(跨月周期取考核期末,如 7月27日-8月27日 → 8月)
m = re.search(r"-(\d{1,2})月(\d{1,2})日", period or "")
if m:
return f"2026{int(m.group(1)):02d}"
m = re.search(r"(\d{4})年(\d{1,2})月", period or "")
if m:
return f"{m.group(1)}{int(m.group(2)):02d}"
m = re.search(r"(\d{4})\s*(\d{2})", sheet_name or "")
if m:
return f"{m.group(1)}{int(m.group(2)):02d}"
m = re.search(r"(\d{1,2})月", period or "")
if m:
return f"2026{int(m.group(1)):02d}"
return fallback or ""
def process_sheet(wb, sheet_name, xls, dry_run=False):
"""解析并归档单个 sheet,返回 (assess_month, 目标数) 或 None"""
ws = wb[sheet_name]
tpl = detect_template(ws)
print(f"📑 Sheet{sheet_name} | 识别模板:{'新模板(8月起)' if tpl == 'new' else '旧模板(202601)'}")
data = parse_sheet_new(ws) if tpl == "new" else parse_sheet_old(ws)
assess_month = month_from_period(data.get("period"), sheet_name, None)
if not assess_month:
print(f" ⚠️ 无法确定考核月,跳过")
return None
if not data["objectives"]:
print(f" ⚠️ {sheet_name} 无目标数据,跳过")
return None
print(f" 被考核人:{data['full_name']} | 岗位:{data['position']} | 上级:{data['supervisor']}")
print(f" 考核周期:{data.get('period')} | assess_month={assess_month} | 月度评价:{data.get('monthly_rating')}")
print(f" 目标数:{len(data['objectives'])}")
for o in data["objectives"]:
print(f" {o['seq']}. {o['objective'][:28]} | 权重{o['weight']} 评分{o.get('score')}")
if dry_run:
print(" (dry-run 未写入)")
return assess_month
pwd = get_mysql_pwd()
if not pwd:
print("❌ 未找到 MYSQL_PWD")
return None
import pymysql
conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=pwd, database=MYSQL_DB, charset="utf8mb4")
cur = conn.cursor()
cur.execute("SELECT id FROM okr_monthly_records WHERE assess_month=%s AND full_name=%s",
(assess_month, data["full_name"]))
for (rid,) in cur.fetchall():
cur.execute("DELETE FROM okr_objectives WHERE record_id=%s", (rid,))
cur.execute("DELETE FROM okr_monthly_records WHERE id=%s", (rid,))
cur.execute(
"INSERT INTO okr_monthly_records (assess_month, full_name, position, supervisor, assess_date, monthly_rating, source_file) "
"VALUES (%s,%s,%s,%s,%s,%s,%s)",
(assess_month, data["full_name"], data["position"], data["supervisor"],
data.get("assess_date", ""), data.get("monthly_rating"), os.path.basename(xls)))
rid = cur.lastrowid
for o in data["objectives"]:
cur.execute(
"INSERT INTO okr_objectives (record_id, seq, objective, krs, completion, score, weight, weighted_score) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s)",
(rid, o["seq"], o["objective"], o["krs"], o["completion"], o.get("score"),
o["weight"], o["weighted_score"]))
conn.commit()
cur.close()
conn.close()
print(f" ✅ 已入库({len(data['objectives'])} 项目标)")
return assess_month
def main():
dry_run = "--dry-run" in sys.argv
month_filter = None
if "--month" in sys.argv:
month_filter = sys.argv[sys.argv.index("--month") + 1]
# 定位 Excel
xls = None
for i, a in enumerate(sys.argv[1:]):
if a == "--file" and i + 2 < len(sys.argv):
xls = sys.argv[i + 2]
elif a.endswith(".xlsx"):
xls = a
if xls is None or not os.path.exists(xls):
cand = sorted(glob.glob("/home/yangxuan/.openclaw/media/inbound/*OKR*.xlsx"))
xls = cand[-1] if cand else None
if not xls or not os.path.exists(xls):
print("❌ 未找到 OKR Excel 文件")
return
print(f"📄 解析文件:{os.path.basename(xls)}")
wb = openpyxl.load_workbook(xls, data_only=True)
# 决定要处理的 sheet 列表
if month_filter:
# 支持范围:如 202602-202607,或单个 202601,或逗号分隔
if "-" in month_filter and len(month_filter) == 13:
start_m, end_m = month_filter.split("-")
start_i, end_i = int(start_m), int(end_m)
months = [str(m) for m in range(start_i, end_i + 1)]
sheets = [n for n in wb.sheetnames if n in months]
else:
parts = [p.strip() for p in month_filter.replace("", ",").split(",")]
sheets = [n for n in wb.sheetnames if n in parts]
if not sheets:
print(f"❌ 文件里未找到指定月份 sheet{month_filter}")
return
else:
# 无 --month:优先 OKR考核(新模板单 sheet)
sheets = [n for n in wb.sheetnames if "OKR考核" in n]
if not sheets:
sheets = wb.sheetnames[:1]
print(f"\n📦 待处理 sheet{sheets}")
done = []
for sn in sheets:
print()
r = process_sheet(wb, sn, xls, dry_run)
if r:
done.append(r)
if dry_run:
print(f"\n🔍 dry-run 完成,共 {len(sheets)} 个 sheet(未写入)")
else:
print(f"\n🎉 已完成 {len(done)} 个月 OKR 归档:{done}")
if __name__ == "__main__":
main()