@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,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()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sab-monthly-summary
|
|
3
|
+
description: >-
|
|
4
|
+
读取上月飞书多维表格"周计划"数据,按模板格式生成飞书云文档月总结。
|
|
5
|
+
当用户说"生成本月总结"、"写月总结"、"生成X月工作总结"时就使用此skill。
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# SAB 月总结生成
|
|
9
|
+
|
|
10
|
+
## 前置依赖
|
|
11
|
+
|
|
12
|
+
- `lark-cli` 已安装并认证
|
|
13
|
+
- Python 脚本位于本 skill 目录下的 `scripts/` 中
|
|
14
|
+
- 依赖 `lark-doc` skill 用于创建飞书文档
|
|
15
|
+
|
|
16
|
+
## 重要规则
|
|
17
|
+
|
|
18
|
+
1. **只汇总用户自己的记录**
|
|
19
|
+
2. **所有数据采集通过固化脚本**,AI 负责生成章节二、三、四的分析内容
|
|
20
|
+
3. **章节一表格严格来自数据**,不要增删或篡改
|
|
21
|
+
4. **脚本路径**:本 skill 的 SKILL.md 所在目录下的 `scripts/`
|
|
22
|
+
|
|
23
|
+
## 流程
|
|
24
|
+
|
|
25
|
+
### 步骤 1:定位用户
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python3 scripts/get_user_info.py
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 步骤 2:确定目标月份
|
|
32
|
+
|
|
33
|
+
如果用户没说具体月份,默认为上个月。
|
|
34
|
+
|
|
35
|
+
### 步骤 3:汇总数据
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python3 scripts/collect_monthly_data.py --open-id <open_id> --month <month>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
输出 JSON,含 `records`(所有周计划记录)和 `temp_work`(从备注提取的临时工作)。
|
|
42
|
+
|
|
43
|
+
### 步骤 4:生成 Markdown
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
python3 scripts/generate_monthly_md.py --data '<json_from_step3>'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
输出包含完整章节骨架的 Markdown:
|
|
50
|
+
- **章节一** 的表格已完整填充(数据驱动,包含临时工作)
|
|
51
|
+
- **章节二、三、四** 标注了原材料数据和 AI 生成提示
|
|
52
|
+
|
|
53
|
+
### 步骤 5:AI 补充分析内容
|
|
54
|
+
|
|
55
|
+
基于 Markdown 中的原材料,生成分析文字:
|
|
56
|
+
|
|
57
|
+
- **章节二 重点工作回顾**:从已完成项中挑选 1-3 项最有价值的,写背景、过程和成果
|
|
58
|
+
- **章节三 存在问题及分析**:从备注/未完成原因中汇总共性问题
|
|
59
|
+
- **章节四 下月工作重点**:列出未完成项 + 跟进措施
|
|
60
|
+
|
|
61
|
+
### 步骤 6:创建飞书文档
|
|
62
|
+
|
|
63
|
+
使用 `lark-doc` skill 创建飞书云文档:
|
|
64
|
+
|
|
65
|
+
- **标题**: `信息技术科YYYY年M月工作总结--{姓名}`
|
|
66
|
+
- **内容**: 补全后的 Markdown
|
|
67
|
+
|
|
68
|
+
## 文档格式模板
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
# 信息技术科 YYYY年M月工作总结
|
|
72
|
+
{姓名}
|
|
73
|
+
|
|
74
|
+
## 一、工作计划完成情况(包括计划外完成)
|
|
75
|
+
(表格)
|
|
76
|
+
|
|
77
|
+
## 二、重点工作回顾
|
|
78
|
+
(AI 生成)
|
|
79
|
+
|
|
80
|
+
## 三、存在问题及分析
|
|
81
|
+
(AI 生成)
|
|
82
|
+
|
|
83
|
+
## 四、下月工作重点
|
|
84
|
+
(AI 生成)
|
|
85
|
+
```
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""汇总指定月份所有周计划记录。
|
|
3
|
+
|
|
4
|
+
用法: python3 collect_monthly_data.py --open-id ou_xxx --month 7
|
|
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
|
+
try:
|
|
26
|
+
data = json.loads(result.stdout)
|
|
27
|
+
except json.JSONDecodeError:
|
|
28
|
+
print(json.dumps({"error": "parse_failed", "stdout": result.stdout[:500], "stderr": result.stderr[:500]}, ensure_ascii=False))
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
if not data.get("ok"):
|
|
32
|
+
print(json.dumps({"error": "lark_cli_failed", "detail": data.get("error", {})}, ensure_ascii=False))
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def safe_val(arr, default=""):
|
|
38
|
+
if isinstance(arr, list):
|
|
39
|
+
return arr[0] if arr else default
|
|
40
|
+
if arr is None:
|
|
41
|
+
return default
|
|
42
|
+
return arr
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main():
|
|
46
|
+
parser = argparse.ArgumentParser()
|
|
47
|
+
parser.add_argument("--open-id", required=True)
|
|
48
|
+
parser.add_argument("--month", required=True, type=int)
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
month = args.month
|
|
52
|
+
|
|
53
|
+
# 先查所有记录(按责任人筛选,内存中再按月份过滤)
|
|
54
|
+
data = run_lark([
|
|
55
|
+
"base", "+record-list",
|
|
56
|
+
"--base-token", config.BASE_TOKEN,
|
|
57
|
+
"--table-id", config.TABLE_IDS["周计划"],
|
|
58
|
+
"--filter-json", json.dumps(
|
|
59
|
+
{"logic": "and", "conditions": [
|
|
60
|
+
["责任人", "intersects", [{"id": args.open_id}]],
|
|
61
|
+
]},
|
|
62
|
+
ensure_ascii=False
|
|
63
|
+
),
|
|
64
|
+
"--as", "user",
|
|
65
|
+
"--format", "json",
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
records = data["data"]["data"]
|
|
69
|
+
field_ids = data["data"]["field_id_list"]
|
|
70
|
+
|
|
71
|
+
idx_content = field_ids.index("fldjdmxh69")
|
|
72
|
+
idx_completion = field_ids.index("fldF9ebqDi")
|
|
73
|
+
idx_remark = field_ids.index("fldu5uAqGU")
|
|
74
|
+
idx_hours = field_ids.index("fldQXhORVG")
|
|
75
|
+
idx_week = field_ids.index("fldAivwku1")
|
|
76
|
+
|
|
77
|
+
all_records = []
|
|
78
|
+
seen_ids = set()
|
|
79
|
+
|
|
80
|
+
for i, rec in enumerate(records):
|
|
81
|
+
record_id = rec.get("_record_id") if isinstance(rec, dict) else None
|
|
82
|
+
record_id = record_id or (data["data"].get("record_id_list") or [None])[i] or "rec_{}".format(i)
|
|
83
|
+
if record_id in seen_ids:
|
|
84
|
+
continue
|
|
85
|
+
seen_ids.add(record_id)
|
|
86
|
+
|
|
87
|
+
week_val = safe_val(rec[idx_week])
|
|
88
|
+
# 过滤:只保留目标月份的记录
|
|
89
|
+
if week_val.startswith("{}月".format(month)):
|
|
90
|
+
all_records.append({
|
|
91
|
+
"record_id": record_id,
|
|
92
|
+
"content": safe_val(rec[idx_content]),
|
|
93
|
+
"completion_status": safe_val(rec[idx_completion]),
|
|
94
|
+
"remark": safe_val(rec[idx_remark]),
|
|
95
|
+
"hours": safe_val(rec[idx_hours], None),
|
|
96
|
+
"week": week_val,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
# 提取临时工作(从备注中识别)
|
|
100
|
+
temp_work = []
|
|
101
|
+
skip_keywords = ["未完成", "延期", "搁置", "等待"]
|
|
102
|
+
for rec in all_records:
|
|
103
|
+
remark = rec.get("remark", "")
|
|
104
|
+
if remark and len(remark) > 10 and not any(k in remark for k in skip_keywords):
|
|
105
|
+
temp_work.append({
|
|
106
|
+
"record_id": rec["record_id"],
|
|
107
|
+
"content": remark,
|
|
108
|
+
"source_week": rec["week"],
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
user_data = run_lark(["contact", "+get-user", "--as", "user"])
|
|
112
|
+
name = user_data["data"]["user"]["name"]
|
|
113
|
+
|
|
114
|
+
result = {
|
|
115
|
+
"name": name,
|
|
116
|
+
"month": month,
|
|
117
|
+
"records": all_records,
|
|
118
|
+
"temp_work": temp_work,
|
|
119
|
+
"total_records": len(all_records),
|
|
120
|
+
"temp_work_count": len(temp_work),
|
|
121
|
+
}
|
|
122
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""共享配置"""
|
|
2
|
+
|
|
3
|
+
BASE_TOKEN = "HBR3bvERVaooCUsFfxcc0fYLndd"
|
|
4
|
+
|
|
5
|
+
TABLE_IDS = {
|
|
6
|
+
"部门人员": "tblxsJA46bTLlUEE",
|
|
7
|
+
"周计划": "tblN6nUNSwah6j2v",
|
|
8
|
+
"月计划": "tblLb0SsT38HFf8X",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
DEFAULT_DEPT = "信息技术"
|
|
12
|
+
|
|
13
|
+
# 人员字段映射
|
|
14
|
+
PERSONNEL_FIELDS = {
|
|
15
|
+
"人员": "fldbGCCdNV",
|
|
16
|
+
"所属组": "flddUR4K4c",
|
|
17
|
+
"人名": "fldrI663hx",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# 周计划可写字段
|
|
21
|
+
WEEKLY_WRITABLE_FIELDS = [
|
|
22
|
+
"责任人", # fldOavYzSr, user
|
|
23
|
+
"月周", # fldAivwku1, select
|
|
24
|
+
"工作计划内容", # fldjdmxh69, text
|
|
25
|
+
"工时", # fldQXhORVG, number
|
|
26
|
+
"完成情况", # fldF9ebqDi, select
|
|
27
|
+
"备注", # fldu5uAqGU, text
|
|
28
|
+
"计划来源", # fldIvgrltV, select
|
|
29
|
+
"业务组", # fldgFNrrqP, select
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# 周计划只读字段(不要写)
|
|
33
|
+
WEEKLY_READONLY_FIELDS = [
|
|
34
|
+
"创建时间", # fldJrGqC4Y, created_at
|
|
35
|
+
"修改人", # fldMZjLGsr, updated_by
|
|
36
|
+
"创建人", # fldnYVOlV6, created_by
|
|
37
|
+
"最后更新时间", # fldzy7XDqO, updated_at
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# 脚本可被 sab-weekly-plan 和 sab-monthly-summary 两个 skill 引用
|
|
41
|
+
# SKILL.md 中的引用路径: ../sab-shared/scripts/
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""创建下周周计划记录:迁移未完成项 + 新建用户口述的计划。
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
python3 create_next_week.py \
|
|
6
|
+
--open-id ou_xxx \
|
|
7
|
+
--group 移动 \
|
|
8
|
+
--next-week "7月第5周" \
|
|
9
|
+
--migrated '[{"content":"xxx","hours":8}]' \
|
|
10
|
+
--new '[{"content":"yyy","hours":4}]'
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import argparse
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
20
|
+
import config
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
parser = argparse.ArgumentParser()
|
|
25
|
+
parser.add_argument("--open-id", required=True)
|
|
26
|
+
parser.add_argument("--group", required=True)
|
|
27
|
+
parser.add_argument("--next-week", required=True)
|
|
28
|
+
parser.add_argument("--migrated", default="[]")
|
|
29
|
+
parser.add_argument("--new", default="[]")
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
migrated = json.loads(args.migrated)
|
|
34
|
+
new_items = json.loads(args.new)
|
|
35
|
+
except json.JSONDecodeError as e:
|
|
36
|
+
print(json.dumps({"ok": False, "error": "invalid_json", "detail": str(e)}, ensure_ascii=False))
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
all_items = migrated + new_items
|
|
40
|
+
if not all_items:
|
|
41
|
+
print(json.dumps({"ok": True, "created": 0}, ensure_ascii=False))
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
fields = ["责任人", "月周", "工作计划内容", "工时", "业务组", "计划来源"]
|
|
45
|
+
rows = []
|
|
46
|
+
for item in all_items:
|
|
47
|
+
row = [
|
|
48
|
+
[{"id": args.open_id}],
|
|
49
|
+
args.next_week,
|
|
50
|
+
item.get("content", ""),
|
|
51
|
+
item.get("hours"),
|
|
52
|
+
args.group,
|
|
53
|
+
item.get("source", "新增计划"),
|
|
54
|
+
]
|
|
55
|
+
rows.append(row)
|
|
56
|
+
|
|
57
|
+
payload = json.dumps({"fields": fields, "rows": rows}, ensure_ascii=False)
|
|
58
|
+
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
[
|
|
61
|
+
"lark-cli", "base", "+record-batch-create",
|
|
62
|
+
"--base-token", config.BASE_TOKEN,
|
|
63
|
+
"--table-id", config.TABLE_IDS["周计划"],
|
|
64
|
+
"--json", payload,
|
|
65
|
+
"--as", "user",
|
|
66
|
+
],
|
|
67
|
+
capture_output=True,
|
|
68
|
+
text=True,
|
|
69
|
+
timeout=30,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
data = json.loads(result.stdout)
|
|
74
|
+
except json.JSONDecodeError:
|
|
75
|
+
data = json.loads(result.stderr) if result.stderr else {"ok": False}
|
|
76
|
+
|
|
77
|
+
if data.get("ok"):
|
|
78
|
+
print(json.dumps({"ok": True, "created": len(rows)}, ensure_ascii=False))
|
|
79
|
+
else:
|
|
80
|
+
print(json.dumps({"ok": False, "error": data.get("error", {})}, ensure_ascii=False))
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|
|
@@ -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()
|