gangtise-openapi-cli 0.45.0 → 0.47.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.
package/README.md CHANGED
@@ -6,18 +6,20 @@
6
6
 
7
7
  README 仅列最近 5 个版本摘要:
8
8
 
9
+ - **v0.47.0 — 2026-10-17**:① **`insight opinion list` 新增 `--category`(观点类别:业绩点评 / 异动点评 / 调研访谈更新 / 交易台 / 行业分析)与 `--broker-tag`(头部券商 / 新财富团队)**,返回行带 `categoryList` / `brokerTagList`。② **`--llm-tag` 改为 28 项投研业务标签**(强烈推荐、超预期、涨价、龙头等,取值见 `--help`);⚠️ **存量脚本要改**:业绩点评、头部券商、新财富团队改用 `--category earningsReview` / `--broker-tag topBroker` / `--broker-tag newFortune`,传给 `--llm-tag` 报 `100005`。同一选项传多个值取并集。③ `--with-content` 不支持 `--category` / `--broker-tag`(本地报错、不发请求),其 `--llm-tag` 仍只收旧的四个值;要按新标签筛又要正文,用 `list` + `detail`。④ **`bond daily-quote` / `bond valuation` 首行比起始日晚超过 14 天时 stderr 提示**(上市较晚或数据起点较晚;多只同查只看整批最早的首行,逐只起点按 `securityCode` 核对)。⑤ 文档:股东结构类 EDE 指标(`shr_*`),大股东系列须传 `rank`、户均持股系列须传 `shareType`。
10
+
11
+ - **v0.46.0 — 2026-10-14**:① **`reference securities-search --category bond`**:按代码或简称搜债券,代码用于 `bond` 命令组。⚠️ **可转债可能出现在任何 `--category` 的结果里**(按简称查股票时常排第一),**只取返回行 `category` 等于所查类别的那一行**;债券代码传给 `fund` 命令会返回空结果、不报错。② **场外基金可按名称搜到**(`--category fund` 同时返回 `.OF` 与场内代码)。③ **计费单价按平台最新价目更新**:一页通等 AI 生成 10/次、业绩点评 / 观点 PK 20/次、热点话题 10/篇、个股看点 1/条、路演四件套 10/条、独立观点列表 1/条、纪要 / 外资研报 / 我的会议下载 30/篇、公告下载 A 股 1 / 港美股 2、`edb-data` 30/指标;额度保护随之调整。④ **数据范围**:`SKILL.md` 按官方权限表逐命令列出正式 / 试用两档,规划查询区间按表来。⑤ **报错口径**:EDE 漏传必填的 `fiscalYear` 报 `100001`;`main-business` / `valuation-analysis` 的 `--field` 写错名字报 `100005`;`screener` 计费按筛选前范围保守估算。完整列表见 CHANGELOG。
12
+
9
13
  - **v0.45.0 — 2026-10-07**:① **`vault drive-copy` 可复制整个文件夹**:`--folder-id` + `--target-parent-id`,连同子文件夹与文件复制到另一空间,返回 `{folderId, newFolderId}`;复制文件的写法不变,两组参数二选一。② **下载文件名**:响应同时给出 UTF-8 文件名与普通文件名时用 UTF-8 的那个。③ `GANGTISE_BASE_URL` 指向非本机地址却用 `http://` 时 stderr 提示一次(凭证会明文传输)。④ 文档:`bond daily-quote` / `bond valuation` 的数据可能比账号窗口短,**按年份取长序列时核对首行日期**;`bond announcement` 按日期查询时有代码的行都带后缀;基金只知道名称时怎么换代码;分钟 K 的可查历史是滚动的短窗口。
10
14
 
11
15
  - **v0.44.0 — 2026-09-30**:**新增 `fund` 公募基金 18 个命令,一律 0.4 积分/次**(按次,与返回行数、基金只数无关):`basic-info` / `nav`(日频净值)/ `fee-rate` / `manager-info`(按姓名精确匹配,同名全返回)/ `manager-history` / `asset-size` / `holder-structure` / `top10-holders` / `asset-allocation` / `stock-portfolio` / `industry-allocation` / `bond-portfolio` / `bond-type-allocation` / `fund-portfolio` / `fund-type-allocation` / `etf-pcf-header` / `etf-pcf-components` / `etf-share-change`。`--security` 用带大写后缀的基金代码(场外 `.OF`,场内 `.SH` / `.SZ`),**代码不存在、不带后缀或后缀小写都返回空结果、不报错**。不分页:单次上限 10000 行,超出整批报 `100006`;日期两端都不传取账号可回溯窗口内的全部,起点早于窗口整批报 `110003`。**整族超时 / 5xx 不自动重发**(避免重复扣费)。取数前注意:各命令单位不同(元 / 万元 / 万份 / 万股);`holder-structure` 的 `holderCount` 是带千分位的字符串;持仓明细只返回证券简称、不返回代码;`fund-type-allocation` 季报期只含重仓基金。逐条见 `gangtise-openapi/references/commands/fund.md`。**另有几处行为变化**:只收一个值的参数重复传直接报错;代码类列表也按顿号 / 分号 / 空格分隔并去重;显式 `--size` 与 `fundamental earning-forecast` 的日期区间估算超过 1000 积分同样要 `--yes`;jsonl / csv 文件一律以换行结尾,csv 不再给 `-3.5%` 这类值加 `'`;全市场分片早于账号窗口的部分跳过而不中止;接口返回下载链接时不带 `--output` 也会下载;A 股公告与 `ai knowledge-batch` 只写日期的 `--end-time` 按当日 23:59:59 换算;`--indicator-param` 里的日期值与 `--date` 同样校验;单次请求另有总时长上限(`GANGTISE_TIMEOUT_MS` 的 2 倍,下载与上传 60 倍);环境变量设成空字符串按未设置处理。完整列表见 CHANGELOG。
12
16
 
13
17
  - **v0.43.1 — 2026-09-27**:① **EDE `fiscalYear` 漏传不报错**:预测类(`frcst_*`)与分红类(`div_cash_yr` 等)漏传时按服务端自定的默认年度取数,数看着正常但未必是你要的年度——**一律显式传 `fiscalYear`**;其余必填参数缺失仍报 `100001`。② **翻页去重**:同一 ID 的任一已出现版本再次出现都按重复去掉(计入 `duplicateRows`),不区分字段顺序。③ **观点 `detail`**:某一批夹有空元素或格式异常的元素时,这一批已返回的正文照常输出;空元素的 ID 列在 `missingIds`,其余没取到的列在 `unfetchedIds`(`unfetchedError` 带 traceId)。④ 文档:`finc_roe_avg_avg` 示例补 `reportDate`;债券三个评级命令的计费说明统一。
