@v587d/capital-generation 2.8.3 → 2.9.1

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +81 -1
  2. package/README.md +40 -19
  3. package/chart-ui/README.md +5 -4
  4. package/chart-ui/client.src.cjs +7 -16
  5. package/chart-ui/index.js +12 -33
  6. package/chart-ui/package.json +2 -2
  7. package/config/client.js +18 -39
  8. package/config/client.src.cjs +79 -217
  9. package/config/index.js +22 -72
  10. package/config/package.json +2 -2
  11. package/cordis.patch.yml +46 -51
  12. package/lib/agents/bash-guard.d.ts +9 -55
  13. package/lib/agents/bash-guard.js +27 -67
  14. package/lib/agents/preset-roster.d.ts +22 -0
  15. package/lib/agents/preset-roster.js +87 -0
  16. package/lib/agents/role-tool-policy.d.ts +46 -0
  17. package/lib/agents/role-tool-policy.js +205 -0
  18. package/lib/agents/root-tool-policy.d.ts +28 -124
  19. package/lib/agents/root-tool-policy.js +164 -237
  20. package/lib/agents/selected-skills-provider.d.ts +2 -6
  21. package/lib/agents/selected-skills-provider.js +27 -84
  22. package/lib/capital-settings.d.ts +27 -0
  23. package/lib/capital-settings.js +23 -0
  24. package/lib/chart/artifact-ref.d.ts +13 -8
  25. package/lib/chart/artifact-ref.js +27 -10
  26. package/lib/chart/downsample.d.ts +2 -5
  27. package/lib/chart/downsample.js +3 -8
  28. package/lib/chart/errors.d.ts +3 -7
  29. package/lib/chart/errors.js +1 -2
  30. package/lib/chart/event-context.d.ts +8 -0
  31. package/lib/chart/event-context.js +39 -0
  32. package/lib/chart/events.d.ts +21 -78
  33. package/lib/chart/events.js +43 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/present-tool.d.ts +28 -0
  37. package/lib/chart/present-tool.js +128 -0
  38. package/lib/chart/runtime.d.ts +5 -18
  39. package/lib/chart/runtime.js +14 -34
  40. package/lib/chart/series.d.ts +10 -17
  41. package/lib/chart/series.js +22 -47
  42. package/lib/chart/source-token.d.ts +5 -13
  43. package/lib/chart/source-token.js +18 -17
  44. package/lib/chart/spec.d.ts +6 -12
  45. package/lib/chart/spec.js +7 -31
  46. package/lib/chart/tool.d.ts +7 -29
  47. package/lib/chart/tool.js +15 -25
  48. package/lib/chart-render-entry.d.ts +10 -0
  49. package/lib/chart-render-entry.js +14 -0
  50. package/lib/chart-source-entry.d.ts +13 -0
  51. package/lib/chart-source-entry.js +20 -0
  52. package/lib/collect-entry.d.ts +9 -0
  53. package/lib/collect-entry.js +10 -0
  54. package/lib/collector-runtime.d.ts +22 -0
  55. package/lib/collector-runtime.js +97 -0
  56. package/lib/data-collector/args.d.ts +6 -24
  57. package/lib/data-collector/args.js +6 -24
  58. package/lib/data-collector/dataset-tools.d.ts +7 -1
  59. package/lib/data-collector/dataset-tools.js +27 -56
  60. package/lib/data-collector/describe.d.ts +8 -33
  61. package/lib/data-collector/describe.js +17 -70
  62. package/lib/data-collector/hub.d.ts +27 -85
  63. package/lib/data-collector/hub.js +19 -85
  64. package/lib/data-collector/query-time.d.ts +7 -26
  65. package/lib/data-collector/query-time.js +8 -27
  66. package/lib/data-collector/store.d.ts +41 -117
  67. package/lib/data-collector/store.js +32 -111
  68. package/lib/data-collector/time-axis.d.ts +16 -76
  69. package/lib/data-collector/time-axis.js +25 -111
  70. package/lib/data-collector/tools.d.ts +6 -6
  71. package/lib/data-collector/tools.js +34 -43
  72. package/lib/dataset-read-entry.d.ts +9 -0
  73. package/lib/dataset-read-entry.js +7 -0
  74. package/lib/index.d.ts +7 -117
  75. package/lib/index.js +43 -422
  76. package/lib/net/eastmoney-client.d.ts +13 -65
  77. package/lib/net/eastmoney-client.js +12 -53
  78. package/lib/net/throttle.d.ts +2 -13
  79. package/lib/net/throttle.js +3 -14
  80. package/lib/ocr/client.d.ts +12 -21
  81. package/lib/ocr/client.js +23 -40
  82. package/lib/ocr/markdown.d.ts +20 -26
  83. package/lib/ocr/markdown.js +23 -36
  84. package/lib/ocr/store.d.ts +14 -47
  85. package/lib/ocr/store.js +15 -45
  86. package/lib/ocr-entry.d.ts +9 -0
  87. package/lib/ocr-entry.js +10 -0
  88. package/lib/policy-entry.d.ts +17 -0
  89. package/lib/policy-entry.js +22 -0
  90. package/lib/present-entry.d.ts +9 -0
  91. package/lib/present-entry.js +13 -0
  92. package/lib/preset-claim.d.ts +22 -0
  93. package/lib/preset-claim.js +24 -0
  94. package/lib/preset-ids.d.ts +38 -0
  95. package/lib/preset-ids.js +46 -0
  96. package/lib/retriever-config.d.ts +37 -0
  97. package/lib/retriever-config.js +36 -0
  98. package/lib/retriever-runtime.d.ts +31 -0
  99. package/lib/retriever-runtime.js +82 -0
  100. package/lib/root-extras-entry.d.ts +17 -0
  101. package/lib/root-extras-entry.js +25 -0
  102. package/lib/runtime-entry.d.ts +15 -0
  103. package/lib/runtime-entry.js +5 -0
  104. package/lib/runtime.d.ts +21 -0
  105. package/lib/runtime.js +33 -0
  106. package/lib/search-entry.d.ts +9 -0
  107. package/lib/search-entry.js +9 -0
  108. package/lib/sources/eastmoney-http.js +20 -68
  109. package/lib/sources/ftshare-rest.d.ts +6 -34
  110. package/lib/sources/ftshare-rest.js +84 -292
  111. package/lib/sources/fuyao-core.d.ts +8 -18
  112. package/lib/sources/fuyao-core.js +10 -21
  113. package/lib/sources/fuyao-rest.d.ts +2 -6
  114. package/lib/sources/fuyao-rest.js +23 -102
  115. package/lib/sources/security-code.d.ts +6 -12
  116. package/lib/sources/security-code.js +6 -12
  117. package/lib/sources/security-identity.d.ts +1 -4
  118. package/lib/sources/security-identity.js +3 -8
  119. package/lib/sources/tencent-http.js +14 -28
  120. package/lib/sources/tencent-public-core.d.ts +8 -60
  121. package/lib/sources/tencent-public-core.js +12 -77
  122. package/lib/sources/wind-mcp.d.ts +0 -5
  123. package/lib/sources/wind-mcp.js +23 -78
  124. package/lib/sources-entry.d.ts +9 -0
  125. package/lib/sources-entry.js +9 -0
  126. package/lib/time/tools.d.ts +8 -31
  127. package/lib/time/tools.js +30 -66
  128. package/lib/time-entry.d.ts +9 -0
  129. package/lib/time-entry.js +7 -0
  130. package/lib/tool-exec.d.ts +45 -11
  131. package/lib/tool-exec.js +59 -6
  132. package/lib/user-customization.d.ts +14 -0
  133. package/lib/user-customization.js +47 -0
  134. package/lib/watchlist/tools.d.ts +3 -13
  135. package/lib/watchlist/tools.js +10 -30
  136. package/lib/watchlist-entry.d.ts +9 -0
  137. package/lib/watchlist-entry.js +6 -0
  138. package/lib/web-retriever/engines.d.ts +6 -11
  139. package/lib/web-retriever/engines.js +9 -14
  140. package/lib/web-retriever/html-markdown.d.ts +14 -34
  141. package/lib/web-retriever/html-markdown.js +39 -75
  142. package/lib/web-retriever/local-fetch.d.ts +22 -58
  143. package/lib/web-retriever/local-fetch.js +33 -77
  144. package/lib/web-retriever/retriever.d.ts +6 -18
  145. package/lib/web-retriever/retriever.js +7 -7
  146. package/lib/web-retriever/sources.d.ts +37 -124
  147. package/lib/web-retriever/sources.js +64 -196
  148. package/lib/web-retriever/tools.d.ts +43 -27
  149. package/lib/web-retriever/tools.js +99 -137
  150. package/lib/web-retriever/wind-client.d.ts +14 -31
  151. package/lib/web-retriever/wind-client.js +16 -32
  152. package/locale/zh.json +1 -1
  153. package/package.json +72 -7
  154. package/preset/capital-generation/README.md +87 -53
  155. package/preset/capital-generation/agent.patch.yml +532 -337
  156. package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +6 -4
  157. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  158. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  159. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -2
  160. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  161. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +30 -33
  162. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  163. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +14 -16
  164. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  165. package/watchlist/client.js +15 -148
  166. package/watchlist/client.src.cjs +148 -662
  167. package/watchlist/holding-rules.js +11 -38
  168. package/watchlist/index.js +127 -397
  169. package/watchlist/package.json +2 -2
