@yionr/plan-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.mjs +175 -0
- package/package.json +21 -0
- package/scripts/collect_monthly_data.py +126 -0
- package/scripts/config.py +38 -0
- package/scripts/create_next_week.py +85 -0
- package/scripts/generate_monthly_md.py +91 -0
- package/scripts/get_current_week.py +79 -0
- package/scripts/get_user_info.py +85 -0
- package/scripts/query_weekly_records.py +90 -0
- package/scripts/update_completion.py +69 -0
- package/skills/sab-monthly-summary/SKILL.md +85 -0
- package/skills/sab-monthly-summary/scripts/collect_monthly_data.py +126 -0
- package/skills/sab-monthly-summary/scripts/config.py +41 -0
- package/skills/sab-monthly-summary/scripts/create_next_week.py +85 -0
- package/skills/sab-monthly-summary/scripts/generate_monthly_md.py +91 -0
- package/skills/sab-monthly-summary/scripts/get_current_week.py +79 -0
- package/skills/sab-monthly-summary/scripts/get_user_info.py +85 -0
- package/skills/sab-monthly-summary/scripts/query_weekly_records.py +90 -0
- package/skills/sab-monthly-summary/scripts/update_completion.py +69 -0
- package/skills/sab-weekly-plan/SKILL.md +109 -0
- package/skills/sab-weekly-plan/scripts/collect_monthly_data.py +126 -0
- package/skills/sab-weekly-plan/scripts/config.py +41 -0
- package/skills/sab-weekly-plan/scripts/create_next_week.py +85 -0
- package/skills/sab-weekly-plan/scripts/generate_monthly_md.py +91 -0
- package/skills/sab-weekly-plan/scripts/get_current_week.py +79 -0
- package/skills/sab-weekly-plan/scripts/get_user_info.py +85 -0
- package/skills/sab-weekly-plan/scripts/query_weekly_records.py +90 -0
- package/skills/sab-weekly-plan/scripts/update_completion.py +69 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""按模板生成月总结 Markdown。
|
|
3
|
+
|
|
4
|
+
用法: python3 generate_monthly_md.py --data '<json_from_collect>'
|
|
5
|
+
输出: Markdown 文本到 stdout
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
parser = argparse.ArgumentParser()
|
|
15
|
+
parser.add_argument("--data", required=True)
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
data = json.loads(args.data)
|
|
20
|
+
except json.JSONDecodeError as e:
|
|
21
|
+
print("ERROR: invalid JSON: {}".format(e), file=sys.stderr)
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
name = data.get("name", "")
|
|
25
|
+
month = data.get("month", "")
|
|
26
|
+
records = data.get("records", [])
|
|
27
|
+
temp_work = data.get("temp_work", [])
|
|
28
|
+
year = 2026
|
|
29
|
+
|
|
30
|
+
lines = []
|
|
31
|
+
lines.append("# 信息技术科 {}年{}月工作总结\n".format(year, month))
|
|
32
|
+
lines.append(name + "\n")
|
|
33
|
+
|
|
34
|
+
# 一、工作计划完成情况
|
|
35
|
+
lines.append("## 一、工作计划完成情况(包括计划外完成)\n")
|
|
36
|
+
|
|
37
|
+
table_rows = []
|
|
38
|
+
idx = 0
|
|
39
|
+
|
|
40
|
+
for rec in records:
|
|
41
|
+
idx += 1
|
|
42
|
+
completion = rec.get("completion_status", "")
|
|
43
|
+
remark = rec.get("remark", "")
|
|
44
|
+
follow_up = ""
|
|
45
|
+
if completion == "未完成":
|
|
46
|
+
follow_up = "移至下月继续跟进"
|
|
47
|
+
|
|
48
|
+
table_rows.append("| {} | {} | {} | {} | {} |".format(
|
|
49
|
+
idx, rec.get("content", ""), completion, remark, follow_up
|
|
50
|
+
))
|
|
51
|
+
|
|
52
|
+
# 临时工作也插入表格
|
|
53
|
+
for tw in temp_work:
|
|
54
|
+
idx += 1
|
|
55
|
+
table_rows.append("| {} | {} | {} | {} | {} |".format(
|
|
56
|
+
idx, tw.get("content", ""), "(临时工作)", tw.get("content", "")[:50], "-"
|
|
57
|
+
))
|
|
58
|
+
|
|
59
|
+
if table_rows:
|
|
60
|
+
lines.append("| 序号 | 内容 | 完成情况 | 原因说明 | 跟进措施 |")
|
|
61
|
+
lines.append("|------|------|---------|---------|---------|")
|
|
62
|
+
lines.extend(table_rows)
|
|
63
|
+
lines.append("")
|
|
64
|
+
|
|
65
|
+
# 二、重点工作回顾
|
|
66
|
+
lines.append("## 二、重点工作回顾\n")
|
|
67
|
+
completed = [r for r in records if r.get("completion_status") == "完成"]
|
|
68
|
+
if completed:
|
|
69
|
+
lines.append("<!-- AI: 从以下已完成项中提炼1-3个重点工作 -->")
|
|
70
|
+
for r in completed[:5]:
|
|
71
|
+
lines.append("<!-- - {} -->".format(r.get("content", "")))
|
|
72
|
+
lines.append("(待 AI 根据上述材料生成)\n")
|
|
73
|
+
|
|
74
|
+
# 三、存在问题及分析
|
|
75
|
+
lines.append("## 三、存在问题及分析\n")
|
|
76
|
+
undone = [r for r in records if r.get("completion_status") == "未完成"]
|
|
77
|
+
if undone:
|
|
78
|
+
lines.append("<!-- AI: 从以下未完成项汇总共性问题 -->")
|
|
79
|
+
for r in undone:
|
|
80
|
+
lines.append("<!-- - {}: {} -->".format(r.get("content", ""), r.get("remark", "")))
|
|
81
|
+
lines.append("(待 AI 根据上述材料生成)\n")
|
|
82
|
+
|
|
83
|
+
# 四、下月工作重点
|
|
84
|
+
lines.append("## 四、下月工作重点\n")
|
|
85
|
+
lines.append("(待 AI 根据上述材料生成)\n")
|
|
86
|
+
|
|
87
|
+
print("\n".join(lines))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""计算当前周和下周的月周值,输出 JSON 到 stdout。
|
|
3
|
+
|
|
4
|
+
月周归属规则:
|
|
5
|
+
- 以周一为周起始
|
|
6
|
+
- 一周中在某个月天数 >= 4 天 → 归属该月
|
|
7
|
+
- 该月第一周从归属该月的第一个周一开始编号
|
|
8
|
+
|
|
9
|
+
用法: python3 get_current_week.py [--date YYYY-MM-DD]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
from datetime import date, timedelta
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_monday(d):
|
|
18
|
+
"""返回日期 d 所在周的周一。"""
|
|
19
|
+
return d - timedelta(days=d.weekday())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def count_month_days(week_monday):
|
|
23
|
+
"""统计一周在各个月的天数。"""
|
|
24
|
+
counts = {}
|
|
25
|
+
for i in range(7):
|
|
26
|
+
day = week_monday + timedelta(days=i)
|
|
27
|
+
counts[day.month] = counts.get(day.month, 0) + 1
|
|
28
|
+
return counts
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_yuezhou(week_monday):
|
|
32
|
+
"""根据周一日期,计算对应的月周字符串。"""
|
|
33
|
+
counts = count_month_days(week_monday)
|
|
34
|
+
target_month = max(counts, key=counts.get)
|
|
35
|
+
|
|
36
|
+
if counts[target_month] < 4:
|
|
37
|
+
target_month = (week_monday + timedelta(days=3)).month
|
|
38
|
+
|
|
39
|
+
year = week_monday.year
|
|
40
|
+
first_day = date(year, target_month, 1)
|
|
41
|
+
cursor = get_monday(first_day)
|
|
42
|
+
|
|
43
|
+
week_num = 0
|
|
44
|
+
while cursor <= week_monday + timedelta(days=7):
|
|
45
|
+
cursor_counts = count_month_days(cursor)
|
|
46
|
+
assigned = max(cursor_counts, key=cursor_counts.get)
|
|
47
|
+
|
|
48
|
+
if assigned == target_month:
|
|
49
|
+
week_num += 1
|
|
50
|
+
if cursor == week_monday:
|
|
51
|
+
return "{}月第{}周".format(target_month, week_num)
|
|
52
|
+
|
|
53
|
+
cursor += timedelta(days=7)
|
|
54
|
+
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main():
|
|
59
|
+
specified_date = None
|
|
60
|
+
for i, arg in enumerate(sys.argv):
|
|
61
|
+
if arg == "--date" and i + 1 < len(sys.argv):
|
|
62
|
+
specified_date = date.fromisoformat(sys.argv[i + 1])
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
today = specified_date or date.today()
|
|
66
|
+
this_monday = get_monday(today)
|
|
67
|
+
next_monday = this_monday + timedelta(days=7)
|
|
68
|
+
|
|
69
|
+
result = {
|
|
70
|
+
"this_week": get_yuezhou(this_monday),
|
|
71
|
+
"next_week": get_yuezhou(next_monday),
|
|
72
|
+
"this_monday": this_monday.isoformat(),
|
|
73
|
+
"next_monday": next_monday.isoformat(),
|
|
74
|
+
}
|
|
75
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
main()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""查部门人员表,返回当前用户的姓名、open_id、所属组。
|
|
3
|
+
|
|
4
|
+
流程:
|
|
5
|
+
1. lark-cli contact +get-user --as user → 获取当前用户 open_id
|
|
6
|
+
2. lark-cli base +record-search → 在部门人员表按人员字段筛选
|
|
7
|
+
3. 返回 {open_id, name, group}
|
|
8
|
+
|
|
9
|
+
用法: python3 get_user_info.py
|
|
10
|
+
输出: {"open_id": "ou_xxx", "name": "张三", "group": "移动"}
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
# 允许脚本从上级目录 import config
|
|
19
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
20
|
+
import config
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run_lark(args):
|
|
24
|
+
"""执行 lark-cli 命令并返回 JSON 结果。失败时退出。"""
|
|
25
|
+
result = subprocess.run(
|
|
26
|
+
["lark-cli"] + args,
|
|
27
|
+
capture_output=True,
|
|
28
|
+
text=True,
|
|
29
|
+
timeout=30,
|
|
30
|
+
)
|
|
31
|
+
try:
|
|
32
|
+
data = json.loads(result.stdout)
|
|
33
|
+
except json.JSONDecodeError:
|
|
34
|
+
print(json.dumps({"error": "parse_failed", "stdout": result.stdout[:500], "stderr": result.stderr[:500]}, ensure_ascii=False))
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
if not data.get("ok"):
|
|
38
|
+
print(json.dumps({"error": "lark_cli_failed", "detail": data.get("error", {})}, ensure_ascii=False))
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
return data
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
# 1. 获取当前用户
|
|
46
|
+
user_data = run_lark(["contact", "+get-user", "--as", "user"])
|
|
47
|
+
user = user_data["data"]["user"]
|
|
48
|
+
open_id = user["open_id"]
|
|
49
|
+
name = user["name"]
|
|
50
|
+
|
|
51
|
+
# 2. 在部门人员表中查找
|
|
52
|
+
record_data = run_lark([
|
|
53
|
+
"base", "+record-list",
|
|
54
|
+
"--base-token", config.BASE_TOKEN,
|
|
55
|
+
"--table-id", config.TABLE_IDS["部门人员"],
|
|
56
|
+
"--filter-json", json.dumps(
|
|
57
|
+
{"logic": "and", "conditions": [
|
|
58
|
+
["人员", "intersects", [{"id": open_id}]],
|
|
59
|
+
]},
|
|
60
|
+
ensure_ascii=False
|
|
61
|
+
),
|
|
62
|
+
"--as", "user",
|
|
63
|
+
"--format", "json",
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
records = record_data["data"]["data"]
|
|
67
|
+
if not records:
|
|
68
|
+
print(json.dumps({"error": "user_not_found", "open_id": open_id, "name": name}, ensure_ascii=False))
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
# fields 顺序: [人名, 人员, 所属组, 推送日期]
|
|
72
|
+
record = records[0]
|
|
73
|
+
group_arr = record[2] # 所属组
|
|
74
|
+
group = group_arr[0] if group_arr else ""
|
|
75
|
+
|
|
76
|
+
result = {
|
|
77
|
+
"open_id": open_id,
|
|
78
|
+
"name": name,
|
|
79
|
+
"group": group,
|
|
80
|
+
}
|
|
81
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""按用户和月周查询周计划记录。
|
|
3
|
+
|
|
4
|
+
用法: python3 query_weekly_records.py --open-id ou_xxx --week "7月第4周"
|
|
5
|
+
输出: JSON 数组
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import argparse
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
15
|
+
import config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_lark(args):
|
|
19
|
+
result = subprocess.run(
|
|
20
|
+
["lark-cli"] + args,
|
|
21
|
+
capture_output=True,
|
|
22
|
+
text=True,
|
|
23
|
+
timeout=30,
|
|
24
|
+
)
|
|
25
|
+
data = json.loads(result.stdout)
|
|
26
|
+
if not data.get("ok"):
|
|
27
|
+
print(json.dumps({"error": "lark_cli_failed", "detail": data.get("error", {})}, ensure_ascii=False))
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
return data
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
parser = argparse.ArgumentParser()
|
|
34
|
+
parser.add_argument("--open-id", required=True)
|
|
35
|
+
parser.add_argument("--week", required=True)
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
open_id = args.open_id
|
|
39
|
+
week = args.week
|
|
40
|
+
|
|
41
|
+
data = run_lark([
|
|
42
|
+
"base", "+record-list",
|
|
43
|
+
"--base-token", config.BASE_TOKEN,
|
|
44
|
+
"--table-id", config.TABLE_IDS["周计划"],
|
|
45
|
+
"--filter-json", json.dumps(
|
|
46
|
+
{"logic": "and", "conditions": [
|
|
47
|
+
["月周", "intersects", [week]],
|
|
48
|
+
["责任人", "intersects", [{"id": open_id}]],
|
|
49
|
+
]},
|
|
50
|
+
ensure_ascii=False
|
|
51
|
+
),
|
|
52
|
+
"--as", "user",
|
|
53
|
+
"--format", "json",
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
records = data["data"]["data"]
|
|
57
|
+
field_ids = data["data"]["field_id_list"]
|
|
58
|
+
|
|
59
|
+
idx_content = field_ids.index("fldjdmxh69")
|
|
60
|
+
idx_completion = field_ids.index("fldF9ebqDi")
|
|
61
|
+
idx_remark = field_ids.index("fldu5uAqGU")
|
|
62
|
+
idx_hours = field_ids.index("fldQXhORVG")
|
|
63
|
+
idx_week = field_ids.index("fldAivwku1")
|
|
64
|
+
|
|
65
|
+
result = []
|
|
66
|
+
for i, rec in enumerate(records):
|
|
67
|
+
def safe_val(arr, default=""):
|
|
68
|
+
if isinstance(arr, list):
|
|
69
|
+
return arr[0] if arr else default
|
|
70
|
+
if arr is None:
|
|
71
|
+
return default
|
|
72
|
+
return arr
|
|
73
|
+
|
|
74
|
+
record_id = (
|
|
75
|
+
rec.get("_record_id") if isinstance(rec, dict) else None
|
|
76
|
+
) or (data["data"].get("record_id_list") or [None])[i] or "rec_{}".format(i)
|
|
77
|
+
result.append({
|
|
78
|
+
"record_id": record_id,
|
|
79
|
+
"content": safe_val(rec[idx_content]),
|
|
80
|
+
"completion_status": safe_val(rec[idx_completion]),
|
|
81
|
+
"remark": safe_val(rec[idx_remark]),
|
|
82
|
+
"hours": safe_val(rec[idx_hours], None),
|
|
83
|
+
"week": safe_val(rec[idx_week]),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""批量更新周计划记录的完成情况和备注。
|
|
3
|
+
|
|
4
|
+
用法: python3 update_completion.py --records '<json_array>'
|
|
5
|
+
json_array 格式: [{"record_id": "rec_xxx", "completion": "完成", "remark": "..."}]
|
|
6
|
+
|
|
7
|
+
输出: {"ok": true, "updated": N}
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import argparse
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
17
|
+
import config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
parser.add_argument("--records", required=True)
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
records = json.loads(args.records)
|
|
27
|
+
except json.JSONDecodeError:
|
|
28
|
+
print(json.dumps({"ok": False, "error": "invalid_json"}, ensure_ascii=False))
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
update_records = {}
|
|
32
|
+
for r in records:
|
|
33
|
+
rid = r["record_id"]
|
|
34
|
+
fields = {}
|
|
35
|
+
if "completion" in r and r["completion"]:
|
|
36
|
+
fields["完成情况"] = r["completion"]
|
|
37
|
+
if "remark" in r:
|
|
38
|
+
fields["备注"] = r["remark"] if r["remark"] else ""
|
|
39
|
+
update_records[rid] = fields
|
|
40
|
+
|
|
41
|
+
payload = json.dumps({"update_records": update_records}, ensure_ascii=False)
|
|
42
|
+
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
[
|
|
45
|
+
"lark-cli", "base", "+record-batch-update",
|
|
46
|
+
"--base-token", config.BASE_TOKEN,
|
|
47
|
+
"--table-id", config.TABLE_IDS["周计划"],
|
|
48
|
+
"--json", payload,
|
|
49
|
+
"--as", "user",
|
|
50
|
+
],
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
timeout=30,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
data = json.loads(result.stdout)
|
|
58
|
+
except json.JSONDecodeError:
|
|
59
|
+
data = json.loads(result.stderr) if result.stderr else {"ok": False}
|
|
60
|
+
|
|
61
|
+
if data.get("ok"):
|
|
62
|
+
print(json.dumps({"ok": True, "updated": len(update_records)}, ensure_ascii=False))
|
|
63
|
+
else:
|
|
64
|
+
print(json.dumps({"ok": False, "error": data.get("error", {})}, ensure_ascii=False))
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|