14
18
 
15
- - **v0.43.0 — 2026-09-26**:① **按条计费列表的额度保护**:省略 `--size` 时先拿到 `total` 估算全量积分(整页超过 50 积分的列表只先取 1 条),超过 1000 积分报错退出 1,加 `--yes` 或传 `--size N` 放行。② **翻页结果的完整性**:跨页重复的行去掉并标 `duplicateRows`;同一 ID 在后面的页内容变了,两版都保留并标 `changedRows`;`total` 正好等于偏移窗口时标 `totalCapped`;都退出 3。热点话题、纪要、A 股 / 港股公告、财报日历查不到内容时按空结果、退出 0(此前退出 3)。③ **`quote` 的 `--field` 在多只证券或全市场时自动补身份列**(日 K 补 `securityCode` / `tradeDate`、分钟 K 补 `securityCode` / `tradeTime`、`realtime` 补 `securityCode`;单只不补),**多只证券只点一列的脚本输出会多出这些列**。④ **多证券日 K 合批**:按行数上限装入尽量多的证券,请求数大幅减少;全市场分片按工作日计(港股 2 个工作日一片),全市场关键字须同时给起止日期。⑤ **首行晚到提示**:行情首行比请求起点晚 14 天以上时 stderr 提示(上市较晚或越过回溯窗口)。⑥ **`ai stock-summary` 与 `fundamental earning-forecast` 超时 / 5xx 不再自动重试**(单次可能扣数千积分)。⑦ 四个枚举参数写错时本地报错;Ctrl-C / `kill` / 终端断开时清理暂存文件,进程以该信号结束(`$?` 为 130 / 143 / 129),脚本循环随之停下。⑧ Agent Skill 主文件精简,细节移到 `references/`。
16
-
17
- - **v0.42.0 — 2026-09-25**:① **数值参数只收十进制写法**:`--size 0x10`、`--size 1e3`、`--limit 5.0` 这类写法发请求前报错并点名参数(此前会被换算成 16 / 1000 / 5 照常发出);数字列表类参数逐项要求整数。**脚本里用了这类写法的,请改成普通整数。** ② **接管道时保留退出码**:`gangtise … | head` 提前关掉读端时,以已判定的退出码(`3` / `4`)结束,此前一律 `0`;脚本用 `set -o pipefail` 即可看到。③ **`GANGTISE_TIMEOUT_MS` 只收整数毫秒**:低于 1 秒回退默认、高于 1 小时按 1 小时,所写数值没有按原样生效时 stderr 提示一次。④ 观点 `detail` 与估值分析的返回结构与约定不符时报错并附 trace,不再按空结果处理。⑤ `fundamental valuation-analysis` 的 `--field` 不含数值列、接口返回 0 行时,stderr 说明原因。⑥ 性能:大批量导出按块写盘;自动翻页时单个慢页对其余页的拖累更小;`GANGTISE_PAGE_CONCURRENCY` 设到 16 以上时并发随之增加。
18
-
19
19
  ### 历史里程碑
20
20
 
21
+ - **v0.43.0**:按条计费列表的额度保护(省略 `--size` 时估算超过 1000 积分须 `--yes`);翻页结果的跨页重复、版本变化与 `total` 封顶检测。
22
+ - **v0.42.0**:数值参数只收十进制写法(`0x10` / `1e3` 这类写法发请求前报错);接管道时保留退出码 `3` / `4`。
21
23
  - **v0.41.0**:新增债券 12 个命令与云盘管理 9 个命令;观点列表改走 v2(列表只含摘要 1 积分/条,正文按 ID 另取);新增会议线索与联网搜索。
22
24
  - **v0.40.0**:自选股股票池可增删改(首批写操作,删池须显式 `--yes`);`indicator time-series` 自动判定日期轴;Token 缓存绑定签发它的凭证。
23
25
  - **v0.39.0**:`csv` / `jsonl` 导出的元信息加上字节数与 `sha256`,转交前可核验;并发导出到同一 `--output` 不再互相掺混,文件被另一次导出替换时退出码 4。
@@ -148,7 +150,7 @@ install_skill ~/.hermes/skills # Hermes
148
150
  |------|--------|------|
149
151
  | **Auth** | `login` / `status` | 认证登录、状态查询 |
150
152
  | **Lookup** | `broker-org list` / `meeting-org list` | 券商/会议机构本地全量枚举表(按名称找 ID 优先 `reference institution-search`;行业/区域/公告分类/题材/申万码用 Reference 接口查) |
151
- | **Insight** | `opinion list` / `detail` | 内资机构观点(列表只含摘要 1 积分/条;`detail` 按 ID 取正文 30 积分/条;`list --with-content` 列表直接带正文 30 积分/条) |
153
+ | **Insight** | `opinion list` / `detail` | 内资机构观点(列表只含摘要 1 积分/条;`detail` 按 ID 取正文 30 积分/条;`list --with-content` 列表直接带正文 30 积分/条;可按 `--category` 观点类别 / `--llm-tag` 投研标签 / `--broker-tag` 机构标签筛) |
152
154
  | | `summary list` / `download` | 纪要(含下载,支持 `--file-type` 选原始/HTML) |
153
155
  | | `pamirs-summary list` / `download` | 帕米尔专家纪要(需单独购买专家纪要库;筛选项比 `summary` 少,无 `--source`/`--institution`/`--participant-role`) |
154
156
  | | `roadshow list` | 路演 |
@@ -167,7 +169,7 @@ install_skill ~/.hermes/skills # Hermes
167
169
  | | `qa list` | 投资者问答 QA(互动平台/电话会议/调研纪要,按证券) |
168
170
  | | `report-image list` / `download` | 研报图表搜索(按关键词,含原图 JPEG 下载) |
169
171
  | | `highlight list` | 会议线索(会议核心要点信息流,按时间倒序;5 积分/条) |
170
- | **Reference** | `securities-search` | GTS Code 搜索(按名称/代码/拼音匹配) |
172
+ | **Reference** | `securities-search` | GTS Code 搜索(按名称/代码/拼音匹配;`--category` 可选 stock / dr / index / fund / bond,按返回行的 `category` 取) |
171
173
  | | `chiefs-search` | 首席分析师 ID 搜索(按姓名/机构/团队匹配) |