@@ -1,16 +1,9 @@
1
1
  /**
2
- * Fuyao REST 的最小请求内核:**零 import 的叶子模块**。
3
- *
4
- * 为什么单独一份:host 平面的嵌套包(`watchlist/index.js`)与插件侧的数据能力
5
- * (`fuyao-rest.ts`)必须共用同一套 base URL、鉴权头、信封 `code` 判定与错误文案——
6
- * "同一知识只有一份实现"(AGENTS.md §9.7)。这一层一旦被别的模块拖进来,host 半边
7
- * 就会顺带加载整棵插件依赖树,所以这里不放校验器、能力定义、也不放任何类型依赖。
8
- *
9
- * 错误文案逐字保持历史形态(Agent 与用户看到的字符串不能因抽取而漂移);结构化的
10
- * `kind` / `code` 是给自选股那一类需要映射成自己错误码的消费者用的。
2
+ * Fuyao request kernel: zero-import leaf shared by the host-plane watchlist package and
3
+ * `fuyao-rest.ts` (base URL, auth header, envelope codes, error text). Do not add validators,
4
+ * capability definitions, or type dependencies here: host code would load the plugin tree.
11
5
  */
12
6
  export declare const DEFAULT_FUYAO_BASE_URL = "https://fuyao.aicubes.cn";
13
- /** 单值参数长度上限;与历史 `MAX_PARAM_LENGTH` 同值。 */
14
7
  export declare const FUYAO_MAX_PARAM_LENGTH = 2048;
15
8
  export type FuyaoApiEnvelope = {
16
9
  code?: number;
@@ -19,7 +12,7 @@ export type FuyaoApiEnvelope = {
19
12
  data?: unknown;
20
13
  };
21
14
  export type FuyaoParams = Record<string, unknown>;
22
- /** 失败归类:`http_429` 与 `code=4001` 都是限流,`closed` 是能力级不可用(重试无意义)。 */
15
+ /** Failure kinds: HTTP 429 and code=4001 are rate limits; `closed` is capability-level (retry is futile). */
23
16
  export type FuyaoFailureKind = 'credential_missing' | 'rate_limited' | 'capability_closed' | 'invalid_parameter' | 'http_error' | 'upstream_error';
24
17
  export declare class FuyaoError extends Error {
25
18
  readonly kind: FuyaoFailureKind;
@@ -33,12 +26,9 @@ export declare class FuyaoError extends Error {
33
26
  });
34
27
  }
35
28
  export declare function normalizeFuyaoBaseUrl(baseUrl: string): string;
36
- /** 只带上声明过的参数;类型与长度不符就拒,绝不把拼写错误的键悄悄发上去。 */
29
+ /** Send only declared params; reject bad type/length so misspelled keys are never silently dropped. */
37
30
  export declare function fuyaoQuery(params: FuyaoParams, allowed: string[]): URLSearchParams;
