gangtise-openapi-cli 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,8 +10,35 @@ function nextDelayMs(attempt) {
10
10
  const grown = POLL_INITIAL_DELAY_MS * 1.6 ** (attempt - 1);
11
11
  return Math.min(POLL_MAX_DELAY_MS, Math.round(grown));
12
12
  }
13
+ // Probed 2026-07-20 against a real viewpoint-debate job: the async endpoints are
14
+ // still entirely on the legacy codes — 410110 "正在生成中" and 410111 "生成失败",
15
+ // both string-typed, both HTTP 400, neither carrying the new `errorType` field.
16
+ // The 2026-07-17 spec renumbers them to 140001 RESULT_GENERATING (409) and
17
+ // 140002 PROCESSING_FAILED (500); those are listed ahead of the switchover
18
+ // because the failure mode is expensive and silent — a `--wait` that does not
19
+ // recognize the pending code aborts the poll on a job already billed 50 credits.
20
+ const PENDING_CODES = new Set(["410110", "140001"]);
21
+ const FAILED_CODES = new Set(["410111", "140002"]);
13
22
  function isAsyncPending(error) {
14
- return error instanceof ApiError && error.code === "410110";
23
+ return error instanceof ApiError && error.code !== undefined && PENDING_CODES.has(error.code);
24
+ }
25
+ function isAsyncFailed(error) {
26
+ return error instanceof ApiError && error.code !== undefined && FAILED_CODES.has(error.code);
27
+ }
28
+ /** Terminal failures are swallowed here rather than rethrown, so this line is the
29
+ * user's only record of them — it has to carry what the global handler in cli.ts
30
+ * would have printed (code, msg, traceId). Without the traceId the failure is
31
+ * unreportable to Gangtise support, and README promises every error line has one. */
32
+ function terminalFailureLine(error) {
33
+ const api = error instanceof ApiError ? error : undefined;
34
+ const code = api?.code ? ` ${api.code}` : "";
35
+ const trace = api?.traceId ? ` [trace ${api.traceId}]` : "";
36
+ const detail = api?.message ? `: ${api.message}` : "";
37
+ // Name the *submit* endpoint explicitly: re-running a `*-check` is a free
38
+ // lookup, it is resubmitting the generation job that re-bills 50 credits for a
39
+ // verdict that will not change (probed 2026-07-20 on sensitive content).
40
+ return `Content generation failed (terminal${code})${trace}${detail}\n`
41
+ + "This dataId is final — re-checking it will not change. Resubmitting the generation task bills again for the same result; change the parameters first.\n";
15
42
  }