172
174
  | | `institution-search` | 机构 ID 搜索(内资券商/外资/牵头/观点机构,按名称匹配) |
173
175
  | | `official-account-search` | 公众号 ID 搜索(按公众号名/机构/分类匹配,返回 accountId) |
@@ -243,7 +245,7 @@ install_skill ~/.hermes/skills # Hermes
243
245
  | | `time-series` | 指标时间序列(多指标 × 单证券 或 单指标 × 多证券,按区间) |
244
246
  | | `screener` | 条件选股(按指标表达式从证券/板块范围筛股;前置 `search` 拿 code) |
245
247
  | **Alternative** | `edb-search` | 行业指标搜索(按关键词匹配,返回 indicatorId 等元信息) |
246
- | | `edb-data` | 行业指标时序数据(批量拉取,最多10个指标) |
248
+ | | `edb-data` | 行业指标时序数据(批量拉取,最多10个指标;30 积分/指标) |
247
249
  | | `concept-info` | 题材指数基本信息(定义/投资逻辑/行业空间/竞争格局;50 积分/次,`--full` 另含催化事件 500 积分/次) |
248
250
  | | `concept-securities` | 题材指数成分股(按分组;50 积分/次,`--full` 另含重点个股标识与纳入理由 500 积分/次) |
249
251
  | **Tool** | `file-parse` / `file-parse-check` | PDF 解析为 Markdown + 图片(异步,返回 ZIP) |
@@ -304,7 +306,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
304
306
  - **流式下载**:二进制响应(PDF 等)直接 `pipeline` 到磁盘,不经过内存缓冲;不带 `--output` 时先写入临时文件,定好文件名后再改名。50MB PDF 内存占用近乎为零。
305
307
  - **流式输出**:`--format jsonl` 或 `csv` 加 `--output <file>` 时,翻页 / 全市场分片 / 多证券分批请求的行**按到达顺序逐批写盘**,取数阶段不持有整份结果,内存不随行数增长;csv 先落临时行文件、收尾时按列并集写表头再转成 csv(磁盘两遍、内存不变)。jsonl / csv 无论写文件还是输出到终端都按批写出,每行以换行结尾。任一只 / 页 / 片失败,命令退出时后台已无取数与写盘,不留 `.part`。
306
308
  - **导出元信息**:`csv` / `jsonl` 落盘时旁边生成 `<文件>.meta.json`——命令行、数据行数、列名、`complete`(写元信息时的判定:退出 3 即 `false`)、`total` / `partial` / `failedPages` / `failedShards` / `truncatedShards` / `droppedColumns` / `missingFields` / `duplicateRows` / `changedRows` / `totalCapped` 等全部完整性标记(以及不影响完整性判定的 `outOfWindowShards`)、抓取时间与时区、CLI 版本,以及数据文件的字节数 `bytes` 与内容哈希 `sha256`。命令行与结果里的 key / secret / token 类字段写成 `[redacted]`。元信息在数据文件发布之后才就位,导出失败不会留下新数据配旧元信息。**转交或归档前建议核一次**:`shasum -a 256 <文件>` 与元信息里的 `sha256` 相等,才说明这份元信息描述的就是旁边这份数据——两个进程同时导出到**同一个 `--output`** 时,最终文件一定是其中某一次的完整产物,但旁边的元信息有可能来自另一次,核哈希就能发现。**输的那一次自己也会报**:收尾时核对 `--output` 是否仍是本次写出的文件(仍是同一个文件就不必回读,否则回读比对哈希),不符就在 stderr 说明「这个文件不是本次命令的产物」并**退出码 4**。这两种格式的文件本身只有数据行,转交之后靠它核验是否完整;`json` 自带标记,不生成。
307
- - **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**不重放的端点例外**(贵档:one-pager 等生成/提交类 + `tool file-parse` 提交 + 50/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载 + 题材 `concept-info` / `concept-securities`(含 `--full`);`bond` 全部 12 个命令(多数按次,三个评级命令按条 / 按债券 / 按发行人)、`fund` 全部 18 个命令(按次)与按次计费的 `tool web-search`;按条计费的 `insight highlight list`、观点 `detail` 与 `list --with-content`,以及单次最多 6000 只、按只计费的 `ai stock-summary` 与行数随日期区间增长的 `fundamental earning-forecast`;另加不计分但不可重复执行的 `vault stock-pool-create` 与云盘的上传 / 新建文件夹 / 复制 / 两个删除,共 64 个):5xx/超时不重放,仅连接失败、429 与 token 自愈重试。**两类端点各有各的理由**:贵档是**重放会重复扣分**——服务端可能已经执行并计费,重发按次计费的再扣一次,重发按篇/按条计费的会把已交付的行再计一次;不计分的那几个是**重放会造成副作用或把成功报成失败**——股票池名不允许重复,重发一个其实已经建成的请求,回来的是 `230006 股票池名称重复`;云盘允许同名,重发上传 / 新建 / 复制会多出一份;重发一个其实已经删掉的删除,回来的是「文件不存在」或 `130002`。**`indicator`(EDE)端点对 `999999` 不重试**——重放一次已计费的查询没有意义(EDE 无数据不用此码,而是保留行列的占位单元格 `null`;代码或参数名写错报 `100003`、缺必填参数报 `100001`,漏传 `fiscalYear` 例外、不报错)。**终态码 `999011`(凭证无效)/ `140002`(终态失败:异步生成失败、参数错误或业务处理失败)在任何 HTTP 状态下都不重试**——凭证错不会因重试而变,`140002` 立即重发得到的还是同一结果。
309
+ - **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**不重放的端点例外**(贵档:one-pager 等生成/提交类 + `tool file-parse` 提交 + 30/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载 + 题材 `concept-info` / `concept-securities`(含 `--full`);`bond` 全部 12 个命令(多数按次,三个评级命令按条 / 按债券 / 按发行人)、`fund` 全部 18 个命令(按次)与按次计费的 `tool web-search`;按条计费的 `insight highlight list`、观点 `detail` 与 `list --with-content`,以及单次最多 6000 只、按只计费的 `ai stock-summary` 与行数随日期区间增长的 `fundamental earning-forecast`;另加不计分但不可重复执行的 `vault stock-pool-create` 与云盘的上传 / 新建文件夹 / 复制 / 两个删除,共 64 个):5xx/超时不重放,仅连接失败、429 与 token 自愈重试。**两类端点各有各的理由**:贵档是**重放会重复扣分**——服务端可能已经执行并计费,重发按次计费的再扣一次,重发按篇/按条计费的会把已交付的行再计一次;不计分的那几个是**重放会造成副作用或把成功报成失败**——股票池名不允许重复,重发一个其实已经建成的请求,回来的是 `230006 股票池名称重复`;云盘允许同名,重发上传 / 新建 / 复制会多出一份;重发一个其实已经删掉的删除,回来的是「文件不存在」或 `130002`。**`indicator`(EDE)端点对 `999999` 不重试**——重放一次已计费的查询没有意义(EDE 无数据不用此码,而是保留行列的占位单元格 `null`;代码或参数名写错报 `100003`、缺必填参数报 `100001`)。**终态码 `999011`(凭证无效)/ `140002`(终态失败:异步生成失败、参数错误或业务处理失败)在任何 HTTP 状态下都不重试**——凭证错不会因重试而变,`140002` 立即重发得到的还是同一结果。
308
310
 
