@lark-people/cli 0.0.17 → 0.0.19

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.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-people/cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "The official CLI for People open platform",
5
5
  "bin": {
6
6
  "people-cli": "scripts/run.js"
@@ -33,7 +33,6 @@
33
33
  "bin/people-cli_*",
34
34
  "skills/",
35
35
  "!skills/people-skill-maker/",
36
- "!skills/people-attendance/",
37
36
  "!skills/people-overtime/",
38
37
  "scripts/install.js",
39
38
  "scripts/install-wizard.js",
@@ -11,12 +11,10 @@ const DEFAULT_PKG = "@lark-people/cli";
11
11
  const DEFAULT_SKILLS_DIR = path.join(__dirname, "..", "skills");
12
12
  const isWindows = process.platform === "win32";
13
13
  // Skills excluded from installation. `people-skill-maker` is an authoring-only
14
- // skill; the people-attendance/people-overtime skills are gated until their
15
- // capabilities are ready for release (keep in sync with
16
- // internal/features/features.go).
14
+ // skill; people-overtime is gated until its capability is ready for release
15
+ // (keep in sync with internal/features/features.go).
17
16
  const EXCLUDED_BUNDLED_SKILLS = new Set([
18
17
  "people-skill-maker",
19
- "people-attendance",
20
18
  "people-overtime",
21
19
  ]);
22
20
 
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: people-attendance
3
+ version: 1.0.0
4
+ description: "飞书考勤:查询和更新考勤数据。查询/更新考勤统计设置与打卡结果。当用户需要查询打卡结果、查询统计设置或更新统计设置时使用。"
5
+ metadata:
6
+ requires:
7
+ bins: ["people-cli"]
8
+ cliHelp: "people-cli attendance --help"
9
+ ---
10
+
11
+ # attendance (v1)
12
+
13
+ **CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../people-shared/SKILL.md`](../people-shared/SKILL.md),其中包含认证、权限处理**
14
+
15
+ ## Shortcuts(推荐优先使用)
16
+
17
+ Shortcut 是对常用操作的高级封装(`people-cli attendance +<verb> [flags]`)。有 Shortcut 的操作优先使用。
18
+
19
+ | Shortcut | 说明 |
20
+ |----------|------|
21
+ | [`+stats-data-query`](../people-cli/references/people-attendance-stats-data/SKILL.md) | Query current user's attendance statistics data (查询当前用户考勤统计数据) |
22
+ | [`+stats-data-admin-query`](../people-cli/references/people-attendance-stats-data/SKILL.md) | Query attendance statistics data for specified users (查询指定用户考勤统计数据) |
23
+ | `+stats-views-query` | Query attendance statistics view settings (查询考勤统计设置) |
24
+ | `+stats-views-update` | Update attendance statistics view settings (更新考勤统计设置) |
25
+ | [`+user-task-query`](../people-cli/references/people-attendance-user-task/SKILL.md) | Query current user's attendance check results (查询当前用户打卡结果) |
26
+ | [`+user-task-admin-query`](../people-cli/references/people-attendance-user-task/SKILL.md) | Query attendance check results for specified users (查询指定用户打卡结果) |
27
+
28
+ 查询打卡结果时,按用户问题自动选择本人或管理员入口;姓名解析、工号权限不足时的人事 user_id 回退,以及休息日/未排班的结果解释,统一遵循 [打卡结果 skill](../people-cli/references/people-attendance-user-task/SKILL.md)。
29
+
30
+ 统计数据查询、人员/日期解析复用、字段选择、何时允许更新视图、更新后验证,以及部分失败/缺字段解释,统一遵循 [统计数据 skill](../people-cli/references/people-attendance-stats-data/SKILL.md)。不要在主 skill 中自行重写该流程。
31
+
32
+ `+stats-data-query` and `+stats-data-admin-query` quick facts:
33
+
34
+ - Both commands require `--stats-type`, `--start-date`, and `--end-date`. Dates must use `YYYYMMDD`.
35
+ - Defaults are `--locale zh`, `--employee-type employee_id`, `--need-history true`, and `--current-group-only true`.
36
+ - Both commands only support `user_access_token` and scope `attendance:task:readonly`.
37
+ - Do not pass `--user-id`; neither command sends `user_id`, and the personal query always uses the current user on the server side.
38
+ - `+stats-data-admin-query` requires `--user-ids`; admin authorization is enforced by the attendance service while still using the current user's token.
39
+ - Responses preserve the full API object, including `user_datas`, `invaild_user_ids`, `unauthorized_user_ids`, and any extra fields.
40
+
41
+ `+stats-views-query` examples:
42
+
43
+ ```bash
44
+ people-cli attendance +stats-views-query --stats-type daily \
45
+ --employee-type employee_id --locale zh --format json
46
+ people-cli attendance +stats-views-query --stats-type month \
47
+ --employee-type employee_no --locale zh --dry-run
48
+ ```
49
+
50
+ `+stats-views-query` behavior:
51
+
52
+ - `--stats-type` is required and must be `daily` or `month`.
53
+ - Defaults are `--locale zh` and `--employee-type employee_id`.
54
+ - The command only supports `user_access_token` and scope `attendance:task:readonly`.
55
+ - Do not pass `--user-id`; the query operates on the current logged-in user's view.
56
+
57
+ `+stats-views-update` examples:
58
+
59
+ ```bash
60
+ people-cli attendance +stats-views-update --stats-type daily \
61
+ --enable-fields 50102,50103 --disable-fields 50104
62
+ people-cli attendance +stats-views-update --stats-type month \
63
+ --disable-fields 50103 --dry-run
64
+ ```
65
+
66
+ `+stats-views-update` behavior:
67
+
68
+ - Field inputs use `code` only. Provide at least one of `--enable-fields` or `--disable-fields`.
69
+ - Codes are deduplicated within a list; conflicts across the two lists fail validation.
70
+ - Unknown, read-only, non-switch, or incomplete query response fields fail before update.
71
+ - Defaults are `--locale zh` and `--employee-type employee_id`.
72
+ - Only `user_access_token` is supported. Do not pass `--user-id`; the query does not send `user_id` and always operates on the current user.
73
+ - The CLI first queries the current full view, keeps unspecified values unchanged, then submits a complete update body. The response body still contains `view.user_id`.
74
+ - Updating requires user scope `attendance:task`.
75
+
76
+ ## API Resources
77
+
78
+ ```bash
79
+ people-cli schema attendance.<resource>.<method> # 调用 API 前必须先查看参数结构
80
+ people-cli attendance <resource> <method> [flags] # 调用 API
81
+ ```
82
+
83
+ > **重要**:使用原生 API 时,必须先运行 `schema` 查看 `--data` / `--params` 参数结构,不要猜测字段格式。
84
+
85
+ ### user_stats_data
86
+
87
+ - `query` —
88
+ - `admin_query` —
89
+
90
+ ### user_stats_view
91
+
92
+ - `query` —
93
+ - `update` —
94
+
95
+ ### user_task
96
+
97
+ - `query` —
98
+ - `admin_query` —
99
+
100
+ ## 权限表
101
+
102
+ | 方法 | 所需 scope |
103
+ |------|-----------|
104
+ | `user_stats_data.query` | `attendance:task:readonly` |
105
+ | `user_stats_data.admin_query` | `attendance:task:readonly` |
106
+ | `user_stats_view.query` | `attendance:task:readonly` |
107
+ | `user_stats_view.update` | `attendance:task` |
108
+ | `user_task.query` | `attendance:task:readonly` |
109
+ | `user_task.admin_query` | `attendance:task:readonly` |
110
+
@@ -0,0 +1,426 @@
1
+ # submission (submission.get / submission.query_month_summary)
2
+
3
+ 用于查询**当前登录用户**:
4
+
5
+ - 指定日期的出勤/加班记录(`submission.get`)
6
+ - 指定月份的月累计汇总(`submission.query_month_summary`)
7
+
8
+ > **前置条件:** 先阅读 [`../../people-shared/SKILL.md`](../../people-shared/SKILL.md)
9
+
10
+ > [!CAUTION]
11
+ > **只支持查询当前登录用户,不支持查询其他人;只读,不支持提交/修改/撤回/删除。**
12
+ > **写操作(提交 / 补提交 / 修改 / 撤回 / 删除 / 导出)一律直接拒绝,不要尝试调用 CLI。**
13
+
14
+ ## 用法
15
+
16
+ ### 步骤1:查看 schema(必须)
17
+
18
+ ```bash
19
+ people-cli schema attendance.submission.get
20
+ people-cli schema attendance.submission.query_month_summary
21
+ ```
22
+
23
+ > 两个接口均为 GET 请求,所有参数均在 `--params` 中传递(query 参数)。
24
+
25
+ ### 步骤2:调用
26
+
27
+ ```bash
28
+ people-cli attendance submission get --params '<query_params_json>' --as user
29
+ people-cli attendance submission query_month_summary --params '<query_params_json>' --as user
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 方法:submission.get
35
+
36
+ ### --params (query 参数)
37
+
38
+ | 参数 | 类型 | 必填 | 取值 / 格式 | 说明 |
39
+ |---|---|---|---|---|
40
+ | `date` | string | 是 | `YYYYMMDD`(**字符串**,如 `"20260408"`) | 查询日期;**不接受** `2026-04-08` / 整数 `20260408` |
41
+ | `time_type` | string | 是 | `"1"` = 出勤提报 | **必传**;缺失会报 `[140001] time_type must be Attendance/Overtime`。本 skill 永远传 `"1"`,不支持加班查询 |
42
+ | `accept_language` | string | 否 | **仅 `"zh-CN"` 或 `"en-US"`**(默认 `zh-CN`) | 语言环境;**字面值严格**,下方 NOTE 详述 |
43
+
44
+ > [!NOTE]
45
+ > - `date` / `time_type` **都是字符串**。常见错误:传整数、传 `"day"` / `"Attendance"` / `"PeriodTime"` / `"0"` / `"3"` / `"4"` 等都会被服务端拒绝。本 skill 唯一合法 `time_type` 取值是 `"1"`。
46
+ > - 不要拆成 `--data`,这是 GET 接口,所有参数都放在 `--params`。
47
+
48
+ > [!CAUTION]
49
+ > **`accept_language` 字面值严格**:服务端只识别 **`zh-CN`** 与 **`en-US`** 这两个值。传 `en` / `en_US` / `en-us` / `en_us` / `english` / `EN-US` / `zh` / `zh_CN` / `cn` 等任何变体,服务端都会**默默回退到中文**,让英文用户拿到中文 `display_name` / 中文 `status` / 中文 `calculation_tag`。
50
+ >
51
+ > 历史评测复现:用户用英文提问 "what was my attendance last week",LLM 直接传 `accept_language="en"`,结果返回的是「【SGP】正常工作时间」「已通过」等中文字段,明显跑偏。
52
+ >
53
+ > **强制行为**:
54
+ > - 英文上下文(用户用英文提问 / 系统语言为英文 / query 主词是英文 attendance 业务词)→ **必须**传 `accept_language="en-US"`,不允许省略。
55
+ > - 中文上下文 → 显式传 `accept_language="zh-CN"`,不省略。
56
+ > - 任何输入变体(`en` / `en_us` / `english` 等)→ LLM 必须先**归一化**为 `en-US` / `zh-CN` 再写入 `--params`。
57
+
58
+ ### 输出字段映射
59
+
60
+ 返回 JSON,主要关注 `data.rows`(数组)。每行 `row` 形如:
61
+ ```json
62
+ {
63
+ "column": [ {"code": "...", "value": "..."}, ... ],
64
+ "children": [ { "column": [ {"code": "...", "value": "..."}, ... ] }, ... ] // 可选;存在时表示当天被拆分
65
+ }
66
+ ```
67
+ 即 `rows[].column[]` 是「键值对」数组,`code` 为字段标识,`value` 为已格式化的展示文本。`rows[].children[]` 与 `rows[].column[]` 同结构,每个 child 的 `column[]` 是当天的一个**拆分明细**。
68
+
69
+ > [!IMPORTANT]
70
+ > **`children` 拆分行(critical,硬规则,唯一逻辑)**:
71
+ > - 服务端在某些场景会把同一天的出勤拆成多条(不同 `calculation_tag` / 不同分段口径)。此时 `rows[i]` 同时返回**外层汇总**(`column`)与**多条明细**(`children[]`)。
72
+ > - **一句话总则**:**只要 `rows[i]` 含非空 `children` 字段,就完全以 `children[].column` 为准**(包含 `calculation_tag` / `status` / `duration` 全部字段,外层 `column` 一律不再读取)。**只有当 `rows[i]` 没有 `children`(或 `children=[]`)时,才取外层 `column`**。
73
+ > - 外层 `column` 在被拆分场景下只是壳子(`calculation_tag` 通常为空字符串);真实数据全部在 `children[].column` 内。
74
+ > - **统一处理(不区分查询类型)**:
75
+ > | 查询类型 | 取值口径 |
76
+ > |---|---|
77
+ > | 当天总时长 | 含 children → `sum(children[].column.duration)`;无 children → 外层 `column.duration` |
78
+ > | 当天 status / 多状态拆分 | 含 children → 遍历 `children[].column.status`,分别列出;无 children → 外层 `column.status` |
79
+ > | calculation_tag 过滤 / 命中 / 求和 | 含 children → 遍历 `children[].column.calculation_tag`;无 children → 外层 `column.calculation_tag` |
80
+ >
81
+ > - **多标签 split 强制规则(命中前必须执行)**:
82
+ > - `calculation_tag` 是**逗号分隔的多标签字符串**(CN:`"【SGP】常规出勤,【SGP】2 倍日薪"`;EN:`"Regular,2 Daily"`)。
83
+ > - 必须先 `tags = [t.strip() for t in calc_tag.split(",") if t.strip()]`,再用 `target in tags` 列表成员精确匹配。
84
+ > - **禁止**直接 `target in calc_tag` 子串匹配(会误命中相似标签、漏命中外层空标签)。
85
+ > - 同义等价集合(同行任一即可命中,但只比对当前响应语种的字面值):
86
+ > - 「2 倍日薪 / 2 Daily / 2x Daily Salary」
87
+ > - 「1.5 倍日薪 / 1.5 Daily / 1.5x Daily Salary」
88
+ > - 「1.5 倍时薪 / 1.5 Hourly / 1.5x Hourly」
89
+ > - 「常规出勤 / Regular」
90
+ > - 「正常工作时间 / Normal Working Hours」
91
+ > - 「调休假 / Time off in Lieu (Overtime)」
92
+ >
93
+ > - **禁止**对同一天同时把外层 `column` 与 `children[]` 都纳入聚合(会重复计 duration / 错误命中标签)。
94
+ > - **禁止**因为外层 `calculation_tag=""` 就给用户回报「无标签」或「无记录」;必须以 children 为准。
95
+ > - **禁止**「不涉及 calculation_tag 时优先外层 column」的例外分支——已废弃。只要有 children,就完全走 children。
96
+ > - **禁止**直接对 `calculation_tag` 做子串包含匹配,必须 split 后做列表成员精确匹配。
97
+ > - **禁止**跨语种联合命中(中文响应里命中英文标签或反之),先按 `accept_language` 锁定语种再比对。
98
+
99
+ #### 高频 `code` 取值
100
+
101
+ | code | 含义 | 典型 value 示例 | LLM 处理建议 |
102
+ |---|---|---|---|
103
+ | `attendance_date` | 出勤日期 | `"2026-04-08"` | 直接展示 |
104
+ | `display_name` | 展示名称 | `"出勤"` / `"加班"` | 直接展示 |
105
+ | `status` | 审批状态 | `"已通过"` / `"审批中"` / `"已拒绝"` / `"更正审批中"` | 过滤时按字符串完全匹配 |
106
+ | `calculation_tag` | 计算标签 / 口径(**逗号分隔的多标签字符串**) | `"Normal Working Hours,Regular"` / `"Regular,2 Daily"` / `"1.5 Hourly,Regular"` / `"【SGP】常规出勤,【SGP】2 倍日薪"` / `"【SGP】1.5 倍时薪,【SGP】常规出勤"` | **必须**先 `split(",")` 成数组并 `strip` 空格,再用 `target in tags` 列表成员精确匹配;**禁止** substring 包含匹配 |
107
+ | `duration` | 时长数值 | `"8"` / `"7.5"` | 转 float 后求和、比较;**单位见 `unit`** |
108
+ | `unit` | 时长单位 | `"小时"` / `"hours"` | 与 `duration` 配对返回 |
109
+ | `start_time` | 开始时间 | `"2026-04-08 09:00"` | — |
110
+ | `end_time` | 结束时间 | `"2026-04-08 18:00"` | — |
111
+
112
+ #### LLM 输出统一结构(推荐)
113
+
114
+ 每条记录最终向用户回报时,建议统一拆分为 4 字段:
115
+
116
+ ```
117
+ {
118
+ "attendance_date": "<row.column.attendance_date>",
119
+ "duration": "<row.column.duration>",
120
+ "unit": "<row.column.unit>",
121
+ "status": "<row.column.status>"
122
+ }
123
+ ```
124
+
125
+ > 当 `row` 带 `children` 时(**唯一逻辑**):
126
+ > - **完全以 `children[].column` 为准**,每条单独输出 `attendance_date` / `duration` / `unit` / `status` / `calculation_tag`,外层 `column` 一律不再读取。
127
+ > - 当天总时长 = `sum(children[].column.duration)`;当天总状态/标签按 children 逐条列出。
128
+ > - **绝不能**汇报 "无 calculation_tag" 仅因为外层 `column.calculation_tag=""`;必须以 children 为准。
129
+
130
+ #### jq 抽取模板(可直接拼到命令尾部)
131
+
132
+ > ⚠️ 唯一逻辑:**有 children 完全走 children;无 children 才用外层 column**。下方所有模板已统一遵循此规则,请勿混用其他写法。
133
+
134
+ ```bash
135
+ # 1. 提取当日所有"有效行"的 attendance_date / duration / unit / status / calculation_tag
136
+ # 规则:若行带 children 则展开为 children[].column;否则用外层 column
137
+ ... --as user | jq '
138
+ .data.rows // [] | map(
139
+ if (.children // []) | length > 0
140
+ then .children
141
+ else [{column: .column}]
142
+ end
143
+ ) | add | map(
144
+ (.column // []) as $c
145
+ | {
146
+ attendance_date: ($c[] | select(.code=="attendance_date") | .value),
147
+ duration: ($c[] | select(.code=="duration") | .value),
148
+ unit: ($c[] | select(.code=="unit") | .value),
149
+ status: ($c[] | select(.code=="status") | .value),
150
+ calculation_tag: ($c[] | select(.code=="calculation_tag") | .value)
151
+ }
152
+ )'
153
+
154
+ # 2. 累加当日 duration(小时)—— 统一 children 优先:有 children 走 children 求和,无 children 用外层 column
155
+ ... --as user | jq '
156
+ [
157
+ .data.rows // [] | .[]
158
+ | (if (.children // []) | length > 0 then .children else [{column: .column}] end)
159
+ | .[].column[]? | select(.code=="duration") | .value | tonumber
160
+ ] | add // 0'
161
+
162
+ # 3. 过滤 status=已通过 且 calculation_tag 命中 "1.5 倍日薪" / "1.5 Daily" / "1.5x Daily Salary"
163
+ # (children 优先 + split 后列表成员精确匹配,而非子串包含)
164
+ ... --as user | jq '
165
+ ["1.5 倍日薪","1.5 Daily","1.5x Daily Salary"] as $targets
166
+ | .data.rows // [] | map(
167
+ if (.children // []) | length > 0 then .children else [{column: .column}] end
168
+ ) | add | map(
169
+ (.column // []) | map({(.code):.value}) | add
170
+ ) | map(. + {tags: ((.calculation_tag // "") | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length>0)))})
171
+ | map(select(
172
+ (.status == "已通过" or .status == "Approved")
173
+ and ([.tags[] | select(. as $t | $targets | index($t))] | length > 0)
174
+ ))'
175
+
176
+ # 4. 多日 fan-out 后按 calculation_tag 列表成员精确匹配求和(统一 children 优先 + split 精确匹配)
177
+ for d in 20260601 20260602 20260603 ... 20260607; do
178
+ people-cli attendance submission get \
179
+ --params "{\"date\":\"$d\",\"time_type\":\"1\",\"accept_language\":\"zh-CN\"}" --as user
180
+ sleep 1
181
+ done | jq -s '
182
+ ["2 倍日薪","2 Daily","2x Daily Salary"] as $targets
183
+ | [ .[].data.rows // [] | map(
184
+ if (.children // []) | length > 0 then .children else [{column: .column}] end
185
+ ) | add ] | add
186
+ | map((.column // []) | map({(.code):.value}) | add)
187
+ | map(. + {tags: ((.calculation_tag // "") | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length>0)))})
188
+ | map(select([.tags[] | select(. as $t | $targets | index($t))] | length > 0))
189
+ | (map(.duration | tonumber) | add // 0)'
190
+ ```
191
+
192
+ #### 空数据处理(重要)
193
+
194
+ > [!CAUTION]
195
+ > **判定"空数据"之前,必须先检查响应顶层 `code` 字段**。如果 `code == 99991400`(频控/限流),即便 `data` 为空或缺失,也**绝不能**当作"无记录"返回,必须按下文「99991400 限流处理」流程告知用户并提示重试。
196
+ >
197
+ > 当上游返回 `code == 0` 且 `data: {}` / `data.rows: []` 时,才进入"无记录"判断。可能原因:
198
+ > 1. 当日确实未提报(合法的「无记录」);
199
+ > 2. 当前 UAT / 环境对应的账号本身没有出勤数据(PPE 测试账号常见);
200
+ > 3. 命中权限点缺失或灰度未开(参见错误码 1000020847 / 1000020904)。
201
+ >
202
+ > 建议回退路径:
203
+ > 1. 若区间 ≥ 一个月,改用 `query_month_summary` 兜底验证账号在该月是否有任何记录;
204
+ > 2. 若整周 / 整月都查不到 → 向用户提示「当前账号在该环境下无出勤数据,请确认 UAT / 环境(`people-cli doctor`)」,**不要**直接给出"出勤为 0 小时"的结论。
205
+
206
+ #### 99991400 限流处理(关键,禁止当作空数据)
207
+
208
+ > [!CAUTION]
209
+ > **遇到 `code == 99991400` 时,绝对不能向用户回报"该日/该月无出勤记录"或"出勤为 0 小时"**。该错误码是上游频控(rate limit),返回的 `data` 为空是限流副作用,**不是真实的业务结果**。
210
+ >
211
+ > 标准处理流程:
212
+ > 1. **识别**:在每次 `submission.get` / `submission.query_month_summary` 调用后,先用 jq 检查 `.code`;命中 99991400 必须立即停止把该次结果计入聚合。
213
+ > ```bash
214
+ > ... --as user | jq 'if .code==99991400 then "RATE_LIMITED" else . end'
215
+ > ```
216
+ > 2. **退避重试**:对触发限流的那一天/那一月,串行 sleep 2~3s 后**最多重试 2 次**;多日批处理时整体改为串行 + `sleep 2`,并优先用 `query_month_summary` 替代逐日 30 次的硬刷。
217
+ > 3. **回报用户**:若重试后仍然 99991400,**必须**明确告诉用户:
218
+ > > 「查询 `<具体日期 / 月份>` 时触发了出勤接口的限流(错误码 99991400),这一天/这一月的数据**未能取回**,请稍后重试。」
219
+ > 并把受影响的日期清单一并列出,让用户知道哪些天需要重试,**禁止**用"无记录 / 0 小时 / 空数据"等措辞替代。
220
+ > 4. **聚合规则**:多日聚合(本周/本月累计)中,若某些日期命中 99991400,结果必须**显式标注"部分日期数据缺失(限流)"**,不要把这些日期当作 0 小时合并求和。
221
+
222
+ #### 常见报错(错误码引导)
223
+
224
+ | code | 含义 | 处理 |
225
+ |---|---|---|
226
+ | `140001` | `time_type must be Attendance or Overtime` | **缺少 / 错传 `time_type`**。本 skill 必须传 `"1"`,不要尝试 `Attendance` / `day` / `0` 等字符串 |
227
+ | `1000020904` | 当前员工未灰度到出勤加班新功能 | 原文透传给用户,不要重试 |
228
+ | `1000020847` | 当前账号没有访问出勤/加班数据的权限点 | 提示「当前账号没有查询该数据的权限点,请联系管理员开通后再试」,**不要重试** |
229
+ | `99991400` | 频控(rate limit) | 串行 + `sleep 1~3s` 退避;区间长时改用 `query_month_summary` 整月一次拿走 |
230
+ | `99991677` | UAT / token 过期 | 提示用户 `people-cli auth login` 重新授权或 `set_uat <new_token>`;**不要继续循环重试** |
231
+
232
+ ---
233
+
234
+ ## 方法:submission.query_month_summary
235
+
236
+ > [!CAUTION]
237
+ > **接口能力硬限制(必读)**:
238
+ > 1. **不返回 children 拆分明细**——只返回顶层折算汇总(如「常规出勤 156 小时」),任何 calculation_tag 拆分都拿不到。
239
+ > 2. **数值已含「审批中」**——已通过 + 所有审批中变体(审批中 / 撤销审批中 / 更正审批中)的合计,无法严格按单一 status 分离。
240
+ > 3. **不支持单日 / 周 / 区间查询**——仅支持单月。
241
+ >
242
+ > **必须强制走 `submission.get` 逐日 fan-out 的月度问题(禁用本接口,否则结果会错)**:
243
+ > - ❌ 「本月 2 倍日薪 / 1.5 倍时薪 / 常规出勤 / 正常工作时间 / 调休假 时长」→ 涉及 calculation_tag 拆分,本接口无 children
244
+ > - ❌ 「本月 calculation_tag = X 的天数 / 时长」→ 同上
245
+ > - ❌ 「本月已通过的出勤 / 仅审批中 / 仅已拒绝」→ 涉及严格 status 过滤,本接口已含审批中
246
+ > - ❌ 「本月含 X 标签且 status = Y 的出勤」→ 涉及双重过滤
247
+ > - ❌ 「How many hours of 2x daily salary attendance do I have this month?」→ 同上
248
+ > - ❌ 评测真实复盘:用户问「本月 2 倍日薪时长」时调本接口看顶层 → 没有 2 倍日薪 → 错误回报「本月没有 2 倍日薪」。事实上 2 倍日薪只存在于 `submission.get` 的 children 里,必须逐日 fan-out。
249
+
250
+ > [!IMPORTANT]
251
+ > **数据口径(必须主动告知用户,不可省略)**:本接口返回的月度汇总**包含「审批中」状态的记录**(含「审批中 / 撤销审批中 / 更正审批中」等所有审批中变体),即数值是已通过加上各类审批中的合计。
252
+ >
253
+ > - 用户问「本月总共出勤多少 / 上月总时长 / 某月汇总」等**通用**月度统计(不涉及标签 / 严格 status 过滤)→ 默认用本接口(一次调用即可拿到结果,无需 30 次 fan-out),但**必须**在回复中显式说明该数值已包含审批中的记录,不要只把数字甩给用户。
254
+ > - 用户问及 calculation_tag / 严格 status 过滤的月度数据 → **不能**用本接口,必须改用 `submission.get` 逐日 fan-out + children 解析,并向用户解释「月度汇总接口不返回 children 拆分、且已含审批中,已切换为按日聚合查询」。
255
+ >
256
+ > **回复模板(必带口径说明,语种镜像)**:
257
+ > - 中文回复尾部追加:
258
+ > ```
259
+ > 说明:本月度汇总包含「审批中」状态的记录(含撤销审批中、更正审批中等),即数值为已通过与各类审批中的合计。
260
+ > ```
261
+ > - 英文回复尾部追加:
262
+ > ```
263
+ > Note: This monthly summary includes records with the Approving / Pending status (covering Approving, Withdraw approving, Pending correction, etc.). The total is the sum of Approved plus all in-approval records.
264
+ > ```
265
+
266
+ ### --params (query 参数)
267
+
268
+ | 参数 | 类型 | 必填 | 取值 / 格式 | 说明 |
269
+ |---|---|---|---|---|
270
+ | `month` | string | 是 | `YYYYMM`(**字符串**,如 `"202605"`、跨年 `"202512"`) | **不接受** `2026-05` / 整数 `202605` |
271
+ | `accept_language` | string | 否 | **仅 `"zh-CN"` 或 `"en-US"`**(默认 `zh-CN`) | 语言环境;**字面值严格**,传 `en` / `en_US` / `en-us` / `en_us` / `english` 等都会被默默回退到中文,必须先归一化为 `en-US` / `zh-CN` 再传 |
272
+
273
+ > [!NOTE]
274
+ > 相对时间(this month / last month / 今年 1 月)必须由 LLM 自行换算为 `YYYYMM` 字符串。换算口径:
275
+ > - this month / 这个月 / 本月 → 当前年月(如 2026-06-10 → `"202606"`)
276
+ > - last month / 上个月 → 当前年月 - 1(跨年时年份要 -1,如 2026-01 的上月 = `"202512"`)
277
+ > - 今年 N 月 / January / May → `<当前年><两位月>`(如「今年 1 月」于 2026 → `"202601"`)
278
+
279
+ ### 输出字段映射
280
+
281
+ 返回 JSON,主要关注 `data.attendance_summary_stats`(数组),每项结构:
282
+
283
+ | 字段 | 类型 | 含义 | 示例 |
284
+ |---|---|---|---|
285
+ | `label` | string | 汇总项标题 | `"6月出勤时长"` |
286
+ | `value` | string | 汇总值(**带单位的字符串**,需自行拆分) | `"126 小时"` |
287
+ | `detail_stats_lists` | array | 明细列表 | `[{"label":"【SGP】常规出勤","value":"126 小时"}]` |
288
+
289
+ #### LLM 输出统一结构(推荐)
290
+
291
+ 把 `value: "126 小时"` 拆分为 `{duration: "126", unit: "小时"}`,最终向用户回报:
292
+ ```
293
+ {
294
+ "attendance_date": "<对应月份,例如 2026-06>",
295
+ "duration": "<value 中的数值部分>",
296
+ "unit": "<value 中的单位部分>",
297
+ "detail": [{label, duration, unit}, ...]
298
+ }
299
+ ```
300
+
301
+ #### jq 抽取模板
302
+
303
+ ```bash
304
+ # 输出 {label, duration, unit}
305
+ ... --as user | jq '
306
+ .data.attendance_summary_stats // [] | map(
307
+ .value as $v
308
+ | {
309
+ label: .label,
310
+ duration: ($v | capture("(?<n>[0-9.]+)").n // ""),
311
+ unit: ($v | capture("[0-9.]+\\s*(?<u>.*)").u // ""),
312
+ detail: (.detail_stats_lists // [])
313
+ }
314
+ )'
315
+ ```
316
+
317
+ #### 常见报错
318
+
319
+ | code | 含义 | 处理 |
320
+ |---|---|---|
321
+ | `1000020904` | 当前员工未灰度到出勤加班新功能 | 原文透传给用户,不要重试 |
322
+ | `1000020847` | 当前账号没有相应权限点 | 提示「当前账号没有查询该数据的权限点,请联系管理员开通后再试」,**不要重试** |
323
+
324
+ #### 空数据处理
325
+
326
+ > [!NOTE]
327
+ > 当 `data.attendance_summary_stats` 为空 / 缺失时,**不要**直接给出「该月出勤为 0」的结论:
328
+ > - 先回头检查 `month` 参数格式是否正确(必须 `YYYYMM` 字符串);
329
+ > - 如果格式正确仍为空,提示用户「该月在当前账号 / 环境下无出勤汇总数据,请确认 UAT / 环境」。
330
+
331
+ ---
332
+
333
+ ## 多日聚合查询规则(重要)
334
+
335
+ 接口本身**只支持单日**。所有「本周/上周/本月/上月/最近两周/上月最后一周」等范围意图,必须按下面的步骤执行:
336
+
337
+ 1. 解析日期范围 → 列出每一个具体日期(YYYYMMDD),未来日期不查询。
338
+ 2. **优先判断能否走 `query_month_summary` 兜底**:当意图是「整月累计」时,一次月度汇总即可,**不要**逐日 30 次。
339
+ 3. 否则逐日调用 `submission.get`(一天一次),**串行**调用 + 小退避(≥1s/次),避免 99991400 限流。
340
+ 4. 在内存中聚合 `rows[].column`:
341
+ - 求和:`sum(duration)` 用于「本周/上周累计出勤时长」。
342
+ - 计数:满足条件的天数,如「上周 < 8 小时的天数」。
343
+ - 过滤:按 `status` / `calculation_tag` / `duration` 阈值过滤;多条件用 AND(如「duration > 8 且 status = 已通过」)。
344
+ - 比较:找最大/最小,如「这周哪天最长」「最近两周哪周更多」。
345
+ 5. 输出时回显具体日期 + 字段值,避免只返回总和而丢失上下文。
346
+
347
+ ### 工作日 / 周末处理
348
+
349
+ - 「本周/上周」**默认 fan-out 周一 ~ 周日 7 天**(含周末),不要主动剔除上周六/上周日;典型 query:`which days last week did I work more than 9 hours` / `上周哪几天出勤超过 9 小时`。
350
+ - 只有当用户明确写出「上周工作日 / 本周工作日 / weekdays」等限定词时,才裁剪到周一~周五。
351
+ - 「本月第 N 个工作日」:从月初起跳过周六周日累计计数,得到目标日期再查询。
352
+ - 用户明确问「上周六/上周日有没有出勤」时仍按所给日期查询;无记录直接告知。
353
+
354
+ ### 时间推理参考(基于当前日期 = 2026-06-10 周三,仅作示例口径,实际应以系统当前日期为准)
355
+
356
+ | 表达 | 解析 |
357
+ |---|---|
358
+ | 今天 / today | 2026-06-10 |
359
+ | 昨天 / yesterday | 2026-06-09 |
360
+ | 前天 | 2026-06-08 |
361
+ | 三天前 / 5 天前 | 2026-06-07 / 2026-06-05 |
362
+ | 这周 / 本周 | 周一(2026-06-08) ~ 今天 |
363
+ | 上周 | 上周一(2026-06-01) ~ 上周日(2026-06-07) |
364
+ | 上上周 | 再往前推 7 天 |
365
+ | 上周一/二/.../五/六/日 | 上周对应工作日 |
366
+ | last Monday / last Friday | 同「上周X」 |
367
+ | 这个月 | 当月 1 号 ~ 今天,month=`"202606"` |
368
+ | 上个月 | 上月 1 号 ~ 上月最后一天,month=`"202605"` |
369
+ | 去年 12 月 | 跨年,month=`"202512"` |
370
+ | 今年 1 月 / January | month=`"202601"` |
371
+ | 月初第一天 / 第一个工作日 | 当月 1 号或第一个非周末日 |
372
+ | May / June 2nd 2026 | 2026-05 / 2026-06-02 |
373
+ | 下周一 / 明天 / 未来日期 | **拒绝查询** |
374
+
375
+ ## 不支持的操作(直接拒绝,不调用 CLI)
376
+
377
+ | 用户意图 | 处理 |
378
+ |---|---|
379
+ | 帮我提交 / 补提交 / submit / backfill 出勤 | 「当前 skill 仅支持查询出勤记录,不支持提交、补提交或修改」 |
380
+ | 帮我撤回 / 取消 / 修改 / 删除出勤 | 「不支持撤回/取消/修改/删除出勤记录」 |
381
+ | 查询他人 / 工号 / employee_id / 下属出勤 | 「只支持查询当前登录账号本人的出勤提报记录」 |
382
+ | 查询未来日期(如下周一) | 「不支持查询未来日期的出勤记录」 |
383
+ | 非法日期(2 月 30 日、13 月) | 「只支持查询合法日期的出勤记录」 |
384
+ | 出勤够不够抵扣迟到 / 加班补贴怎么算 | 「当前只支持查询出勤数据,不支持其他联动能力」 |
385
+ | 导出出勤记录 | 「不支持导出操作」 |
386
+ | 加班 / 补加班 / 加班审批 | 「当前 skill 不支持加班查询或审批」 |
387
+
388
+ ## 所需权限
389
+
390
+ | Scope |
391
+ |---|
392
+ | `corehr:people_cli` |
393
+
394
+ ## 示例
395
+
396
+ ```bash
397
+ # 查看参数结构
398
+ people-cli schema attendance.submission.get
399
+ people-cli schema attendance.submission.query_month_summary
400
+
401
+ # 查询指定日期的出勤记录
402
+ people-cli attendance submission get \
403
+ --params '{"date":"20260408","time_type":"1","accept_language":"zh-CN"}' --as user
404
+
405
+ # 查询本月累计汇总(推荐:一次调用)
406
+ people-cli attendance submission query_month_summary \
407
+ --params '{"month":"202606","accept_language":"zh-CN"}' --as user
408
+
409
+ # 多日聚合:上周累计出勤(逐日查询后求和;串行 + sleep 退避)
410
+ for d in 20260601 20260602 20260603 20260604 20260605; do
411
+ people-cli attendance submission get \
412
+ --params "{\"date\":\"$d\",\"time_type\":\"1\",\"accept_language\":\"zh-CN\"}" --as user
413
+ sleep 1
414
+ done
415
+ # → 在内存中按 calculation_tag / status / duration 过滤后再求和
416
+
417
+ # 跨年单月汇总
418
+ people-cli attendance submission query_month_summary \
419
+ --params '{"month":"202512","accept_language":"zh-CN"}' --as user
420
+
421
+ # 英文环境:accept_language 必须是 "en-US"(不是 en / en_US / en-us / en_us)
422
+ people-cli attendance submission get \
423
+ --params '{"date":"20260609","time_type":"1","accept_language":"en-US"}' --as user
424
+ people-cli attendance submission query_month_summary \
425
+ --params '{"month":"202606","accept_language":"en-US"}' --as user
426
+ ```