weaver-work-cli 0.1.32 → 0.1.33
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/README.md +2 -2
- package/dist/chunks/{archive-UMGX72K6.js → archive-FVFY6GZN.js} +2 -2
- package/dist/chunks/{archive-oa-N2IYBLF3.js → archive-oa-NW3OD4UV.js} +2 -2
- package/dist/chunks/{asset-GLEGKFG5.js → asset-7QJKMIDV.js} +2 -2
- package/dist/chunks/{attachment-content-AQNX5N3S.js → attachment-content-OSAVA6K3.js} +3 -2
- package/dist/chunks/{blog-E44OTVDA.js → blog-I7SDPNW4.js} +2 -2
- package/dist/chunks/{caizhilian-DQUFZXZL.js → caizhilian-O37ZIQNY.js} +2 -2
- package/dist/chunks/{calendar-JKPIRKFD.js → calendar-7KXE4ED3.js} +2 -2
- package/dist/chunks/{chunk-4YC43QQ4.js → chunk-2FLWXFVS.js} +4 -4
- package/dist/chunks/{chunk-QWY6PGL6.js → chunk-2LV74IWP.js} +361 -56
- package/dist/chunks/chunk-3SO6QL6U.js +73 -0
- package/dist/chunks/{chunk-DW6AUUKS.js → chunk-6ATGWRZM.js} +6 -4
- package/dist/chunks/chunk-BYATLLEF.js +68 -0
- package/dist/chunks/chunk-BYBSCN4X.js +932 -0
- package/dist/chunks/{chunk-2GA5ZIQL.js → chunk-D223PTXU.js} +1 -0
- package/dist/chunks/{chunk-H7J4DC5R.js → chunk-EEWPQE3O.js} +2 -2
- package/dist/chunks/{chunk-6RKD4DRZ.js → chunk-GFPXQIY2.js} +2 -2
- package/dist/chunks/{chunk-C6R3B2ML.js → chunk-HEL5XTYI.js} +12 -6
- package/dist/chunks/{chunk-CVZ2JOSW.js → chunk-IHUYP6LO.js} +1 -1
- package/dist/chunks/{chunk-YRVDW4WN.js → chunk-ILMBNVSG.js} +2 -2
- package/dist/chunks/{chunk-G3QJULO2.js → chunk-KAS45KNR.js} +1 -1
- package/dist/chunks/{chunk-H6XSTSLM.js → chunk-MEEFGWTM.js} +54 -196
- package/dist/chunks/{chunk-GCG2NMWR.js → chunk-MQ2I5AA3.js} +4 -2
- package/dist/chunks/chunk-NHA62QD7.js +774 -0
- package/dist/chunks/{chunk-WKI2FBIQ.js → chunk-OCU4RLHC.js} +641 -309
- package/dist/chunks/{chunk-WSKBSQ2U.js → chunk-PRHJGPMA.js} +2 -2
- package/dist/chunks/{chunk-6EFWDL7Y.js → chunk-Q6LWB5HX.js} +2 -2
- package/dist/chunks/{chunk-IURUFGZK.js → chunk-RBGOMK4Y.js} +50 -33
- package/dist/chunks/chunk-VBCFFFGT.js +226 -0
- package/dist/chunks/{ebuilder-form-V5FG2EHC.js → chunk-WRRJ4GG2.js} +22 -1148
- package/dist/chunks/{chunk-E4Z23GHM.js → chunk-WY74UBJ6.js} +264 -750
- package/dist/chunks/conflict-SFNEVJY5.js +21 -0
- package/dist/chunks/continuation-RN6PZN2M.js +11 -0
- package/dist/chunks/cowork-CKWYHXYB.js +2508 -0
- package/dist/chunks/ebuilder-form-KOLJRVZD.js +121 -0
- package/dist/chunks/{ehr-HMFY6VIL.js → ehr-WFOC3B4D.js} +1174 -20
- package/dist/chunks/{esb-ZYSVB7SG.js → esb-LJZTN663.js} +4 -4
- package/dist/chunks/{fna-B7RKPAZD.js → fna-F7QRIIZC.js} +2 -2
- package/dist/chunks/{host-45WED2KK.js → host-BCGUVEWX.js} +3 -3
- package/dist/chunks/host-BQBRHVR3.js +23 -0
- package/dist/chunks/{host-I4Z6BFYX.js → host-DGE2TZD4.js} +3 -3
- package/dist/chunks/{host-KNZ52LJZ.js → host-KXPTEWFS.js} +3 -3
- package/dist/chunks/{host-OCH2BCCV.js → host-VQ6JJXGQ.js} +3 -3
- package/dist/chunks/{hrm-2KAZGUHI.js → hrm-NI6XAKW5.js} +3 -3
- package/dist/chunks/{im-ACJJWH4U.js → im-7TRTF3KT.js} +88 -12
- package/dist/chunks/{invoice-ZK3WCX2U.js → invoice-AOXWJKYT.js} +3 -3
- package/dist/chunks/{jiuchuanhui-5PQD3IL2.js → jiuchuanhui-7NQWEZAW.js} +2 -2
- package/dist/chunks/{lazy-dispatch-JQXBW3GM.js → lazy-dispatch-RNS4EE4S.js} +4 -4
- package/dist/chunks/{mail-S2HDDXO2.js → mail-VDHJAB4O.js} +3 -3
- package/dist/chunks/{meeting-QVN3I37L.js → meeting-GA7XCNNP.js} +214 -43
- package/dist/chunks/{okr-QNG4VC2Q.js → okr-TQIRERPQ.js} +2 -2
- package/dist/chunks/operations-5QL3AECO.js +11 -0
- package/dist/chunks/{plan-A25EMW6U.js → plan-4RY4LN2P.js} +2 -2
- package/dist/chunks/{project-G6XV66DZ.js → project-D6XT2OC5.js} +2 -2
- package/dist/chunks/{registry-DC6KR3NH.js → registry-JHRIDQ3A.js} +4 -4
- package/dist/chunks/{registry-2VLUAESA.js → registry-OZSMDVPP.js} +2 -2
- package/dist/chunks/{registry-TI3CIUXF.js → registry-QKMI43Q6.js} +8 -6
- package/dist/chunks/{registry-7C5UMIPL.js → registry-VOGKFFTR.js} +2 -2
- package/dist/chunks/{room-recommend-WRTHO6Y2.js → room-recommend-D4EUOOWY.js} +4 -3
- package/dist/chunks/{schedule-KFIITGSK.js → schedule-XPFBHPCV.js} +4 -3
- package/dist/chunks/{workflow-DJ7SYWAF.js → workflow-HLBZRWIY.js} +172 -6
- package/dist/index.js +30 -25
- package/dist/internal/e10/auth-recovery.js +14 -4
- package/dist/shortcuts/contract/host.js +34 -59
- package/dist/shortcuts/cowork/continuation.js +70 -0
- package/dist/shortcuts/cowork/errors.js +59 -0
- package/dist/shortcuts/cowork/host.js +172 -0
- package/dist/shortcuts/cowork/index.js +166 -0
- package/dist/shortcuts/cowork/manifest.js +65 -0
- package/dist/shortcuts/cowork/operations/discuss-write.js +277 -0
- package/dist/shortcuts/cowork/operations/item-write.js +380 -0
- package/dist/shortcuts/cowork/operations/member-write.js +283 -0
- package/dist/shortcuts/cowork/operations/permission-write.js +286 -0
- package/dist/shortcuts/cowork/operations/read.js +423 -0
- package/dist/shortcuts/cowork/operations/registry.js +39 -0
- package/dist/shortcuts/cowork/operations/review-write.js +157 -0
- package/dist/shortcuts/cowork/operations/shared.js +215 -0
- package/dist/shortcuts/cowork/operations/types.js +24 -0
- package/dist/shortcuts/cowork/operations.js +19 -0
- package/dist/shortcuts/ehr/host.js +4 -1
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +757 -0
- package/dist/shortcuts/ehr/operations/recruit-position.js +367 -0
- package/dist/shortcuts/ehr/operations/recruit-stats.js +133 -0
- package/dist/shortcuts/ehr/operations/recruit-write.js +8 -16
- package/dist/shortcuts/ehr/operations/recruit.js +35 -10
- package/dist/shortcuts/ehr/operations/registry.js +6 -0
- package/dist/shortcuts/im/diagnostics.js +97 -0
- package/dist/shortcuts/im/host.js +58 -31
- package/dist/shortcuts/im/index.js +7 -2
- package/dist/shortcuts/im/operations/at-mention.js +127 -0
- package/dist/shortcuts/im/operations/file-user.js +10 -2
- package/dist/shortcuts/im/operations/group-read.js +26 -5
- package/dist/shortcuts/im/operations/group-write.js +35 -28
- package/dist/shortcuts/im/operations/msg-sync.js +7 -2
- package/dist/shortcuts/im/operations/msg-write.js +76 -10
- package/dist/shortcuts/im/operations/quote-mention.js +119 -0
- package/dist/shortcuts/im/operations/session.js +1 -1
- package/dist/shortcuts/im/operations/shared.js +10 -0
- package/dist/shortcuts/im/operations/types.js +13 -0
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/host.js +4 -1
- package/dist/shortcuts/meeting/host.js +27 -34
- package/dist/shortcuts/meeting/operations/create.js +4 -1
- package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +78 -7
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +168 -26
- package/dist/shortcuts/meeting/operations/eb/factory.js +69 -1
- package/dist/shortcuts/meeting/operations/eb/index.js +16 -3
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +14 -3
- package/dist/shortcuts/meeting/operations/eb-fallback.js +159 -0
- package/dist/shortcuts/meeting/operations/env.js +93 -46
- package/dist/shortcuts/meeting/operations/form-fields.js +110 -0
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/standard-guard.js +71 -0
- package/dist/shortcuts/meeting/operations.js +16 -0
- package/dist/shortcuts/workflow/index.js +110 -1
- package/dist/shortcuts/workflow/operations/form.js +24 -3
- package/dist/shortcuts/workflow/operations/read.js +104 -23
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +448 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +106 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +47 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +30 -23
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +2 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +2 -4
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +138 -181
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +12 -7
- package/docs/e10-auth.md +1 -1
- package/docs/im.md +8 -1
- package/docs/todo-smart-convert/README.md +1 -1
- package/docs/todo-smart-convert/data-contract.md +2 -2
- package/docs/todo-smart-convert/subgroup-rules.md +6 -0
- package/package.json +1 -1
- package/skill-template/domains/yimiaoban.md +3 -3
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +5 -5
- package/skills/weaver-e10-caizhilian/SKILL.md +5 -5
- package/skills/weaver-e10-calendar/SKILL.md +5 -5
- package/skills/weaver-e10-esb/SKILL.md +5 -5
- package/skills/weaver-e10-hrm/SKILL.md +5 -5
- package/skills/weaver-e10-jinchengda/SKILL.md +2 -2
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -5
- package/skills/weaver-e10-jucailin/SKILL.md +18 -12
- package/skills/weaver-e10-jucailin/references/recruit-create.md +104 -0
- package/skills/weaver-e10-mail/SKILL.md +5 -5
- package/skills/weaver-e10-meeting/SKILL.md +171 -25
- package/skills/weaver-e10-meeting/references/meeting-eb.md +4 -3
- package/skills/weaver-e10-meeting/references/meeting-queries.md +9 -6
- package/skills/weaver-e10-meeting/references/meeting-verification.md +8 -6
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-okr/SKILL.md +5 -5
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +5 -5
- package/skills/weaver-e10-shared/SKILL.md +12 -12
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -4
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -5
- package/skills/weaver-e10-workflow/SKILL.md +29 -5
- package/skills/weaver-e10-workflow/aigroup/default.json +88 -0
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +66 -14
- package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +1 -1
- package/skills/weaver-e10-workflow/references/workflow-memory.md +1 -1
- package/skills/weaver-e10-workflow/references/workflow-query.md +12 -10
- package/skills/weaver-e10-workflow/references/workflow-view.md +2 -2
- package/skills/weaver-e10-workflow/tools/todo-serve.cjs +239 -0
- package/skills/weaver-e10-workflow/{scripts → tools}/todo-summary.cjs +218 -28
- package/skills/weaver-e10-yepiaotong/SKILL.md +10 -8
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +10 -10
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +40 -37
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -4
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +27 -38
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +4 -4
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +37 -29
- package/skills/weaver-e10-yimiaoban/SKILL.md +156 -33
- package/skills/weaver-e10-yimiaoban/references/ding-write.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/error-codes.md +5 -1
- package/skills/weaver-e10-yimiaoban/references/group-read.md +5 -1
- package/skills/weaver-e10-yimiaoban/references/group-write.md +6 -4
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -0
- package/skills/weaver-e10-yimiaoban/references/msg-write.md +34 -2
- package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +5 -2
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +79 -58
- package/skills/weaver-e10-yimiaoban/references/user-remind.md +2 -2
- package/skills/weaver-e10-ziguanjia/SKILL.md +5 -5
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { activeRuleFor } from './aigroupRules.js';
|
|
1
2
|
import { WORKFLOW_ENDPOINTS } from '../../endpoints.js';
|
|
2
3
|
import { saveSearchIndex } from '../../state.js';
|
|
3
4
|
import { buildSearchQuery } from '../read.js';
|
|
4
5
|
import { projectDisplayRows } from './todoListRows.js';
|
|
5
6
|
import { responseData } from '../shared.js';
|
|
6
7
|
import { openSummaryCache, SUMMARY_CACHE_FILE_NAME } from './summaryCache.js';
|
|
7
|
-
import { detectBusinessKind } from './businessRules.js';
|
|
8
8
|
import { fetchDetailFacts, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
9
|
-
import {
|
|
9
|
+
import { GROUP_MIN_ITEMS_FOR_SUBGROUP, isExpiringTodayRow, isOverdueRow, runGroup, } from './todoSmartConvert.group.js';
|
|
10
10
|
import { DEFAULT_TABID, FETCH_PAGE_SIZE, LIST_ENDPOINT_BY_CATEGORY, MAX_FETCH_LIMIT, MAX_FETCH_PAGES, guardListOnlyHost, readBoundedInt, readCategory, readFetchLimit, readPageSize, rowFromAny, } from './todoSmartConvert.core.js';
|
|
11
11
|
const DEFAULT_REQUEST_SUMMARY_MAX = 50;
|
|
12
12
|
export async function collectRawTodos(args, env) {
|
|
@@ -170,7 +170,7 @@ export async function collectRawTodos(args, env) {
|
|
|
170
170
|
const fieldsById = new Map();
|
|
171
171
|
if (focusText) {
|
|
172
172
|
const targetRows = wfIdGroup
|
|
173
|
-
.filter((group) => !group.topPriority &&
|
|
173
|
+
.filter((group) => !group.topPriority && activeRuleFor(group.title)?.aiGroup?.groupRule.aiContext === '流程详情')
|
|
174
174
|
.flatMap((group) => group.requestDatas ?? []);
|
|
175
175
|
const targetIds = [...new Set(targetRows.map((row) => row.requestId).filter(Boolean))];
|
|
176
176
|
if (targetIds.length > 0) {
|
|
@@ -189,26 +189,33 @@ export async function collectRawTodos(args, env) {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
// 给模型的紧凑视图使用组内序号,aiGroups 可用 seq 引用。
|
|
192
|
-
groupingBrief = wfIdGroup.map((group) =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
192
|
+
groupingBrief = wfIdGroup.map((group) => {
|
|
193
|
+
const groupRule = activeRuleFor(group.title)?.aiGroup?.groupRule;
|
|
194
|
+
// 分组口径随素材一起下发:模型生成 aiGroups 时必须遵循(只有 focus=false 的语义分组才有意义)
|
|
195
|
+
const groupingPrompt = groupRule && groupRule.focus === false
|
|
196
|
+
&& typeof groupRule.groupingPrompt === 'string' ? groupRule.groupingPrompt.trim() : '';
|
|
197
|
+
return {
|
|
198
|
+
groupId: group.id,
|
|
199
|
+
title: group.title,
|
|
200
|
+
count: group.count,
|
|
201
|
+
topPriority: group.topPriority,
|
|
202
|
+
...(groupingPrompt ? { groupingPrompt } : {}),
|
|
203
|
+
items: (group.requestDatas ?? []).map((row, index) => ({
|
|
204
|
+
idx: index + 1,
|
|
205
|
+
requestId: row.requestId,
|
|
206
|
+
title: row.requestNameTitle ?? '',
|
|
207
|
+
// 客户问题支持类无侧重点时按这两个布尔分组(模型不用自己解析日期文案)
|
|
208
|
+
overdue: isOverdueRow(row),
|
|
209
|
+
expiringToday: isExpiringTodayRow(row),
|
|
210
|
+
...(row.userOverTime ? { userOverTime: row.userOverTime } : {}),
|
|
211
|
+
...(row.summary ? { summary: row.summary } : {}),
|
|
212
|
+
// `summaryCached:true` = 这条已有本地摘要(按 requestId__lastFeedBackDateTime 命中),
|
|
213
|
+
// 分组仍要做(聚类看标题),但**不要再给它写摘要**。
|
|
214
|
+
...(cachedSummaryIds.has(row.requestId) ? { summaryCached: true } : {}),
|
|
215
|
+
...(fieldsById.has(row.requestId) ? { fields: fieldsById.get(row.requestId) } : {}),
|
|
216
|
+
})),
|
|
217
|
+
};
|
|
218
|
+
});
|
|
212
219
|
// 用户侧重点原样回显:分组/排序都按「标题语义 + 侧重点」,模型照它决定子组与顺序。
|
|
213
220
|
const groupingFocus = typeof args.groupFocus === 'string' ? args.groupFocus.trim() : '';
|
|
214
221
|
groupInfo = {
|
|
@@ -331,6 +331,7 @@ export function rowFromAny(raw) {
|
|
|
331
331
|
lastFeedBackDateTime: pick([
|
|
332
332
|
'lastFeedBackDateTime', 'lastfeedbackdatetime', 'lastFeedbackDateTime', 'lastfeedbacktime', 'lastFeedbackTime',
|
|
333
333
|
]) ?? '',
|
|
334
|
+
...(pick(['planFinishDatetime', 'planfinishdatetime']) ? { planFinishDatetime: pick(['planFinishDatetime', 'planfinishdatetime']) } : {}),
|
|
334
335
|
summary: pick(['summary']),
|
|
335
336
|
workflowName: pick(['workflowName', 'workflowname', 'workflowTypeName'], true),
|
|
336
337
|
receivedTime: pick(['receivedTime', 'receivedtime', 'receivedatetime', 'operatedatetime', 'stayStartAt']),
|
|
@@ -196,6 +196,7 @@ export function toRequestData(row) {
|
|
|
196
196
|
creater: row.creater,
|
|
197
197
|
createrName: row.createrName,
|
|
198
198
|
lastFeedBackDateTime: row.lastFeedBackDateTime,
|
|
199
|
+
...(row.planFinishDatetime ? { planFinishDatetime: row.planFinishDatetime } : {}),
|
|
199
200
|
summary: row.summary ?? '',
|
|
200
201
|
};
|
|
201
202
|
}
|
package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { activeRuleFor } from './aigroupRules.js';
|
|
1
2
|
import { workflowValidationError } from '../../errors.js';
|
|
2
3
|
import { asRecord, readBoundedInt } from './todoSmartConvert.core.js';
|
|
3
4
|
import { detectBusinessKind, isDeterministicGroupKind, GROUP_SUMMARY_TAIL_TEXT, } from './businessRules.js';
|
|
@@ -191,7 +192,7 @@ export async function runGroupSummaryContext(args, env) {
|
|
|
191
192
|
}
|
|
192
193
|
const deterministicGroups = groups
|
|
193
194
|
.filter((group) => !group.topPriority
|
|
194
|
-
&&
|
|
195
|
+
&& activeRuleFor(group.title)?.aiGroup?.groupSummary.needAI === false)
|
|
195
196
|
.reduce((sum, group) => sum + groupItemsOf(group).length, 0);
|
|
196
197
|
return {
|
|
197
198
|
data: {
|
|
@@ -25,7 +25,7 @@ const inputSchema = schemaOf({
|
|
|
25
25
|
},
|
|
26
26
|
aiGroups: {
|
|
27
27
|
type: 'array',
|
|
28
|
-
description: 'smartGroup 的二次分组计划。每项必须提供 groupId、title
|
|
28
|
+
description: 'smartGroup 的二次分组计划。每项必须提供 groupId、title、requestIds。',
|
|
29
29
|
items: {
|
|
30
30
|
type: 'object',
|
|
31
31
|
additionalProperties: false,
|
|
@@ -33,10 +33,8 @@ const inputSchema = schemaOf({
|
|
|
33
33
|
groupId: { type: 'string' },
|
|
34
34
|
title: { type: 'string' },
|
|
35
35
|
requestIds: { type: 'array', minItems: 1, items: { type: 'string' } },
|
|
36
|
-
seq: { type: 'array', minItems: 1, items: { type: 'integer', minimum: 1 } },
|
|
37
36
|
},
|
|
38
|
-
required: ['groupId', 'title'],
|
|
39
|
-
oneOf: [{ required: ['requestIds'] }, { required: ['seq'] }],
|
|
37
|
+
required: ['groupId', 'title', 'requestIds'],
|
|
40
38
|
},
|
|
41
39
|
},
|
|
42
40
|
aiSummaries: {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { workflowValidationError } from '../../errors.js';
|
|
2
|
-
import {
|
|
2
|
+
import { renderGroupSummaryTemplate, } from './businessRules.js';
|
|
3
3
|
import { asRecord, readBoundedInt, } from './todoSmartConvert.core.js';
|
|
4
|
-
import {
|
|
4
|
+
import { PRIORITY_GROUP_ID, PRIORITY_GROUP_TITLE, runGroup, GROUP_MIN_ITEMS_FOR_SUBGROUP, } from './todoSmartConvert.group.js';
|
|
5
5
|
import { fetchDetailFacts, stampsOfRows } from './todoSmartConvert.detail.js';
|
|
6
|
+
import { activeRuleFor, evaluateFocusKeywords, primeRules } from './aigroupRules.js';
|
|
6
7
|
import { openSummaryCache } from './summaryCache.js';
|
|
7
8
|
const DEFAULT_REQUEST_SUMMARY_MAX = 50;
|
|
8
9
|
/** 把一批条目包成「单独展示」项(优先组、以及普通组里没同类可合并的条目都用它)。 */
|
|
@@ -38,9 +39,9 @@ export function readAiGroups(value) {
|
|
|
38
39
|
if (!record) {
|
|
39
40
|
throw workflowValidationError('ai_groups_invalid', `${label} 必须是对象 { groupId, title, requestIds }`, { retryAllowed: false });
|
|
40
41
|
}
|
|
41
|
-
const unknownKeys = Object.keys(record).filter((key) => !['groupId', 'title', 'requestIds'
|
|
42
|
+
const unknownKeys = Object.keys(record).filter((key) => !['groupId', 'title', 'requestIds'].includes(key));
|
|
42
43
|
if (unknownKeys.length > 0) {
|
|
43
|
-
throw workflowValidationError('ai_groups_invalid', `${label} 不接受字段:${unknownKeys.join(', ')};只支持 groupId / title / requestIds
|
|
44
|
+
throw workflowValidationError('ai_groups_invalid', `${label} 不接受字段:${unknownKeys.join(', ')};只支持 groupId / title / requestIds`, { retryAllowed: false, details: { unknownKeys } });
|
|
44
45
|
}
|
|
45
46
|
const groupId = typeof record.groupId === 'string' ? record.groupId.trim() : '';
|
|
46
47
|
if (!groupId) {
|
|
@@ -51,25 +52,11 @@ export function readAiGroups(value) {
|
|
|
51
52
|
throw workflowValidationError('ai_groups_invalid', `${label}.title 必填:这是 AI 给该子分组起的名字,结构里要原样输出`, { retryAllowed: false });
|
|
52
53
|
}
|
|
53
54
|
const hasIds = Array.isArray(record.requestIds) && record.requestIds.length > 0;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw workflowValidationError('ai_groups_invalid', `${label} 不能同时给 requestIds 与 seq:用 seq(组内序号,推荐)或 requestIds(显式 ID)二选一`, { retryAllowed: false });
|
|
57
|
-
}
|
|
58
|
-
if (!hasIds && !hasSeq) {
|
|
59
|
-
throw workflowValidationError('ai_groups_invalid', `${label} 需要 requestIds 或 seq(组内 1-based 序号,见 collect 的 groupingBrief)`, { retryAllowed: false });
|
|
60
|
-
}
|
|
61
|
-
const seqNumbers = [];
|
|
62
|
-
if (hasSeq) {
|
|
63
|
-
for (const item of record.seq) {
|
|
64
|
-
const n = typeof item === 'number' ? item : Number(item);
|
|
65
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
66
|
-
throw workflowValidationError('ai_groups_invalid', `${label}.seq 只接受 ≥1 的整数序号,收到 ${String(item)}`, { retryAllowed: false });
|
|
67
|
-
}
|
|
68
|
-
seqNumbers.push(n);
|
|
69
|
-
}
|
|
55
|
+
if (!hasIds) {
|
|
56
|
+
throw workflowValidationError('ai_groups_invalid', `${label} 必须给 requestIds(非空的 requestId 字符串数组)`, { retryAllowed: false });
|
|
70
57
|
}
|
|
71
58
|
const ids = [];
|
|
72
|
-
for (const item of
|
|
59
|
+
for (const item of record.requestIds) {
|
|
73
60
|
const id = typeof item === 'string' ? item.trim() : typeof item === 'number' ? String(item) : '';
|
|
74
61
|
if (!id) {
|
|
75
62
|
throw workflowValidationError('ai_groups_invalid', `${label}.requestIds 只接受非空的 requestId 字符串`, { retryAllowed: false });
|
|
@@ -77,7 +64,7 @@ export function readAiGroups(value) {
|
|
|
77
64
|
ids.push(id);
|
|
78
65
|
}
|
|
79
66
|
const bucket = map.get(groupId) ?? [];
|
|
80
|
-
bucket.push({ groupId, title, requestIds: [...new Set(ids)]
|
|
67
|
+
bucket.push({ groupId, title, requestIds: [...new Set(ids)] });
|
|
81
68
|
map.set(groupId, bucket);
|
|
82
69
|
});
|
|
83
70
|
return map;
|
|
@@ -109,42 +96,32 @@ function rowMetricsOf(row, metrics) {
|
|
|
109
96
|
* 金额算不出来时(表单没有金额字段、标题里也没有 `X元`)退回「标题清单」模板,
|
|
110
97
|
* 而不是编一个 `¥0 ~ ¥0` —— 半截统计比不给更糟。
|
|
111
98
|
*/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
99
|
+
/**
|
|
100
|
+
* 取一条流程在某个「指标名」上的值(规则 `focusKeyword` 求值用)。
|
|
101
|
+
*
|
|
102
|
+
* - `sysArg=true`:指标名就是 `getTodoListData` 返回的属性名(如 `planFinishDatetime`);
|
|
103
|
+
* - `sysArg=false`:先映射到结构化指标(金额 / 天数 / 假别),再退到「详情字段名包含指标名」;
|
|
104
|
+
* 都取不到返回 `undefined` —— 调用方按「不命中 + warning」处理(2026-10-28 用户定:
|
|
105
|
+
* 正则提不到就平铺并告警,不做 AI 兜底)。
|
|
106
|
+
*/
|
|
107
|
+
function metricValueOf(row, metric, sysArg, facts, metrics) {
|
|
108
|
+
if (sysArg) {
|
|
109
|
+
const key = Object.keys(row).find((item) => item.toLowerCase() === metric.toLowerCase());
|
|
110
|
+
return key ? row[key] : undefined;
|
|
119
111
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
112
|
+
const rowMetrics = metrics?.get(row.requestId);
|
|
113
|
+
if (rowMetrics) {
|
|
114
|
+
if (/金额|费用|价|合计/u.test(metric) && typeof rowMetrics.amount === 'number')
|
|
115
|
+
return rowMetrics.amount;
|
|
116
|
+
if (/天数|时长/u.test(metric) && typeof rowMetrics.leaveDays === 'number')
|
|
117
|
+
return rowMetrics.leaveDays;
|
|
118
|
+
if (/假别|假种|假期/u.test(metric) && typeof rowMetrics.leaveType === 'string')
|
|
119
|
+
return rowMetrics.leaveType;
|
|
127
120
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.filter((value) => typeof value === 'number' && value > 0);
|
|
133
|
-
// 防御:能进阈值组的前提就是「金额 ≤100」,所以这里必然有金额;
|
|
134
|
-
// 万一真的一个都没有,留空交 AI 写,绝不编一个 ¥0 ~ ¥0。
|
|
135
|
-
if (amounts.length === 0)
|
|
136
|
-
return null;
|
|
137
|
-
const sum = Number(amounts.reduce((total, value) => total + value, 0).toFixed(2));
|
|
138
|
-
return formatFinanceGroupSummary({
|
|
139
|
-
min: Math.min(...amounts),
|
|
140
|
-
max: Math.max(...amounts),
|
|
141
|
-
sum,
|
|
142
|
-
categories: items.map((item) => item.feeCategory),
|
|
143
|
-
creators,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
// 其他类不再产汇总项(isDeterministicGroupKind 已排除),走到这里直接返回 null。
|
|
147
|
-
return null;
|
|
121
|
+
const fact = facts?.get(row.requestId);
|
|
122
|
+
const field = (fact?.fields ?? []).find((item) => String(item.fieldName ?? '').includes(metric));
|
|
123
|
+
const value = field?.value;
|
|
124
|
+
return value === undefined || String(value).trim() === '' ? undefined : value;
|
|
148
125
|
}
|
|
149
126
|
/**
|
|
150
127
|
* 最终结构里的 `totalCount`:**去重后的待办条数**(= collect 阶段取到的全部条数)。
|
|
@@ -229,6 +206,8 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
229
206
|
// markdown 是中间态的东西,最终结构里没有,相关提示一并丢掉,免得误导调用方。
|
|
230
207
|
const warnings = (base.warnings ?? [])
|
|
231
208
|
.filter((text) => !text.startsWith('markdown 只展示前'));
|
|
209
|
+
// aigroup 规则(default.json + user-intents.json):每个 operation 只加载一次。
|
|
210
|
+
warnings.push(...primeRules(env.ctx).warnings);
|
|
232
211
|
if (skipDetail) {
|
|
233
212
|
warnings.push('withDetailContext=false:本次未拉取任何详情——请假/财务阈值改为按标题识别、跳过整组空判定,也不产 summaryBatches');
|
|
234
213
|
}
|
|
@@ -243,7 +222,7 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
243
222
|
topPriority: true,
|
|
244
223
|
title: group.title,
|
|
245
224
|
count: group.count,
|
|
246
|
-
//
|
|
225
|
+
// 大分组(含优先组)默认展开;只有汇总项(ai二次分组)收缩。
|
|
247
226
|
defaultExpanded: true,
|
|
248
227
|
displayItems: requestItemsOf(group.requestDatas),
|
|
249
228
|
};
|
|
@@ -256,126 +235,79 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
256
235
|
* 注意:不存在 `other` 兜底汇总项(2026-10-26 用户定稿),漏分 / 子组解散一律落到这里。
|
|
257
236
|
*/
|
|
258
237
|
const singles = [];
|
|
259
|
-
const kind = detectBusinessKind(group.title);
|
|
260
|
-
const rawPlans = plans?.get(group.id) ?? [];
|
|
261
|
-
/** 意图是否适用于本组(Q15 专属/通用意图表):不适用 = 按「没有意图」处理。 */
|
|
262
|
-
const groupHasFocus = hasFocus && focusAppliesToKind(groupFocus, kind);
|
|
263
|
-
/**
|
|
264
|
-
* aiGroups 什么时候被采纳:
|
|
265
|
-
* - 意图适用于本组:只有本组有计划才分;没给计划 = 视为无意图,退回确定性规则(Q8)。
|
|
266
|
-
* - 内部留言:有没有意图都采纳(无意图按流程标题分、有意图按详情+意图分,素材由 collect 准备)。
|
|
267
|
-
* - 其余类型:意图不适用就一律忽略(Q9)。
|
|
268
|
-
*/
|
|
269
|
-
const plansAllowed = groupHasFocus || kind === 'internal-message';
|
|
270
|
-
const groupPlans = plansAllowed ? rawPlans : [];
|
|
271
|
-
if (rawPlans.length > 0 && !plansAllowed) {
|
|
272
|
-
// 没有意图时不要说成「意图不适用于本类型」——那是误导,真实原因是本类型默认不做二次分组。
|
|
273
|
-
warnings.push(hasFocus
|
|
274
|
-
? `「${group.title}」意图「${groupFocus}」不适用于本类型,已忽略 aiGroups,按无意图规则处理`
|
|
275
|
-
: `「${group.title}」${BUSINESS_KIND_LABEL[kind]}默认不按标题二次分组,已忽略 aiGroups(要按侧重点分组请在 groupFocus 里说明)`);
|
|
276
|
-
}
|
|
277
238
|
/** 该组整体平铺(不进入 ai二次分组)。 */
|
|
278
239
|
const flatReturn = () => ({
|
|
279
240
|
id: group.id,
|
|
280
241
|
topPriority: false,
|
|
281
242
|
title: group.title,
|
|
282
243
|
count: group.count,
|
|
283
|
-
defaultExpanded:
|
|
244
|
+
defaultExpanded: true,
|
|
284
245
|
displayItems: requestItemsOf(group.requestDatas),
|
|
285
246
|
});
|
|
286
|
-
|
|
287
|
-
|
|
247
|
+
const rawPlans = plans?.get(group.id) ?? [];
|
|
248
|
+
/** 本组生效规则(`aigroup` JSON):没有 `aiGroup` ⇒ 不做二次分组。 */
|
|
249
|
+
const rule = activeRuleFor(group.title);
|
|
250
|
+
if (!rule?.aiGroup) {
|
|
288
251
|
if (rawPlans.length > 0)
|
|
289
|
-
warnings.push(`「${group.title}
|
|
290
|
-
|
|
252
|
+
warnings.push(`「${group.title}」未配置 aiGroup,已忽略 aiGroups`);
|
|
253
|
+
if (!rule?.needSummary)
|
|
254
|
+
warnings.push(`「${group.title}」不需要 AI 摘要,summary 用标题兜底`);
|
|
255
|
+
warnings.push(`「${group.title}」未配置 ai二次分组,${group.requestDatas.length} 条平铺`);
|
|
291
256
|
return flatReturn();
|
|
292
257
|
}
|
|
293
|
-
|
|
258
|
+
const groupRule = rule.aiGroup.groupRule;
|
|
259
|
+
const focusMode = groupRule.focus === true;
|
|
260
|
+
const groupPlans = rawPlans;
|
|
261
|
+
// 全局硬门槛:组内 <3 条一律平铺(规则/计划都覆盖不了)。
|
|
294
262
|
if (group.requestDatas.length < GROUP_MIN_ITEMS_FOR_SUBGROUP) {
|
|
295
263
|
if (rawPlans.length > 0)
|
|
296
|
-
warnings.push(`「${group.title}」不足
|
|
264
|
+
warnings.push(`「${group.title}」不足 ${GROUP_MIN_ITEMS_FOR_SUBGROUP} 条(硬门槛),忽略 aiGroups 并平铺`);
|
|
297
265
|
return flatReturn();
|
|
298
266
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
* 内部留言(Q1/Q16,2026-10-27 用户明确):**≥3 条必然做 ai二次分组**,没有「平铺」这个第三态。
|
|
306
|
-
*
|
|
307
|
-
* 只有两种维度,且都必须由模型给出计划(CLI 不替模型分组):
|
|
308
|
-
* - 侧重点适用于内部留言 ⇒ 按「侧重点 + 流程详情数据」分组;
|
|
309
|
-
* - 没有侧重点,或侧重点不适用 ⇒ **按流程标题分组**。
|
|
310
|
-
*
|
|
311
|
-
* 「没给计划」不是合法输入:静默平铺会把 200+ 条留言堆成一片(五年那批实测 223 条全平铺),
|
|
312
|
-
* 而且 warning 会被误读成「技术上无法分组」。这里直接要求补计划,保持规则单一确定性。
|
|
313
|
-
*
|
|
314
|
-
* 注意:门槛在前面 D1 已经判过(<3 条直接平铺),所以走到这里一定 ≥3 条。
|
|
315
|
-
*/
|
|
316
|
-
if (kind === 'internal-message' && groupPlans.length === 0) {
|
|
317
|
-
throw workflowValidationError('ai_groups_required', `「${group.title}」属于内部留言且不少于 ${GROUP_MIN_ITEMS_FOR_SUBGROUP} 条,必须给本组 aiGroups:`
|
|
318
|
-
+ (groupHasFocus
|
|
319
|
-
? `用户侧重点「${groupFocus}」适用于内部留言,请按侧重点结合流程详情数据分组`
|
|
320
|
-
: '当前没有适用于内部留言的侧重点,请**按流程标题**分组(同主题的通知/公告归到同一子组)')
|
|
321
|
-
+ `;可用 groupId:${group.id},组内序号 1..${group.requestDatas.length}`, {
|
|
267
|
+
/** `focus:false` 必须给计划(没有「平铺」这个第三态);`focus:true` 没计划时按 focusKeyword 确定性成组。 */
|
|
268
|
+
if (!focusMode && groupPlans.length === 0) {
|
|
269
|
+
const groupingPrompt = typeof groupRule.groupingPrompt === 'string' ? groupRule.groupingPrompt.trim() : '';
|
|
270
|
+
throw workflowValidationError('ai_groups_required', `「${group.title}」按规则要求做 AI 二次分组(focus=false,上下文:${groupRule.aiContext ?? '流程标题'}),`
|
|
271
|
+
+ `必须给本组 aiGroups;可用 groupId:${group.id},requestIds 取本组 ${group.requestDatas.length} 条`
|
|
272
|
+
+ (groupingPrompt ? `。分组口径(groupingPrompt):${groupingPrompt}` : ''), {
|
|
322
273
|
retryAllowed: true,
|
|
323
|
-
details: {
|
|
274
|
+
details: {
|
|
275
|
+
groupId: group.id,
|
|
276
|
+
count: group.requestDatas.length,
|
|
277
|
+
aiContext: groupRule.aiContext ?? '',
|
|
278
|
+
...(groupingPrompt ? { groupingPrompt } : {}),
|
|
279
|
+
},
|
|
324
280
|
});
|
|
325
281
|
}
|
|
326
282
|
if (groupPlans.length === 0) {
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const value =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
unknown += 1;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (eligible.length > 0) {
|
|
347
|
-
subGroups.push({
|
|
348
|
-
title: kind === 'leave' ? LEAVE_GROUP_TITLE : FINANCE_GROUP_TITLE,
|
|
349
|
-
requestDatas: eligible,
|
|
350
|
-
});
|
|
351
|
-
warnings.push(`「${group.title}」按${kind === 'leave' ? '请假类 1天以内' : '财务类 100元以内'}确定性汇总,其余条目平铺`);
|
|
352
|
-
}
|
|
353
|
-
if (unknown > 0) {
|
|
354
|
-
warnings.push(`「${group.title}」有 ${unknown} 条流程详情数据取不到${kind === 'leave' ? '请假天数' : '报销金额'},不做 ai二次分组、按单条平铺`);
|
|
355
|
-
}
|
|
283
|
+
// 规则 focus=true 且没有计划:按 focusKeyword 确定性成一组(与业务类型无关)。
|
|
284
|
+
const conditions = groupRule.focusKeyword ?? [];
|
|
285
|
+
const sysArg = groupRule.sysArg === true;
|
|
286
|
+
const hit = [];
|
|
287
|
+
const missing = new Set();
|
|
288
|
+
for (const row of group.requestDatas) {
|
|
289
|
+
const resolve = (metric) => {
|
|
290
|
+
const value = metricValueOf(row, metric, sysArg, facts, metrics);
|
|
291
|
+
if (value === undefined)
|
|
292
|
+
missing.add(metric);
|
|
293
|
+
return value;
|
|
294
|
+
};
|
|
295
|
+
if (conditions.length > 0 && evaluateFocusKeywords(conditions, resolve))
|
|
296
|
+
hit.push(row);
|
|
297
|
+
else
|
|
298
|
+
singles.push(row);
|
|
356
299
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (isExpiredUserOverTime(row.userOverTime) || isExpiringTodayRow(row))
|
|
363
|
-
urgentRows.push(row);
|
|
364
|
-
else
|
|
365
|
-
rest.push(row);
|
|
366
|
-
}
|
|
367
|
-
singles.push(...rest);
|
|
368
|
-
if (urgentRows.length > 0) {
|
|
369
|
-
subGroups.push({ title: ISSUE_SUPPORT_GROUP_TITLE, requestDatas: urgentRows });
|
|
370
|
-
warnings.push(`「${group.title}」按客户问题支持类口径:${urgentRows.length} 条已过期或今日到期,其余 ${rest.length} 条平铺`);
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
warnings.push(`「${group.title}」没有已过期或今日到期的条目(userOverTime 未命中),全部平铺`);
|
|
374
|
-
}
|
|
300
|
+
const conditionText = conditions.map((condition) => condition.join(' ')).join(' 且 ');
|
|
301
|
+
const title = groupRule.groupTitle?.trim() || conditionText || '汇总';
|
|
302
|
+
if (hit.length > 0) {
|
|
303
|
+
subGroups.push({ title, requestDatas: hit });
|
|
304
|
+
warnings.push(`「${group.title}」按规则「${title}」命中 ${hit.length} 条(${conditionText}),其余 ${group.requestDatas.length - hit.length} 条平铺`);
|
|
375
305
|
}
|
|
376
306
|
else {
|
|
377
|
-
|
|
378
|
-
|
|
307
|
+
warnings.push(`「${group.title}」规则「${title}」命中 0 条${conditionText ? `(${conditionText})` : ''},全部平铺`);
|
|
308
|
+
}
|
|
309
|
+
if (missing.size > 0) {
|
|
310
|
+
warnings.push(`「${group.title}」指标取不到:${[...missing].join('、')},相关条目按单条平铺;请检查规则里的指标名`);
|
|
379
311
|
}
|
|
380
312
|
}
|
|
381
313
|
else {
|
|
@@ -385,22 +317,12 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
385
317
|
* 模型若拿它当子组名,CLI 必须逐条核阈值——否则 2000 元的报销会出现在「100元以内」里。
|
|
386
318
|
* 超阈值的条目剔出、转单条展示(一条都不丢),并点名。
|
|
387
319
|
*/
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
: kind === 'finance' ? FINANCE_GROUP_TITLE : '';
|
|
320
|
+
/** 保留标题 = 规则里的 groupTitle:计划若用它当子组名,必须逐条核 focusKeyword。 */
|
|
321
|
+
const reservedTitle = groupRule.groupTitle ?? '';
|
|
391
322
|
const droppedByThreshold = [];
|
|
392
323
|
for (const plan of groupPlans) {
|
|
393
324
|
const datas = [];
|
|
394
|
-
|
|
395
|
-
const planRequestIds = plan.requestIds.length > 0
|
|
396
|
-
? plan.requestIds
|
|
397
|
-
: plan.seq.map((n) => {
|
|
398
|
-
const row = group.requestDatas[n - 1];
|
|
399
|
-
if (!row) {
|
|
400
|
-
throw workflowValidationError('ai_group_seq_out_of_range', `aiGroups 给「${group.title}」的 seq 里有 ${n},但该组只有 ${group.requestDatas.length} 条(序号从 1 开始)`, { retryAllowed: true, details: { groupId: group.id, seq: n, count: group.requestDatas.length } });
|
|
401
|
-
}
|
|
402
|
-
return row.requestId;
|
|
403
|
-
});
|
|
325
|
+
const planRequestIds = plan.requestIds;
|
|
404
326
|
for (const requestId of planRequestIds) {
|
|
405
327
|
const data = byRequestId.get(requestId);
|
|
406
328
|
if (!data) {
|
|
@@ -417,13 +339,11 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
417
339
|
if (reservedTitle && plan.title === reservedTitle) {
|
|
418
340
|
const kept = [];
|
|
419
341
|
const dropped = [];
|
|
342
|
+
const conditions = groupRule.focusKeyword ?? [];
|
|
343
|
+
const sysArg = groupRule.sysArg === true;
|
|
420
344
|
for (const row of datas) {
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
const hit = kind === 'leave'
|
|
424
|
-
? (typeof value === 'number' && value <= LEAVE_DAY_THRESHOLD)
|
|
425
|
-
: (typeof value === 'number' && value > 0 && value <= FINANCE_AMOUNT_THRESHOLD);
|
|
426
|
-
(hit ? kept : dropped).push(row);
|
|
345
|
+
const resolve = (metric) => metricValueOf(row, metric, sysArg, facts, metrics);
|
|
346
|
+
(conditions.length > 0 && evaluateFocusKeywords(conditions, resolve) ? kept : dropped).push(row);
|
|
427
347
|
}
|
|
428
348
|
if (dropped.length > 0) {
|
|
429
349
|
droppedByThreshold.push(...dropped);
|
|
@@ -454,10 +374,50 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
454
374
|
singles.push(...uncovered);
|
|
455
375
|
warnings.push(`「${group.title}」有 ${uncovered.length} 条未被 AI 分组,按单条平铺(不造 other 子组)`);
|
|
456
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* `focus=true` 的组**最多只留一个汇总项**(2026-10-28 用户定:侧重点产出多个子组时,
|
|
379
|
+
* 只保留涉及数据最多的那个,其余条目平铺——多组会造成重复展示、批量处理口径有争议)。
|
|
380
|
+
*/
|
|
381
|
+
if (focusMode && subGroups.length > 1) {
|
|
382
|
+
const sorted = [...subGroups].sort((a, b) => b.requestDatas.length - a.requestDatas.length);
|
|
383
|
+
const [keep] = sorted;
|
|
384
|
+
const dropped = sorted.slice(1);
|
|
385
|
+
const droppedCount = dropped.reduce((sum, sub) => sum + sub.requestDatas.length, 0);
|
|
386
|
+
for (const sub of dropped)
|
|
387
|
+
singles.push(...sub.requestDatas);
|
|
388
|
+
subGroups.length = 0;
|
|
389
|
+
subGroups.push(keep);
|
|
390
|
+
warnings.push(`「${group.title}」侧重点产出 ${sorted.length} 个子组,按规则只保留条数最多的「${keep.title}」`
|
|
391
|
+
+ `(${keep.requestDatas.length} 条),其余 ${droppedCount} 条改单条平铺`);
|
|
392
|
+
}
|
|
457
393
|
}
|
|
394
|
+
/** 组摘要:规则 `needAI:false` 时用 JSON `template` 渲染,否则留空交 AI。 */
|
|
395
|
+
const renderSubGroupSummary = (sub) => {
|
|
396
|
+
const groupSummary = rule.aiGroup?.groupSummary;
|
|
397
|
+
if (!groupSummary || groupSummary.needAI || !groupSummary.template)
|
|
398
|
+
return '';
|
|
399
|
+
if (groupRule.groupTitle && sub.title !== groupRule.groupTitle)
|
|
400
|
+
return '';
|
|
401
|
+
const rows = sub.requestDatas;
|
|
402
|
+
const items = rows.map((row) => rowMetricsOf(row, metrics));
|
|
403
|
+
const amounts = items
|
|
404
|
+
.map((item) => item.amount)
|
|
405
|
+
.filter((value) => typeof value === 'number' && value > 0);
|
|
406
|
+
if (groupSummary.template.includes('{{min}}') && amounts.length === 0)
|
|
407
|
+
return '';
|
|
408
|
+
return renderGroupSummaryTemplate({
|
|
409
|
+
template: groupSummary.template,
|
|
410
|
+
tail: groupSummary.tail,
|
|
411
|
+
count: rows.length,
|
|
412
|
+
restCount: singles.length,
|
|
413
|
+
categories: items.map((item) => item.leaveType ?? item.feeCategory),
|
|
414
|
+
amounts,
|
|
415
|
+
creators: rows.map((row) => row.createrName),
|
|
416
|
+
});
|
|
417
|
+
};
|
|
458
418
|
// 条数降序;sort 稳定,同条数保持 AI 给出的先后顺序。
|
|
459
419
|
// 有侧重点时沿用模型顺序;没有侧重点时按条数降序。
|
|
460
|
-
if (!
|
|
420
|
+
if (!focusMode)
|
|
461
421
|
subGroups.sort((a, b) => b.requestDatas.length - a.requestDatas.length);
|
|
462
422
|
const groupItems = subGroups.map((sub, index) => ({
|
|
463
423
|
// 键顺序照新目标结构:type → id → title → count → summary → defaultExpanded → requestDatas
|
|
@@ -466,11 +426,7 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
466
426
|
title: sub.title,
|
|
467
427
|
count: sub.requestDatas.length,
|
|
468
428
|
// 组摘要口径不变(Q4):只有 CLI 自己造的三类固定组用固定文案;AI 子组一律留空交 groupSummary。
|
|
469
|
-
summary: (
|
|
470
|
-
|| (kind === 'finance' && sub.title === FINANCE_GROUP_TITLE)
|
|
471
|
-
|| (kind === 'issue-support' && sub.title === ISSUE_SUPPORT_GROUP_TITLE))
|
|
472
|
-
? (deterministicGroupSummary(kind, sub.requestDatas, metrics, singles.length) ?? '')
|
|
473
|
-
: '',
|
|
429
|
+
summary: renderSubGroupSummary(sub),
|
|
474
430
|
// 汇总项自身默认收缩(与大分组的展开状态互相独立)
|
|
475
431
|
defaultExpanded: false,
|
|
476
432
|
requestDatas: sub.requestDatas,
|
|
@@ -480,7 +436,8 @@ export async function runSmartGroup(args, env, options = {}) {
|
|
|
480
436
|
topPriority: false,
|
|
481
437
|
title: group.title,
|
|
482
438
|
count: group.count,
|
|
483
|
-
|
|
439
|
+
// 大分组默认展开(与汇总项自身的收缩状态互相独立)
|
|
440
|
+
defaultExpanded: true,
|
|
484
441
|
// 汇总项在前、单独条目在后。
|
|
485
442
|
displayItems: [...groupItems, ...requestItemsOf(singles)],
|
|
486
443
|
};
|