309
311
  <!-- no-replay-endpoints
310
312
  上面那句点名的「不重放」端点,完整清单如下(endpoint key,与 `gangtise raw list` 一致):
@@ -411,7 +413,7 @@ vault.stock-pool.create
411
413
 
412
414
  规则:
413
415
  - **省略 `--size` 一律拉全量**(无论是否传时间范围),CLI 自动翻页查完
414
- - **按条计费的列表有额度保护**:省略 `--size` 时,CLI 先拿到 `total`,按 `total × 单价` 估算全量要花的积分,超过 1000 积分就报错、退出码 1,不再往下拉,报错写明估算值。整页不超过 50 积分的列表用第一页拿 `total`;整页更贵的(路演 / 调研 / 策略会 / 论坛、独立观点、会议线索、个股线索、热点话题、`--with-content`)先只取 1 条:结果最多 1 条时它就是全部结果;否则放行后从头翻页,这 1 条会多计一次费(知道要多少条就直接传 `--size N`,不走这一步)。确认要全量就加 `--yes`,只要一部分就传 `--size N`——显式 `--size` 按 `min(--size, total) × 单价` 估算仍超过 1000 积分时同样报错(`--size 100000` 不能当「全量」绕过),要 `--yes`。`fundamental earning-forecast` 不分页,按日期区间估算(工作日数 × 3 条 × 0.5),同一条线。免费列表不受影响
416
+ - **按条计费的列表有额度保护**:省略 `--size` 时,CLI 先拿到 `total`,按 `total × 单价` 估算全量要花的积分,超过 1000 积分就报错、退出码 1,不再往下拉,报错写明估算值。整页不超过 50 积分的列表用第一页拿 `total`;整页更贵的(路演 / 调研 / 策略会 / 论坛、会议线索、个股线索、热点话题、`--with-content`)先只取 1 条:结果最多 1 条时它就是全部结果;否则放行后从头翻页,这 1 条会多计一次费(知道要多少条就直接传 `--size N`,不走这一步)。确认要全量就加 `--yes`,只要一部分就传 `--size N`——显式 `--size` 按 `min(--size, total) × 单价` 估算仍超过 1000 积分时同样报错(`--size 100000` 不能当「全量」绕过),要 `--yes`。`fundamental earning-forecast` 不分页,按日期区间估算(工作日数 × 3 条 × 0.5),同一条线。免费列表不受影响
415
417
  - 数据量未知时,可先 `--size 1` 从 stderr 的 `Total: N` 探明量级,再决定是否全量
416
418
  - 如果显式传了 `--size`,则按指定值翻页,直到达到 `size` 或数据取完
417
419
  - `--from` 必须是非负整数,`--size` 必须是正整数;非法数字会在本地直接报 `ValidationError`,不会继续请求 API
@@ -433,7 +435,7 @@ vault.stock-pool.create
433
435
 
434
436
  推荐工作流:先 `list` 再 `download`,文件名自动正确且零额外成本。
435
437
 
436
- 🔴 **缓存未命中时不会自动回查 list 接口**。回查要拉最近 200 条记录(4 次请求),而这些 list 多数按 **0.1 积分/条**计费,约 20 积分——这笔开销只用于取一个更易读的文件名(下载本身 10–50 积分),所以默认不回查。需要时加 `--resolve-title`:
438
+ 🔴 **缓存未命中时不会自动回查 list 接口**。回查要拉最近 200 条记录(4 次请求),而这些 list 多数按 **0.1 积分/条**计费,约 20 积分——这笔开销只用于取一个更易读的文件名(下载本身 1–30 积分),所以默认不回查。需要时加 `--resolve-title`:
437
439
 