38
- /**
39
- * 发一次 Fuyao 请求并返回信封。调用方自己决定如何消费 `data`;
40
- * 非 0 信封、非 2xx HTTP、2004 能力封闭都以 `FuyaoError` 抛出,文案与历史一致。
41
- */
31
+ /** One Fuyao call returning the envelope; non-zero code, non-2xx and 2004 become `FuyaoError`. */
42
32
  export declare function fuyaoRequest(input: {
43
33
  baseUrl: string;
44
34
  apiKey: string | undefined;
@@ -46,7 +36,7 @@ export declare function fuyaoRequest(input: {
46
36
  search: URLSearchParams;
47
37
  signal: AbortSignal;
48
38
  }): Promise<FuyaoApiEnvelope>;
49
- /** 行数组位置:这些端点的行都在 `data.item` 下(信封里 `data` 可能为 null)。 */
39
+ /** Row array lives at `data.item`; `data` may be null. */
50
40
  export declare function fuyaoItems(envelope: FuyaoApiEnvelope): FuyaoParams[];
51
- /** `data.timestamp`(毫秒);无有效数据时上游给 null,不补零。 */
41
+ /** `data.timestamp` in ms; upstream returns null when there is no data (do not zero-fill). */
52
42
  export declare function fuyaoTimestamp(envelope: FuyaoApiEnvelope): number | null;
@@ -1,16 +1,9 @@
1
1
  /**
2
- * Fuyao REST 的最小请求内核:**零 import 的叶子模块**。
3
- *
4
- * 为什么单独一份:host 平面的嵌套包(`watchlist/index.js`)与插件侧的数据能力
5
- * (`fuyao-rest.ts`)必须共用同一套 base URL、鉴权头、信封 `code` 判定与错误文案——
6
- * "同一知识只有一份实现"(AGENTS.md §9.7)。这一层一旦被别的模块拖进来,host 半边
7
- * 就会顺带加载整棵插件依赖树,所以这里不放校验器、能力定义、也不放任何类型依赖。
8
- *
9
- * 错误文案逐字保持历史形态(Agent 与用户看到的字符串不能因抽取而漂移);结构化的
10
- * `kind` / `code` 是给自选股那一类需要映射成自己错误码的消费者用的。
2
+ * Fuyao request kernel: zero-import leaf shared by the host-plane watchlist package and
3
+ * `fuyao-rest.ts` (base URL, auth header, envelope codes, error text). Do not add validators,
4
+ * capability definitions, or type dependencies here: host code would load the plugin tree.
11
5
  */
12
6
  export const DEFAULT_FUYAO_BASE_URL = 'https://fuyao.aicubes.cn';
13
- /** 单值参数长度上限;与历史 `MAX_PARAM_LENGTH` 同值。 */
14
7
  export const FUYAO_MAX_PARAM_LENGTH = 2048;
15
8
  export class FuyaoError extends Error {
16
9
  kind;
@@ -29,7 +22,7 @@ export class FuyaoError extends Error {
29
22
  export function normalizeFuyaoBaseUrl(baseUrl) {
30
23
  return baseUrl.replace(/\/$/, '');
31
24
  }
32
- /** 只带上声明过的参数;类型与长度不符就拒,绝不把拼写错误的键悄悄发上去。 */
25
+ /** Send only declared params; reject bad type/length so misspelled keys are never silently dropped. */
33
26
  export function fuyaoQuery(params, allowed) {
34
27
  const search = new URLSearchParams();
35
28
  for (const name of allowed) {
@@ -46,10 +39,7 @@ export function fuyaoQuery(params, allowed) {
46
39
  }
47
40
  return search;
48
41
  }
49
- /**
50
- * 发一次 Fuyao 请求并返回信封。调用方自己决定如何消费 `data`;
51
- * 非 0 信封、非 2xx HTTP、2004 能力封闭都以 `FuyaoError` 抛出,文案与历史一致。
52
- */
42
+ /** One Fuyao call returning the envelope; non-zero code, non-2xx and 2004 become `FuyaoError`. */
53
43
  export async function fuyaoRequest(input) {
54
44
  if (!input.apiKey) {
55
45
  throw new FuyaoError('credential_missing', 'FUYAO_API_KEY is not configured');
@@ -65,16 +55,15 @@ export async function fuyaoRequest(input) {
65
55
  if (!response.ok)
66
56
  throw new FuyaoError('http_error', `Fuyao HTTP error ${response.status}`, { status: response.status });
67
57
  const envelope = await response.json();
68
- // 2004 = 该能力是同花顺 AI 客户端专用,未开放外部接入(实测 capital-flow / high-frequency 均如此)。
69
- // 这是能力级不可用,不是瞬时故障:错误文案必须让调用方停止重试,否则会白烧配额与回合。
58
+ // 2004 = capability is AI-client-only and closed to external access; retrying burns quota.
70
59
  if (envelope.code === 2004) {
71
60
  throw new FuyaoError('capability_closed', 'Fuyao API error 2004: 该数据能力为同花顺 AI 客户端专用,当前未开放外部接入;不要重试,改用其他能力或如实告知用户该能力不可用', { code: 2004, requestId: envelope.request_id });
72
61
  }
73
- // 4001 = 频率超限(官方口径:避免立即连续重试)。
62
+ // 4001 = rate limited; official guidance says do not retry immediately.
74
63
  if (envelope.code === 4001) {
75
64
  throw new FuyaoError('rate_limited', `Fuyao API error 4001: ${envelope.message ?? 'request failed'}${envelope.request_id ? ` (request_id: ${envelope.request_id})` : ''}`, { code: 4001, requestId: envelope.request_id });
76
65
  }
77
- // 1002 / 1003 = 参数格式与取值域(自选股要靠它区分"这个标的取不到"与"请求写错了")。
66
+ // 1002 / 1003 = parameter format/domain errors; watchlist maps these to its own codes.
78
67
  if (envelope.code === 1002 || envelope.code === 1003) {
79
68
  throw new FuyaoError('invalid_parameter', `Fuyao API error ${envelope.code}: ${envelope.message ?? 'request failed'}${envelope.request_id ? ` (request_id: ${envelope.request_id})` : ''}`, { code: envelope.code, requestId: envelope.request_id });
80
69
  }
@@ -83,13 +72,13 @@ export async function fuyaoRequest(input) {
83
72
  }
84
73
  return envelope;
85
74
  }
86
- /** 行数组位置:这些端点的行都在 `data.item` 下(信封里 `data` 可能为 null)。 */
75
+ /** Row array lives at `data.item`; `data` may be null. */
87
76
  export function fuyaoItems(envelope) {
88
77
  const data = envelope.data;
89
78
  const rows = data?.item;
90
79
  return Array.isArray(rows) ? rows : [];
91
80
  }
92
- /** `data.timestamp`(毫秒);无有效数据时上游给 null,不补零。 */
81
+ /** `data.timestamp` in ms; upstream returns null when there is no data (do not zero-fill). */
93
82
  export function fuyaoTimestamp(envelope) {
94
83
  const data = envelope.data;
95
84
  return typeof data?.timestamp === 'number' ? data.timestamp : null;
@@ -1,13 +1,9 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
2
  import type { DataSource } from '../data-collector/hub.js';
3
- /** 每次执行时解析 API Key 的注入点:符合 credentials 服务「按次解析、不跨操作缓存」的约定。 */
3
+ /** API key resolved per execution; credentials service caches nothing across calls. */
4
4
  export type FuyaoApiKeyResolver = () => Promise<string | undefined>;
5
- /** 批量代码接口的原始 token 上限(按文档:去重前校验)。 */
6
5
  declare const MAX_BATCH_CODES = 100;
7
- /**
8
- * 历史 K 线 / 财务时间区间上限:上游规定 end - start 不超过 10 年(超出返回 1003)。
9
- * 本地用 3660 天(含闰日冗余)保守前置拦截,只挡明显越界,不与上游口径冲突。
10
- */
6
+ /** Upstream max end - start is 10 years (overrun returns 1003); 3660 days is a conservative pre-check. */
11
7
  declare const MAX_HISTORY_WINDOW_MS: number;
12
8
  export declare function createFuyaoRestSources(resolveApiKey: FuyaoApiKeyResolver, baseUrl?: string): DataSource[];
13
9
  export declare function resolveFuyaoApiKey(ctx: Context, credentialRef?: string): Promise<string | undefined>;
@@ -3,27 +3,13 @@ import { getDataTimeContract } from '../time/tools.js';
3
3
  import { DEFAULT_FUYAO_BASE_URL, FUYAO_MAX_PARAM_LENGTH, fuyaoQuery, fuyaoRequest } from './fuyao-core.js';
4
4
  const DEFAULT_BASE_URL = DEFAULT_FUYAO_BASE_URL;
5
5
  /**
6
- * 参数规范化与校验的契约:
7
- *
8
- * - `normalize` 同时完成「未知参数拦截 → 必填检查 → 语义校验 → 规范化」四件事,
9
- * 并且必须幂等:Hub 会在入队前调用一次(用于 merge key 与 params_digest),
10
- * execute 再调用一次(直接调用 source 时也要生效)。
11
- * - 规范化在 digest 之前发生,保证「语义相同、书写不同」的请求(大小写、空白、
12
- * 逗号列表中的重复项、可忽略的默认值)命中同一个 Dataset,而不是各写一份。
13
- * - 所有错误都是本地错误:不发请求、不落盘、不做网络层猜测。
14
- * - **不注入上游默认值**:省略参数与显式传默认值是两种请求形态,各自的 digest 稳定;
15
- * 把上游默认值写进本地代码,会让 digest 的含义随上游默认值漂移而悄悄改变。
6
+ * normalize must be idempotent: Hub calls it before enqueue (digest) and execute calls it again.
7
+ * Canonicalize before the digest so equivalent spellings share one Dataset; never inject upstream
8
+ * defaults, which would make omitted and explicit defaults drift apart.
16
9
  */
17
- /** A 股完整代码:六位数字 + 交易所后缀。裸代码会被拒绝(上游禁止自行拼接后缀)。 */
18
10
  const A_SHARE_CODE = /^\d{6}\.(SH|SZ|BJ)$/;
19
- /** 指数代码:交易所指数(`.SH`/`.SZ`)与同花顺指数(`.TI`)。 */
20
11
  const INDEX_CODE = /^\d{6}\.(SH|SZ|BJ|TI)$/;
21
12
  const EXCHANGES = ['SH', 'SZ', 'BJ'];
22
- /**
23
- * ticker_search 的交易所与资产类型**按官方契约全量放开**(含期货/期权交易所与 futures/options)。
24
- * 注意这是元数据域:本 preset 主动不接入期货/期权的**数据**端点,但"能否消歧一个代码"与
25
- * "能否取到它的行情"是两件事——本地收窄枚举只会让上游支持的查询在这里莫名失败。
26
- */
27
13
  const SEARCH_EXCHANGES = ['SH', 'SZ', 'BJ', 'SSE', 'SZSE', 'CFFEX', 'SHFE', 'INE', 'DCE', 'CZCE', 'GFEX'];
28
14
  const SEARCH_ASSET_TYPES = ['a-share', 'a-share-index', 'forex', 'fund-otc', 'fund-etf', 'fund-lof', 'fund-reits', 'futures', 'options'];
29
15
  const CATALOG_ASSET_TYPES = ['a-share', 'a-share-index', 'fund-otc', 'fund-etf', 'fund-lof', 'fund-reits', 'forex', 'futures', 'options'];
@@ -39,31 +25,22 @@ const ANOMALY_TAGS = ['LIMIT_UP', 'LIMIT_DOWN', 'SHARP_RISE', 'SHARP_FALL', 'RAP
39
25
  const DRAGON_TIGER_BOARDS = ['all', 'org', 'hot_money'];
40
26
  const LIMIT_DOWN_SORT_FIELDS = ['last_limit_time', 'first_limit_time', 'last_price', 'price_change_ratio_pct', 'turnover_ratio_pct'];
41
27
  const LIMIT_BREAK_SORT_FIELDS = ['price_change_ratio_pct', 'open_times', 'last_price', 'turnover_ratio_pct', 'turnover'];
42
- /** 个股异动批量查询的原始 token 上限(按文档:去重前校验)。 */
43
28
  const MAX_ANOMALY_CODES = 50;
44
- /** 基金代码:六位数字 + 市场后缀(.OF 场外 / .SH / .SZ)。 */
45
29
  const FUND_CODE = /^\d{6}\.(OF|SH|SZ)$/;
46
30
  const NAV_RANGES = ['week', 'month', 'tmonth', 'hyear', 'year', 'twoyear', 'tyear', 'fyear'];
47
31
  const NAV_TYPES = ['unit', 'adj', 'unit,adj'];
48
32
  const MERGE_SCOPES = ['all', 'merged', 'separate'];
49
- /** 基金场内历史 K 线窗口上限:上游规定最长 5 个自然年(不是 A 股的 10 年)。 */
33
+ /** Fund on-exchange K-line window: 5 natural years max, not the A-share 10. */
50
34
  const MAX_FUND_HISTORY_WINDOW_MS = 1830 * 24 * 60 * 60 * 1000;
51
- /** 基金前十大持有人的 limit 上限(实测 0 / 11 / 201 均返回 1003)。 */
35
+ /** Top-10 holders limit max is 10; 0/11/201 return 1003. */
52
36
  const MAX_TOP_HOLDERS_LIMIT = 10;
53
- /** 基金经理区间收益的 range 枚举(与 nav 的 8 值枚举不同,只有 5 个)。 */
54
37
  const MANAGER_RANGES = ['month', 'tmonth', 'year', 'nowyear', 'now'];
55
38
  const OFFERING_SUBSCRIBE = ['active', 'upcoming'];
56
- /** 单值参数长度上限;与 `fuyao-core` 的查询串硬上限同值(同一份知识)。 */
57
39
  const MAX_PARAM_LENGTH = FUYAO_MAX_PARAM_LENGTH;
58
40
  const MAX_TEXT_LENGTH = 512;
59
- /** 批量代码接口的原始 token 上限(按文档:去重前校验)。 */
60
41
  const MAX_BATCH_CODES = 100;
61
- /**
62
- * 历史 K 线 / 财务时间区间上限:上游规定 end - start 不超过 10 年(超出返回 1003)。
63
- * 本地用 3660 天(含闰日冗余)保守前置拦截,只挡明显越界,不与上游口径冲突。
64
- */
42
+ /** Upstream max end - start is 10 years (overrun returns 1003); 3660 days is a conservative pre-check. */
65
43
  const MAX_HISTORY_WINDOW_MS = 3660 * 24 * 60 * 60 * 1000;
66
- /** 递归按键排序的规范化 JSON:语义相同、键序不同的 JSON 参数会归一到同一个 digest。 */
67
44
  function canonicalJson(value) {
68
45
  if (value === null || typeof value !== 'object')
69
46
  return JSON.stringify(value);
@@ -72,11 +49,6 @@ function canonicalJson(value) {
72
49
  const record = value;
73
50
  return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
74
51
  }
75
- /**
76
- * 需要 URL 编码 JSON 传参的端点(backtest 条件、indicators 选择器、quota 分类)。
77
- * 校验只做"能解析 + 顶层形状正确 + 长度受限",不臆造上游的内部字段白名单;
78
- * 规范化后重新序列化,保证等价 JSON 命中同一 Dataset。
79
- */
80
52
  function jsonParam(value, name, kind, maxLength = MAX_PARAM_LENGTH) {
81
53
  const raw = requiredText(value, name, maxLength);
82
54
  let parsed;
@@ -99,7 +71,6 @@ function optionalJsonParam(value, name, kind) {
99
71
  return undefined;
100
72
  return jsonParam(value, name, kind);
101
73
  }
102
- /** 小写枚举的统一规范化:`LIVE` 与 `live` 是同一个请求。 */
103
74
  const lowerCase = (input) => input.toLowerCase();
104
75
  function fail(message) {
105
76
  throw new Error(message);
@@ -112,7 +83,6 @@ function paramsObject(value) {
112
83
  function isRecord(value) {
113
84
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
114
85
  }
115
- /** 未知参数一律拒绝:normalize 会重建参数对象,放行等于静默丢弃模型的拼写错误。 */
116
86
  function assertKnownParameters(values, allowed) {
117
87
  for (const name of Object.keys(values)) {
118
88
  if (!allowed.includes(name))
@@ -171,7 +141,6 @@ function requiredEnum(value, name, allowed, normalize) {
171
141
  fail(`missing required parameter: ${name}`);
172
142
  return enumValue(value, name, allowed, normalize);
173
143
  }
174
- /** `YYYY-MM-DD` 日期(企业行为事件的 from/to):格式与日历有效性都校验。 */
175
144
  function dateString(value, name) {
176
145
  const raw = text(value, name, 32);
177
146
  const matched = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
@@ -191,7 +160,7 @@ function dateString(value, name) {
191
160
  fail(`parameter ${name} is outside the supported date range`);
192
161
  return raw;
193
162
  }
194
- /** 财务指标报告期:`yyyy-1`..`yyyy-4`(1 一季报 / 2 中报 / 3 三季报 / 4 年报)。 */
163
+ /** `yyyy-1`..`yyyy-4` = Q1 / interim / Q3 / annual. */
195
164
  function reportPeriod(value, name) {
196
165
  const raw = text(value, name, 16);
197
166
  const matched = /^(\d{4})-([1-4])$/.exec(raw);
@@ -201,7 +170,6 @@ function reportPeriod(value, name) {
201
170
  fail(`parameter ${name} is outside the supported period range`);
202
171
  return raw;
203
172
  }
204
- /** 规范化单个 A 股代码:trim + 大写 + 后缀校验。 */
205
173
  function codeToken(value, name) {
206
174
  const raw = text(value, name, 32).toUpperCase();
207
175
  if (!A_SHARE_CODE.test(raw)) {
@@ -209,7 +177,6 @@ function codeToken(value, name) {
209
177
  }
210
178
  return raw;
211
179
  }
212
- /** 规范化单个指数代码(允许同花顺 `.TI`)。 */
213
180
  function indexCodeToken(value, name) {
214
181
  const raw = text(value, name, 32).toUpperCase();
215
182
  if (!INDEX_CODE.test(raw)) {
@@ -217,10 +184,7 @@ function indexCodeToken(value, name) {
217
184
  }
218
185
  return raw;
219
186
  }
220
- /**
221
- * 规范化逗号分隔代码列表:去空白、去重、保序;空串与非法代码都拒绝。
222
- * `maximum` 按文档口径在**去重前**校验原始 token 数。
223
- */
187
+ /** `maximum` counts raw tokens before dedupe. */
224
188
  function codeList(value, name, options = {}) {
225
189
  const raw = requiredText(value, name, MAX_PARAM_LENGTH);
226
190
  const tokens = raw.split(',').map((token) => token.trim()).filter((token) => token.length > 0);
@@ -238,7 +202,6 @@ function codeList(value, name, options = {}) {
238
202
  }
239
203
  return unique.join(',');
240
204
  }
241
- /** 逗号分隔的枚举列表:小写、去重、保序;任一非法值整体拒绝。 */
242
205
  function enumList(value, name, allowed) {
243
206
  const raw = requiredText(value, name, MAX_TEXT_LENGTH);
244
207
  const unique = [];
@@ -255,11 +218,7 @@ function enumList(value, name, allowed) {
255
218
  function objectSchema(properties, required = [], description) {
256
219
  return { type: 'object', properties, required, additionalProperties: false, ...(description ? { description } : {}) };
257
220
  }
258
- /**
259
- * 能力执行侧的信封调用:请求与 `code` 判定住在 `fuyao-core.ts`(host 平面的自选股
260
- * 与这里共用一份),本层只把它恢复成 Hub 期望的 `{ data, schema }` 形态。
261
- * 错误文案逐字保持历史形态(2004 / 4001 / 1002 那几条都会透到 Agent 眼前)。
262
- */
221
+ /** Envelope call lives in `fuyao-core.ts`; error texts are passed through verbatim. */
263
222
  async function callFuyao(baseUrl, apiKey, path, params, signal, outputSchema) {
264
223
  const envelope = await fuyaoRequest({ baseUrl, apiKey, path, search: params, signal });
265
224
  return { data: envelope.data ?? null, schema: outputSchema };
@@ -285,15 +244,13 @@ function matchesField(value, spec) {
285
244
  case 'array':
286
245
  return Array.isArray(value);
287
246
  case 'json':
288
- // "保留上游结构"的字段:文档可能写 object、实际返回 array 或 null
289
- // (实测基金诊断的 dimensions/resilience 就是数组)。只拒绝 undefined。
247
+ // Documentation says object but upstream may return an array or null; reject only undefined.
290
248
  return true;
291
249
  }
292
250
  }
293
251
  function matchesFields(record, fields) {
294
252
  return Object.keys(fields).every((name) => matchesField(record[name], fields[name]));
295
253
  }
296
- /** envelope 元数据:字段可缺席或为 null;出现时必须是可用的整数。 */
297
254
  function optionalInteger(value) {
298
255
  if (value === undefined || value === null)
299
256
  return true;
@@ -303,7 +260,6 @@ function optionalInteger(value) {
303
260
  return Number.isSafeInteger(Number(value));
304
261
  return false;
305
262
  }
306
- /** 输出护栏:只拒绝结构不可用的响应,不做业务推断。 */
307
263
  function buildGuard(spec) {
308
264
  return (data) => {
309
265
  if (spec.dataArray) {
@@ -311,8 +267,6 @@ function buildGuard(spec) {
311
267
  return false;
312
268
  return data.every((row) => isRecord(row) && matchesFields(row, spec.dataArray));
313
269
  }
314
- // custom-only:顶层可能是数组(quota/list、indicators/line、indicators/table),
315
- // 由校验器自己判断形状,不能在这里先做 isRecord 拦截。
316
270
  if (spec.custom && !spec.data && !spec.item)
317
271
  return spec.custom(data);
318
272
  if (!isRecord(data))
@@ -333,7 +287,7 @@ function buildGuard(spec) {
333
287
  return true;
334
288
  };
335
289
  }
336
- /** 财务指标:data.abilities[].indicators[],value 是原始数值字符串或 null(不转 number,保留精度)。 */
290
+ /** Value stays a raw numeric string to preserve precision. */
337
291
  function validateAbilities(data) {
338
292
  if (!isRecord(data))
339
293
  return false;
@@ -349,15 +303,12 @@ function validateAbilities(data) {
349
303
  && (indicator.value === null || typeof indicator.value === 'string'));
350
304
  });
351
305
  }
352
- // timestamp 与 total 在无有效数据时可以是 null(文档明确,如基金行情快照);运行时护栏一直放行 null,
353
- // 模型可见 schema 也必须一致,否则 describe_capability 给出的契约是错的。
354
306
  const nullableInteger = { oneOf: [{ type: 'integer' }, { type: 'null' }] };
355
307
  const envelopeData = (item) => ({ type: 'object', properties: { timestamp: nullableInteger, total: nullableInteger, item }, additionalProperties: true });
356
308
  const searchOutput = envelopeData({ type: 'array', items: { type: 'object', properties: { thscode: { type: 'string' }, ticker: { type: 'string' }, name: { type: 'string' }, exchange: { type: 'string' }, asset_type: { type: 'string' }, currency: { type: 'string' } }, additionalProperties: true } });
357
309
  const snapshotOutput = envelopeData({ type: 'array', items: { type: 'object', properties: { thscode: { type: 'string' }, ticker: { type: 'string' }, last_price: { type: 'number' }, price_change: { type: 'number' }, price_change_ratio_pct: { type: 'number' }, open_price: { type: 'number' }, high_price: { type: 'number' }, low_price: { type: 'number' }, prev_price: { type: 'number' }, volume: { type: 'number' }, turnover: { type: 'number' } }, additionalProperties: true } });
358
310
  const klineOutput = envelopeData({ type: 'array', items: { type: 'object', properties: { date_ms: { type: 'integer' }, open_price: { type: 'number' }, high_price: { type: 'number' }, low_price: { type: 'number' }, close_price: { type: 'number' }, volume: { type: 'number' }, turnover: { type: 'number' } }, additionalProperties: true } });
359
311
  const calendarOutput = envelopeData({ type: 'array', items: { type: 'object', properties: { date_ms: { type: 'integer' }, date: { type: 'string' } }, additionalProperties: true } });
360
- /** 行契约 -> 输出 schema 的字段描述(模型可见,只描述字段名与类型)。 */
361
312
  function rowSchema(fields) {
362
313
  const properties = {};
363
314
  for (const [name, spec] of Object.entries(fields)) {
@@ -382,10 +333,8 @@ const indexCatalogFields = { thscode: 'text', name: 'text?' };
382
333
  const indexConstituentFields = { thscode: 'text', ticker: 'text?', name: 'text?' };
383
334
  const indexQuoteFields = { thscode: 'text', ticker: 'text?', last_price: 'number?', price_change: 'number?', price_change_ratio_pct: 'number?', open_price: 'number?', high_price: 'number?', low_price: 'number?', prev_price: 'number?', volume: 'number?', turnover: 'number?' };
384
335
  const indexHistoryFields = { date_ms: 'number', open_price: 'number?', high_price: 'number?', low_price: 'number?', close_price: 'number?', volume: 'number?', turnover: 'number?' };
385
- /** 财务三表共有的期间标识字段(严格)与币种(宽容)。 */
386
336
  const limitDownFields = { thscode: 'text', ticker: 'text?', name: 'text?', last_price: 'number?', price_change_ratio_pct: 'number?', first_limit_time: 'text?', last_limit_time: 'text?', turnover_ratio_pct: 'number?' };
387
337
  const limitBreakFields = { thscode: 'text', ticker: 'text?', name: 'text?', last_price: 'number?', price_change_ratio_pct: 'number?', open_times: 'number?', turnover_ratio_pct: 'number?', turnover: 'number?' };
388
- /** 飙升榜与热股榜共用字段;heat 是上游原始**字符串**(保留精度,不转 number)。 */
389
338
  const rankListFields = { thscode: 'text', ticker: 'text?', name: 'text?', rank: 'number?', heat: 'text?', rank_change: 'number?', rank_trend: 'text?' };
390
339
  const hotStockHistoryFields = { thscode: 'text', ticker: 'text?', name: 'text?', rank: 'number?' };
391
340
  const rankTrendFields = { thscode: 'text', ticker: 'text?', date: 'text', date_ms: 'number?', rank: 'number?' };
@@ -397,10 +346,8 @@ const fundQuoteFields = { thscode: 'text', ticker: 'text?', last_price: 'number?
397
346
  const fundHistoryFields = { date_ms: 'number', open_price: 'number?', high_price: 'number?', low_price: 'number?', close_price: 'number?', volume: 'number?', turnover: 'number?' };
398
347
  const fundNavFields = { nav_date: 'number', unit_nav: 'number?', adj_nav: 'number?' };
399
348
  /**
400
- * 收益率行没有主标识字段,且**任何单个 return_* 都可能缺席**:官方示例就只有 8 个
401
- * (return_month / tmonth / hyear / year / tyear / fyear / nowyear / now,没有 return_week)。
402
- * 因此全部字段设为可选,另用 validateFundReturns 要求"至少命中一个收益/排名族字段",
403
- * 既不放行垃圾结构,也不会误杀官方合法响应。
349
+ * No identity field and any single `return_*` may be absent (official example has 8, no return_week);
350
+ * all fields optional, but a row needs one return/rank field.
404
351
  */
405
352
  const fundReturnsFields = {
406
353
  return_week: 'number?', return_month: 'number?', return_tmonth: 'number?', return_hyear: 'number?',
@@ -413,7 +360,6 @@ const fundReturnsFields = {
413
360
  rank_total_week: 'number?', rank_total_month: 'number?', rank_total_tmonth: 'number?', rank_total_hyear: 'number?',
414
361
  rank_total_year: 'number?', rank_total_twoyear: 'number?', rank_total_tyear: 'number?', rank_total_fyear: 'number?',
415
362
  };
416
- /** 收益/排名族的字段前缀;一行至少命中一个才算可用,其余情况判契约不符。 */
417
363
  const FUND_RETURN_PREFIXES = ['return_', 'peer_average_', 'rank_'];
418
364
  function validateFundReturns(data) {
419
365
  if (!isRecord(data) || !Array.isArray(data.item))
@@ -443,10 +389,9 @@ const fundFinancialIndicatorFields = { ...fundFinancialDateFields, ...fundNumber
443
389
  const fundDividendFields = { per_ten_cash_before_tax: 'number?', per_ten_cash_after_tax: 'number?', progress: 'text?', publish_date_ms: 'number?', registration_date_ms: 'number?', ex_dividend_date_ms: 'number?', payment_date_ms: 'number?', reinvestment_date_ms: 'number?', profit_base_date_ms: 'number?', in_dividend_date_ms: 'number?' };
444
390
  const fundOfferingFields = { thscode: 'text', ticker: 'text?', subscription_start_ms: 'number?', subscription_end_ms: 'number?' };
445
391
  const fundQuotaSummaryFields = { name: 'text', unlimited: 'text?', total_limit: 'text?', total: 'text?', buy: 'text?' };
446
- /** 实测:dimensions/peer_dimensions/resilience/peer_resilience 是**数组**、probabilities/ranges 为 null(文档写 object)。 */
392
+ /** dimensions/peer_* /resilience are arrays and probabilities/ranges null, despite docs saying object. */
447
393
  const fundDiagnosticsFields = { thscode: 'text', ticker: 'text?', fund_type: 'text?', peer_code: 'text?', dimensions: 'json?', peer_dimensions: 'json?', probabilities: 'json?', ranges: 'json?', resilience: 'json?', peer_resilience: 'json?' };
448
394
  const fundBacktestIndicatorFields = { indicator_code: 'text', indicator_name: 'text?', id: 'number?', type: 'number?', value_kind: 'number?', support_operation: 'text?', unit: 'text?', description: 'text?', state_rules: 'text?', create_time: 'text?', update_time: 'text?' };
449
- /** quota/list 是三层嵌套(data[] → sub_tab[] → fund_list[]),没有 item[] 信封。 */
450
395
  function validateQuotaList(data) {
451
396
  if (!Array.isArray(data))
452
397
  return false;
@@ -467,7 +412,6 @@ function validateQuotaList(data) {
467
412
  });
468
413
  });
469
414
  }
470
- /** indicators/line:time_range 时间轴 + indexes 元信息 + data 序列,三层结构。 */
471
415
  function validateIndicatorLine(data) {
472
416
  if (!isRecord(data))
473
417
  return false;
@@ -485,7 +429,6 @@ function validateIndicatorLine(data) {
485
429
  return series.values.every((value) => isRecord(value) && Number.isSafeInteger(value.idx));
486
430
  });
487
431
  }
488
- /** indicators/table:total + indexes + data[],值可为 JSON 或 null。 */
489
432
  function validateIndicatorTable(data) {
490
433
  if (!isRecord(data))
491
434
  return false;
@@ -516,7 +459,7 @@ function normalizeQuote(params) {
516
459
  const normalized = {};
517
460
  if (params.thscodes !== undefined) {
518
461
  normalized.thscodes = codeList(params.thscodes, 'thscodes');
519
- // 按文档:传入 thscodes 时上游忽略分页;这里直接丢弃,避免产生语义等价的多个 Dataset。
462
+ // Upstream ignores paging with thscodes; drop it so equivalent requests share one Dataset.
520
463
  return normalized;
521
464
  }
522
465
  if (params.limit !== undefined)
@@ -525,7 +468,6 @@ function normalizeQuote(params) {
525
468
  normalized.offset = integer(params.offset, 'offset', { minimum: 0 });
526
469
  return normalized;
527
470
  }
528
- /** 时间区间型请求的共用校验:成对出现、end >= start、窗口不超过 10 年。 */
529
471
  function timeWindow(params, normalized) {
530
472
  const { start, end } = params;
531
473
  if ((start === undefined) !== (end === undefined))
@@ -592,7 +534,6 @@ function normalizeCorporateActions(params) {
592
534
  normalized.to = to;
593
535
  return normalized;
594
536
  }
595
- /** 财务三表共用参数:最近 N 期(limit)与时间区间(start+end)互斥、二选一。 */
596
537
  function normalizeFinancial(params) {
597
538
  const normalized = {
598
539
  thscode: codeToken(requiredText(params.thscode, 'thscode', 32), 'thscode'),
@@ -648,7 +589,6 @@ function normalizeIndexConstituents(params) {
648
589
  function normalizeIndexQuote(params) {
649
590
  return { thscodes: codeList(params.thscodes, 'thscodes', { kind: 'index' }) };
650
591
  }
651
- /** 基金代码规范化:trim + 大写 + 市场后缀校验(不接受逗号与裸代码)。 */
652
592
  function fundCodeToken(value, name) {
653
593
  const raw = text(value, name, 32).toUpperCase();
654
594
  if (!FUND_CODE.test(raw)) {
@@ -656,11 +596,9 @@ function fundCodeToken(value, name) {
656
596
  }
657
597
  return raw;
658
598
  }
659
- /** 基金经理 / 基金公司标识:格式上游未公开,只做非空与长度约束。 */
660
599
  function identifierToken(value, name) {
661
600
  return requiredText(value, name, 64);
662
601
  }
663
- /** 只带 thscode 的基金端点(基本资料、业绩快照、持仓、持有人等)。 */
664
602
  function onlyFundCode(params) {
665
603
  return { thscode: fundCodeToken(requiredText(params.thscode, 'thscode', 32), 'thscode') };
666
604
  }
@@ -689,8 +627,8 @@ function normalizeFundNav(params) {
689
627
  function normalizeFundStockHistory(params) {
690
628
  return {
691
629
  thscode: fundCodeToken(requiredText(params.thscode, 'thscode', 32), 'thscode'),
692
- // report_type 的枚举上游未公开(实测 quarter 可用;annual/half 返回 5003 即上游无该类型数据,
693
- // 而不是参数错误),因此本地不做白名单,只拒绝空值与明显非法字符,避免误拒上游新增类型。
630
+ // report_type enum is undocumented: quarter works; annual/half return 5003 (no such data, not a
631
+ // bad parameter), so do not whitelist.
694
632
  report_type: text(requiredText(params.report_type, 'report_type', 32), 'report_type'),
695
633
  end_date: dateString(requiredText(params.end_date, 'end_date', 32), 'end_date'),
696
634
  };
@@ -713,7 +651,6 @@ function normalizeFundManager(params) {
713
651
  function normalizeFundCompany(params) {
714
652
  return { company_id: identifierToken(params.company_id, 'company_id') };
715
653
  }
716
- /** 报告期查询(bond-history 与 stock-history 同构)。 */
717
654
  function normalizeFundReportType(params) {
718
655
  const normalized = { thscode: fundCodeToken(requiredText(params.thscode, 'thscode', 32), 'thscode') };
719
656
  if (params.report_type !== undefined)
@@ -736,7 +673,6 @@ function normalizeManagerRange(params) {
736
673
  range: requiredEnum(params.range, 'range', MANAGER_RANGES, lowerCase),
737
674
  };
738
675
  }
739
- /** 指标画线:indexes 为分组数组,time_range 为时间范围对象(都是 JSON 字符串)。 */
740
676
  function normalizeFundIndicatorLine(params) {
741
677
  const indexes = jsonParam(params.indexes, 'indexes', 'array');
742
678
  const groups = JSON.parse(indexes);
@@ -825,7 +761,6 @@ function normalizeLimitDownPool(params) {
825
761
  function normalizeLimitBreakPool(params) {
826
762
  return normalizePoolQuery(params, LIMIT_BREAK_SORT_FIELDS);
827
763
  }
828
- /** 飙升榜 / 热股榜:period 取 day(日/24 小时级)或 hour(小时级)。 */
829
764
  function normalizeRankList(params) {
830
765
  const normalized = {};
831
766
  if (params.period !== undefined)
@@ -846,7 +781,7 @@ function normalizeHotStockRankTrend(params) {
846
781
  end_date: endDate,
847
782
  };
848
783
  }
849
- /** 异动标签:大小写不敏感、去重保序;空 token 与未知标签都拒绝(上游对二者都返回 1002)。 */
784
+ /** Empty or unknown tags return 1002. */
850
785
  function anomalyTagList(value, name) {
851
786
  const raw = requiredText(value, name, MAX_TEXT_LENGTH);
852
787
  const tokens = raw.split(',').map((token) => token.trim().toUpperCase());
@@ -962,7 +897,6 @@ function endpointDefinitions() {
962
897
  normalize: () => ({}),
963
898
  guard: { item: { date_ms: 'number', date: 'text' } },
964
899
  },
965
- // ── Phase 2:代码表与除复权事件 ──────────────────────────────────────────
966
900
  {
967
901
  name: 'get_meta_tickers_list',
968
902
  capability: 'ticker_list',
@@ -1007,7 +941,6 @@ function endpointDefinitions() {
1007
941
  item: { ticker: 'text?', ex_date_ms: 'number', dividend_per_share: 'number?', per_share_bonus: 'number?' },
1008
942
  },
1009
943
  },
1010
- // ── Phase 2:财务三表与财务指标 ──────────────────────────────────────────
1011
944
  {
1012
945
  name: 'get_a_share_financials_income_statements',
1013
946
  capability: 'income_statement',
@@ -1098,7 +1031,6 @@ function endpointDefinitions() {
1098
1031
  normalize: normalizeFinancialIndicators,
1099
1032
  guard: { data: { thscode: 'text', report: 'text' }, custom: validateAbilities },
1100
1033
  },
1101
- // ── Phase 2:估值与集合竞价 ─────────────────────────────────────────────
1102
1034
  {
1103
1035
  name: 'get_a_share_valuations_snapshot',
1104
1036
  capability: 'valuation',
@@ -1130,7 +1062,6 @@ function endpointDefinitions() {
1130
1062
  normalize: normalizeAuction,
1131
1063
  guard: { data: { auction_phase: 'text?', data_status: 'text?' }, item: auctionFields },
1132
1064
  },
1133
- // ── Phase 2:盘面特色数据 ───────────────────────────────────────────────
1134
1065
  {
1135
1066
  name: 'get_a_share_special_data_limit_up_pool',
1136
1067
  capability: 'limit_up_pool',
@@ -1177,7 +1108,6 @@ function endpointDefinitions() {
1177
1108
  normalize: () => ({}),
1178
1109
  guard: { data: { window: 'object' }, item: ladderFields },
1179
1110
  },
1180
- // ── Phase 2:指数 ───────────────────────────────────────────────────────
1181
1111
  {
1182
1112
  name: 'get_a_share_index_catalog_ths_index_list',
1183
1113
  capability: 'index_catalog',
@@ -1238,7 +1168,6 @@ function endpointDefinitions() {
1238
1168
  normalize: normalizeIndexHistory,
1239
1169
  guard: { item: indexHistoryFields },
1240
1170
  },
1241
- // ── Phase 3:盘面特色数据(doc 详情段 + 真实探针确认可用)────────────────
1242
1171
  {
1243
1172
  name: 'get_a_share_special_data_limit_down_pool',
1244
1173
  capability: 'limit_down_pool',
@@ -1414,7 +1343,6 @@ function endpointDefinitions() {
1414
1343
  item: dragonTigerStockFields,
1415
1344
  },
1416
1345
  },
1417
- // ── Phase 3:短线风向标竞价基准 ─────────────────────────────────────────
1418
1346
  {
1419
1347
  name: 'get_a_share_auction_short_term_benchmark',
1420
1348
  capability: 'auction_benchmark',
@@ -1434,11 +1362,7 @@ function endpointDefinitions() {
1434
1362
  },
1435
1363
  guard: { data: { date: 'text', date_ms: 'number' }, item: auctionBenchmarkFields },
1436
1364
  },
1437
- // ── Phase 3B:公募基金(14 个核心端点;契约 = 文档详情段 + 真实探针)────────
1438
- // 定位规则:这些端点全部只吃**单数 thscode**(基金代码带 .OF/.SH/.SZ 后缀),
1439
- // 经理与公司分别用 manager_id / company_id;ths_api.md 里的 fund_type(otc/exchange/reits)
1440
- // 实测并不存在(上游会忽略未知参数,我们不忽略)。金额/份额单位多处文档未给出,
1441
- // 一律按原值转述、不擅自换算。
1365
+ // Fund endpoints take a singular thscode; `fund_type` does not exist; amount units are undocumented.
1442
1366
  {
1443
1367
  name: 'get_fund_profile_detail',
1444
1368
  capability: 'fund_profile',
@@ -1659,9 +1583,8 @@ function endpointDefinitions() {
1659
1583
  normalize: normalizeFundCompany,
1660
1584
  guard: { item: fundCompanyFields },
1661
1585
  },
1662
- // ── Phase 3C:基金进阶子模块(19 个端点;已确证全部开放、非 AI 客户端专用)──
1663
- // 口径提醒:基金各页**金额币种单位文档未给出**(对比 A 股财务页明写"原币元"),
1664
- // 一律按原值转述;多处百分比为"百分数原值",但回测 metrics 口径文档自相矛盾。
1586
+ // Fund amount/currency units are undocumented (A-share statements say 原币元, funds do not):
1587
+ // report raw values; percentages are raw but the backtest metrics docs contradict themselves.
1665
1588
  {
1666
1589
  name: 'get_fund_performance_indicators_historical',
1667
1590
  capability: 'fund_performance_history',
@@ -2008,20 +1931,18 @@ function createSource(definition, baseUrl, resolveApiKey) {
2008
1931
  time_contract: getDataTimeContract(capability),
2009
1932
  name,
2010
1933
  source: 'api:fuyao',
2011
- // 内部数据源身份证(provider.kind.resource):宿主路由/审计用,不进入模型协议。
2012
1934
  data_key: buildDataKey('fuyao', 'api', path),
2013
1935
  source_label: 'fuyao',
2014
1936
  paginated: definition.paginated === true,
2015
1937
  cacheMaxAgeMs: definition.cacheMaxAgeMs,
2016
- // 行数组位置由端点自己声明(护栏里的 itemKey 就是同一份知识):龙虎榜的行
2017
- // 在 `stock_items` 下,不声明就会被存储层按 `item` 猜成「非行集合」。
1938
+ // The endpoint declares its row array; dragon-tiger rows are under `stock_items`, otherwise
1939
+ // storage guesses `item` and sees no row collection.
2018
1940
  rowShape: { rowKey: definition.guard.itemKey ?? 'item', ...(definition.guard.rowKeys === undefined ? {} : { rowKeys: definition.guard.rowKeys }) },
2019
1941
  summary,
2020
1942
  description,
2021
1943
  input_schema: inputSchema,
2022
1944
  output_schema: outputSchema,
2023
1945
  };
2024
- /** 幂等:Hub 入队前调用一次(digest 口径),execute 再调用一次(直连调用也要生效)。 */
2025
1946
  const normalize = (params) => {
2026
1947
  const values = paramsObject(params);
2027
1948
  assertKnownParameters(values, allowed);