@thinkingai/ae-cli 6.1.18 → 6.1.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.
- package/README.md +2 -0
- package/README.zh.md +6 -0
- package/dist/{auth-ROB2EDYV.js → auth-QPEVVBKT.js} +13 -7
- package/dist/auth-UMJAA72O.js +15 -0
- package/dist/{capability-DKMYUTLC.js → capability-ASI62I4A.js} +11 -10
- package/dist/{chunk-UW5UN47B.js → chunk-533JK3YX.js} +2 -0
- package/dist/{chunk-4XXOWOTA.js → chunk-6QK7SYYY.js} +5 -4
- package/dist/{chunk-TUKQZTMI.js → chunk-7K24F7T2.js} +2 -0
- package/dist/{chunk-OO6XR6LK.js → chunk-AMBFK2K3.js} +2 -0
- package/dist/{sync-HKIOZXQE.js → chunk-B5HMQAOM.js} +31 -540
- package/dist/{chunk-Y3LOALAV.js → chunk-DKEXH6Q5.js} +4 -3
- package/dist/{chunk-VTWMIC5L.js → chunk-E2JKXMVO.js} +2 -0
- package/dist/{chunk-JHENBQ5B.js → chunk-FR2EDAQM.js} +1 -0
- package/dist/{chunk-ZQ47LWTI.js → chunk-GJXRF7Y6.js} +6 -5
- package/dist/{chunk-DWO43OIB.js → chunk-JQ3ENZZH.js} +2 -0
- package/dist/chunk-JYMGHGCE.js +191 -0
- package/dist/{chunk-BYYS3ANB.js → chunk-KEWVSO64.js} +5 -4
- package/dist/{chunk-3FY3RJ26.js → chunk-KZ6ZHF5J.js} +2 -1
- package/dist/{chunk-ZQKDZXDO.js → chunk-O6K2ODUE.js} +3 -2
- package/dist/chunk-QL6R6TSK.js +25215 -0
- package/dist/{chunk-J2DEBMRF.js → chunk-RWXIXOZG.js} +22 -9
- package/dist/{chunk-QZ3AS4KK.js → chunk-S5PONFBM.js} +5 -4
- package/dist/{chunk-4NN5IWVN.js → chunk-T6OSFQZD.js} +2 -0
- package/dist/{chunk-3KWQYGYI.js → chunk-TKHSULJT.js} +2 -0
- package/dist/{chunk-6EIJSNBD.js → chunk-Y74WTIKJ.js} +2 -0
- package/dist/{client-L2YDMHQ6.js → client-N7NGZUNI.js} +4 -3
- package/dist/{community-report-client-C7WDGET3.js → community-report-client-6OL6GBQW.js} +3 -2
- package/dist/{config-BMYZX2UE.js → config-XNMACCKX.js} +8 -7
- package/dist/{data-integration-QEKDWQDY.js → data-integration-4NZGZ45E.js} +913 -92
- package/dist/index.js +38 -36
- package/dist/{local-data-upload-client-4YYHSYD6.js → local-data-upload-client-EFHDLXFG.js} +3 -2
- package/dist/{memory-I2WXDTV2.js → memory-HBJQ2GC5.js} +8 -7
- package/dist/{metadata-I4C2EWUN.js → metadata-62EWBEIG.js} +12 -11
- package/dist/{model-HLHIEFMU.js → model-BHLJ2QMT.js} +10 -9
- package/dist/{personal-semantic-preference-LIPACBDX.js → personal-semantic-preference-JBVMAI7L.js} +11 -10
- package/dist/{project-semantic-RT3R2VQD.js → project-semantic-BQ4MPUS5.js} +12 -11
- package/dist/sync-MUWPRM5I.js +522 -0
- package/dist/{te-agent-BR6VDBNX.js → te-agent-WQZTMEK4.js} +10 -9
- package/dist/{te-analysis-7VUNUYWZ.js → te-analysis-MNCG65LU.js} +107 -20
- package/dist/{te-community-5DMNKJWY.js → te-community-BNE6KREL.js} +8 -7
- package/dist/{te-dataops-6P5IKWNJ.js → te-dataops-TXLKM2W6.js} +467 -67
- package/dist/{te-engage-KZPR5R22.js → te-engage-TPKLRVTL.js} +84 -10
- package/dist/{te-experiment-6BITX4RD.js → te-experiment-6BYKTCY7.js} +85 -12
- package/dist/{te-kb-RCLSSH2Q.js → te-kb-BUD6N3NI.js} +311 -24
- package/dist/{te-system-FXITO2JG.js → te-system-44LZJDTQ.js} +573 -37
- package/dist/{te-team-ADOC2ROP.js → te-team-XMP47YXE.js} +10 -7
- package/dist/{update-YCYCKJOO.js → update-UODFOZ4P.js} +7 -6
- package/package.json +18 -9
- package/skills/ae-analysis/references/analysis_gateway_assets.md +1 -1
- package/skills/ae-analysis/references/audience_models.md +18 -0
- package/skills/ae-analysis/references/command_index.md +2 -2
- package/skills/ae-analysis/references/project_timezone_update.md +13 -4
- package/skills/ae-analysis/references/property_create.md +2 -0
- package/skills/ae-analysis/references/super_metadata_batch_create.md +2 -0
- package/skills/ae-analysis/references/user_cluster_models.md +2 -0
- package/skills/ae-analysis/references/user_cluster_update.md +8 -4
- package/skills/ae-analysis/references/user_tag_create.md +7 -1
- package/skills/ae-analysis/references/user_tag_models.md +8 -2
- package/skills/ae-data-integration/SKILL.md +1 -0
- package/skills/ae-data-integration/references/error-handling.md +54 -1
- package/skills/ae-data-integration/references/source-inspect.md +18 -2
- package/skills/ae-data-integration/references/transform.md +3 -1
- package/skills/ae-data-integration/references/ue-routing.md +23 -0
- package/skills/ae-dataops/SKILL.md +11 -1
- package/skills/ae-dataops/references/dataops-backfill.md +135 -0
- package/skills/ae-engage/SKILL.md +5 -0
- package/skills/ae-engage/references/build-task-save-guide.md +5 -1
- package/skills/ae-engage/references/save-flow.md +37 -1
- package/skills/ae-engage/references/save-task.md +6 -0
- package/skills/ae-experiment/SKILL.md +6 -2
- package/skills/ae-experiment/references/save_metric.md +20 -8
- package/skills/ae-kb/SKILL.md +120 -9
- package/skills/ae-system/SKILL.md +28 -30
- package/skills/ae-system/references/channel-management.md +303 -0
- package/dist/auth-GBMV6TEJ.js +0 -14
- package/dist/capability-HYVVPG25.js +0 -352
- package/dist/chunk-EFH4XWYC.js +0 -556
- package/dist/chunk-JRJY5DMJ.js +0 -71
- package/dist/chunk-OMPRXM3V.js +0 -349
- package/dist/chunk-QNOLN2LJ.js +0 -509
- package/dist/chunk-RJDU7NYP.js +0 -1198
- package/dist/chunk-RNAALWJK.js +0 -98
- package/dist/chunk-SERWF6G5.js +0 -13
- package/dist/memory-3ORCR7JH.js +0 -893
- package/dist/metadata-VUOQJE26.js +0 -339
- package/dist/model-UGRDX4MW.js +0 -139
- package/dist/personal-semantic-preference-OEISBRHM.js +0 -239
- package/dist/project-semantic-FFPWFPIW.js +0 -1114
- package/dist/sync-TFHU2UTG.js +0 -10261
- package/dist/te-agent-VLYOV7S4.js +0 -3894
- package/dist/te-analysis-4YGQL5RC.js +0 -9357
- package/dist/te-community-ISDQWJU7.js +0 -1859
- package/dist/te-dataops-CVULXNVB.js +0 -2209
- package/dist/te-engage-N5WI32H6.js +0 -4898
- package/dist/te-experiment-UVR4HLND.js +0 -988
- package/dist/te-system-K2GYMCTB.js +0 -2213
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
1
2
|
import {
|
|
2
3
|
getAuthHeaders,
|
|
3
4
|
kbApi
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-JYMGHGCE.js";
|
|
5
6
|
import {
|
|
6
7
|
printError,
|
|
7
8
|
printOutput
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-KEWVSO64.js";
|
|
10
|
+
import "./chunk-GJXRF7Y6.js";
|
|
11
|
+
import "./chunk-Y74WTIKJ.js";
|
|
12
|
+
import "./chunk-RWXIXOZG.js";
|
|
13
|
+
import "./chunk-O6K2ODUE.js";
|
|
14
|
+
import "./chunk-KZ6ZHF5J.js";
|
|
15
|
+
import "./chunk-533JK3YX.js";
|
|
16
|
+
import "./chunk-FR2EDAQM.js";
|
|
14
17
|
|
|
15
18
|
// src/commands/te-team/shared.ts
|
|
16
19
|
var API_PREFIX = process.env.AE_API_PREFIX ?? "/agent";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
1
2
|
import {
|
|
2
3
|
buildVersionInstallPlan,
|
|
3
4
|
fetchCliConfig,
|
|
@@ -7,19 +8,19 @@ import {
|
|
|
7
8
|
printError,
|
|
8
9
|
printOutput,
|
|
9
10
|
recordVersionSyncResult
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KEWVSO64.js";
|
|
11
12
|
import {
|
|
12
13
|
peekCliToken
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-RWXIXOZG.js";
|
|
14
15
|
import {
|
|
15
16
|
missingAeHostHint
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-O6K2ODUE.js";
|
|
17
18
|
import {
|
|
18
19
|
getActiveHost,
|
|
19
20
|
normalizeUrl
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-KZ6ZHF5J.js";
|
|
22
|
+
import "./chunk-533JK3YX.js";
|
|
23
|
+
import "./chunk-FR2EDAQM.js";
|
|
23
24
|
|
|
24
25
|
// src/commands/update.ts
|
|
25
26
|
var HOST_OPTION_DESC = "Override active AE host URL (e.g., https://ta.thinkingdata.cn)";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thinkingai/ae-cli",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.19",
|
|
4
4
|
"description": "CLI tool for ThinkingAI (AE) analytics platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"dev": "tsx src/index.ts",
|
|
20
|
-
"build": "tsup src/index.ts --format esm --outDir dist && node scripts/copy-tracking-assets.mjs",
|
|
20
|
+
"build": "tsup src/index.ts --format esm --outDir dist --clean --config tsup.config.ts && node scripts/copy-tracking-assets.mjs",
|
|
21
21
|
"generate:analysis-skill": "tsx scripts/generate-analysis-skill-index.mts",
|
|
22
22
|
"verify:config": "node test/config-commands.test.mjs",
|
|
23
23
|
"verify:json-utils": "npx tsx tests/json-utils.test.ts",
|
|
@@ -42,15 +42,17 @@
|
|
|
42
42
|
"verify:agent-tools": "node --test test/agent-command-registry.test.mjs && tsx tests/te-agent-credentials.test.ts && tsx tests/te-agent-unified-auth.test.ts && tsx tests/te-agent-conversations.test.ts && tsx scripts/verify-agent-tools.mjs && npm run verify:approval && npm run verify:sandbox-tools",
|
|
43
43
|
"verify:approval": "tsx tests/approval-command.test.ts && tsx tests/approval-cli-client.test.ts && tsx tests/approval-skill.test.ts",
|
|
44
44
|
"verify:memory": "tsx tests/memory-command.test.ts && tsx tests/memory-cli-client.test.ts && tsx tests/memory-skill.test.ts",
|
|
45
|
-
"verify:system-tools": "node scripts/verify-system-tools.mjs && npx tsx tests/system-domain.test.ts && npx tsx tests/te-agent-download.test.ts && npx tsx tests/sandbox-tool-package.test.ts",
|
|
45
|
+
"verify:system-tools": "node scripts/verify-system-tools.mjs && npx tsx tests/system-domain.test.ts && npx tsx tests/channel-cli-http.test.ts && npx tsx tests/te-agent-download.test.ts && npx tsx tests/sandbox-tool-package.test.ts",
|
|
46
46
|
"verify:kb-skill": "node test/kb-skill-query-workflow.test.mjs",
|
|
47
|
+
"verify:kb-sources": "tsx test/kb-list-sources-command.test.mjs",
|
|
47
48
|
"verify:sandbox-tools": "tsx test/sandbox-tools.test.ts",
|
|
48
49
|
"verify:skill-version-management": "tsx tests/skill-version-management.test.ts",
|
|
49
50
|
"verify:skill-reference-files": "tsx tests/skill-reference-files.test.ts",
|
|
50
|
-
"verify:tracking-tools": "tsx test/tracking-client.test.mjs && tsx test/tracking-skill-flow.test.mjs && node test/tracking-commands.test.mjs && tsx tests/tracking-display-name-sync.test.ts && tsx tests/local-data-inspect-convert.test.ts && tsx tests/local-data-upload-client.test.ts && tsx tests/local-data-upload-command.test.ts && tsx tests/local-data-encoding-sniff.test.ts && tsx tests/local-data-time-formats.test.ts && tsx tests/local-data-eight-types.test.ts && tsx tests/local-data-mapping-extras.test.ts && tsx tests/local-data-plan.test.ts && tsx tests/local-data-handoff.test.ts && tsx tests/local-data-reuse.test.ts && tsx tests/local-data-conflicts-resolutions.test.ts && tsx tests/runner-variadic-flags.test.ts && node test/local-data-skill.test.mjs",
|
|
51
|
+
"verify:tracking-tools": "tsx test/tracking-client.test.mjs && tsx test/tracking-skill-flow.test.mjs && node test/tracking-commands.test.mjs && tsx tests/tracking-display-name-sync.test.ts && tsx tests/local-data-inspect-convert.test.ts && tsx tests/local-data-upload-client.test.ts && tsx tests/local-data-upload-command.test.ts && tsx tests/local-data-encoding-sniff.test.ts && tsx tests/local-data-time-formats.test.ts && tsx tests/local-data-xlsx-dates.test.ts && tsx tests/local-data-xlsx-hidden-sheets.test.ts && tsx tests/local-data-xlsx-formulas.test.ts && tsx tests/local-data-title-rows.test.ts && tsx tests/local-data-xlsx-merged-cells.test.ts && tsx tests/local-data-xlsx-shared-strings.test.ts && tsx tests/local-data-xlsx-namespace-prefix.test.ts && tsx tests/local-data-value-distribution.test.ts && tsx tests/local-data-summary-rows.test.ts && tsx tests/local-data-duplicate-keys.test.ts && tsx tests/local-data-row-balance.test.ts && tsx tests/local-data-eight-types.test.ts && tsx tests/local-data-mapping-extras.test.ts && tsx tests/local-data-plan.test.ts && tsx tests/local-data-handoff.test.ts && tsx tests/local-data-reuse.test.ts && tsx tests/local-data-conflicts-resolutions.test.ts && tsx tests/runner-variadic-flags.test.ts && node test/local-data-skill.test.mjs",
|
|
51
52
|
"verify:self-check-overlay": "node test/self-check-overlay.test.mjs",
|
|
52
53
|
"verify:update-check": "npx tsx test/update-check.test.mjs",
|
|
53
54
|
"verify:version-sync": "npx tsx tests/version-sync.test.ts",
|
|
55
|
+
"verify:dependency-hygiene": "node --test test/dependency-hygiene.test.mjs",
|
|
54
56
|
"qa-changed": "node scripts/qa-changed.mjs",
|
|
55
57
|
"self-check": "node self-check/scan.mjs",
|
|
56
58
|
"check:release": "node self-check/release-gate.mjs",
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
"check:agents-docs": "node scripts/check-agents-docs-sync.mjs",
|
|
60
62
|
"prepublishOnly": "npm run check:release && npm run build",
|
|
61
63
|
"start": "node dist/index.js",
|
|
62
|
-
"test": "npx tsx src/index.ts --help && npm run verify:retired-api-command && npm run verify:sandbox-tools"
|
|
64
|
+
"test": "npx tsx src/index.ts --help && npm run verify:retired-api-command && npm run verify:sandbox-tools && npm run verify:dependency-hygiene"
|
|
63
65
|
},
|
|
64
66
|
"repository": {
|
|
65
67
|
"type": "git",
|
|
@@ -77,11 +79,10 @@
|
|
|
77
79
|
},
|
|
78
80
|
"dependencies": {
|
|
79
81
|
"@types/json-bigint": "^1.0.4",
|
|
80
|
-
"archiver": "^
|
|
82
|
+
"archiver": "^8.0.0",
|
|
81
83
|
"cli-table3": "^0.6.5",
|
|
82
84
|
"commander": "^12.1.0",
|
|
83
85
|
"csv-parse": "^7.0.2",
|
|
84
|
-
"exceljs": "^4.4.0",
|
|
85
86
|
"iconv-lite": "^0.7.3",
|
|
86
87
|
"jq-wasm": "^3.0.0-jq-1.8.2",
|
|
87
88
|
"jschardet": "^3.1.4",
|
|
@@ -95,16 +96,24 @@
|
|
|
95
96
|
"xlsx": "^0.18.5"
|
|
96
97
|
},
|
|
97
98
|
"devDependencies": {
|
|
98
|
-
"@types/archiver": "^
|
|
99
|
-
"@types/iconv-lite": "^0.0.1",
|
|
99
|
+
"@types/archiver": "^8.0.0",
|
|
100
100
|
"@types/node": "^22.0.0",
|
|
101
101
|
"@types/stream-json": "^1.7.8",
|
|
102
102
|
"@types/turndown": "^5.0.6",
|
|
103
103
|
"@types/unzipper": "^0.10.11",
|
|
104
104
|
"@types/ws": "^8.5.0",
|
|
105
|
+
"exceljs": "^4.4.0",
|
|
105
106
|
"jszip": "^3.10.1",
|
|
106
107
|
"tsup": "^8.0.0",
|
|
107
108
|
"tsx": "^4.0.0",
|
|
108
109
|
"typescript": "^5.5.0"
|
|
110
|
+
},
|
|
111
|
+
"overrides": {
|
|
112
|
+
"exceljs": {
|
|
113
|
+
"archiver": "8.0.0",
|
|
114
|
+
"fast-csv": "5.0.7",
|
|
115
|
+
"unzipper": "0.12.5",
|
|
116
|
+
"uuid": "11.1.1"
|
|
117
|
+
}
|
|
109
118
|
}
|
|
110
119
|
}
|
|
@@ -145,7 +145,7 @@ Prefer the run/artifact commands over hand-written HTTP, Python, or curl. Analys
|
|
|
145
145
|
| `user-cluster-member list` | `analysis.user_cluster_member.list` | Bounded inline cluster members | `--cluster-name`, optional properties/fields/query/limit/offset | Member rows |
|
|
146
146
|
| `user-cluster-member export` | `analysis.user_cluster_member.export` | Stream native full cluster members as csv.gz | `--cluster-name`, optional properties | Async artifact descriptor |
|
|
147
147
|
| `user-cluster create` | `analysis.user_cluster.create` | Create condition/sql cluster directly from semantic intent | `--cluster-name`, `--display-name`, `--definition-request` | Create result and canonical request |
|
|
148
|
-
| `user-cluster update` | `analysis.user_cluster.update` | Update condition/sql cluster | `--cluster-name`, fields to change, optional `--definition-request` | Update result |
|
|
148
|
+
| `user-cluster update` | `analysis.user_cluster.update` | Update condition/sql cluster after confirmation without changing its type or analysis entity | `--cluster-name`, fields to change, optional `--definition-request` with the existing type, then `--yes` after dry-run and explicit confirmation | Update result |
|
|
149
149
|
| `user-cluster create-id` | `analysis.user_cluster.create_id` | Map imported values to an entity and create a cluster | `--display-name`, `--entity-id`, exactly one input source, conditional `--association-property` | Processing state; poll get for final match summary |
|
|
150
150
|
| `user-cluster update-id` | `analysis.user_cluster.update_id` | Remap imported values for an ID cluster | `--cluster-name`, exactly one input source, conditional `--association-property` | Processing state; poll get for final match summary |
|
|
151
151
|
| `user-cluster refresh` | `analysis.user_cluster.refresh` | Trigger cluster recompute | `--cluster-name` | Refresh result |
|
|
@@ -25,12 +25,30 @@ A property is either a name or a typed object:
|
|
|
25
25
|
|
|
26
26
|
## Time range
|
|
27
27
|
|
|
28
|
+
Use semantic `mode`, `unit`, and `value` fields. Always pass `unit` with `recent` or `previous`; the supported units are `day`, `week`, `month`, `quarter`, and `year`.
|
|
29
|
+
|
|
30
|
+
| User intent | `time_range` |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Today | `{"mode":"recent","unit":"day","value":1}` |
|
|
33
|
+
| This week | `{"mode":"recent","unit":"week","value":1}` |
|
|
34
|
+
| This month | `{"mode":"recent","unit":"month","value":1}` |
|
|
35
|
+
| This quarter | `{"mode":"recent","unit":"quarter","value":1}` |
|
|
36
|
+
| This year | `{"mode":"recent","unit":"year","value":1}` |
|
|
37
|
+
| Yesterday | `{"mode":"previous","unit":"day","value":1}` |
|
|
38
|
+
| Previous month | `{"mode":"previous","unit":"month","value":1}` |
|
|
39
|
+
| From a fixed date through today | `{"mode":"start_to_today","start_time":"2026-07-01"}` |
|
|
40
|
+
| From a fixed date through yesterday | `{"mode":"start_to_yesterday","start_time":"2026-07-01"}` |
|
|
41
|
+
|
|
42
|
+
Use `custom` only when both boundaries are fixed:
|
|
43
|
+
|
|
28
44
|
```json
|
|
29
45
|
{"mode":"recent","unit":"day","value":7}
|
|
30
46
|
{"mode":"previous","unit":"day","value":30}
|
|
31
47
|
{"mode":"custom","start_time":"2026-07-01","end_time":"2026-07-07"}
|
|
32
48
|
```
|
|
33
49
|
|
|
50
|
+
Do not pass backend `recent_day` encodings inside `time_range`; the capability compiles the semantic object to the existing analysis representation.
|
|
51
|
+
|
|
34
52
|
## Filter group
|
|
35
53
|
|
|
36
54
|
Groups use `{relation,items}`. Each item has `field`, `operator`, and optional `values`:
|
|
@@ -113,7 +113,7 @@ This is the exhaustive command and flag inventory for the analysis skill. Read t
|
|
|
113
113
|
| `ae-cli analysis user-cluster get` | analysis.user_cluster.get | read | `--project-id` (number; required) — Numeric project ID.<br>`--cluster-names` (json; required) — JSON array of exact cluster_name values. | [user_cluster_get.md](user_cluster_get.md) |
|
|
114
114
|
| `ae-cli analysis user-cluster list` | analysis.user_cluster.list | read | `--project-id` (number; required) — Numeric project ID.<br>`--queries` (json; optional) — Optional JSON array of 1 to 20 keyword filters. Results match any keyword.<br>`--fields` (json; optional) — Optional result field projection JSON array.<br>`--limit` (number; optional, min=1, max=200) — Directory page size. Default: 50, max: 200. Values outside 1..200 are rejected.<br>`--offset` (number; optional, min=0) — Zero-based directory page offset. Default: 0. Negative values are rejected.<br>`--authenticated-only` (boolean; optional) — Return only resources whose metadata can be resolved under the current identity. | [user_cluster_list.md](user_cluster_list.md) |
|
|
115
115
|
| `ae-cli analysis user-cluster refresh` | analysis.user_cluster.refresh | write | `--project-id` (number; required) — Numeric project ID.<br>`--cluster-name` (string; required) — Exact cluster_name. Discover real values with analysis user-cluster list first. | [user_cluster_refresh.md](user_cluster_refresh.md) |
|
|
116
|
-
| `ae-cli analysis user-cluster update` | analysis.user_cluster.update | write | `--project-id` (number; required) — Numeric project ID.<br>`--cluster-name` (string; required) — Exact cluster_name. Discover real values with analysis user-cluster list first.<br>`--display-name` (string; optional, minLength=1, maxLength=80) — Optional new display name. Maximum: 80 characters.<br>`--definition-request` (json; optional) — Optional semantic snake_case cluster definition. Read user_cluster_models.md; raw filts, C-codes, ftv, columnName, and backend DTOs are rejected.<br>`--authenticated-only` (boolean; optional) — Return only resources whose metadata can be resolved under the current identity.<br>`--remark` (string; optional, maxLength=400) — Optional new remark. Maximum: 400 characters.<br>`--zone-offset` (number; optional) — Optional timezone offset. UTC+8 is 8; UTC-5 is -5.<br>`--auto-refresh-cron` (string; optional) — Optional Quartz cron expression for an existing enabled auto-refresh schedule. This does not enable auto refresh. | [user_cluster_update.md](user_cluster_update.md) |
|
|
116
|
+
| `ae-cli analysis user-cluster update` | analysis.user_cluster.update | high-risk-write | `--project-id` (number; required) — Numeric project ID.<br>`--cluster-name` (string; required) — Exact cluster_name. Discover real values with analysis user-cluster list first.<br>`--display-name` (string; optional, minLength=1, maxLength=80) — Optional new display name. Maximum: 80 characters.<br>`--definition-request` (json; optional) — Optional semantic snake_case cluster definition. Its type must match the existing cluster type; update cannot change the analysis entity. Read user_cluster_models.md; raw filts, C-codes, ftv, columnName, and backend DTOs are rejected.<br>`--authenticated-only` (boolean; optional) — Return only resources whose metadata can be resolved under the current identity.<br>`--remark` (string; optional, maxLength=400) — Optional new remark. Maximum: 400 characters.<br>`--zone-offset` (number; optional) — Optional timezone offset. UTC+8 is 8; UTC-5 is -5.<br>`--auto-refresh-cron` (string; optional) — Optional Quartz cron expression for an existing enabled auto-refresh schedule. This does not enable auto refresh. | [user_cluster_update.md](user_cluster_update.md) |
|
|
117
117
|
| `ae-cli analysis user-cluster update-id` | analysis.user_cluster.update_id | write | `--project-id` (number; required) — Numeric project ID.<br>`--cluster-name` (string; required) — Exact cluster_name. Discover real values with analysis user-cluster list first.<br>`--display-name` (string; optional, minLength=1, maxLength=80) — Optional new display name. Maximum: 80 characters.<br>`--input-file` (string; optional) — Local CSV path. The CLI uploads it with purpose analysis.user.id_import before executing the create/update capability.<br>`--input-file-id` (string; optional) — Reuse a file already uploaded for purpose analysis.user.id_import.<br>`--file-content` (string; optional) — Headerless UTF-8 CSV. No header row. Exactly one non-empty column per row: association-property value for the primary user entity, otherwise the entity ID.<br>`--remarks` (string; optional, maxLength=400) — Optional remarks. Maximum: 400 characters.<br>`--association-property` (string; optional) — Required only for the primary user entity. The first CSV column is matched against this allowed user property; #user_id is forbidden. Omit for non-primary entities. | [user_cluster_update_id.md](user_cluster_update_id.md) |
|
|
118
118
|
| `ae-cli analysis user-cluster-member export` | analysis.user_cluster_member.export | read | `--cluster-name` (string; required) — Exact cluster_name. Discover real values with analysis user-cluster list first.<br>`--project-id` (number; required) — Numeric project ID.<br>`--property-names` (json; optional) — Optional JSON array of user property names to include.<br>`--request-id` (string; optional) — Optional caller-supplied cli_<32 lowercase hex> lifecycle ID. ae-cli generates and prints one before dispatch when omitted.<br>`--artifact-format` (string; optional) — Artifact format: jsonl or csv. Default: jsonl. Both formats use native full-download streaming and gzip compression.<br>`--timeout-seconds` (number; optional, min=1, max=21600) — Async runtime in seconds. Default and max: 21600 (6 hours); cancel earlier with analysis query cancel --run-id <run_id>.<br>`--wait` (boolean; optional) — Wait for the remote run and artifact to reach a terminal state. Polling uses short inspect requests; interrupting does not cancel the remote run.<br>`--wait-timeout-seconds` (number; optional, min=1, max=21600) — Maximum time this CLI process waits. Default: 600 seconds; expiry never cancels the remote run.<br>`--output` (string; optional) — Wait, then stream the completed artifact to this local file. Implies --wait.<br>`--force` (boolean; optional) — Allow --output to atomically replace an existing file. Without this flag, existing paths are refused. | [user_cluster_member_export.md](user_cluster_member_export.md) |
|
|
119
119
|
| `ae-cli analysis user-cluster-member list` | analysis.user_cluster_member.list | read | `--cluster-name` (string; required) — Exact cluster_name. Discover real values with analysis user-cluster list first.<br>`--project-id` (number; required) — Numeric project ID.<br>`--property-names` (json; optional) — Optional JSON array of user property names to include.<br>`--fields` (json; optional) — Optional result fields. Defaults to #user_id, #account_id, and #distinct_id.<br>`--query` (string; optional) — Optional keyword filter.<br>`--use-cache` (boolean; optional) — Whether to use query cache. Default: true.<br>`--preview-rows` (number; optional, min=1, max=100000) — Maximum business rows returned per result. Default: 1000, matching the UI member query. Maximum: 100000.<br>`--request-id` (string; optional) — Optional caller-supplied cli_<32 lowercase hex> lifecycle ID. ae-cli generates and prints one before dispatch when omitted.<br>`--timeout-seconds` (number; optional, min=1, max=180) — Sync timeout seconds. Default: 120, max: 180. | [user_cluster_member_list.md](user_cluster_member_list.md) |
|
|
@@ -253,7 +253,7 @@ This is the exhaustive command and flag inventory for the analysis skill. Read t
|
|
|
253
253
|
| `ae-cli project role-user list` | project.role_user.list | read | `--project-id` (number; required) — Numeric project ID.<br>`--role-name` (string; required) — Role name. | [project_role_user_list.md](project_role_user_list.md) |
|
|
254
254
|
| `ae-cli project timezone get` | project.timezone.get | read | `--project-id` (number; required) — Numeric project ID. | [project_timezone_get.md](project_timezone_get.md) |
|
|
255
255
|
| `ae-cli project timezone overview` | project.timezone.overview | read | `--project-id` (number; required) — Numeric project ID. | [project_timezone_overview.md](project_timezone_overview.md) |
|
|
256
|
-
| `ae-cli project timezone update` | project.timezone.update | write | `--project-id` (number; required) — Numeric project ID.<br>`--payload` (json; required) —
|
|
256
|
+
| `ae-cli project timezone update` | project.timezone.update | write | `--project-id` (number; required) — Numeric project ID.<br>`--payload` (json; required) — Item-specific snake_case payload: timezone_toggle uses {"toggle":true}; zone_offset uses {"column_name":"#zone_offset"}; user_timezone uses {"column_name":"user_timezone","codetable":"optional_table"}; project_timezone_display uses {"display_timezones":[{"timezone":8,"is_default":true}]}.<br>`--item` (string; required) — Timezone item: timezone_toggle, zone_offset, user_timezone, project_timezone_display. | [project_timezone_update.md](project_timezone_update.md) |
|
|
257
257
|
| `ae-cli project user-id-items update` | project.user_id_items.update | write | `--project-id` (number; required) — Numeric project ID.<br>`--payload` (json; required) — Required snake_case capability payload. Read the dedicated command reference for its semantic shape; an empty object is not a generic valid payload. | [project_user_id_items_update.md](project_user_id_items_update.md) |
|
|
258
258
|
| `ae-cli system admin list` | system.admin.list | read | `--company-id` (number; required, min=1) — Company ID. | [system_admin_list.md](system_admin_list.md) |
|
|
259
259
|
| `ae-cli system admin remove` | system.admin.remove | high-risk-write | `--company-id` (number; required, min=1) — Company ID.<br>`--target-open-id` (string; required) — Target member open ID. | [system_admin_remove.md](system_admin_remove.md) |
|
|
@@ -7,13 +7,22 @@ Do not use it for unrelated project-management actions or for fields not present
|
|
|
7
7
|
Command:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
ae-cli project timezone update --project-id <project_id> --
|
|
11
|
-
ae-cli project timezone update --
|
|
10
|
+
ae-cli project timezone update --project-id <project_id> --item timezone_toggle --payload '{"toggle":true}'
|
|
11
|
+
ae-cli project timezone update --project-id <project_id> --item timezone_toggle --payload '{"toggle":true}' --dry-run
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Capability id: `project.timezone.update`.
|
|
15
15
|
|
|
16
|
-
Input sends `project_id`, `payload`, `item`.
|
|
16
|
+
Input sends `project_id`, `payload`, and `item`. The payload is selected by `item`; use exactly one of these snake_case shapes:
|
|
17
|
+
|
|
18
|
+
| `item` | `payload` | Notes |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `timezone_toggle` | `{"toggle":true}` | `toggle` is required and boolean. Use `true` to enable multi-timezone and `false` to disable it. Never send `time_zone_enabled`. |
|
|
21
|
+
| `zone_offset` | `{"column_name":"#zone_offset"}` | `column_name` is required and selects the event property used as the timezone offset. |
|
|
22
|
+
| `user_timezone` | `{"column_name":"user_timezone","codetable":"timezone_code_table"}` | `column_name` is required. `codetable` is optional. |
|
|
23
|
+
| `project_timezone_display` | `{"display_timezones":[{"timezone":8,"is_default":true},{"timezone":0,"is_default":false}]}` | `display_timezones` is a non-empty array. `timezone` is an integer from -12 through 14, or 99 for no fixed display timezone; `is_default` is optional and boolean. |
|
|
24
|
+
|
|
25
|
+
Do not infer a payload field from the `project timezone get` output. In particular, its `time_zone_enabled` response field is not accepted by the update DTO; `timezone_toggle` always uses `payload.toggle`.
|
|
17
26
|
|
|
18
27
|
Output uses the gateway envelope: success is `ok=true,data,meta`; failure is `ok=false,error`. Preserve `request_id` and `invocation_id` when present.
|
|
19
28
|
|
|
@@ -21,5 +30,5 @@ Output uses the gateway envelope: success is `ok=true,data,meta`; failure is `ok
|
|
|
21
30
|
| Parameter | Required | Description |
|
|
22
31
|
|---|---|---|
|
|
23
32
|
| `--project-id` | Yes | Numeric project ID. |
|
|
24
|
-
| `--payload` | Yes |
|
|
33
|
+
| `--payload` | Yes | Item-specific payload from the table above. Do not send camelCase aliases or response-only fields. |
|
|
25
34
|
| `--item` | Yes | Timezone item: timezone_toggle, zone_offset, user_timezone, project_timezone_display. |
|
|
@@ -13,6 +13,8 @@ ae-cli analysis-meta property create --dry-run
|
|
|
13
13
|
|
|
14
14
|
Capability id: `metadata.property.create`.
|
|
15
15
|
|
|
16
|
+
Authorization requires the single project function permission `editSuperMeta` with the `metadata:write` scope. In the zh-CN permission UI, this permission is labeled `元数据管理 > 编辑`; the corresponding English label is `Metadata Management > Edit`. This is the same project permission used by `metadata.super_metadata.batch_create`. If either command returns `PROJECT_PERMISSION_DENIED`, do not retry with a different payload and do not describe the two capability IDs as two separate permissions. Ask a project administrator to grant this shared project permission to the current identity.
|
|
17
|
+
|
|
16
18
|
Input sends `project_id`, `table_type`, `payload`.
|
|
17
19
|
|
|
18
20
|
Output is a successful gateway envelope with no business data. Read back with `property get` using the same table type.
|
|
@@ -12,6 +12,8 @@ ae-cli analysis-meta super-metadata batch-create --project-id <project_id> --eve
|
|
|
12
12
|
|
|
13
13
|
Capability id: `metadata.super_metadata.batch_create`.
|
|
14
14
|
|
|
15
|
+
Authorization requires the single project function permission `editSuperMeta` with the `metadata:write` scope. In the zh-CN permission UI, this permission is labeled `元数据管理 > 编辑`; the corresponding English label is `Metadata Management > Edit`. This is the same project permission used by `metadata.property.create`. If either command returns `PROJECT_PERMISSION_DENIED`, do not retry with a different payload and do not describe the two capability IDs as two separate permissions. Ask a project administrator to grant this shared project permission to the current identity.
|
|
16
|
+
|
|
15
17
|
Input sends `project_id` plus any non-empty JSON arrays among `events`, `event_properties`, and `user_properties`. Use snake_case object fields exactly as documented by the common-service schema:
|
|
16
18
|
|
|
17
19
|
- Event items: `event_name`, optional `event_desc`, optional `remark`, optional `super_event_prop_names`.
|
|
@@ -7,6 +7,8 @@ Top-level variants:
|
|
|
7
7
|
- condition: `{"type":"condition","conditions":{"relation":"and|or","items":[...]}}`
|
|
8
8
|
- SQL: `{"type":"sql","sql":"...","params":[{"name":"partdate","type":"part_date","recent_day":"1-7"}]}`
|
|
9
9
|
|
|
10
|
+
For `user-cluster update`, `type` must match the existing cluster type. The update command cannot change a cluster between condition and SQL, and it cannot change the cluster's analysis entity.
|
|
11
|
+
|
|
10
12
|
For SQL clusters, quote Trino special identifiers with double quotes, for example `{"type":"sql","sql":"SELECT \"#user_id\" FROM v_user_1 WHERE vip_level >= 3"}`. This applies to identifiers containing `#`, `$`, `@`, spaces, or punctuation; single quotes are string literals.
|
|
11
13
|
|
|
12
14
|
If a SQL cluster reads an event table, include a predicate on the quoted `"$part_date"` date-partition column; the backend rejects event-table SQL without it. This does not apply to the user-table example above.
|
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Update a condition or SQL user cluster. Discover the exact `cluster_name` first.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This is a `high-risk-write`. First run the final command with `--dry-run`, summarize the exact cluster and fields that will change, and wait for explicit user confirmation. Then run the unchanged command with `--yes`. Do not use it for ID-file membership replacement, to create a missing cluster, to change its analysis entity, or to change it between condition and SQL types.
|
|
6
|
+
|
|
7
|
+
Supplying `--definition-request` automatically starts recomputation after the definition is updated; do not call `user-cluster refresh` afterward. Updating only `--display-name` or `--remark` does not recompute. `--auto-refresh-cron` changes an existing enabled auto-refresh schedule and does not enable auto refresh. A successful update means the definition was saved, not that the new result is complete. Poll `user-cluster get` until `progress=100` and `refresh_end_time` is not older than `update_time` before using `users_num` or querying members.
|
|
6
8
|
|
|
7
9
|
The response distinguishes both paths. A definition update returns `computation.triggered_automatically=true`, `result_freshness.is_stale=true`, and normally `next_action=poll_get` with an exact capability/input pair. A display-name/remark-only update returns `computation.status=not_triggered`, `result_freshness.status=fresh`, and `next_action=none`.
|
|
8
10
|
|
|
9
|
-
Flags: `--project-id`, `--cluster-name` required. Optional: `--display-name`, `--definition-request`, `--authenticated-only`, `--remark`, `--zone-offset`, `--auto-refresh-cron`.
|
|
11
|
+
Flags: `--project-id`, `--cluster-name` required. Optional: `--display-name`, `--definition-request`, `--authenticated-only`, `--remark`, `--zone-offset`, `--auto-refresh-cron`. There is no `--entity-id` update flag. When the definition changes, `definition_request.type` must match the existing cluster type; it selects the definition variant but does not change the saved type.
|
|
10
12
|
|
|
11
13
|
`display_name` is at most 80 characters and `remark` is at most 400 characters. The CLI rejects violations before dispatch. `cluster_name` is an existing exact identifier and cannot be renamed by update.
|
|
12
14
|
|
|
13
15
|
Read `user_cluster_models.md` before changing the definition. The backend validates and compiles `definition_request` inside update and refuses to modify the cluster if clarification is required. Condition definitions are saved as mixed-condition clusters.
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
|
-
ae-cli analysis user-cluster update --project-id <project_id> --cluster-name retained_users --display-name "Retained Users v2"
|
|
18
|
+
ae-cli analysis user-cluster update --project-id <project_id> --cluster-name retained_users --display-name "Retained Users v2" --dry-run
|
|
19
|
+
|
|
20
|
+
ae-cli analysis user-cluster update --project-id <project_id> --cluster-name retained_users --display-name "Retained Users v2" --yes
|
|
17
21
|
|
|
18
|
-
ae-cli analysis user-cluster update --project-id <project_id> --cluster-name retained_users --auto-refresh-cron '0 30 2 * * ? *'
|
|
22
|
+
ae-cli analysis user-cluster update --project-id <project_id> --cluster-name retained_users --auto-refresh-cron '0 30 2 * * ? *' --dry-run
|
|
19
23
|
```
|
|
@@ -10,10 +10,16 @@ Flags: `--project-id`, `--tag-name`, `--display-name`, `--definition-request` re
|
|
|
10
10
|
|
|
11
11
|
`tag_name` is a machine identifier: 1-80 characters, starts with a letter, and contains only letters, digits, or underscores. `display_name` is 1-80 characters. The CLI rejects violations before dispatch.
|
|
12
12
|
|
|
13
|
-
Read `user_tag_models.md` before constructing `--definition-request`. Create does not accept `--remark`; set it later with `user-tag update` when needed.
|
|
13
|
+
Read `user_tag_models.md` before constructing `--definition-request`. Dynamic first/last ranges use semantic `time_range` values such as `{"mode":"recent","unit":"month","value":1}` for this month or `{"mode":"start_to_today","start_time":"2026-07-01"}` for a fixed start date through today. Create does not accept `--remark`; set it later with `user-tag update` when needed.
|
|
14
14
|
|
|
15
15
|
The backend validates and compiles the definition inside the create operation; if metadata is ambiguous or missing, creation fails without creating the tag.
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
ae-cli analysis user-tag create --project-id <project_id> --tag-name high_value --display-name "High Value" --definition-request '{"type":"condition","condition_values":[{"value":"high","events":[{"event":"pay","operator":"gte","value":3,"aggregation":"count","time_range":{"mode":"recent","unit":"day","value":30}}]}]}'
|
|
19
19
|
```
|
|
20
|
+
|
|
21
|
+
First/last tag for this month:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ae-cli analysis user-tag create --project-id <project_id> --tag-name latest_platform_this_month --display-name "Latest Platform This Month" --definition-request '{"type":"first_last","first_last":{"event":"login","occurrence":"last","property":"platform","time_range":{"mode":"recent","unit":"month","value":1}}}'
|
|
25
|
+
```
|
|
@@ -32,10 +32,16 @@ Required: `event`, `aggregation`. `property`, `time_range`, and `filters` are op
|
|
|
32
32
|
|
|
33
33
|
## First/last tag
|
|
34
34
|
|
|
35
|
-
Required: `event`, `occurrence=first|last`, and exactly one value source: `calculation` or `property`. `time_range` and `filters` are optional. Filters support only event properties and user properties. A string `field` is an event property; use `{name,type:"user_property"}` for a user property. Supplying neither or both value sources is rejected before execution.
|
|
35
|
+
Required: `event`, `occurrence=first|last`, and exactly one value source: `calculation` or `property`. `time_range` and `filters` are optional. Filters support only event properties and user properties. A string `field` is an event property; use `{name,type:"user_property"}` for a user property. Supplying neither or both value sources is rejected before execution. Use the semantic time mappings in [`audience_models.md`](audience_models.md) for dynamic ranges such as today, this month, or a fixed start date through today.
|
|
36
36
|
|
|
37
37
|
```json
|
|
38
|
-
{"type":"first_last","first_last":{"event":"login","occurrence":"last","property":"platform","time_range":{"mode":"recent","unit":"
|
|
38
|
+
{"type":"first_last","first_last":{"event":"login","occurrence":"last","property":"platform","time_range":{"mode":"recent","unit":"month","value":1},"filters":{"relation":"and","items":[{"field":{"name":"country","type":"user_property"},"operator":"eq","values":["US"]}]}}}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
From a fixed date through today:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{"type":"first_last","first_last":{"event":"login","occurrence":"first","calculation":"specific_time","time_range":{"mode":"start_to_today","start_time":"2026-07-01"}}}
|
|
39
45
|
```
|
|
40
46
|
|
|
41
47
|
## SQL tag
|
|
@@ -17,6 +17,7 @@ Two entrances lead here: the AE Agent dialog (attach / plus-button upload) and `
|
|
|
17
17
|
- Do not invent account IDs, distinct IDs, event times, event names, projects, APPIDs, receivers, or timezones.
|
|
18
18
|
- `value_mapping` and `random_pool` are explicit user decisions. Never invent them.
|
|
19
19
|
- Never auto-fill a missing time for `track`/`track_*` rows. A missing time on user-profile rows may be filled with the current time only by setting `missing_time: 'now'` and only after the user explicitly confirms it.
|
|
20
|
+
- The mapping's fill-in options (`missing_time: 'now'`, `account_id_value`/`distinct_id_value`, `random_pool`, `exclude_columns`, `value_mapping`) record a decision the user has already made; they are never to make a validation failure disappear. When `convert` quarantines rows, present the failure first — the error `code`, the row count, its share of the total, and which events or time ranges are affected — and state the consequence in the user's terms ("3,000 events would carry a time that is not when they happened"). Only after the user has seen that may one of these options be set. When the evidence is insufficient, report the batch as not passed and pending customer data; never shrink the upload scope to manufacture a pass.
|
|
20
21
|
- Do not read or send an AE access token or CLI token to `/sync_json`. The receiver request uses only APPID and UE data.
|
|
21
22
|
- Never execute `data-integration upload` until the user has seen the target, mapping, valid/quarantined counts, batches, and dry-run and has explicitly confirmed that upload.
|
|
22
23
|
- A blocked manifest requires a second, explicit clean-subset decision. Never add `--allow-clean-subset` implicitly.
|
|
@@ -52,7 +52,7 @@ the user decide — never to guess an encoding or structure and retry silently.
|
|
|
52
52
|
| `LOCAL_DATA_INPUT_INVALID` | Generic parse failure (malformed CSV/TSV/JSON/XLS) | Verify encoding and structure, then retry without changing the source |
|
|
53
53
|
| `LOCAL_DATA_JSONL_INVALID` | A JSONL line is not valid JSON (`location.record` names the line) | Point at the offending record |
|
|
54
54
|
| `LOCAL_DATA_JSON_ROOT_INVALID` | JSON root is not an object or array | Check the file's top-level shape |
|
|
55
|
-
| `LOCAL_DATA_XLSX_INVALID` | Workbook metadata missing / no readable sheets / worksheet entry missing | Re-export the workbook |
|
|
55
|
+
| `LOCAL_DATA_XLSX_INVALID` | Workbook metadata missing / no readable sheets / worksheet entry missing | Re-export the workbook (sheet recognition accepts `<sheet>` and namespaced `<x:sheet>` alike, so this is a real metadata gap) |
|
|
56
56
|
| `LOCAL_DATA_SET_NOT_FOUND` / `LOCAL_DATA_SET_REQUIRED` | Sheet or JSON Path not found / ambiguous | Ask which `--data-set` to use |
|
|
57
57
|
|
|
58
58
|
A parse error is never a reason to change the mapping or the tracking plan. Report the code and
|
|
@@ -81,6 +81,59 @@ Write failures (disk full `ENOSPC`, permission denied `EACCES`) do not hang the
|
|
|
81
81
|
output streams fail with a clear `Failed to write "<path>"` message telling the user to check disk
|
|
82
82
|
space and directory permissions. Fix the environment, then re-run into a fresh output directory.
|
|
83
83
|
|
|
84
|
+
## File-level data-quality severity
|
|
85
|
+
|
|
86
|
+
The classes above are per row or per field. The user also needs one verdict for the file as a whole,
|
|
87
|
+
and its signals arrive scattered across the manifest and stderr. Grade them before reporting so the
|
|
88
|
+
same file gets the same verdict no matter who reports it.
|
|
89
|
+
|
|
90
|
+
| Severity | Signal | Handling |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| Critical | Established with the user to be a cumulative snapshot or an aggregate report (see [ue-routing.md](ue-routing.md)); a required identity or time column empty for the whole file | Do not upload. Resolve with the user first |
|
|
93
|
+
| High | `manifest.output.invalid_records` is a large share of the row count; inspect reported `leading_title_rows` or `header_signal` and the user has not yet said whether those rows are a title/banner; inspect reported `xlsx_structure.merged_covered_cells` and the user has not yet said whether the merged label belongs on the rows the block covers; `summary_rows` was reported and the user has not yet said whether those rows are totals or real records; `duplicate_keys` was reported and the user has not yet said whether the repeated rows are separate observations | Upload is allowed, but name the item explicitly in the confirmation gate and in the completion response |
|
|
94
|
+
| Medium | `manifest.output.skipped_fields`; ragged delimited rows; `manifest.output.flatten_misses`; `manifest.output.unreadable_cells`; `xlsx_structure.hidden_rows` or `hidden_columns` read as data | Report the counts; the gate is unchanged |
|
|
95
|
+
| Low | `manifest.output.lan_ip_records` | Report once |
|
|
96
|
+
|
|
97
|
+
Rules:
|
|
98
|
+
|
|
99
|
+
- Severity is reported, never silently applied. A Critical finding stops the pipeline and is stated
|
|
100
|
+
as a data finding, not as a program error.
|
|
101
|
+
- Grade only what the run actually emitted. Every signal that belongs at High or Critical above now
|
|
102
|
+
has a detector, so grade it from what the run reported — never tell the user the tool checked
|
|
103
|
+
something it did not.
|
|
104
|
+
- `unreadable_cells` counts XLSX cells that carried no value the tool may use, grouped by cause:
|
|
105
|
+
`formula_no_cached_value` (the file stores a formula but not the result Excel last computed),
|
|
106
|
+
`error_value` (`#N/A`, `#DIV/0!`, …), `unreadable_object` (an unrecognized cell shape). The cells
|
|
107
|
+
read as missing and their rows are kept, so the record count says nothing about them — a column
|
|
108
|
+
that is empty in AE while the spreadsheet looks full is this. The tool never evaluates a formula
|
|
109
|
+
and never guesses a result; the fix is to recalculate and re-export in Excel, or to export values
|
|
110
|
+
instead of formulas. `inspect` reports the same counts before conversion.
|
|
111
|
+
- `xlsx_structure` records worksheet layout the rows themselves cannot carry: `merged_covered_cells`
|
|
112
|
+
counts cells that are empty only because a merged block covers them (Excel shows the value on the
|
|
113
|
+
block's first row), `hidden_rows` and `hidden_columns` name what the worksheet hides. The default
|
|
114
|
+
read changes none of it, so these counts describe what was uploaded: a column mostly missing in AE
|
|
115
|
+
while the spreadsheet looks full is the first of them. `merged_cells_filled` and
|
|
116
|
+
`excluded_hidden_rows` say what the run did about it, which is only ever what the user asked for
|
|
117
|
+
via `--fill-merged-cells` / `--exclude-hidden-rows` (mapping: `fill_merged_cells` /
|
|
118
|
+
`exclude_hidden_rows`). XLSX only — a legacy `.xls` workbook is not scanned.
|
|
119
|
+
- `summary_rows` names rows that read as a summary line rather than an observation, by `row` (the
|
|
120
|
+
data-row ordinal) and `signals`: `total_label` (a cell reads as `合计` / `总计` / `小计` / `汇总` / `Total` /
|
|
121
|
+
`Subtotal`) and `column_total` (a number equal to the total of its column's other rows). The rows
|
|
122
|
+
were converted like any other, so this is a finding about what was uploaded: one fabricated event
|
|
123
|
+
whose amount is the whole group's, and a column whose reported `sum` is twice its real total. There
|
|
124
|
+
is no flag that drops a data row, because a row labelled `合计` is sometimes a real record; the fix
|
|
125
|
+
is to remove it from the source file or re-export without it. Any format, `.xls` included.
|
|
126
|
+
- `duplicate_keys` names rows the source repeated under the same business key, by `key_columns` (the
|
|
127
|
+
columns compared), `duplicate_groups`, `extra_rows` (surplus records an upload would carry), and
|
|
128
|
+
`groups` with `count`, the data-row `rows`, and a `key_hash` prefix — never the key's own values.
|
|
129
|
+
Nothing was removed: a repeat is sometimes a real pair of records, two order lines in the same
|
|
130
|
+
checkout second, and AE appends accepted events with no way to un-send one, so ask the user whether
|
|
131
|
+
the rows are separate observations; if not, have them remove the rows from the source file. Values
|
|
132
|
+
are compared as written, so a repeat spelled two ways is missed, and `tracking_truncated` means
|
|
133
|
+
distinct keys outran the scan's budget and there may be more. Any format, `.xls` included.
|
|
134
|
+
- A large quarantine share has no fixed threshold. State the ratio and the dominant error `code`,
|
|
135
|
+
and let the user judge.
|
|
136
|
+
|
|
84
137
|
## Cross-cutting rules
|
|
85
138
|
|
|
86
139
|
- Classify first, act second. Match on `code`, not on message text.
|
|
@@ -10,6 +10,8 @@ Confirm:
|
|
|
10
10
|
|
|
11
11
|
Accept one or more CSV, TSV, TXT, JSON, JSONL (NDJSON), XLS, or XLSX files. CSV/TSV/TXT/JSON/JSONL/XLSX have no hard size limit; files over 1 GB print a stderr warning with an estimated processing time and suggest splitting. XLS over 100 MB prints a memory-risk warning (the legacy parser loads the whole workbook, roughly 5-10x file size); XLS over 1 GB is still rejected — convert it to XLSX or split it first.
|
|
12
12
|
|
|
13
|
+
XLSX worksheet recognition matches `<sheet>` and namespaced `<x:sheet>` alike: some cleaning/export tools rewrite the default OOXML namespace as a prefix, and the workbook reads the same either way. The worksheet *row* nodes themselves are still read unprefixed, so a workbook whose row data is also namespaced falls back to CSV.
|
|
14
|
+
|
|
13
15
|
## Inspect without exposing raw values
|
|
14
16
|
|
|
15
17
|
Before the full inspection (which streams and profiles the entire file and can take
|
|
@@ -39,16 +41,30 @@ If `selection_required=true`, show only the Sheet/JSON Path candidates and ask t
|
|
|
39
41
|
ae-cli data-integration inspect --input-file '<path>' --data-set '<candidate-id>' --source-timezone '<iana-timezone>'
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
Report row/column counts, field types, missing/unique/time-parse ratios, UE eligibility, mapping confidence, and warnings. Samples are bounded (up to 5 distinct, truncated) — summarize, never paste them. ID-like columns (`id`, `*_id`, `*_key`, `*_code`, `*_no`, `*_num`) stay `string` even when every value is numeric; JSON-encoded object/array values inside CSV cells are recognized as `object`/`list`, not `string`. IP- and UUID-named columns are additionally checked against their value specs: inspect warns how many non-empty values are invalid IPv4/IPv6, private/LAN IPs, or non-UUID strings, so the user can decide whether to map them as `ip_field`/`uuid_field`. Read [UE routing](ue-routing.md) before choosing a branch.
|
|
44
|
+
Report row/column counts, field types, missing/unique/time-parse ratios, UE eligibility, mapping confidence, and warnings. Samples are bounded (up to 5 distinct, truncated) — summarize, never paste them. ID-like columns (`id`, `*_id`, `*_key`, `*_code`, `*_no`, `*_num`) stay `string` even when every value is numeric; JSON-encoded object/array values inside CSV cells are recognized as `object`/`list`, not `string`. IP- and UUID-named columns are additionally checked against their value specs: inspect warns how many non-empty values are invalid IPv4/IPv6, private/LAN IPs, or non-UUID strings, so the user can decide whether to map them as `ip_field`/`uuid_field`. Excel columns whose cells carry a date number format infer as `datetime` and are named in a warning (see **Excel date cells** below). Read [UE routing](ue-routing.md) before choosing a branch.
|
|
43
45
|
|
|
44
46
|
A stderr `Warning: … column count different from the header row …` means the CSV/TSV has ragged rows (extra fields dropped, missing fields treated as empty); report it as a data-quality signal. For how every pipeline failure — abnormal data, parse errors, and program errors — is classified and handled, see [error handling](error-handling.md).
|
|
45
47
|
|
|
48
|
+
**Value frequency and numeric distribution.** A distinct count says how many different values a column holds, not whether they are worth uploading. Two per-column fields answer that, and both appear in `inspect` output only — never in the convert manifest:
|
|
49
|
+
|
|
50
|
+
- `value_frequency` — the 10 most frequent values as `{value, count, ratio}`, values truncated the way `samples` are and counted after truncation. It is reported only for a column whose distinct values all fit the tracked budget (200), so the counts are exact and complete when present; a column past the budget reports nothing here and its `unique_count` is the field to read instead. Use it to separate an enum from free text: a `渠道` column with three values is a property worth uploading, and its listed values are also what a `value_mapping` decision is made from. A single value covering every row usually means an export artifact, not data — propose `exclude_columns` and let the user decide.
|
|
51
|
+
- `numeric_summary` — `count`, `min`, `max`, `sum`, `mean`, `p25`, `median`, `p75` for a column that inferred as `number`. `count` is the values that read as numbers, which is below the column's non-missing count when the column is mixed. `count`/`min`/`max`/`sum`/`mean` are always exact; the quantiles come from a bounded sample on large columns and then `quantiles_approximate: true` says so. Use it to check the magnitude before it is locked into an AE property: a mean far below the maximum on a monotonically climbing column is the signature of a cumulative snapshot rather than a per-row measure (see [UE routing](ue-routing.md)), an amount whose values are 100× the expected size is a 分/元 unit mismatch, and a column that is entirely one number carries no signal.
|
|
52
|
+
|
|
53
|
+
Summarize both — report the shape of the distribution and the names of the values, and do not paste the whole table into the conversation. They are read out of the customer's file like `samples` are.
|
|
54
|
+
|
|
46
55
|
## Advanced input
|
|
47
56
|
|
|
48
57
|
- **Headerless files** — inspect auto-detects a missing header row on CSV/TSV and reports `no_headers: true` with a `header_detection` verdict and `auto_headers` placeholders (`col_1..col_N`); the first row is already treated as data. `--headerless` forces the same behavior without detection. Never keep the `col_1..col_N` placeholders — they carry no business meaning. For each column, read its bounded samples and inferred type and propose a meaningful name, present every proposal to the user (column position, sample summary, suggested name), and let the user confirm or rename each one; record the confirmed names in the mapping's `headers` field. When the user already knows the names, re-run inspect with `--headers 'col1,col2,...'` so the recommended mapping carries them.
|
|
58
|
+
- **Title rows above the header** — an exported report often puts a caption in the first cell (`2026年3月销售明细`) and the real header row underneath. Read as-is, the caption becomes the file's only column name, every real column name is lost, and the header row is counted as a data row. Inspect reports the suspected rows under `leading_title_rows` (row ordinals and non-empty cell counts only, never the cell text) and warns — but it does **not** change what it read: the first row was still used as the header, because Excel exports legitimately carry numeric header rows (`2024`, `2025`) that look like data, and there is no flag that puts a header row back once it has been treated as data. So the report is a question for the user. When they confirm those rows are a title or banner, re-run inspect with `--skip-rows N` (N is exactly the last ordinal listed); the rerun reads the real header row, reports `skipped_rows`, and carries `skip_rows` into the recommended mapping so `convert` reads the same rows inspect profiled. On XLSX, inspect additionally reports `header_signal` when the row it used as the header looks like data — same rule: reported, not applied; resolve it with `--headers` or `--headerless`. This covers CSV/TSV and XLSX; a legacy `.xls` workbook accepts `--skip-rows` but is not scanned for title rows.
|
|
59
|
+
- **Summary and total rows** — an exported report ends with a 合计 row, and a grouped one repeats 小计 after every group. Those rows are not observations: uploaded, each becomes an event that never happened whose amount is the whole group's revenue, and profiled, it doubles the column's `sum` and turns its `max` into the total. Nothing in the row itself says so, so inspect flags them under `summary_rows` and warns. Each entry carries `row` (the data-row ordinal, the same numbering `invalid.rows.jsonl` and `--salvage-from` use) and `signals`: `total_label` when a cell reads as a total label (`合计` / `总计` / `小计` / `汇总` as a prefix, `Total` / `Subtotal` / `Sum` as the whole cell), naming the column in `label_column` but never the cell text; `column_total` when a number on that row equals the total of its column's other rows, listing those columns in `total_columns`. A row can raise one signal or both — a labelled group subtotal holds its group's total, not the column's, so only the label fires. Nothing is removed and nothing is changed: `convert` writes these rows as records, and every number reported for their columns counts them, which is exactly why the finding has to be read. There is no flag that drops a data row, because a row labelled `合计` is sometimes a real business record; when the user confirms a row is a total, ask them to remove it from the source file or re-export without it, then inspect again. `convert` repeats the finding in `manifest.output.summary_rows`, since by upload time a subtotal row with a plausible identity and time is indistinguishable from data. This covers every format, `.xls` included — the check reads rows, not worksheet structure.
|
|
60
|
+
- **Repeated business keys** — a customer re-exports a report whose range overlaps the last export, or pastes two sheets together, and the same observation arrives twice. Uploaded, each repeat is a second event: that user's revenue doubles, every funnel counts them twice, and AE appends accepted events with no way to un-send one — so the only place this is fixable is before the upload. Inspect compares each row's business key against the rows before it and reports repeats under `duplicate_keys`, plus a warning. The report carries `key_columns` (the columns actually compared — always read it, since the key is what the finding means), `checked_rows`, `duplicate_groups`, `extra_rows` (surplus records an upload would carry), and `groups`, each with `count`, the data-row `rows` (the numbering `invalid.rows.jsonl` and `--salvage-from` use), and a `key_hash` prefix that distinguishes groups without revealing values — the key's own text is never reported. `groups_truncated` / `rows_truncated` mean the list is bounded, not that the counts are; `tracking_truncated` means distinct keys outran the scan's budget and there may be more. The key comes from the mapping's identity, time, and event-name columns on `convert`, and from column-name matching on `inspect`; a single column is never a key, so a file with no recognizable time column is not scanned at all (identity alone would call every returning user's second row a repeat). Two limits to state when reporting: values are compared as written, so `2026-03-01 10:00:00` and `2026/03/01 10:00:00` are two different keys and a repeat spelled two ways is missed; and a source's own unique key (an order id) is not compared on unless the mapping names it as identity, time, or event. Nothing is removed — a repeat is sometimes a real pair of records, two order lines in the same checkout second — so ask the user whether the rows are separate observations, and if they are not, have them remove the rows from the source file and inspect again. `convert` repeats the finding in `manifest.output.duplicate_keys`, because by upload time both copies are ordinary valid records; it describes the whole source file even on a `--salvage-from` run, since every valid row of that file ends up in AE. This covers every format, `.xls` included — the check reads rows, not worksheet structure.
|
|
49
61
|
- **TSV / TXT** — `.tsv` and `.tab` use a tab delimiter with no quoting convention; `.txt` and unknown extensions are content-sniffed into CSV, TSV, or NDJSON.
|
|
50
62
|
- **Encoding** — text files are auto-detected (UTF-8, GBK, GB2312, Big5, and others); no flag is needed.
|
|
51
|
-
- **Excel
|
|
63
|
+
- **Excel date cells** — a cell whose number format is a date or date+time is read as the wall-clock timestamp shown in Excel, not as the Excel serial number stored behind it, so the column infers as `datetime` and can serve as the time field. Inspect lists every such column in a warning. Treat that warning as a question to the user, not as a note: the same column profiled as `number` before this behavior existed, so if any part of this file was already sent to AE, the property may have been received as a number and its type is now locked — it cannot be changed to datetime, and the column has to be re-sent under a new property name. Ask whether the column was uploaded before, and only map it once the user answers. Elapsed-duration formats (`[h]:mm:ss` and the equivalent built-ins) are durations rather than points in time and stay `number`.
|
|
64
|
+
- **Excel formula cells** — a spreadsheet stores a formula and, next to it, the result Excel last computed. That cached result is the value: it is read normally, including a result of `0` or `""`, which are real values and not blanks. This tool never evaluates a formula and never guesses a result, so a cell holding a formula the file never computed has nothing to upload; it is read as missing and counted, as is an Excel error value (`#N/A`, `#DIV/0!`, …). Inspect reports the counts per column in a warning and `convert` repeats them in `manifest.output.unreadable_cells`. Report them: the rows are kept and the record count is unchanged, so this is the only explanation for a column that is empty in AE while the spreadsheet looks full. When a column that matters reads as missing this way, ask the user to recalculate and re-export in Excel, or to export values instead of formulas, before uploading. This covers XLSX; a legacy `.xls` workbook goes through a different parser and is not counted here.
|
|
65
|
+
- **Merged cells, hidden rows, and hidden columns** — a sheet maintained by hand merges a label down the rows it covers (`区域` spanning one region's block). Excel keeps that value on the block's first row only and stores every row below it as an empty cell, so a column that looks full on screen arrives mostly missing, and the AE property built from it would be empty for most events. The same worksheet may also hide a row inside a data block or hide a whole column. None of this travels with a row, so inspect scans the worksheet structure separately and reports it under `xlsx_structure`: `merged_ranges` with `merged_range_samples` (references such as `A3:A5`, never cell text), `merged_covered_cells` per column, `hidden_rows` with `hidden_row_samples` (source row numbers as Excel numbers them), and `hidden_columns` by header name. The default read is unchanged, so the report is a question for the user, and each answer is a flag: `--fill-merged-cells` copies each block's value into the cells its own range covers — bounded to the range, never overwriting a value that is there and never inventing one when the block's own cell is empty, so it is not a forward fill; `--exclude-hidden-rows` leaves hidden rows out. Neither is on by default: those cells really are empty in the file, and a row hidden inside a data block may still be real data — unlike a hidden *worksheet* (below), which is excluded by default. Hidden columns have no flag at all; when the user confirms one is not data, list it in the mapping's `exclude_columns`. Both flags are carried into the recommended mapping as `fill_merged_cells` / `exclude_hidden_rows`, which is what makes `convert` read the rows inspect profiled — `convert` has no read flags of its own — and `convert` repeats the findings in `manifest.output.xlsx_structure`, the only record of a layout the converted rows no longer show. This covers XLSX; a legacy `.xls` workbook is not scanned, so ask the user about merged labels and hidden rows there instead of trusting silence.
|
|
66
|
+
- **Hidden worksheets** — a worksheet hidden in the workbook is left out of the `--data-set` candidates and out of `--merge-sheets`, because a sheet the file does not show is usually scratch space, a lookup table, or a superseded draft rather than rows anyone meant to upload. Inspect lists each one under `excluded_sheets` (with `reason: hidden`); report those names to the user, since they are the only explanation for a row count lower than the workbook appears to hold. Their headers are also left out of `header_consistency`, so a stale hidden draft cannot make a mergeable workbook look ragged. A hidden sheet stays readable when the user names it in `--data-set` — the command then warns on stderr that the selected sheet is hidden. Only pass a hidden sheet after the user says that is what they want. When *every* worksheet is hidden there is no candidate left, and inspect fails with `LOCAL_DATA_ALL_DATA_SETS_HIDDEN` whose hint lists the hidden sheets; treat that as a question about which sheet holds the real data, not as an unreadable file. This detection covers XLSX only: a legacy `.xls` workbook's sheet list is unfiltered, so a hidden sheet there still appears as a candidate and is still merged — for `.xls`, ask the user to confirm the sheet list instead of trusting it.
|
|
67
|
+
- **Excel sheets** — `--merge-sheets` streams every visible worksheet in file order instead of a single selected sheet; otherwise ask which sheet/`--data-set` to use. Inspect also reports `header_consistency` (`all_same` or `different`) across a workbook's sheets, with `header_details` listing each sheet's header row when they differ; prefer `--merge-sheets` only when headers match. Matching headers establish a shared structure, not disjoint rows: a detail sheet and a summary sheet, or `1月` and `1月修订版`, usually carry identical headers and would be merged and reported twice over. Before merging, confirm with the user that the sources are mutually exclusive partitions (one month per sheet, no overlap) rather than overlapping, revised, or derived views of the same rows, and show each sheet's row count and time coverage range in that confirmation so an overlap is visible. The same rule applies to repeated `--input-file`.
|
|
52
68
|
- **Multi-file type conflicts** — when the same column has different inferred types across files, present each conflict and resolve with `--type-resolutions` on `convert` (see [transform](transform.md)).
|
|
53
69
|
|
|
54
70
|
## Nested flattening (NDJSON/JSON records and JSON-encoded CSV/TSV/Excel cells)
|
|
@@ -48,7 +48,9 @@ ae-cli data-integration convert \
|
|
|
48
48
|
--output-dir '.ae-cli/data-integration/runs/<run-id>'
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Merging several sources into one run assumes they are mutually exclusive partitions of the same data set. Confirm that with the user before converting — matching headers do not rule out a detail/summary pair or an original/revised pair — and present each source's row count and time coverage range so an overlap is visible. The same applies to `--merge-sheets` (see [source-inspect.md](source-inspect.md)).
|
|
52
|
+
|
|
53
|
+
The command never modifies the source. Inspect `manifest.json`; summarize valid and quarantined counts and the block reason, and check the conservation equation first: `manifest.output.source_rows` must equal `valid_records + invalid_records` — a mismatch means rows were dropped or duplicated between the source and the output, so report the three numbers to the user and do not upload until it is explained. A salvage run's `source_rows` is only the rows it re-processed, not the whole file. If `manifest.output.skipped_fields` is present, tell the user how many `#ip`/`#uuid` values were invalid and dropped (the rows were otherwise kept); if `manifest.output.lan_ip_records` is present, tell the user that many `#ip` values are private/LAN addresses that AE cannot geolocate. Neither blocks the manifest. If `manifest.output.flatten_misses` is present — or a stderr `Warning: flatten rule "X" did not materialize for N row(s).` fires — a `flatten_rules` path missed some rows: re-check the dot path against the source shape (or confirm the column is legitimately optional); the rows are otherwise kept. A stderr `Warning: … column count different from the header row …` means ragged CSV/TSV rows were tolerated (extra fields dropped, missing fields treated as empty) — surface it as a data-quality note. A blocked manifest whose reason is `The source contained no data rows.` means the file had zero data rows; do not re-run the same command on it. Do not expose rows from `invalid.rows.jsonl` unless the user specifically asks to inspect the local quarantine. For the full failure taxonomy and how to respond, see [error handling](error-handling.md).
|
|
52
54
|
|
|
53
55
|
## Re-report only the failed rows (salvage loop)
|
|
54
56
|
|
|
@@ -22,6 +22,28 @@ Classification order:
|
|
|
22
22
|
|
|
23
23
|
Aggregated metrics, pivot tables, cross-tabs, model outputs, free-form documents, and records without real identity/time should normally use local analysis.
|
|
24
24
|
|
|
25
|
+
### Time coverage is not native granularity
|
|
26
|
+
|
|
27
|
+
A parseable time column establishes only when the rows are stamped, not what period each metric
|
|
28
|
+
covers. A daily report and a cumulative snapshot both look like one row per user per point in time,
|
|
29
|
+
so they satisfy every condition above and are then ingested as per-period events — inflating totals
|
|
30
|
+
in a way that stays invisible in ratios, because numerator and denominator scale together.
|
|
31
|
+
|
|
32
|
+
Native granularity must come from the user, a data dictionary, or a complete period structure in the
|
|
33
|
+
data itself. Do not infer it from the file name, the first/last date, the interval between rows, or
|
|
34
|
+
the row count; none of those is evidence.
|
|
35
|
+
|
|
36
|
+
When the data carries numeric columns and any of the following holds, ask the user to state whether
|
|
37
|
+
each row's value is the amount that occurred in that period or the total accumulated up to that
|
|
38
|
+
point, and do not proceed until they answer:
|
|
39
|
+
|
|
40
|
+
- Paired start/end time columns (`start_date`/`end_date`, `period_begin`/`period_end`).
|
|
41
|
+
- Values for one identity that never decrease over time.
|
|
42
|
+
- Column names carrying a to-date sense (`cumulative`, `total`, `ltv`, `累计`, `总`).
|
|
43
|
+
|
|
44
|
+
An unanswered question, a cumulative snapshot, or overlapping periods route to local analysis
|
|
45
|
+
instead.
|
|
46
|
+
|
|
25
47
|
## Route to local analysis
|
|
26
48
|
|
|
27
49
|
Choose local analysis when:
|
|
@@ -29,6 +51,7 @@ Choose local analysis when:
|
|
|
29
51
|
- The user wants insights, not project ingestion.
|
|
30
52
|
- UE identity or time prerequisites are missing.
|
|
31
53
|
- Each row is an aggregate rather than a user/event record.
|
|
54
|
+
- The rows are a cumulative snapshot, or their native granularity could not be established.
|
|
32
55
|
- Conversion would invent semantics or discard important structure.
|
|
33
56
|
- The user declines an uncertain mapping or destination.
|
|
34
57
|
|