438
440
  ```bash
439
441
  gangtise insight research download --report-id 432092410345574400 --resolve-title
@@ -467,6 +469,8 @@ gangtise insight opinion list --keyword AI --size 20
467
469
  gangtise insight opinion detail --chief-opinion-id 4047400241800281391 --chief-opinion-id 2581139
468
470
  # 想让列表直接带正文(30 积分/条);注意内资旧版结构:标题与正文在 contentList.title / contentList.content
469
471
  gangtise insight opinion list --keyword AI --size 20 --with-content
472
+ # 按观点类别 / 投研业务标签 / 机构标签筛(同一选项传多个值取并集;--with-content 不支持 --category / --broker-tag)
473
+ gangtise insight opinion list --category earningsReview --llm-tag beatExpect --broker-tag topBroker --size 20
470
474
  gangtise insight summary list --keyword 算力
471
475
 
472
476
  # 帕米尔专家纪要(需单独购买专家纪要库;全文搜索 + 时间倒序)
@@ -530,7 +534,7 @@ gangtise insight summary download --summary-id 4906813 --file-type 2
530
534
  # 财报日历:注意用 --start-date/--end-date(按 publishDate 过滤),不是 --start-time
531
535
  gangtise insight performance-calendar list --start-date 2026-07-01 --end-date 2026-07-25 \
532
536
  --market aShares --category performanceForecast --size 20 --format json
533
- # 下载业绩报告原文(仅 hasAttachment: true 的记录;A股 10 积分 / 港美股 20 积分)
537
+ # 下载业绩报告原文(仅 hasAttachment: true 的记录;A股 1 积分 / 港美股 2 积分)
534
538
  gangtise insight performance-calendar download --performance-report-id 33753017 --output ./业绩预告.pdf
535
539
 
536
540
  # 会议线索:会议核心要点信息流(按时间倒序;5 积分/条,务必带 --size 控量)
@@ -550,6 +554,8 @@ gangtise reference securities-search --keyword "贵州茅台" --category stock
550
554
  gangtise reference securities-search --keyword "600519" --category stock
551
555
  gangtise reference securities-search --keyword gzmt --top 5
552
556
  gangtise reference securities-search --keyword "银行" --category stock --category index
557
+ # 债券:按代码或简称查,代码用于 bond 命令组;按简称查股票时同名可转债也会出现,按返回的 category 取
558
+ gangtise reference securities-search --keyword 甬金转债 --category bond
553
559
 
554
560
  # 首席分析师 ID 搜索(按姓名/机构/团队;拿 chiefId 供 insight opinion list --chief 使用)
555
561
  gangtise reference chiefs-search --keyword 东吴证券 --top 3 --format json
@@ -869,7 +875,6 @@ gangtise indicator cross-section --indicator qte_close --security 600519.SH \
869
875
 
870
876
  # 必填参数:缺了会报 100001 并点名,按 parameterList 的 required 补齐再取:
871
877
  # N 期统计补 periodNum、年度/分红/预测类补 fiscalYear;区间类要特定区间时传 sDate(可选,起始日,tradeDate 仍是终点)
872
- # ⚠️ fiscalYear 漏传不报错:按一个服务端自定的默认年度取数(不随 --date 变),该年度有数就返回它、看着正常,没有就是 null,要显式传
873
878
  # finc_roe_avg_avg 是报告期类,日期用 reportDate:
874
879
  gangtise indicator cross-section --indicator finc_roe_avg_avg --security 600519.SH \
875
880
  --date 2026-03-31 --indicator-param "finc_roe_avg_avg:periodNum=4" \
@@ -2,7 +2,8 @@ import { Command, Option } from "commander";
2
2
  import { collectList, collectNames, dateArg, maybeArray, parseNumberOption } from "../core/args.js";
3
3
  import { maxUnitsOf } from "../core/endpoints.js";
4
4
  import { ValidationError } from "../core/errors.js";
5
- import { emit } from "./shared.js";
5
+ import { emit, noteLateStart } from "./shared.js";
6
+ const BOND_LATE_CAUSE = "The bond may have listed later, or this data starts later than the requested range — the endpoint returns from its first day of data without an error.";
6
7
  export const bond = new Command("bond").description("Bond APIs: profiles, issuers, quotes, valuations, cash flows, ratings, announcements");
7
8
  function requireBondCodes(codes, max) {
8
9
  if (!codes.length)
@@ -13,7 +14,7 @@ function requireBondCodes(codes, max) {
13
14
  return unique;
14
15
  }
15
16
  const bondSecurityOption = (command) => command
16
- .option("--security <code>", "Bond code, e.g. 019742.SH / 123456.SZ / 220205.IB (repeatable). Short names and pinyin are rejected — resolve them with 'reference securities-search' first", collectList, [])
17
+ .option("--security <code>", "Bond code, e.g. 019742.SH / 123456.SZ / 220205.IB (repeatable). Short names and pinyin are rejected — resolve them with 'reference securities-search --category bond' first", collectList, [])
17
18
  .option("--field <field>", "Field to return (repeatable); omit for all. An unsupported name rejects the whole call with 100003", collectList, [])
18
19
  .option("--format <format>", "Output format", "table").option("--output <path>");
19
20
  const addBondRange = (name, endpointKey, describe, rangeHelp, max) => bondSecurityOption(bond.command(name).description(describe))
@@ -31,12 +32,20 @@ addBondRange("exercise-notice", "bond.exercise-notice", "Put/call exercise sched
31
32
  bondSecurityOption(bond.command("daily-quote").description("Daily close quotes (exchange + CFETS): dirty/clean price, YTM, duration, convexity"))
32
33
  .requiredOption("--start-date <date>", "Trade date range start (yyyy-MM-dd)", dateArg("--start-date"))
33
34
  .requiredOption("--end-date <date>", "Trade date range end (yyyy-MM-dd)", dateArg("--end-date"))
34
- .action((options) => emit(options, (client) => client.call("bond.daily-quote", { securityList: requireBondCodes(options.security), startDate: options.startDate, endDate: options.endDate, fieldList: maybeArray(options.field) })));
35
+ .action((options) => emit(options, async (client) => {
36
+ const data = await client.call("bond.daily-quote", { securityList: requireBondCodes(options.security), startDate: options.startDate, endDate: options.endDate, fieldList: maybeArray(options.field) });
37
+ noteLateStart(data, options.startDate, "tradeDate", "bond daily-quote", BOND_LATE_CAUSE);
38
+ return data;
39
+ }));
35
40
  bondSecurityOption(bond.command("valuation").description("Shanghai Clearing House valuations: price, yield, duration, convexity, PVBP"))
36
41
  .requiredOption("--start-date <date>", "Valuation date range start (yyyy-MM-dd)", dateArg("--start-date"))
37
42
  .requiredOption("--end-date <date>", "Valuation date range end (yyyy-MM-dd)", dateArg("--end-date"))
38
43
  .addOption(new Option("--confidence-level <level>", "Valuation confidence; defaults to 推荐 server-side, which already filters out 不推荐").choices(["推荐", "不推荐"]))
39
- .action((options) => emit(options, (client) => client.call("bond.valuation", { securityList: requireBondCodes(options.security), startDate: options.startDate, endDate: options.endDate, confidenceLevel: options.confidenceLevel, fieldList: maybeArray(options.field) })));
44
+ .action((options) => emit(options, async (client) => {
45
+ const data = await client.call("bond.valuation", { securityList: requireBondCodes(options.security), startDate: options.startDate, endDate: options.endDate, confidenceLevel: options.confidenceLevel, fieldList: maybeArray(options.field) });
46
+ noteLateStart(data, options.startDate, "tradeDate", "bond valuation", BOND_LATE_CAUSE);
47
+ return data;
48
+ }));
40
49
  function issuerSelector(security, issuer) {
41
50
  if (security.length && issuer.length)
42
51
  throw new ValidationError("--security and --issuer are mutually exclusive: pass bond codes or issuer names, not both");
@@ -2,7 +2,7 @@ import { Command } from "commander";
2
2
  import { collectNames } from "../core/args.js";
3
3
  import { date, format, list, output, query, requiredList, value } from "./shared.js";
4
4
  export const fund = new Command("fund").description("Fund APIs: profiles, NAV, fees, managers, scale, holders, holdings and allocation, ETF creation/redemption");
5
- const code = () => requiredList("--security <code>", "Fund code, e.g. 005827.OF / 159967.SZ / 510300.SH (repeatable, comma-separated accepted)", "fundCodeList");
5
+ const code = () => requiredList("--security <code>", "Fund code, e.g. 005827.OF / 159967.SZ / 510300.SH (repeatable, comma-separated accepted). From a fund name: 'reference securities-search --category fund', keeping only rows whose category is fund", "fundCodeList");
6
6
  const range = (what) => [
7
7
  date("--start-date <date>", `${what} range start (yyyy-MM-dd); omit both ends for everything in your account's history window`, "startDate"),