16
43
  export async function pollAsyncContent(client, getContentEndpoint, dataId, format, output) {
17
44
  for (let attempt = 1; attempt <= POLL_MAX_ATTEMPTS; attempt++) {
@@ -23,8 +50,8 @@ export async function pollAsyncContent(client, getContentEndpoint, dataId, forma
23
50
  }
24
51
  }
25
52
  catch (error) {
26
- if (error instanceof ApiError && error.code === "410111") {
27
- process.stderr.write("Content generation failed (terminal). Do not retry.\n");
53
+ if (isAsyncFailed(error)) {
54
+ process.stderr.write(terminalFailureLine(error));
28
55
  return "failed";
29
56
  }
30
57
  if (!isAsyncPending(error)) {
@@ -55,8 +82,8 @@ export async function checkAsyncContent(client, getContentEndpoint, dataId, form
55
82
  }
56
83
  }
57
84
  catch (error) {
58
- if (error instanceof ApiError && error.code === "410111") {
59
- process.stderr.write("Content generation failed (terminal). Do not retry.\n");
85
+ if (isAsyncFailed(error)) {
86
+ process.stderr.write(terminalFailureLine(error));
60
87
  process.exitCode = 1;
61
88
  return;
62
89
  }
@@ -4,14 +4,20 @@ import path from "node:path";
4
4
  import { pipeline } from "node:stream/promises";
5
5
  import { request } from "undici";
6
6
  import { isTokenCacheValid, normalizeToken, readTokenCache, requireAccessCredentials, writeTokenCache } from "./auth.js";
7
- import { ApiError, ValidationError } from "./errors.js";
7
+ import { ApiError, attachEnvelopeTraceId, ValidationError } from "./errors.js";
8
8
  import { ENDPOINTS, resolveTimeoutMs } from "./endpoints.js";
9
9
  import { getLookupData } from "./lookupData/index.js";
10
10
  import { decodeResponseBody, getDispatcher, isVerbose, logTiming, markRetryable, PAGE_CONCURRENCY, parseRetryAfterMs, runWithConcurrency, withRetry } from "./transport.js";
11
- // Auth errors that warrant a forced re-login + one replay. 8000014/8000015 are
12
- // AK/SK errors; 0000001008 is a server-side token invalidation (the token still
13
- // looks valid by local expiry, so only a forced refresh recovers it).
14
- const AUTH_RETRY_CODES = new Set(["8000014", "8000015", "0000001008"]);
11
+ // Auth errors that warrant a forced re-login + one replay: the token was rejected
12
+ // server-side while still looking valid by local expiry, so only a forced refresh
13
+ // recovers it. 0000001008 is the legacy code (probed 2026-07-20: still what the
14
+ // token filter emits); 999002 TOKEN_INVALID is its 2026-07-17 replacement, listed
15
+ // ahead of the rollout so self-heal does not silently die when the filter switches.
16
+ // 8000014/8000015 are the retired AK/SK codes, kept for older server builds.
17
+ // 999011 CREDENTIAL_INVALID is not here and could not act if it were — it comes from
18
+ // auth.login, which runs useAuth=false and so never reaches this check. Its "never
19
+ // replay" guarantee lives in transport's TERMINAL_API_CODES instead.
20
+ const AUTH_RETRY_CODES = new Set(["8000014", "8000015", "0000001008", "999002"]);
15
21
  export class GangtiseClient {
16
22
  config;
17
23
  refreshPromise = null;
@@ -126,17 +132,20 @@ export class GangtiseClient {
126
132
  }
127
133
  throw new ApiError(`API request failed (HTTP ${statusCode})`, undefined, statusCode, parsed, retryAfterMs);
128
134
  }
129
- unwrapEnvelope(parsed, statusCode) {
135
+ unwrapEnvelope(parsed, statusCode, retryAfterMs) {
130
136
  if (!this.isEnvelope(parsed)) {
131
137
  return parsed;
132
138
  }
133
139
  const code = parsed.code === undefined ? undefined : String(parsed.code);
134
140
  const ok = parsed.status === true || parsed.success === true || code === "000000" || code === "0";
135
141
  if (!ok) {
136
- throw new ApiError(parsed.msg || "API request failed", code, statusCode, parsed);
142
+ throw new ApiError(parsed.msg || "API request failed", code, statusCode, parsed, retryAfterMs);
137
143
  }
138
144
  if ('data' in parsed) {
139
- return parsed.data;
145
+ // Carry the envelope's traceId onto the payload: the EDE endpoints wrap a
146
+ // second envelope inside `data` and raise their own failures from it, by
147
+ // which point this is the only traceId in reach.
148
+ return attachEnvelopeTraceId(parsed.data, parsed.traceId);
140
149
  }
141
150
  return parsed;
142
151
  }
@@ -397,7 +406,7 @@ export class GangtiseClient {
397
406
  if (response.statusCode >= 400) {
398
407
  this.throwHttpError(parsed, response.statusCode, retryAfterMs);
399
408
  }
400
- return this.unwrapEnvelope(parsed, response.statusCode);
409
+ return this.unwrapEnvelope(parsed, response.statusCode, retryAfterMs);
401
410
  }
402
411
  catch (error) {
403
412
  await this.refreshAuthIfRecoverable(error, useAuth, authState, usedAuthorization);
@@ -491,7 +500,7 @@ export class GangtiseClient {
491
500
  if (response.statusCode >= 400) {
492
501
  this.throwHttpError(parsed, response.statusCode, retryAfterMs);
493
502
  }
494
- data = this.unwrapEnvelope(parsed, response.statusCode);
503
+ data = this.unwrapEnvelope(parsed, response.statusCode, retryAfterMs);
495
504
  }
496
505
  catch (error) {
497
506
  await this.refreshAuthIfRecoverable(error, true, authState, authorization);
@@ -10,26 +10,111 @@ export class ValidationError extends CliError {
10
10
  }
11
11
  export class DownloadError extends CliError {
12
12
  }
13
+ /** Outer-envelope traceId, stashed on the unwrapped payload so it survives
14
+ * `unwrapEnvelope` discarding the envelope. Needed by the EDE endpoints alone:
15
+ * they double-wrap, and probing 2026-07-20 showed the traceId lives only on the
16
+ * OUTER envelope — the inner failure envelope (`{code, status:false, msg}`) has
17
+ * none. Without this the one correlation id Gangtise support can trace is gone
18
+ * exactly where the inner failure is raised. Non-enumerable so it never reaches
19
+ * JSON/CSV output. */
20
+ export const ENVELOPE_TRACE_ID = Symbol("gangtise.envelopeTraceId");
21
+ export function attachEnvelopeTraceId(payload, traceId) {
22
+ if (payload && typeof payload === "object" && (typeof traceId === "string" || typeof traceId === "number")) {
23
+ Object.defineProperty(payload, ENVELOPE_TRACE_ID, { value: String(traceId), enumerable: false, configurable: true });
24
+ }
25
+ return payload;
26
+ }
27
+ /** Keyed by the code as a string — `unwrapEnvelope` runs every envelope code
28
+ * through `String()` first, which matters because the 2026-07-17 error-code
29
+ * overhaul emits the new codes as JSON *numbers* while legacy codes stay strings.
30
+ *
31
+ * Both generations are listed on purpose. Probed 2026-07-20: the rollout is
32
+ * partial — the business layer already answers with the new codes (`999011`,
33
+ * `130001`, `130002`, `100003`, `999010`), but the outer token filter still
34
+ * emits `0000001007` / `0000001008` / `900002`. Dropping either set would leave
35
+ * a live code hintless. */
13
36
  const ERROR_HINTS = {
14
- "999999": "Gangtise 系统错误,请稍后重试。",
15
- "999997": "当前账号未开通该接口权限。",
16
- "999995": "当前账号积分不足。",
17
- "900002": "请求缺少 uid。",
18
- "900001": "请求参数为空或缺少必填项。",
19
- "100003": "参数值非法——服务端不会指明是哪个参数,多为枚举参数拼写或取值超范围(如 --source / --question-category / --answer-important),对照命令 --help 列出的合法值检查。",
37
+ // The hint is appended after the server's own msg, so it must carry the *action*,
38
+ // never restate the diagnosis — "资源不存在 资源不存在,确认 ID 有效" reads as a stutter.
39
+ // ── 服务统一层 999xxx ──
40
+ "999001": "检查 GANGTISE_TOKEN 或 AK/SK 是否已 export。",
41
+ "999002": "有 AK/SK 时 CLI 会自动重新登录重试一次,否则请重新登录。",
42
+ "999003": "定制接口需联系客户经理开通。",
43
+ "999004": "换一条本账号可见的记录重试。",
44
+ "999005": "联系客户经理充值,或缩小查询范围降低消耗。",
45
+ "999006": "触发限流,稍后再试或联系客户经理提额;429 所有端点都退避重试,5xx 仅普通端点重试(贵档 no-replay 端点的 5xx 不重放,但其 429 仍重试)。",
46
+ // CLI 自身发不出这三类请求(method / Content-Type 都由 endpoint 定义固定),
47
+ // 只有 raw call 打错端点或服务端行为变化才可能撞上。实测 2026-07-20 分别落到
48
+ // 900002 / 999999 / 100003,此处为服务端接上新码后的预置。
49
+ "999007": "请求方法不支持——`raw call` 时确认该 endpoint 是 GET 还是 POST。",
50
+ "999008": "Content-Type 不支持,该接口只收 application/json。",
51
+ "999009": "请求体无法解析,检查 JSON 是否合法。",
52
+ "999010": "`raw call` 传的 endpoint key 可能已下线,用 `gangtise raw list` 核对。",
53
+ "999011": "检查 GANGTISE_ACCESS_KEY / GANGTISE_SECRET_KEY 是否写反或未 export。",
54
+ "999012": "联系客户经理。",
55
+ "999013": "联系客户经理续期。",
56
+ "999014": "联系客户经理。",
57
+ "999015": "联系客户经理开通长期 token。",
58
+ "999016": "联系客户经理登记当前出口 IP。",
59
+ "999999": "请稍后重试;持续失败请带上面的 trace 报障。",
60
+ // ── 业务通用 1xxxxx ──
61
+ "100001": "对照命令 --help 检查必填项。",
62
+ "100002": "检查数值/字符串参数是否传反。",
63
+ // 实测两种形态都有:类型/范围错的 msg 带字段(「请求体字段类型不匹配: size 期望类型
64
+ // Integer」「limit 最小为 1,最大为 10000」),枚举错的 msg 只有笼统的「参数值非法」。
65
+ // 条件句让两种形态都读得通——v0.25.0 的旧文案断言"服务端不会指明",与前一种直接打架。
66
+ "100003": "msg 已指明字段名或取值范围时直接按 msg 改;msg 只说「参数值非法」时多为枚举参数拼写错误(如 --source / --question-category / --answer-important),对照命令 --help 列出的合法值检查。",
67
+ "100004": "检查 --size / --from 是否为非负数且未超单页上限。",
68
+ "100005": "对照命令 --help 列出的合法取值检查。",
69
+ "100006": "缩短日期范围或调小 --size / --limit。",
70
+ // 按参数名判断,不要按命令组:AI 下 management-discuss 的 --report-date 是 date,
71
+ // 而同属 AI 的 knowledge-batch 收时间戳或 datetime——旧文案笼统写"AI 用 datetime"会把
72
+ // --report-date 的用户越导越错。
73
+ "110001": "看参数名:`--*-date` 用 YYYY-MM-DD,`--*-time` 用 \"YYYY-MM-DD HH:mm:ss\"(`ai knowledge-batch` 的 --start-time/--end-time 收时间戳或 datetime,CLI 统一转 13 位毫秒)。",
74
+ "110002": "起始晚于结束——检查 --start-date/--end-date 或 --start-time/--end-time 的先后。",
75
+ "110003": "缩短查询窗口后重试。",
76
+ "120001": "用 `gangtise reference securities-search` 确认代码与后缀(如 600519.SH / 00700.HK)。",
77
+ "130001": "未找到数据——先核对查询条件;EDE 指标端点此码也可能是未开通该指标权限,仍失败联系客户经理。",
78
+ "130002": "确认下载 ID 有效且本账号可见;下载类还需检查 --file-type 取值是否合法(非法 file-type 也归此码)。",
79
+ "130003": "该条记录可能未附带文件。",
80
+ // 下载类命令各有各的 ID 参数(--report-id / --announcement-id / --chunk-id /
81
+ // --summary-id / --conference-id / --record-id / --file-id / --article-id /
82
+ // --independent-opinion-id);--data-id 是异步 *-check 用的,不产生此码。
83
+ "130004": "下载 ID 需为数字,检查该命令的 --*-id 参数是否传对。",
84
+ "130005": "对照命令 --help 检查 --file-type / --content-type 取值。",
85
+ "140001": "稍后用对应 *-check 命令查询。",
86
+ "140002": "异步生成失败(终态)——换参数重新提交,重试同一 dataId 不会变。",
87
+ // ── 接口专有 2xxxxx ──
88
+ "210001": "换一篇,或改用 list 取正文摘要。",
89
+ "220001": "改用 list 取正文摘要。",
90
+ "230001": "只有自己上传的文件可下载。",
91
+ "240001": "换更早的 --period(如 2025q3 → 2025interim)。",
92
+ "240002": "改述后重新提交。",
93
+ "240003": "对照命令 --help 检查取值。",
94
+ "250001": "检查 resourceType 与 sourceId 组合(两者都来自 knowledge-batch 返回)。",
95
+ // ── 旧码(2026-07-20 实测仍在线,或历史遗留) ──
96
+ "0000001007": "请求未携带 Bearer token,检查 GANGTISE_TOKEN 或 AK/SK 是否已 export。",
20
97
  "0000001008": "Token 已失效(多为他处登录挤掉本会话);有 AK/SK 时 CLI 会自动重新登录重试一次,否则请重新登录。",
21
- "8000014": "GANGTISE_ACCESS_KEY 错误。",
22
- "8000015": "GANGTISE_SECRET_KEY 错误。",
23
- "8000016": "开发账号状态异常。",
24
- "8000018": "开发账号已到期。",
25
- "903301": "今日调用次数已达上限。",
26
- "410110": "异步内容生成中,稍后用对应 *-check 命令查询。",
27
- "410111": "异步内容生成失败(终态),请更换参数后重新提交。",
28
- "410004": "数据未找到或无指标权限,请检查查询条件与指标权限。",
29
- "430004": "下载失败(官方未文档化错误码),请确认 reportId 有效或更换 --file-type 重试。",
30
- "430007": "行情查询超出限制,请缩短日期范围。",
31
- "433007": "数据源不匹配,请检查 resourceType sourceId 组合。",
32
- "10011401": "白名单未开通,请联系管理员。",
98
+ "900001": "对照命令 --help 检查必填项。",
99
+ "900002": "请求方法不正确(服务端 msg 为「请求类型有误」)——`raw call` 时检查该 endpoint 是 GET 还是 POST。",
100
+ "903301": "次日再试,或联系客户经理提额。",
101
+ // EDE 专有旧码,未被 2026-07-17 重排收编但仍是 indicator 取数的主要报错
102
+ // (references/commands/indicator.md 把这两个列为首要排查项)。
103
+ "410001": "补齐 --indicator / --security;`time-series` 不支持「多指标 × 多证券」,改用 `indicator cross-section`。",
104
+ "410106": "读 `indicator search --format json` 的 parameterList,用 --indicator-param 补上 required:true 的参数(如 periodNum / startDate / fiscalYear)。",
105
+ "410004": "换证券或日期确认该条件下本应有数据;仍失败多为未开通该指标,联系客户经理。",
106
+ "410110": "稍后用对应 *-check 命令查询。",
107
+ "410111": "终态,换参数后重新提交,重试同一请求不会变。",
108
+ "430004": "确认 reportId 有效,或更换 --file-type 重试(官方未文档化错误码)。",
109
+ "430007": "缩短日期范围或调小 --limit。",
110
+ "433007": "检查 resourceType 与 sourceId 组合(两者都来自 knowledge-batch 返回)。",
111
+ "8000014": "检查 GANGTISE_ACCESS_KEY 是否正确、是否与 SECRET_KEY 写反。",
112
+ "8000015": "检查 GANGTISE_SECRET_KEY 是否正确、是否与 ACCESS_KEY 写反。",
113
+ "8000016": "联系客户经理核查账号状态。",
114
+ "8000018": "联系客户经理续期。",
115
+ "999995": "联系客户经理充值,或缩小查询范围降低消耗。",
116
+ "999997": "联系客户经理开通。",
117
+ "10011401": "联系客户经理开通白名单。",
33
118
  };
34
119
  export class ApiError extends CliError {
35
120
  code;
@@ -51,4 +136,17 @@ export class ApiError extends CliError {
51
136
  this.retryAfterMs = retryAfterMs;
52
137
  this.hint = hintOverride ?? (code ? ERROR_HINTS[code] : undefined);
53
138
  }
139
+ /** Server-side correlation id from the 2026-07-17 envelope
140
+ * (`{code, errorType, msg, status, data, traceId}`). Read off `details` rather
141
+ * than threading a 7th positional constructor arg through every call site.
142
+ * Worth surfacing: it is the only handle Gangtise support can trace a 999999 by. */
143
+ get traceId() {
144
+ if (!this.details || typeof this.details !== "object")
145
+ return undefined;
146
+ // Fall back to the outer envelope's id for double-wrapped (EDE) responses,
147
+ // whose inner failure envelope carries no traceId of its own.
148
+ const details = this.details;
149
+ const value = details.traceId ?? details[ENVELOPE_TRACE_ID];
150
+ return typeof value === "string" || typeof value === "number" ? String(value) : undefined;
151
+ }
54
152
  }
@@ -16,7 +16,11 @@ export function unwrapIndicatorData(raw) {
16
16
  // (status/msg/data) so a non-envelope object that merely carries a `code`
17
17
  // field can't be misread as a failure.
18
18
  if (!ok && ("status" in record || "msg" in record || "data" in record)) {
19
- throw new ApiError(typeof record.msg === "string" && record.msg ? record.msg : "Indicator API request failed", code);
19
+ // Pass `record` as details: the inner envelope carries no traceId of its
20
+ // own, but unwrapEnvelope attached the outer one to this object and
21
+ // ApiError.traceId falls back to it. Without it these failures — the EDE
22
+ // 999999 / 130001 that most need reporting — reach the user trace-less.
23
+ throw new ApiError(typeof record.msg === "string" && record.msg ? record.msg : "Indicator API request failed", code, undefined, record);
20
24
  }
21
25
  if (ok && "data" in record)
22
26
  return record.data;
@@ -77,6 +77,19 @@ export const PAGE_CONCURRENCY = resolvePageConcurrency(process.env.GANGTISE_PAGE
77
77
  const RETRYABLE_HTTP_STATUS = new Set([429, 500, 502, 503, 504]);
78
78
  const RETRYABLE_NETWORK_CODES = new Set(["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN", "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_SOCKET", "UND_ERR_HEADERS_TIMEOUT", "UND_ERR_BODY_TIMEOUT"]);
79
79
  const RETRYABLE_API_CODES = new Set(["999999"]);
80
+ // Never replayed on any HTTP status:
81
+ // - 999011 CREDENTIAL_INVALID: bad AK/SK, only from auth.login (useAuth=false, so it
82
+ // never consults AUTH_RETRY_CODES, and it declares no retry policy) — a 5xx would
83
+ // otherwise be retried twice by the status rule.
84
+ // - 140002 PROCESSING_FAILED: the async *-check endpoints (get-content) declare no
85
+ // retry policy, so a 140002@500 would be retried 2× by the default policy BEFORE
86
+ // asyncContent's FAILED_CODES gets to call it terminal — that guard sits above
87
+ // client.call's withRetry and cannot see the retries. 140002 means "generation
88
+ // failed" (terminal by definition) and only those async endpoints can return it,
89
+ // so a blanket rule is safe and skips the wasted retries. Server still emits the
90
+ // legacy 410111 today (410111@400 isn't retryable anyway); this is a forward guard
91
+ // for the documented 140002@500.
92
+ const TERMINAL_API_CODES = new Set(["999011", "140002"]);
80
93
  // Connect-phase / DNS failures: the request provably never reached the server, so a
81
94
  // replay cannot double-execute (or double-bill) anything even under "no-replay".
82
95
  const NO_REPLAY_NETWORK_CODES = new Set(["ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN", "UND_ERR_CONNECT_TIMEOUT"]);
@@ -85,6 +98,8 @@ function isRetryableError(error, policy) {
85
98
  return true;
86
99
  }
87
100
  if (error instanceof ApiError) {
101
+ if (error.code && TERMINAL_API_CODES.has(error.code))
102
+ return false;
88
103
  if (error.statusCode === 429)
89
104
  return true;
90
105
  if (policy === "no-replay")
@@ -1,2 +1,2 @@
1
1
  // Auto-generated — DO NOT EDIT
2
- export const CLI_VERSION = "0.27.0";
2
+ export const CLI_VERSION = "0.28.0";
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gangtise-openapi
3
- version: "0.27.0"
3
+ version: "0.28.0"
4
4
  description: |-
5
5
  通过 gangtise CLI 直接调用 Gangtise OpenAPI,拉取投研原始数据、批量导出、下载文件、调用 AI 能力。
6
6
 
@@ -33,7 +33,7 @@ description: |-
33
33
  - 翻页 → 首页拿 total 后剩余页并发拉取
34
34
  - K 线 `--security all` 跨日期 → 自动按日切片并合并
35
35
  - 5xx / `429` / 网络错误 / `999999` → 自动指数退避重试(🔴 贵档端点例外:仅连接失败 / 429 / token 自愈重试,5xx/超时不重放防重复扣分,v0.26.0;`indicator` 端点对 `999999` 不重试——该码=查询无数据,v0.27.0)
36
- - Token 失效(`0000001008` 服务端失效 / `8000014` / `8000015` AK/SK 错误)→ 自动重新登录并重试一次
36
+ - Token 失效(`0000001008` / `999002`,含已废弃的 `8000014`/`8000015`)→ 自动重新登录并重试一次;凭证错 `999011` → **不重试**(AK/SK 不对不会自己好),查环境变量
37
37
  8. **参数命名差异**:Insight/Quote/Vault 用 `--security`,Fundamental/AI 用 `--security-code`(例外:`ai stock-summary` 用 `--security`,`ai security-clue` 用 `--gts-code`)。
38
38
  9. **调试**:`--verbose` 或 `GANGTISE_VERBOSE=1` 打印每个请求的耗时/字节数到 stderr。
39
39
 
@@ -69,7 +69,7 @@ description: |-
69
69
  - **免费**:所有 `quote` 行情、`fundamental` 报表/主营/估值/股东(**盈利预测除外**)、`reference`/`constant` 查询(含 `official-account-search`)、`alternative edb-search`、`vault`(record/wechat/股票池/drive/AI云盘)、`insight report-image list`
70
70
  - **0.1/条 list**:research / foreign-report / official-account / announcement(A/港/美) / summary / qa 的 list、`vault my-conference-list`;`insight report-image download` 0.1/张
71
71
  - **按条(观点/含详情类 list)**:independent-opinion list 与 `ai security-clue` 5;roadshow/site-visit/strategy/forum list 20;opinion / foreign-opinion list 30;`fundamental earning-forecast` 0.5;`ai stock-summary` 3(无看点的证券不返回也不扣);`alternative edb-data` 30
72
- - **各 download(/篇)**:announcement / official-account 10;research / announcement-hk / announcement-us 20;independent-opinion 30;summary / foreign-report / my-conference 50
72
+ - **各 download(/篇)**:announcement / official-account / research 10;announcement-hk / announcement-us 20;independent-opinion 30;summary / foreign-report / my-conference 50
73
73
  - 🔴 **按次贵**:`ai knowledge-batch` 10、`management-discuss-*` 10;AI Agent(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `earnings-review` / `viewpoint-debate` / `theme-tracking`)**50/次**;`ai hot-topic` 50/篇
74
74
  - 🔴 **极贵**:`alternative concept-info` / `concept-securities` **500/次**
75
75
  - ⚠️ **同参数重复调用不免费**:按次计费无缓存命中豁免(2026-07-11 实测 `one-pager` 重复调用每次扣分,即使秒回缓存内容)——生成类结果拿到后自行留存复用,别为"刷新"重调;CLI 已对上述 🔴 贵档端点关闭 5xx/超时自动重放(v0.26.0),50/篇 的 `summary` / `foreign-report` / `my-conference` download 同样不重放(v0.27.0),正是为防重复扣分
@@ -110,7 +110,7 @@ description: |-
110
110
  | 投资者问答 / 互动平台 / 电话会议 / 调研纪要 QA | `insight qa list`(按证券,`--security-code` 必填;`--source`/`--question-category`/`--answer-important` 精筛) |
111
111
  | 研报图表 / 研报图片搜索 | `insight report-image list`(`--keyword`;下载原图 `insight report-image download --chunk-id`) |
112
112
  | 跨类型语义搜索(研报+纪要+...) | `ai knowledge-batch`(多个 `--resource-type`) |
113
- | 知识库原文下载(搜到后取全文) | `ai knowledge-resource-download`(前置:`knowledge-batch` 拿 `resourceType`+`sourceId`;`433007`=组合不匹配) |
113
+ | 知识库原文下载(搜到后取全文) | `ai knowledge-resource-download`(前置:`knowledge-batch` 拿 `resourceType`+`sourceId`;`250001`/旧 `433007`=组合不匹配) |
114
114
  | 一页通 / 投资逻辑 / 同业对比 / 调研提纲 | `ai one-pager / investment-logic / peer-comparison / research-outline` |
115
115
  | 个股看点 / 投研总结 / 公司速览 | `ai stock-summary`(`--security` 代码或 `aShares`/`hkStocks` 全市场;仅 A 股/港股) |
116
116
  | 业绩点评(异步) | `ai earnings-review` |
@@ -198,7 +198,7 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
198
198
  | **下载** | 各 `download` | stdout = 文件路径字符串 | 直接读 stdout 整行 |
199
199
  | **AI 内容** | one-pager / investment-logic / peer-comparison / research-outline | `{content: "markdown文本"}` | 取 `content` 直接呈现 |
200
200
  | **K 线** | quote * | `{list: [{tradeDate, ...}]}` | 按 tradeDate 排序,取需要的尾部 |
201
- | **异步(含 *-check)** | earnings-review / viewpoint-debate / earnings-review-check / viewpoint-debate-check | 提交 `{dataId, status, hint}`;check 成功 `{date, content}` / pending `{status:"pending"}` 或抛 `410110` | 见下方"异步任务流程" |
201
+ | **异步(含 *-check)** | earnings-review / viewpoint-debate / earnings-review-check / viewpoint-debate-check | 提交 `{dataId, status, hint}`;check 成功 `{date, content}` / pending `{status:"pending"}` 或抛 `140001`(旧 `410110`) | 见下方"异步任务流程" |
202
202
 
203
203
  完整字段对照见 `references/response-schema.md`。
204
204
 
@@ -209,7 +209,7 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
209
209
  - **`--wait`(推荐)**:命令带 `--wait` 阻塞到出结果(CLI 内轮询最长 ≈316s)。**把工具/命令超时设到 ≥360s**,否则外层先超时。直接拿 `{date, content}` 呈现。
210
210
  - **手动轮询**(不带 `--wait`):① 提交 → 拿 `{dataId, status, hint}`;② 间隔 ~30s 调 `*-check --data-id <id>`(预算给足 ~2-3 分钟);③ `{date, content}`=成功 / `{status:"pending"}`=继续等 / 终态失败=换参重试;④ 多次仍 pending → 把 `dataId` 交用户稍后再 check。
211
211
 
212
- **别把原始码甩给用户**:`410110`=生成中(继续等)、`410111`=终态失败(换参),按 `status` + 退出码判断后用人话说明。
212
+ **别把原始码甩给用户**:`140001`/旧 `410110`=生成中(继续等)、`140002`/旧 `410111`=终态失败(换参),按 `status` + 退出码判断后用人话说明。
213
213
 
214
214
  ### 呈现规范
215
215
 
@@ -232,34 +232,75 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
232
232
  | 最新一期 / 最新报告期(财报) | — | — | 省略 `--fiscal-year`,传 `--period latest`(默认) |
233
233
  | 最新观点 / 今日观点 | 1 天范围 + `--rank-type 2` | — | — |
234
234
 
235
- 参数命名:Insight/Vault/AI `--start-time` / `--end-time`(datetime);Quote/Fundamental `--start-date` / `--end-date`(date)。**三个例外**:`ai knowledge-batch` 的 `--start-time`/`--end-time` **13 位毫秒时间戳**(传 datetime 字符串会报 `expected a finite number`);`ai hot-topic` 用 `--start-date`/`--end-date`(date);`quote minute-kline` 虽属 Quote 却用 `--start-time`/`--end-time` 且为 datetime。
235
+ 日期参数**按参数名判断、不按命令组**(命令组会误导——AI 里既有 `--start-time` 又有 `--date`/`--report-date`):名字带 `-date` 的(`--start-date`/`--end-date`/`--date`/`--report-date`)一律 `YYYY-MM-DD`,覆盖 Quote/Fundamental、AI `theme-tracking`(`--date`)/`hot-topic`/`management-discuss-*`(`--report-date`)、Alternative `edb-data`、Indicator `cross-section`(`--date`)/`time-series`;名字带 `-time` 的(`--start-time`/`--end-time`)用 `YYYY-MM-DD[ HH:mm[:ss]]`(秒可省、空格或 `T` 分隔)或 10/13 位时间戳,覆盖 Insight/Vault list、`quote minute-kline`、`ai security-clue`、`ai knowledge-batch`。其中 **A 股公告(`insight announcement list`)与 `knowledge-batch` 会把输入转成 13 位毫秒**(10 位秒自动 ×1000),其余 `-time` 命令(含 `announcement-hk`/`announcement-us`)原样透传字符串;CLI 输入统一接受 10/13 位纯数字或 `YYYY-MM-DD[ HH:mm[:ss]]`(同上:秒可省、空格或 `T` 分隔)。
236
236
 
237
237
  支持时间倒序的命令加 `--rank-type 2`:opinion / summary / research / foreign-report / announcement / announcement-hk / announcement-us / foreign-opinion / independent-opinion / official-account。其他 list 命令按 API 默认排序。
238
238
 
239
239
  ## 异常处理
240
240
 
241
+ 服务端 2026-07-17 重排了错误码(41 个公开码,三层:`999xxx` 服务统一层 / `1xxxxx` 业务通用层 / `2xxxxx` 接口专有层),信封新增 `errorType` 和 `traceId`。
242
+
243
+ **2026-07-20 逐码实测的结论:迁移是按「错误处理层」而非按业务模块进行的,不能假定文档即现状。**
244
+ - 判别方式:**新码 `code` 是 JSON 数字且带 `errorType`;旧码是字符串且没有**。但这判断的是**这一条错误路径**切没切,不是整个接口——同一个 Insight 接口内,参数校验已发新码 `100003`、路由不存在发新码 `999010`,方法用错却仍发旧码 `900002`。(成功响应也没有 `errorType`,别拿它当判据。)
245
+ - **异步端点(`earnings-review` / `viewpoint-debate`)的生成状态没切**——实测仍是 `410110`/`410111`,HTTP 400,无 `errorType`
246
+ - **token 过滤器没切**——仍是 `0000001007`/`0000001008`;方法路由层的 `900002` 同理
247
+ - 参数校验层、路由层已切
248
+ - 更外层的未知路径(不属于任何已识别路由)**根本不返回统一信封**,是纯文本 `default backend - 404`
249
+ - CLI 对两代都认,报错行带 `[trace <id>]`,**报障给 Gangtise 时务必带上这个 traceId**
250
+
251
+ **实测确认在用的码**(按遇到概率排;✅=已实测复现)
252
+
241
253
  | 错误码 | 含义 | CLI 行为 | Agent 是否介入 |
242
254
  |--------|------|---------|--------------|
243
- | `999999` | 系统错误;但 **`indicator`(EDE)仍会用此码 + HTTP 500 表示查询无数据**(节假日 / 未来日期 / 未覆盖标的,2026-07-11 实测)——单元格级缺值才是 `null` | 普通端点自动重试 ×2;🔴 贵档与 `indicator` 端点不重试(CLI hint 会直接提示「多为查询无数据」) | `indicator` 遇到先检查日期/标的是否该有数据,别盲目重试 |
244
- | `410110` | 异步生成中 | 异步轮询逻辑视为 pending | 继续等 |
245
- | `410111` | 异步生成失败 | 终态 | **不重试**,建议换参数 |
246
- | `410106` / 缺参 | `indicator` **缺必填参数**(服务端现直接指明缺哪个,如「必填参数 periodNum 不能为空」;以 HTTP 500 返回故 CLI 重试 ×2) | **自动重试 ×2** | `indicator search --format json` 的 `parameterList`,补 `required:true` 参数(periodNum/startDate/fiscalYear) |
247
- | `0000001008` | Token 服务端失效(他处登录挤掉) | **强制重新登录并重试一次** | AK/SK 时无法自愈,提示重新登录 |
248
- | `8000014` / `8000015` | AK/SK 错误 | **自动刷新 token 并重试一次** | 再失败提示检查 env |
249
- | `8000016` / `8000018` | 账号异常 / 到期 | — | 提示联系管理员 |
250
- | `999997` | 未开通权限 | — | 联系管理员 |
251
- | `999995` | 积分不足 | — | 联系管理员 |
252
- | `903301` | 今日调用上限 | **不重试** | 告知用户次日重试或升级配额 |
253
- | `433007` | 数据源不匹配 | — | 检查 `resourceType + sourceId` 组合 |
254
- | `410004` | 数据未找到,或**该指标无权限**(服务端复用此码;`indicator` 内层失败会带具体 msg 如"指标无权限") | — | 检查查询条件与指标权限 |
255
- | `430007` | 行情查询超出限制 | | 缩短日期范围;全市场场景应已自动分片 |
256
- | `430004` | 研报下载报错(官方未文档化,实测出现于 download) | — | 确认 reportId 有效;换 `--file-type` 或换一篇验证 |
257
- | `900001` | 请求参数缺失 | | 检查必填项(如 `--indicator` / `--date`) |
258
- | `100003` | 参数值非法(服务端不指明是哪个参数) | — | 对照命令 `--help` 检查枚举参数拼写/取值(如 `--source` / `--question-category` / `--answer-important`),**不要重试同命令** |
259
- | `900002` | 请求缺少 uid | — | `gangtise auth status` 查登录状态,重登后重试 |
260
- | `10011401` | 白名单未开通 | | 联系管理员 |
261
- | HTTP 5xx / `ECONNRESET` / 超时 | 网络/服务端 | **自动指数退避重试 ×2** | 仍失败提示用户 |
262
- | `ValidationError` | 本地参数校验失败 | — | 检查 `--from` / `--size` / `--limit` 数值,**不要重试同命令** |
255
+ | `100003` | 参数值非法——**最宽的兜底码**:类型错、`limit` 越界都归这里。**msg 通常已指明字段**(如「请求体字段类型不匹配: size 期望类型 Integer」「limit 最小为 1,最大为 10000」),先读 msg 再猜 | | msg 指的字段改;msg 没指明才对照 `--help` 查枚举拼写,**不要重试同命令** |
256
+ | `999999` | 系统错误;但 **`indicator`(EDE)用此码 + HTTP 500 表示查询无数据**(节假日 / 未来日期 / 未覆盖标的,2026-07-11 实测)——单元格级缺值才是 `null` | 普通端点自动重试 ×2;🔴 贵档与 `indicator` 端点不重试 | `indicator` 遇到先检查日期/标的是否该有数据,别盲目重试 |
257
+ | `410110` | **异步生成中**(HTTP 400,旧码未切)。新码 `140001`,CLI 两码都认 | 轮询视为 pending | 继续等 |
258
+ | `410111` | **异步生成失败**(HTTP 400,旧码未切)。新码 `140002`,CLI 两码都认 | 终态 | **不重试**,换参数 |
259
+ | `130002` | 资源不存在——**下载类的兜底码**:`reportId` 不存在 / 非数字 / `fileType` 非法**全归这里**(`130003`/`130004`/`130005` 实测均未启用) | | 确认 ID 有效且本账号可见;换 `--file-type` 或换一篇验证 |
260
+ | `130001` | 数据未找到,或**该指标无权限**(`indicator` 内层失败会带具体 msg 如"指标无权限") | | 检查查询条件与指标权限 |
261
+ | `100001` | 缺必填参数——**msg 带字段名**(「缺少必填参数: reportId」) | — | msg 指的字段补上 |
262
+ | `110001` / `110002` | 日期格式错(msg 带字段名)/ 起晚于止。**哪个格式报错、哪个被静默误读是端点相关的**(实测 `fundamental` 对 `2020/01/01` 报 110001,`insight research list` 对 `30/06/2025` 却宽松解析返回数据)——别按命令组预判 | — | 按参数名:`--*-date` 用 `YYYY-MM-DD`、`--*-time` 用 `YYYY-MM-DD HH:mm:ss`;`ai knowledge-batch` 的 --start-time/--end-time 收时间戳或 datetime,CLI 统一转 13 位毫秒 |
263
+ | `120001` | 证券代码无效——msg 带原因(「非有效A股」)。**只有 Fundamental 系报**,Quote 系静默返回空 | — | `reference securities-search` 确认代码与后缀(`600519.SH` / `00700.HK`) |
264
+ | `100006` | 查询/下载数量超限——**取代旧 `430007`**;实测 `fund-flow` 全市场不传日期即此码 | | 缩短日期范围或调小 `--size`/`--limit`;全市场场景应已自动分片 |
265
+ | `240001` | 财报期未披露或超出查询期(`earnings-review` 提交阶段就报,**不扣积分**) | — | 换更早的 `--period`(`2025q3` `2025interim`) |
266
+ | `250001` | 不支持的数据源——**取代旧 `433007`** | — | 检查 `resourceType + sourceId` 组合 |
267
+ | `999011` | 开发账号凭证无效——**取代旧 `8000014`/`8000015`,已合并,不再区分 AK 错还是 SK 错** | 登录即失败,**不重试** | 检查 `GANGTISE_ACCESS_KEY`/`GANGTISE_SECRET_KEY` 是否写反或未 export |
268
+ | `999010` | 接口地址不存在 | — | `raw call` key 可能已下线,用 `gangtise raw list` 核对 |
269
+ | `0000001008` | Token 服务端失效(他处登录挤掉)——**旧码未切,token 自愈依赖它** | **强制重新登录并重试一次** | AK/SK 时无法自愈,提示重新登录 |
270
+ | `0000001007` | 请求未携带 Bearer token | — | 检查 `GANGTISE_TOKEN` / AK/SK 是否已 export |
271
+ | `900002` | **请求方法不正确**(msg「请求类型有误」,HTTP 405)——旧文档写作"缺少 uid"是错的 | — | `raw call` 时确认该 endpoint 是 GET 还是 POST |
272
+ | `410106` / 缺参 | `indicator` 缺必填参数(msg 直接指明,如「必填参数 periodNum 不能为空」;HTTP 500 故 CLI 重试 ×2) | **自动重试 ×2** | `indicator search --format json` 的 `parameterList` 补 `required:true` 参数 |
273
+
274
+ **⚠️ 实测发现的坑(都是"不报错"型,最难发现)**
275
+ - 🔴 **日期只写 `YYYY-MM-DD`、时间只写 `YYYY-MM-DD HH:mm:ss`(或 10/13 位时间戳);CLI v0.28.0 起 date 与 datetime 两类、含所有 insight/vault 透传参数都本地拦截**。服务端对「年在后」格式**日月顺序随分隔符翻转**且静默误解析(HTTP 200、不回显实际用的日期):`07/01/2026`(斜杠)读成 **2026-01-07**、`07-01-2026`(横杠)读成 **2026-07-01**,差半年。实测 `insight research list --start-time`:`07/01/2026` 命中 1562 条、`07-01-2026` 命中 210 条(分别 = 标准 `2026-01-07` / `2026-07-01`);`quote day-kline`/`kline-hk`/`kline-us`/`index`、`fundamental balance-sheet` 同理。v0.28.0 前透传命令(research/summary/announcement-hk/us/vault/minute-kline 等)**静默放行**,且同值在本地转时间戳的 `announcement`(A 股)与透传的 hk/us 之间还会差半年、都 exit 0。现在全部在发请求前报 `ValidationError`,**但绕过 CLI 直连接口务必自己保证格式**
276
+ - **财报接口的日期按「报告期末」过滤**,不是公告日:`fundamental balance-sheet` 等的 `--start-date`/`--end-date` 匹配的是 `endDate` 字段(如 `20200630`),响应里的 `announcementDate`(如 `20200729`)只是公告日。**查某期财报要传季度末日期**(`2020-06-30` / `2020-03-31` / `2020-09-30` / `2020-12-31`);传 `2020-07-01` 这类非报告期日期会返回 0 行,属正常行为,不是没数据
277
+ - 🔴 **Quote 系对非法证券代码不报错**,静默返回 `total:0` 空列表——无法区分"代码写错"和"该票该区间真无数据"。**空结果先回头核对代码后缀**。Fundamental 系会正常报 `120001`
278
+ - **枚举值拼错、分页参数越界服务端不报错**——静默忽略该条件返回全量/正常结果。所以 `100004`/`100005` 实测触发不到。CLI 只对**部分**参数加了本地白名单(`--top` 上限;`--category` 仅 `reference securities-search` / `institution-search` / `official-account-search` 三个命令),**`insight research --category` 等仍是自由字符串、拼错不报错也不生效**。**拼错的筛选条件会伪装成"结果正常",枚举拼写要自己保证**
279
+ - **`viewpoint-debate` 传敏感内容不会被提前拦截**——实测不返回 `240002`,而是照常受理、扣满 50 积分、生成阶段才以 `410111` 失败。**提交前自己把关措辞**
280
+ - **`ai one-pager` 的非法 `mode` 被静默忽略**,照常生成并扣 50 积分
281
+
282
+ **官方文档列出、但实测未触发的码**(遇到再查,多数被上面的兜底码接管)
283
+
284
+ | 错误码 | 含义 | 实测情况 |
285
+ |--------|------|---------|
286
+ | `999001` / `999002` | 缺 token / token 无效 | 实际返回旧码 `0000001007` / `0000001008` |
287
+ | `999007` / `999008` / `999009` | 方法/媒体类型/请求体不支持 | 实际返回 `900002` / `999999` / `100003` |
288
+ | `999003` / `999004` / `999005` / `999006` | 无接口权限 / 无资源权限 / 积分不足 / 限流 | 未构造出(需特定账号状态) |
289
+ | `999012`–`999016` | 账号禁用/过期、租户失效、无长期 token、IP 不合规 | 未构造出 |
290
+ | `100002` / `100004` / `100005` | 类型错 / 分页非法 / 枚举非法 | 类型错归 `100003`;后两者服务端静默忽略 |
291
+ | `110003` | 超出时间范围限制 | 未触发(1900 年至今的范围仍正常返回) |
292
+ | `130003` / `130004` / `130005` | 无文件可下 / ID 非数字 / 文件类型不支持 | 全部归 `130002` |
293
+ | `140001` / `140002` | 结果生成中 / 处理失败 | 异步端点仍用 `410110` / `410111` |
294
+ | `210001` / `220001` / `230001` | 研报/观点/分享文件不支持下载 | 未构造出 |
295
+ | `240002` / `240003` | 敏感词 / 模式不支持 | 敏感词走 `410111`;`one-pager` 的非法 `mode` 被静默忽略 |
296
+ | `903301` / `10011401` | 今日调用上限 / 白名单未开通 | 历史遗留,**均未实测触发**。不臆断对应新码——`10011401` 按语义更接近 `999003`(未开通接口权限)而非 `999016`(IP 限制),别据此去查 IP |
297
+
298
+ **非错误码**
299
+
300
+ | 情形 | CLI 行为 | Agent 是否介入 |
301
+ |------|---------|--------------|
302
+ | HTTP 5xx / `ECONNRESET` / 超时 | **自动指数退避重试 ×2**(🔴 贵档端点不重放) | 仍失败提示用户 |
303
+ | `ValidationError` | 本地参数校验失败 | 检查 `--from` / `--size` / `--limit` 数值,**不要重试同命令** |
263
304
 
264
305
  **其他场景**:
265
306
  - CLI 未安装 → `npm install -g gangtise-openapi-cli`
@@ -284,21 +325,23 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
284
325
  3. 行业 ID 用错体系:`--industry`(用 `citicIndustry` 码 `1008001xx`)/ `--research-area`(用 `gangtiseIndustry`:行业 `1008001xx` + 方向 `122000xxx`)/ `--gts-code`(申万 `821xxx.SWI`)——三套体系不同,详见 `references/commands/reference-and-lookup.md`
285
326
  4. `--rating` / `--category` 等枚举值拼错(参考对应命令的 references 文件)
286
327
 
287
- **`8000014` / `8000015` 反复**(CLI 已自动重试一次仍失败)
328
+ **`999011` 凭证无效**(旧码 `8000014`/`8000015`;服务端已合并为一个码,不再指明是 AK 错还是 SK 错,**登录直接失败、CLI 不重试**)
288
329
  1. `echo $GANGTISE_ACCESS_KEY` 验环境变量是否 export
289
330
  2. AK 和 SK 是否写反
290
- 3. 账号是否到期 / 异常(`gangtise auth status`)
331
+ 3. 账号是否到期 / 异常(`gangtise auth status`;对应 `999012`/`999013`)
291
332
 
292
- **异步任务 `410111` 反复**(该报告期数据未生成)
293
- 1. 换更早的 `--period`(如 `2025q3` → `2025interim`)
294
- 2. `report-date` 用已发布的标准期:`xxxx-06-30` / `xxxx-12-31`
295
- 3. 直接告知用户该期数据暂不可用
333
+ **异步任务 `410111` 反复**(生成失败,终态)
334
+ 1. `viewpoint-debate`:先检查观点措辞——实测敏感内容不会被提前拦截,会扣满 50 积分再以 `410111` 失败
335
+ 2. `earnings-review`:换更早的 `--period`(如 `2025q3` `2025interim`)
336
+ 3. `report-date` 用已发布的标准期:`xxxx-06-30` / `xxxx-12-31`
337
+ 4. 若提交阶段就返回 `240001`(财报期未披露),说明该期不可查且**未扣积分**,别再换参数试
338
+ 5. 直接告知用户该期数据暂不可用
296
339
 
297
340
  **K 线返回的不是"最近"几条** → 只用 `--limit` 截的是窗口开头。必须改用 `--start-date`/`--end-date` 拉范围,再从结果尾部按 `tradeDate` 取最近 N 条。
298
341
 
299
342
  **翻页很慢 / 卡住** → `--verbose` 看哪一页慢;可 `GANGTISE_PAGE_CONCURRENCY=10` 提速,或缩小时间范围。
300
343
 
301
- **`--security all` 报 `430007`** 单日数据仍超 10K 行(极端情况)→ 临时改用更窄的 `--start-date`/`--end-date`,或改为单只 `--security` 单独拉。
344
+ **`--security all` 报 `100006`**(旧码 `430007`)→ 单日数据仍超 10K 行(极端情况)→ 临时改用更窄的 `--start-date`/`--end-date`,或改为单只 `--security` 单独拉。
302
345
 
303
346
  **AI agent 命令(one-pager 等)超时** → 服务端生成耗时长,CLI 默认 30s → `GANGTISE_TIMEOUT_MS=120000` 后重试。
304
347
 
@@ -9,14 +9,14 @@
9
9
  ## 知识库搜索 `ai knowledge-batch`
10
10
 
11
11
  ```bash
12
- gangtise ai knowledge-batch --query <text> [--query <text2>] [--top <n>] [--resource-type <n>] [--knowledge-name <name>] [--start-time <ms>] [--end-time <ms>]
12
+ gangtise ai knowledge-batch --query <text> [--query <text2>] [--top <n>] [--resource-type <n>] [--knowledge-name <name>] [--start-time <ts|datetime>] [--end-time <ts|datetime>]
13
13
  ```
14
14
 
15
15
  - `--query`(**必选**,可重复,最多 5 个):缺失时本地报错,不发空请求
16
16
  - `--top` 默认 10,最大 20
17
17
  - `--resource-type`:`10` 券商研报 | `11` 外资研报 | `20` 内部报告 | `40` 首席观点 | `50` 公司公告 | `51` 港股公告 | `60` 会议平台纪要 | `70` 调研纪要公告 | `80` 网络资源纪要 | `90` 产业公众号
18
18
  - `--knowledge-name`:`system_knowledge_doc` 系统知识库 | `tenant_knowledge_doc` 机构知识库
19
- - `--start-time` / `--end-time`:13 位毫秒时间戳,按时间范围过滤
19
+ - `--start-time` / `--end-time`:13/10 位时间戳或 `YYYY-MM-DD[ HH:mm[:ss]]`(秒可省、空格或 `T` 分隔;CLI 统一转 13 位毫秒,10 位秒自动 ×1000),按时间范围过滤
20
20
 
21
21
  ## 知识资源下载 `ai knowledge-resource-download`
22
22
 
@@ -24,7 +24,7 @@ gangtise ai knowledge-batch --query <text> [--query <text2>] [--top <n>] [--reso
24
24
  gangtise ai knowledge-resource-download --resource-type <n> --source-id <id> [--output <path>]
25
25
  ```
26
26
 
27
- `resourceType + sourceId` 必须匹配(来自 knowledge-batch 返回),错配返回 `433007`。
27
+ `resourceType + sourceId` 必须匹配(来自 knowledge-batch 返回),错配返回 `250001`(旧 `433007`)。
28
28
 
29
29
  ## 投研线索 `ai security-clue`
30
30
 
@@ -86,7 +86,7 @@ gangtise ai earnings-review-check --data-id <id>
86
86
  - `--period`:`年份+报告期`,如 `2025q3`(q1/interim/q3/annual),仅 A 股,覆盖最近 6 期
87
87
  - `--wait`(**推荐**):阻塞等待到出结果(最长约 5 分钟:14 次指数退避轮询 5s→30s,累计 ≈316s)——**用它时把工具/命令超时设到 ≥360s**,否则外层先超时
88
88
  - 不带 `--wait` 的手动轮询:① `earnings-review` → 拿 `{dataId, status, hint}` → ② 间隔 ~30s `*-check`(预算 ~2-3 分钟)→ pending 继续 → 多次仍 pending 交用户稍后手动 check
89
- - 错误码:`410110` 生成中(继续等待);`410111` 生成失败(终态,不重试)
89
+ - 错误码:`140001`(旧 `410110`)生成中,继续等待;`140002`(旧 `410111`)生成失败,终态不重试。CLI 两代码都识别
90
90
 
91
91
  ## 观点 PK `ai viewpoint-debate`(异步)
92
92
 
@@ -74,7 +74,7 @@ gangtise fundamental main-business --security-code <code> [--breakdown <type>] [
74
74
  - `--breakdown`(默认 `product`):`product` 按产品 | `industry` 按行业 | `region` 按地区
75
75
  - `--period`:`interim` 中报 | `annual` 年报(可重复)
76
76
  - 默认时间窗:`endDate` 当前日期、`startDate` 三年前
77
- - **不支持 `--fiscal-year`**(误传触发 900001);按年份筛选用 `--start-date`/`--end-date`
77
+ - **不支持 `--fiscal-year`**(误传触发 `100001`/`100003`,旧 `900001`);按年份筛选用 `--start-date`/`--end-date`
78
78
 
79
79
  ## 估值分析 `fundamental valuation-analysis`
80
80
 
@@ -100,7 +100,7 @@ gangtise indicator cross-section --indicator qte_close --security 600519.SH \
100
100
  | `410001` | 入参错误:没传指标/证券,或 `time-series` 传了「多指标 × 多证券」 | 补齐 `--indicator`/`--security`;多 × 多改用 `cross-section` |
101
101
  | 缺参报错(曾为 `410106`) | **缺必填参数**:服务端现已直接指明缺哪个,如「指标 X 的必填参数 periodNum(期数) 不能为空」(仍以 HTTP 500 返回,CLI 重试 2 次后透出该消息) | 读 `search --format json` 的 `parameterList`,把 `required:true` 的参数用 `--indicator-param` 补上 |
102
102
  | `999999` | **多为整查询无数据**(节假日 / 未来日期 / 未覆盖标的;单元格级缺值才是 `null`),也可能是真系统故障——服务端不区分两者 | CLI 对 indicator 端点**不重试此码**(v0.27.0)并在 hint 中提示;先核对日期是交易日、标的在覆盖范围,确认应有数据仍报错才按系统故障处理 |
103
- | `410004` | 数据未找到,或**该指标无权限**(内层信封失败会带具体 msg,如"指标无权限";此码被服务端复用) | 检查查询条件与指标权限;换证券/日期仍失败多为无权限,联系管理员开通 |
103
+ | `130001`(旧 `410004`) | 数据未找到,或**该指标无权限**(内层信封失败会带具体 msg,如"指标无权限";此码被服务端复用) | 检查查询条件与指标权限;换证券/日期仍失败多为无权限,联系管理员开通 |
104
104
 
105
105
  ### 必填参数(`410106` 的根因)
106
106
 
@@ -70,6 +70,7 @@ gangtise insight research download --report-id <id> [--file-type <n>] [--output
70
70
  - `--rating-change`:`upgrade` | `maintain` | `downgrade` | `initiate`
71
71
  - `--source`:`1` PDF研报 | `2` 公众号
72
72
  - `--file-type`(download):`1` 原始PDF(默认)| `2` Markdown
73
+ - **积分**:list 0.1/条;download 10/篇
73
74
 
74
75
  ## 外资研报 `insight foreign-report list/download`
75
76