@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.
Files changed (28) hide show
  1. package/cli.mjs +175 -0
  2. package/package.json +21 -0
  3. package/scripts/collect_monthly_data.py +126 -0
  4. package/scripts/config.py +38 -0
  5. package/scripts/create_next_week.py +85 -0
  6. package/scripts/generate_monthly_md.py +91 -0
  7. package/scripts/get_current_week.py +79 -0
  8. package/scripts/get_user_info.py +85 -0
  9. package/scripts/query_weekly_records.py +90 -0
  10. package/scripts/update_completion.py +69 -0
  11. package/skills/sab-monthly-summary/SKILL.md +85 -0
  12. package/skills/sab-monthly-summary/scripts/collect_monthly_data.py +126 -0
  13. package/skills/sab-monthly-summary/scripts/config.py +41 -0
  14. package/skills/sab-monthly-summary/scripts/create_next_week.py +85 -0
  15. package/skills/sab-monthly-summary/scripts/generate_monthly_md.py +91 -0
  16. package/skills/sab-monthly-summary/scripts/get_current_week.py +79 -0
  17. package/skills/sab-monthly-summary/scripts/get_user_info.py +85 -0
  18. package/skills/sab-monthly-summary/scripts/query_weekly_records.py +90 -0
  19. package/skills/sab-monthly-summary/scripts/update_completion.py +69 -0
  20. package/skills/sab-weekly-plan/SKILL.md +109 -0
  21. package/skills/sab-weekly-plan/scripts/collect_monthly_data.py +126 -0
  22. package/skills/sab-weekly-plan/scripts/config.py +41 -0
  23. package/skills/sab-weekly-plan/scripts/create_next_week.py +85 -0
  24. package/skills/sab-weekly-plan/scripts/generate_monthly_md.py +91 -0
  25. package/skills/sab-weekly-plan/scripts/get_current_week.py +79 -0
  26. package/skills/sab-weekly-plan/scripts/get_user_info.py +85 -0
  27. package/skills/sab-weekly-plan/scripts/query_weekly_records.py +90 -0
  28. package/skills/sab-weekly-plan/scripts/update_completion.py +69 -0
@@ -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()
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: sab-weekly-plan
3
+ description: >-
4
+ 读写飞书多维表格"周计划"表,反馈本周周计划完成情况、创建下周周计划。
5
+ 当用户说"写本周计划"、"反馈周计划"、"更新完成情况"时就使用此skill。
6
+ ---
7
+
8
+ # SAB 周计划管理
9
+
10
+ ## 前置依赖
11
+
12
+ - `lark-cli` 已安装并认证
13
+ - Python 脚本位于本 skill 目录下的 `scripts/` 中
14
+
15
+ ## 重要规则
16
+
17
+ 1. **只操作用户自己的记录**
18
+ 2. **空完成情况必须逐条追问用户**,不允许自行推断或跳过
19
+ 3. **所有数据操作必须通过固化脚本**,不要直接拼接 lark-cli 命令
20
+ 4. **脚本路径**:本 skill 的 SKILL.md 所在目录下的 `scripts/`
21
+
22
+ ## 流程
23
+
24
+ ### 步骤 1:定位用户
25
+
26
+ ```bash
27
+ python3 scripts/get_user_info.py
28
+ ```
29
+
30
+ 从输出 JSON 中获取 `open_id`、`name`、`group`。
31
+
32
+ ### 步骤 2:计算当前月周
33
+
34
+ ```bash
35
+ python3 scripts/get_current_week.py
36
+ ```
37
+
38
+ 从输出 JSON 中获取 `this_week`、`next_week`。
39
+
40
+ ### 步骤 3:查询本周记录
41
+
42
+ ```bash
43
+ python3 scripts/query_weekly_records.py --open-id <open_id> --week <this_week>
44
+ ```
45
+
46
+ 输出为 JSON 数组,每条含 `record_id`、`content`、`completion_status`、`remark`、`hours`、`week`。
47
+
48
+ 将记录展示给用户,逐条标注当前状态。
49
+
50
+ ### 步骤 4:与用户交互确认完成情况
51
+
52
+ 展示本周所有记录清单给用户,并逐条确认:
53
+
54
+ ```
55
+ 你本周共有 N 条工作计划:
56
+
57
+ 1. [完成] 内核升级(工时 4h)
58
+ 2. [空] 织带接口(工时 8h) ← 请确认:这条完成了吗?
59
+ 3. [完成] 监控脚本(工时 2h)
60
+ 4. [空] 数据迁移(工时 6h) ← 请确认:这条完成了吗?
61
+ ```
62
+
63
+ **规则**:
64
+ - 用户已明确提及的项,直接更新
65
+ - 完成情况为空的项,必须逐条追问,不能跳过
66
+ - 用户说明未完成原因时,记录到备注
67
+
68
+ ### 步骤 5:更新完成情况
69
+
70
+ 将确认后的结果构造为 JSON 数组,调用脚本:
71
+
72
+ ```bash
73
+ python3 scripts/update_completion.py \
74
+ --records '[{"record_id":"rec_xxx","completion":"完成","remark":""},{"record_id":"rec_yyy","completion":"未完成","remark":"依赖的后端接口还没好"}]'
75
+ ```
76
+
77
+ ### 步骤 6:确认下周计划
78
+
79
+ 询问用户下周要做的新事项,以及未完成项的处置。
80
+
81
+ ### 步骤 7:创建下周记录
82
+
83
+ 将迁移项和新增项合并,调用脚本:
84
+
85
+ ```bash
86
+ python3 scripts/create_next_week.py \
87
+ --open-id <open_id> \
88
+ --group <group> \
89
+ --next-week <next_week> \
90
+ --migrated '[{"content":"织带接口","hours":8}]' \
91
+ --new '[{"content":"批量导出功能","hours":8}]'
92
+ ```
93
+
94
+ ### 步骤 8:汇报结果
95
+
96
+ 向用户总结本次操作:更新了 N 条本周完成情况,创建了 M 条下周计划。
97
+
98
+ ## 字段参考
99
+
100
+ | 字段名 | 类型 | 说明 |
101
+ |--------|------|------|
102
+ | 责任人 | user | 当前用户 open_id |
103
+ | 月周 | select | 如"7月第4周" |
104
+ | 工作计划内容 | text | 工作描述 |
105
+ | 工时 | number | 单位: 时 |
106
+ | 完成情况 | select | 完成/未完成/其他 |
107
+ | 备注 | text | 未完成原因或补充说明 |
108
+ | 计划来源 | select | 默认"新增计划" |
109
+ | 业务组 | select | 从部门人员表查得 |
@@ -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()