8
8
  date("--end-date <date>", `${what} range end (yyyy-MM-dd)`, "endDate"),
@@ -25,7 +25,14 @@ const officialAccount = new Command("official-account");
25
25
  const qa = new Command("qa");
26
26
  const reportImage = new Command("report-image");
27
27
  query(opinion, "list", {
28
- endpoint: (options) => options.withContent ? "insight.opinion.list-with-content" : "insight.opinion.list",
28
+ endpoint: (options) => {
29
+ if (!options.withContent)
30
+ return "insight.opinion.list";
31
+ if (options.category.length || options.brokerTag.length) {
32
+ throw new ValidationError("--category / --broker-tag do not work with --with-content: its v1 list ignores them and would return unfiltered rows at 30 credits/row. Filter with 'opinion list', then fetch bodies with 'opinion detail'");
33
+ }
34
+ return "insight.opinion.list-with-content";
35
+ },
29
36
  fields: [
30
37
  rankType(),
31
38
  list("--research-area <id>", RESEARCH_AREA_CITIC_OR_DIRECTION, "researchAreaList"),
@@ -34,9 +41,11 @@ query(opinion, "list", {
34
41
  list("--broker <id>", "Broker ID", "brokerList"),
35
42
  list("--industry <id>", "Industry ID", "industryList"),
36
43
  list("--concept <id>", "Concept ID", "conceptList"),
37
- list("--llm-tag <tag>", "Semantic tag", "llmTagList"),
44
+ list("--category <name>", "Opinion category: earningsReview/abnormalReview/researchUpdate/tradingDesk/industryAnalysis", "categoryList"),
45
+ list("--llm-tag <tag>", "Research tag, several = any of them. Recommendation: strongRcmd/undervalued/highElasticity/highDividend/reversal/bottom/beatExpect/expectGap/oversold/targetPrice/certainty; earnings: highGrowth/withProfitForecast/withGuidance/marginalImprovement; supply chain: priceHike/tightSupplyDemand/capacityRelease/newOrder/orderSurge/keyCustomer/soleSupplier/coreSupplier/industryEntry; moat: leader/moat/scarcity/breakthrough", "llmTagList"),
46
+ list("--broker-tag <tag>", "Broker tag: topBroker/newFortune", "brokerTagList"),
38
47
  list("--source <source>", "Source", "sourceList"),
39
- flag("--with-content", "Return the full body inline (v1 list, 30 credits/row) instead of the 200-char brief (1 credit/row). v1 shape: title and body sit under contentList.title / contentList.content, and there is no brief"),
48
+ flag("--with-content", "Return the full body inline (v1 list, 30 credits/row) instead of the 200-char brief (1 credit/row). v1 shape: title and body sit under contentList.title / contentList.content, and there is no brief. Takes no --category / --broker-tag, and --llm-tag only strongRcmd/earningsReview/topBroker/newFortune"),
40
49
  format(), output("Output path"),
41
50
  ...timeFilters(),
42
51
  ],
@@ -5,7 +5,7 @@ query(reference, "securities-search", {
5
5
  endpoint: "reference.securities-search",
6
6
  fields: [
7
7
  required("--keyword <text>", "Search keyword (name/code/pinyin/English)", "keyword"),
8
- choiceList("--category <type>", "Category: stock/dr/index/fund", "category", ["stock", "dr", "index", "fund"]),
8
+ choiceList("--category <type>", "Category: stock/dr/index/fund/bond. Convertible bonds can come back under any category — keep the rows whose returned category matches", "category", ["stock", "dr", "index", "fund", "bond"]),
9
9
  top(10),
10
10
  format(), output(),
11
11
  ],
@@ -72,7 +72,7 @@ export function earliestRowDate(data, field) {
72
72
  }
73
73
  return earliest;
74
74
  }
75
- export function noteLateStart(data, requestedStart, field, label) {
75
+ export function noteLateStart(data, requestedStart, field, label, cause = "The security may have listed or resumed trading later, or the range reaches past your account's history window for this data — the endpoint returns what lies inside the window without an error.") {
76
76
  const failedShards = data?.failedShards;
77
77
  if (Array.isArray(failedShards) && failedShards.length > 0)
78
78
  return;
@@ -85,7 +85,7 @@ export function noteLateStart(data, requestedStart, field, label) {
85
85
  const lateDays = (Date.parse(`${first}T00:00:00Z`) - Date.parse(`${start}T00:00:00Z`)) / 86_400_000;
86
86
  if (lateDays <= LATE_START_DAYS)
87
87
  return;
88
- process.stderr.write(`[gangtise] note: ${label} starts at ${first}, ${lateDays} days after the requested start ${start}. The security may have listed or resumed trading later, or the range reaches past your account's history window for this data — the endpoint returns what lies inside the window without an error.\n`);
88
+ process.stderr.write(`[gangtise] note: ${label} starts at ${first}, ${lateDays} days after the requested start ${start}. ${cause}\n`);
89
89
  }
90
90
  export async function runDownload(client, endpointKey, query, options) {
91
91
  if (options.output) {
@@ -550,7 +550,7 @@ export class GangtiseClient {
550
550
  }).catch((error) => {
551
551
  const isIndicatorFetch = endpoint.key === 'indicator.cross-section' || endpoint.key === 'indicator.time-series' || endpoint.key === 'indicator.screener';
552
552
  if (isIndicatorFetch && error instanceof ApiError && error.code === '999999') {
553
- throw new ApiError(error.message, error.code, error.statusCode, error.details, error.retryAfterMs, 'EDE 取数故障。先核对参数再重试:参数名以 indicator search 的 parameterList 为准(传错名会报 100003 并指名)、日期匹配指标周期(财务报表类用报告期末如 2025-12-31,PE/PB 等日频估值用交易日)、标的在 scopeList 覆盖内、required 参数已补。注意此码不表示无数据——无数据返回保留行列的 null 单元格;code 或参数名写错报 100003、缺必填参数报 100001,都会指名(fiscalYear 例外:漏传不报错,返回 null 或默认年度的值)。');
553
+ throw new ApiError(error.message, error.code, error.statusCode, error.details, error.retryAfterMs, 'EDE 取数故障。先核对参数再重试:参数名以 indicator search 的 parameterList 为准(传错名会报 100003 并指名)、日期匹配指标周期(财务报表类用报告期末如 2025-12-31,PE/PB 等日频估值用交易日)、标的在 scopeList 覆盖内、required 参数已补。注意此码不表示无数据——无数据返回保留行列的 null 单元格;code 或参数名写错报 100003、缺必填参数报 100001,都会指名。');
554
554
  }
555
555
  throw error;
556
556
  });
@@ -89,7 +89,7 @@ const ENDPOINT_DEFS = {
89
89
  path: "/application/open-insight/summary/v2/download/file",
90
90
  kind: "download",
91
91
  description: "Download summary file",
92
- billing: { per: "document", price: 50 },
92
+ billing: { per: "document", price: 30 },
93
93
  retry: "no-replay",
94
94
  },
95
95
  "insight.pamirs-summary.list": {
@@ -112,7 +112,7 @@ const ENDPOINT_DEFS = {
112
112
  path: "/application/open-insight/schedule/roadshow/getList",
113
113
  kind: "json",
114
114
  description: "List roadshows",
115
- billing: { per: "row", price: 20 },
115
+ billing: { per: "row", price: 10 },
116
116
  pagination: { enabled: true, maxPageSize: 50 },
117
117
  rowId: "id",
118
118
  rowIdUnverified: true,
@@ -122,7 +122,7 @@ const ENDPOINT_DEFS = {
122
122
  path: "/application/open-insight/schedule/site-visit/getList",
123
123
  kind: "json",
124
124
  description: "List site visits",
125
- billing: { per: "row", price: 20 },
125
+ billing: { per: "row", price: 10 },
126
126
  pagination: { enabled: true, maxPageSize: 50 },
127
127
  rowId: "id",
128
128
  rowIdUnverified: true,
@@ -132,7 +132,7 @@ const ENDPOINT_DEFS = {
132
132
  path: "/application/open-insight/schedule/strategy-meeting/getList",
133
133
  kind: "json",
134
134
  description: "List strategy meetings",
135
- billing: { per: "row", price: 20 },
135
+ billing: { per: "row", price: 10 },
136
136
  pagination: { enabled: true, maxPageSize: 50 },
137
137
  rowId: "id",
138
138
  rowIdUnverified: true,
@@ -142,7 +142,7 @@ const ENDPOINT_DEFS = {
142
142
  path: "/application/open-insight/schedule/forum/getList",
143
143
  kind: "json",
144
144
  description: "List forums",
145
- billing: { per: "row", price: 20 },
145
+ billing: { per: "row", price: 10 },
146
146
  pagination: { enabled: true, maxPageSize: 50 },
147
147
  rowId: "id",
148
148
  rowIdUnverified: true,
@@ -160,8 +160,8 @@ const ENDPOINT_DEFS = {
160
160
  method: "GET",
161
161
  path: "/application/open-insight/schedule/performance-calendar/download/file",
162
162
  kind: "download",
163
- description: "Download an earnings report file (A-share 10 credits, HK/US 20)",
164
- billing: { per: "document", price: 20 },
163
+ description: "Download an earnings report file (A-share 1 credit, HK/US 2)",
164
+ billing: { per: "document", price: 2 },
165
165
  },
166
166
  "insight.research.list": {
167
167
  method: "POST",
@@ -193,7 +193,7 @@ const ENDPOINT_DEFS = {
193
193
  path: "/application/open-insight/foreign-report/download/file",
194
194
  kind: "download",
195
195
  description: "Download foreign report",
196
- billing: { per: "document", price: 50 },
196
+ billing: { per: "document", price: 30 },
197
197
  retry: "no-replay",
198
198
  },
199
199
  "insight.announcement.list": {
@@ -210,7 +210,7 @@ const ENDPOINT_DEFS = {
210
210
  path: "/application/open-insight/announcement/download/file",
211
211
  kind: "download",
212
212
  description: "Download A-share announcement file",
213
- billing: { per: "document", price: 10 },
213
+ billing: { per: "document", price: 1 },
214
214
  },
215
215
  "insight.announcement-hk.list": {
216
216
  method: "POST",
@@ -226,7 +226,7 @@ const ENDPOINT_DEFS = {
226
226
  path: "/application/open-insight/announcement-hk/download/file",
227
227
  kind: "download",
228
228
  description: "Download HK announcement file",
229
- billing: { per: "document", price: 20 },
229
+ billing: { per: "document", price: 2 },
230
230
  },
231
231
  "insight.announcement-us.list": {
232
232
  method: "POST",
@@ -242,7 +242,7 @@ const ENDPOINT_DEFS = {
242
242
  path: "/application/open-insight/announcement-us/download/file",
243
243
  kind: "download",
244
244
  description: "Download US announcement file",
245
- billing: { per: "document", price: 20 },
245
+ billing: { per: "document", price: 2 },
246
246
  },
247
247
  "insight.foreign-opinion.list": {
248
248
  method: "POST",
@@ -276,7 +276,7 @@ const ENDPOINT_DEFS = {
276
276
  path: "/application/open-insight/independent-opinion/getList",
277
277
  kind: "json",
278
278
  description: "List foreign independent analyst opinions",
279
- billing: { per: "row", price: 5 },
279
+ billing: { per: "row", price: 1 },
280
280
  pagination: { enabled: true, maxPageSize: 50 },
281
281
  rowId: "independentOpinionId",
282
282
  rowIdUnverified: true,
@@ -812,7 +812,7 @@ const ENDPOINT_DEFS = {
812
812
  path: "/application/open-ai/stock-summary/getList",
813
813
  kind: "json",
814
814
  description: "Stock highlights (refined research summary per security)",
815
- billing: { per: "row", price: 3, maxUnits: 6000 },
815
+ billing: { per: "row", price: 1, maxUnits: 6000 },
816
816
  retry: "no-replay",
817
817
  timeoutMs: 120_000,
818
818
  },
@@ -843,7 +843,7 @@ const ENDPOINT_DEFS = {
843
843
  path: "/application/open-ai/agent/one-pager",
844
844
  kind: "json",
845
845
  description: "Generate one pager",
846
- billing: { per: "call", price: 50 },
846
+ billing: { per: "call", price: 10 },
847
847
  timeoutMs: 120_000,
848
848
  retry: "no-replay",
849
849
  },
@@ -852,7 +852,7 @@ const ENDPOINT_DEFS = {
852
852
  path: "/application/open-ai/agent/investment-logic",
853
853
  kind: "json",
854
854
  description: "Generate investment logic",
855
- billing: { per: "call", price: 50 },
855
+ billing: { per: "call", price: 10 },
856
856
  timeoutMs: 120_000,
857
857
  retry: "no-replay",
858
858
  },
@@ -861,7 +861,7 @@ const ENDPOINT_DEFS = {
861
861
  path: "/application/open-ai/agent/peer-comparison",
862
862
  kind: "json",
863
863
  description: "Generate peer comparison",
864
- billing: { per: "call", price: 50 },
864
+ billing: { per: "call", price: 10 },
865
865
  timeoutMs: 120_000,
866
866
  retry: "no-replay",
867
867
  },
@@ -870,7 +870,7 @@ const ENDPOINT_DEFS = {
870
870
  path: "/application/open-ai/agent/earnings-review-getid",
871
871
  kind: "json",
872
872
  description: "Get earnings review ID",
873
- billing: { per: "call", price: 50 },
873
+ billing: { per: "call", price: 20 },
874
874
  retry: "no-replay",
875
875
  },
876
876
  "ai.earnings-review.get-content": {
@@ -884,7 +884,7 @@ const ENDPOINT_DEFS = {
884
884
  path: "/application/open-ai/agent/theme-tracking",
885
885
  kind: "json",
886
886
  description: "Get theme tracking daily report",
887
- billing: { per: "call", price: 50 },
887
+ billing: { per: "call", price: 10 },
888
888
  timeoutMs: 120_000,
889
889
  retry: "no-replay",
890
890
  },
@@ -893,7 +893,7 @@ const ENDPOINT_DEFS = {
893
893
  path: "/application/open-ai/agent/research-outline",
894
894
  kind: "json",
895
895
  description: "Get company research outline",
896
- billing: { per: "call", price: 50 },
896
+ billing: { per: "call", price: 10 },
897
897
  timeoutMs: 120_000,
898
898
  retry: "no-replay",
899
899
  },
@@ -902,7 +902,7 @@ const ENDPOINT_DEFS = {
902
902
  path: "/application/open-ai/hot-topic/getList",
903
903
  kind: "json",
904
904
  description: "List hot topic reports",
905
- billing: { per: "row", price: 50 },
905
+ billing: { per: "row", price: 10 },
906
906
  pagination: { enabled: true, maxPageSize: 20 },
907
907
  retry: "no-replay",
908
908
  },
@@ -911,7 +911,7 @@ const ENDPOINT_DEFS = {
911
911
  path: "/application/open-ai/management-discuss/from-announcement",
912
912
  kind: "json",
913
913
  description: "Management discussion from financial reports (half-year/annual)",
914
- billing: { per: "call", price: 10 },
914
+ billing: { per: "call", price: 5 },
915
915
  timeoutMs: 120_000,
916
916
  retry: "no-replay",
917
917
  },
@@ -929,7 +929,7 @@ const ENDPOINT_DEFS = {
929
929
  path: "/application/open-ai/agent/viewpoint-debate-getid",
930
930
  kind: "json",
931
931
  description: "Get viewpoint debate ID",
932
- billing: { per: "call", price: 50 },
932
+ billing: { per: "call", price: 20 },
933
933
  retry: "no-replay",
934
934
  },
935
935
  "ai.viewpoint-debate.get-content": {
@@ -1045,7 +1045,7 @@ const ENDPOINT_DEFS = {
1045
1045
  path: "/application/open-vault/my-conference/download/file",
1046
1046
  kind: "download",
1047
1047
  description: "Download my conference resource",
1048
- billing: { per: "document", price: 50 },
1048
+ billing: { per: "document", price: 30 },
1049
1049
  retry: "no-replay",
1050
1050
  },
1051
1051
  "vault.wechat-message.list": {
@@ -1122,6 +1122,7 @@ const ENDPOINT_DEFS = {
1122
1122
  path: "/application/open-alternative/EDB/getData",
1123
1123
  kind: "json",
1124
1124
  description: "Get industry indicator time-series data by indicator ID list",
1125
+ billing: { per: "indicator", price: 30, maxUnits: 10 },
1125
1126
  },
1126
1127
  "alternative.concept-info": {
1127
1128
  method: "POST",
@@ -7,7 +7,7 @@ function traceSuffix(source) {
7
7
  }
8
8
  export function zipFieldRow(fields, row, source) {
9
9
  if (row.length !== fields.length) {
10
- throw new ValidationError(`响应字段数与 fieldList 不匹配(fieldList ${fields.length} 项、该行返回 ${row.length} 个值)——按位置拍平会把值贴到错误的字段上,已拒绝输出。带 --field 的命令多为传了该接口不存在的字段名(上游只返回有效字段的值、字段名却按请求回显):核对 --field 取值(如 quote realtime 没有 close,最新价是 latestPrice),不确定就不传 --field(返回全量字段最稳)。没有 --field 的命令(如 alternative edb-data)出现此错,是上游响应结构异常,请报障${traceSuffix(source)}。`);
10
+ throw new ValidationError(`响应字段数与 fieldList 不匹配(fieldList ${fields.length} 项、该行返回 ${row.length} 个值)——按位置拍平会把值贴到错误的字段上,已拒绝输出。这是响应结构异常:带 --field 的命令先去掉 --field 重试(返回全量字段最稳);没有 --field 的命令(如 alternative edb-data)或去掉后仍报此错,请报障${traceSuffix(source)}。`);
11
11
  }
12
12
  return fields.reduce((acc, field, index) => {
13
13
  acc[String(field)] = row[index];