@v587d/capital-generation 2.8.2 → 2.9.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/CHANGELOG.md +87 -1
- package/README.md +41 -20
- package/chart-ui/README.md +1 -1
- package/chart-ui/client.src.cjs +7 -16
- package/chart-ui/index.js +12 -33
- package/chart-ui/package.json +2 -2
- package/config/client.js +18 -39
- package/config/client.src.cjs +79 -217
- package/config/index.js +22 -72
- package/config/package.json +2 -2
- package/cordis.patch.yml +46 -51
- package/lib/agents/bash-guard.d.ts +9 -55
- package/lib/agents/bash-guard.js +27 -67
- package/lib/agents/preset-roster.d.ts +22 -0
- package/lib/agents/preset-roster.js +87 -0
- package/lib/agents/role-tool-policy.d.ts +46 -0
- package/lib/agents/role-tool-policy.js +205 -0
- package/lib/agents/root-tool-policy.d.ts +28 -124
- package/lib/agents/root-tool-policy.js +164 -237
- package/lib/agents/selected-skills-provider.d.ts +2 -6
- package/lib/agents/selected-skills-provider.js +27 -84
- package/lib/capital-settings.d.ts +27 -0
- package/lib/capital-settings.js +23 -0
- package/lib/chart/artifact-ref.d.ts +3 -8
- package/lib/chart/artifact-ref.js +3 -8
- package/lib/chart/downsample.d.ts +2 -5
- package/lib/chart/downsample.js +3 -8
- package/lib/chart/errors.d.ts +3 -7
- package/lib/chart/errors.js +1 -2
- package/lib/chart/event-context.d.ts +8 -0
- package/lib/chart/event-context.js +39 -0
- package/lib/chart/events.d.ts +21 -78
- package/lib/chart/events.js +40 -189
- package/lib/chart/html.d.ts +2 -5
- package/lib/chart/html.js +8 -15
- package/lib/chart/runtime.d.ts +5 -18
- package/lib/chart/runtime.js +14 -34
- package/lib/chart/series.d.ts +10 -17
- package/lib/chart/series.js +22 -47
- package/lib/chart/source-token.d.ts +5 -13
- package/lib/chart/source-token.js +18 -17
- package/lib/chart/spec.d.ts +6 -12
- package/lib/chart/spec.js +7 -31
- package/lib/chart/tool.d.ts +6 -29
- package/lib/chart/tool.js +9 -23
- package/lib/chart-render-entry.d.ts +9 -0
- package/lib/chart-render-entry.js +17 -0
- package/lib/chart-source-entry.d.ts +13 -0
- package/lib/chart-source-entry.js +20 -0
- package/lib/collect-entry.d.ts +9 -0
- package/lib/collect-entry.js +10 -0
- package/lib/collector-runtime.d.ts +22 -0
- package/lib/collector-runtime.js +97 -0
- package/lib/data-collector/args.d.ts +6 -24
- package/lib/data-collector/args.js +6 -24
- package/lib/data-collector/dataset-tools.d.ts +7 -1
- package/lib/data-collector/dataset-tools.js +27 -56
- package/lib/data-collector/describe.d.ts +8 -33
- package/lib/data-collector/describe.js +17 -70
- package/lib/data-collector/hub.d.ts +27 -85
- package/lib/data-collector/hub.js +19 -85
- package/lib/data-collector/query-time.d.ts +7 -26
- package/lib/data-collector/query-time.js +8 -27
- package/lib/data-collector/store.d.ts +40 -117
- package/lib/data-collector/store.js +32 -111
- package/lib/data-collector/time-axis.d.ts +16 -76
- package/lib/data-collector/time-axis.js +25 -111
- package/lib/data-collector/tools.d.ts +6 -6
- package/lib/data-collector/tools.js +34 -43
- package/lib/dataset-read-entry.d.ts +9 -0
- package/lib/dataset-read-entry.js +7 -0
- package/lib/index.d.ts +7 -117
- package/lib/index.js +43 -422
- package/lib/net/eastmoney-client.d.ts +13 -65
- package/lib/net/eastmoney-client.js +12 -53
- package/lib/net/throttle.d.ts +2 -13
- package/lib/net/throttle.js +3 -14
- package/lib/ocr/client.d.ts +12 -21
- package/lib/ocr/client.js +23 -40
- package/lib/ocr/markdown.d.ts +20 -26
- package/lib/ocr/markdown.js +23 -36
- package/lib/ocr/store.d.ts +14 -47
- package/lib/ocr/store.js +15 -45
- package/lib/ocr-entry.d.ts +9 -0
- package/lib/ocr-entry.js +10 -0
- package/lib/policy-entry.d.ts +17 -0
- package/lib/policy-entry.js +22 -0
- package/lib/preset-claim.d.ts +22 -0
- package/lib/preset-claim.js +24 -0
- package/lib/preset-ids.d.ts +38 -0
- package/lib/preset-ids.js +46 -0
- package/lib/retriever-config.d.ts +37 -0
- package/lib/retriever-config.js +36 -0
- package/lib/retriever-runtime.d.ts +31 -0
- package/lib/retriever-runtime.js +82 -0
- package/lib/root-extras-entry.d.ts +17 -0
- package/lib/root-extras-entry.js +25 -0
- package/lib/runtime-entry.d.ts +15 -0
- package/lib/runtime-entry.js +5 -0
- package/lib/runtime.d.ts +21 -0
- package/lib/runtime.js +33 -0
- package/lib/search-entry.d.ts +9 -0
- package/lib/search-entry.js +9 -0
- package/lib/sources/eastmoney-http.js +20 -68
- package/lib/sources/ftshare-rest.d.ts +6 -31
- package/lib/sources/ftshare-rest.js +1194 -225
- package/lib/sources/fuyao-core.d.ts +8 -18
- package/lib/sources/fuyao-core.js +10 -21
- package/lib/sources/fuyao-rest.d.ts +2 -6
- package/lib/sources/fuyao-rest.js +23 -102
- package/lib/sources/security-code.d.ts +6 -12
- package/lib/sources/security-code.js +6 -12
- package/lib/sources/security-identity.d.ts +1 -4
- package/lib/sources/security-identity.js +3 -8
- package/lib/sources/tencent-http.js +14 -28
- package/lib/sources/tencent-public-core.d.ts +8 -60
- package/lib/sources/tencent-public-core.js +12 -77
- package/lib/sources/wind-mcp.d.ts +0 -5
- package/lib/sources/wind-mcp.js +23 -78
- package/lib/sources-entry.d.ts +9 -0
- package/lib/sources-entry.js +9 -0
- package/lib/time/tools.d.ts +8 -31
- package/lib/time/tools.js +39 -64
- package/lib/time-entry.d.ts +9 -0
- package/lib/time-entry.js +7 -0
- package/lib/tool-exec.d.ts +45 -11
- package/lib/tool-exec.js +59 -6
- package/lib/user-customization.d.ts +14 -0
- package/lib/user-customization.js +47 -0
- package/lib/watchlist/tools.d.ts +3 -13
- package/lib/watchlist/tools.js +10 -30
- package/lib/watchlist-entry.d.ts +9 -0
- package/lib/watchlist-entry.js +6 -0
- package/lib/web-retriever/engines.d.ts +6 -11
- package/lib/web-retriever/engines.js +9 -14
- package/lib/web-retriever/html-markdown.d.ts +14 -34
- package/lib/web-retriever/html-markdown.js +39 -75
- package/lib/web-retriever/local-fetch.d.ts +22 -58
- package/lib/web-retriever/local-fetch.js +33 -77
- package/lib/web-retriever/retriever.d.ts +6 -18
- package/lib/web-retriever/retriever.js +7 -7
- package/lib/web-retriever/sources.d.ts +37 -124
- package/lib/web-retriever/sources.js +64 -196
- package/lib/web-retriever/tools.d.ts +43 -27
- package/lib/web-retriever/tools.js +99 -137
- package/lib/web-retriever/wind-client.d.ts +14 -31
- package/lib/web-retriever/wind-client.js +16 -32
- package/locale/zh.json +1 -1
- package/package.json +67 -7
- package/preset/capital-generation/README.md +87 -53
- package/preset/capital-generation/agent.patch.yml +526 -337
- package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
- package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
- package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
- package/watchlist/client.js +15 -148
- package/watchlist/client.src.cjs +148 -662
- package/watchlist/holding-rules.js +11 -38
- package/watchlist/index.js +127 -397
- package/watchlist/package.json +2 -2
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { type QueryResult, type QuerySpec } from './query.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* 原始 Dataset 只由宿主写入,Dataset/Profile 都通过 createIfAbsent 发布。
|
|
6
|
-
* Agent 只能使用 dataset_id 和 opaque artifact_ref,不能传入真实路径。
|
|
3
|
+
* Workspace-local Dataset/profile store: immutable artifacts, host-written only. Agents receive
|
|
4
|
+
* dataset_id / opaque artifact_ref, never real paths; raw rows never enter agent messages.
|
|
7
5
|
*/
|
|
8
6
|
export declare const WORKSPACE_DATA_DIR = "capital-data";
|
|
9
7
|
export declare const DATASETS_SUBDIR = "capital-data/datasets";
|
|
@@ -14,23 +12,17 @@ export declare const PROFILE_FILE = "profile.json";
|
|
|
14
12
|
export declare const DEFAULT_RETENTION_MS: number;
|
|
15
13
|
export declare const MAX_RAW_READ_BYTES: number;
|
|
16
14
|
export declare const MAX_PROFILE_BYTES: number;
|
|
17
|
-
/**
|
|
15
|
+
/** Max distinct category values tracked; beyond this distinct_count is null and truncated is set. */
|
|
18
16
|
export declare const MAX_CATEGORY_DISTINCT = 2000;
|
|
19
|
-
/** `top_values` 只展示前 N 个高频取值,其余由 truncated 显式披露。 */
|
|
20
17
|
export declare const MAX_CATEGORY_TOP_VALUES = 5;
|
|
21
|
-
/**
|
|
18
|
+
/** Structure-digest caps that keep profile.json bounded. */
|
|
22
19
|
export declare const MAX_STRUCTURE_ENTRIES = 24;
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* 超过就会被剪掉中间段,而不是被完整读到。长数组按下面的档位逐级截断,截断了什么
|
|
27
|
-
* 一律写进 `omitted`,不做静默省略。
|
|
21
|
+
* Document content budget in code points; must stay under the preset's 8192 prune threshold.
|
|
22
|
+
* Truncated arrays are reported in `omitted`, never silently dropped.
|
|
28
23
|
*/
|
|
29
24
|
export declare const MAX_DOCUMENT_CHARS = 6000;
|
|
30
|
-
/**
|
|
31
|
-
* 呈现层(图表)一次性读取行集的上限。超过就拒绝,而不是把几百 MB 拉进宿主内存:
|
|
32
|
-
* 图表是**有界呈现**,不是通用分析通道。
|
|
33
|
-
*/
|
|
25
|
+
/** Presentation rows are capped: charts are bounded rendering, not a general analysis channel. */
|
|
34
26
|
export declare const MAX_PRESENTATION_ROWS = 200000;
|
|
35
27
|
export declare const ARTIFACT_SCHEME = "workspace://";
|
|
36
28
|
export interface DatasetRef {
|
|
@@ -50,7 +42,7 @@ export interface DatasetRef {
|
|
|
50
42
|
export type DatasetShape = 'array' | 'envelope_item' | 'document' | 'none';
|
|
51
43
|
export interface DatasetInspection extends DatasetRef {
|
|
52
44
|
query_access: {
|
|
53
|
-
/** true =
|
|
45
|
+
/** true = host can read it and produce facts (rows queryable; documents profile-only). */
|
|
54
46
|
readable: boolean;
|
|
55
47
|
shape: DatasetShape;
|
|
56
48
|
reason?: string;
|
|
@@ -94,16 +86,9 @@ export interface ProfileValidation {
|
|
|
94
86
|
violations: ProfileValidationViolation[];
|
|
95
87
|
}
|
|
96
88
|
/**
|
|
97
|
-
* Profile
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* Fuyao 数据里最常见的四类问题:
|
|
101
|
-
* - `statistics` 加 `count` / `sum`:回答「合计多少、覆盖多少条」;
|
|
102
|
-
* - `categories`:字符串列的 distinct 与高频取值,回答「有哪些类别、分布如何」;
|
|
103
|
-
* - `time_facts`:时间列的覆盖范围 + 首行/末行的数值,回答「最新值、区间涨跌」——
|
|
104
|
-
* 只报 min/max 会被模型讲成「先涨到最高再回落」,这是实测过的失真来源;
|
|
105
|
-
* - `structure`:单元格里的数组/对象展开成有界路径摘要,回答「里面有几层、
|
|
106
|
-
* 多少个叶子」,避免只报一句「这一格是 object」。
|
|
89
|
+
* Profile turns raw rows into facts the model can safely cite: per-column type/missing counts,
|
|
90
|
+
* statistics, category values, time facts and bounded structure digests. Time facts carry
|
|
91
|
+
* first/last row values because min/max alone get narrated as a trend.
|
|
107
92
|
*/
|
|
108
93
|
export interface ProfileStatistic {
|
|
109
94
|
count?: number;
|
|
@@ -120,58 +105,41 @@ export interface ProfileCategoryValue {
|
|
|
120
105
|
count: number;
|
|
121
106
|
}
|
|
122
107
|
export interface ProfileCategory {
|
|
123
|
-
/** 精确去重数;超过枚举上限时为 null(此时 truncated=true)。 */
|
|
124
108
|
distinct_count: number | null;
|
|
125
109
|
top_values: ProfileCategoryValue[];
|
|
126
|
-
/** true = 还有未展示的取值,`top_values` 不是全部。 */
|
|
127
110
|
truncated: boolean;
|
|
128
111
|
}
|
|
129
112
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* 存在的理由:`date_ms` 这类列的取值是 epoch 毫秒,模型既无法直接读出"这是哪一天",
|
|
133
|
-
* 也无法把"近 1 个月"翻译回毫秒;没有这一块事实,它只能自己推算——实测单步
|
|
134
|
-
* 41,961 字符的思考里绝大部分是时间戳算术(真机会话 1e44050e)。
|
|
135
|
-
* 块内只放**观测到的**事实,不做猜测:推断不出就报 `unknown`。
|
|
113
|
+
* Time-axis semantics: value format plus the offset observed in the column itself. Never guessed;
|
|
114
|
+
* an undecidable axis is reported as `unknown`.
|
|
136
115
|
*/
|
|
137
116
|
export interface ProfileTimeAxis {
|
|
138
117
|
column: string;
|
|
139
118
|
value_format: 'epoch_ms' | 'date_string' | 'unknown';
|
|
140
|
-
/** 该列的取值按哪个时区解释(epoch_ms 且能推断时才有)。 */
|
|
141
119
|
time_zone?: string;
|
|
142
|
-
/** 上面的时区的 `+08:00` 形态;`time_zone` 已是 `UTC+08:00` 时不重复(避免同一事实写两遍)。 */
|
|
143
120
|
utc_offset?: string;
|
|
144
|
-
/** 取值对齐到当地午夜、日历日,还是无法判断。 */
|
|
145
121
|
aligned_to?: 'local_midnight' | 'calendar_day' | 'unknown';
|
|
146
122
|
}
|
|
147
|
-
/**
|
|
123
|
+
/** A ready-to-use window: calendar range plus the real query bounds for this column. */
|
|
148
124
|
export interface ProfileTimeWindow {
|
|
149
125
|
name: string;
|
|
150
126
|
start_date: string;
|
|
151
127
|
end_date: string;
|
|
152
|
-
/** `>= value_ge` 即"从这个窗口的第一天开始"(epoch 轴为当地午夜)。 */
|
|
153
128
|
value_ge: number | string;
|
|
154
|
-
/** `<= value_le` 即"到这个窗口的最后一天结束"。 */
|
|
155
129
|
value_le: number | string;
|
|
156
|
-
/**
|
|
130
|
+
/** Real first/last date present in the Dataset; a non-trading start moves to the next session. */
|
|
157
131
|
data_from: string | null;
|
|
158
132
|
data_to: string | null;
|
|
159
133
|
}
|
|
160
134
|
export interface ProfileTimeFacts {
|
|
161
135
|
time_column: string;
|
|
162
|
-
/** 文件顺序是否随时间递增;null = 时间列有缺失,无法判断。 */
|
|
163
136
|
ordered_ascending: boolean | null;
|
|
164
|
-
/** 时间列的最小/最大值(覆盖范围),原值不改写。 */
|
|
165
137
|
covered_from: unknown;
|
|
166
138
|
covered_to: unknown;
|
|
167
|
-
/** 覆盖范围的人类可读日期;只对 epoch_ms 轴出现(date_string 轴本身可读)。 */
|
|
168
139
|
covered_from_iso?: string;
|
|
169
140
|
covered_to_iso?: string;
|
|
170
|
-
/** 时间轴语义:形态与时区偏移。 */
|
|
171
141
|
axis: ProfileTimeAxis;
|
|
172
|
-
/** 常用窗口(近 1 月 / 近 3 月 / 近 1 年 / 年初至今)的现成边界。 */
|
|
173
142
|
windows?: ProfileTimeWindow[];
|
|
174
|
-
/** 首行与末行的「时间列 + 数值列」取值(文件顺序),用于首末值与区间变化。 */
|
|
175
143
|
first: Record<string, unknown>;
|
|
176
144
|
last: Record<string, unknown>;
|
|
177
145
|
}
|
|
@@ -181,17 +149,13 @@ export interface ProfileStructureField {
|
|
|
181
149
|
}
|
|
182
150
|
export interface ProfileStructure {
|
|
183
151
|
type: 'array' | 'object';
|
|
184
|
-
/** 该路径在数据中出现的次数(行内多次出现会累加)。 */
|
|
185
152
|
occurrences: number;
|
|
186
|
-
/** 未抽样时,所有出现位置的元素总数;对象路径不返回该字段。 */
|
|
187
153
|
total_elements?: number;
|
|
188
|
-
/**
|
|
154
|
+
/** Present only when sampling capped the scan; it is not the complete count. */
|
|
189
155
|
sampled_elements?: number;
|
|
190
|
-
/** 数组:空数组的出现次数与长度极值。 */
|
|
191
156
|
empty_count: number;
|
|
192
157
|
min_length: number;
|
|
193
158
|
max_length: number;
|
|
194
|
-
/** 对象:直接字段名与观察到的类型(有界)。 */
|
|
195
159
|
fields: ProfileStructureField[];
|
|
196
160
|
}
|
|
197
161
|
export interface ProfilePayload {
|
|
@@ -254,8 +218,8 @@ export interface FsLike {
|
|
|
254
218
|
}, signal?: AbortSignal, sandboxPolicy?: SandboxPolicyResultLike): Promise<unknown>;
|
|
255
219
|
readText(target: FsTargetLike, signal?: AbortSignal): Promise<string>;
|
|
256
220
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
221
|
+
* Raw bytes for external parsers (local PDF/images). The host enforces maxBytes on this seam:
|
|
222
|
+
* overflow throws FS_TOO_LARGE and never returns truncated content.
|
|
259
223
|
*/
|
|
260
224
|
readBytes?(target: FsTargetLike, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
|
|
261
225
|
listDir(target: FsTargetLike, signal?: AbortSignal): Promise<Array<{
|
|
@@ -278,18 +242,12 @@ export interface QueryDatasetInput {
|
|
|
278
242
|
query: QuerySpec;
|
|
279
243
|
signal?: AbortSignal;
|
|
280
244
|
}
|
|
281
|
-
/**
|
|
282
|
-
* 时间轴探针结果:形态 + 已出现的日期 + 列清单。
|
|
283
|
-
*
|
|
284
|
-
* 三条消费路径共用同一份事实,避免"谁猜时间列"出现第二套判据:
|
|
285
|
-
* ① `query_dataset` 的日期筛选归一;② `resolve_data_time_range` 的数据集模式;
|
|
286
|
-
* ③ 报错信息里列出可选列。
|
|
287
|
-
*/
|
|
245
|
+
/** Time-axis probe shared by date normalization, dataset time windows and error hints. */
|
|
288
246
|
export interface TimeAxisSnapshot {
|
|
289
247
|
dataset_id: string;
|
|
290
248
|
columns: string[];
|
|
291
249
|
axis: ProfileTimeAxis;
|
|
292
|
-
/**
|
|
250
|
+
/** Probe dates (ascending, deduplicated); used to clamp windows to real trading days. */
|
|
293
251
|
dates: string[];
|
|
294
252
|
}
|
|
295
253
|
export interface ProfileDatasetResult extends ProfileRef {
|
|
@@ -303,14 +261,13 @@ export interface ProfileDatasetResult extends ProfileRef {
|
|
|
303
261
|
schema?: ProfilePayload['schema'];
|
|
304
262
|
validation?: ProfilePayload['validation'];
|
|
305
263
|
warnings: string[];
|
|
306
|
-
/**
|
|
264
|
+
/** Document Datasets only: bounded content, never persisted with the profile. */
|
|
307
265
|
document?: ProfileDocument;
|
|
308
266
|
}
|
|
309
267
|
export interface ProfileDocument {
|
|
310
|
-
/** 有界后的文档内容;连最小档位都放不下时为 null。 */
|
|
311
268
|
content: unknown;
|
|
312
269
|
truncated: boolean;
|
|
313
|
-
/**
|
|
270
|
+
/** Every truncation is disclosed here; no silent omission. */
|
|
314
271
|
omitted: Array<{
|
|
315
272
|
path: string;
|
|
316
273
|
kept: number;
|
|
@@ -333,7 +290,7 @@ export interface SaveDatasetInput {
|
|
|
333
290
|
schema: object | null;
|
|
334
291
|
row_count: number | null;
|
|
335
292
|
data: unknown;
|
|
336
|
-
/** Host-only row
|
|
293
|
+
/** Host-only row key for envelope-shaped json_rows data. */
|
|
337
294
|
row_key?: string;
|
|
338
295
|
signal?: AbortSignal;
|
|
339
296
|
}
|
|
@@ -353,11 +310,8 @@ export interface WorkspaceDatasetStoreOptions {
|
|
|
353
310
|
}
|
|
354
311
|
export declare const DATASET_ID_PATTERN: RegExp;
|
|
355
312
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* 拒绝:空串、绝对路径(含 Windows 盘符)、任何 `..` 段、反斜杠(本仓统一 `/`)。
|
|
359
|
-
* 这里只做形态校验;真正的沙箱归属由 `resolveContained` 证明。两道关卡都要过——
|
|
360
|
-
* 形态校验是为了给出可读错误,归属校验才是安全边界。
|
|
313
|
+
* Validate a workspace-relative path shape. Containment is proven separately by resolveContained;
|
|
314
|
+
* both checks are required, and this one only makes errors readable.
|
|
361
315
|
*/
|
|
362
316
|
export declare function normalizeWorkspaceRelativePath(value: string): string;
|
|
363
317
|
export declare class WorkspaceDatasetStore {
|
|
@@ -375,11 +329,8 @@ export declare class WorkspaceDatasetStore {
|
|
|
375
329
|
profileDataset(input: ProfileDatasetInput): Promise<ProfileDatasetResult>;
|
|
376
330
|
queryDataset(input: QueryDatasetInput): Promise<QueryResult>;
|
|
377
331
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* 只读前 {@link MAX_AXIS_PROBE_ROWS} 行推断形态与偏移:时间列一定在每行都出现,日线数据
|
|
381
|
-
* 的有序性也让前若干行足以定出粒度;这样这个探针**可以随查询逐次调用**,
|
|
382
|
-
* 代价与"读一份 16MB raw.json 只为一个问题"不同量级。
|
|
332
|
+
* Read-only time-axis probe shared by the date normalizer and dataset time-window mode.
|
|
333
|
+
* Samples at most MAX_AXIS_PROBE_ROWS rows, so it is cheap enough to call per query.
|
|
383
334
|
*/
|
|
384
335
|
describeTimeAxis(input: {
|
|
385
336
|
session: SessionLike;
|
|
@@ -389,15 +340,9 @@ export declare class WorkspaceDatasetStore {
|
|
|
389
340
|
}): Promise<TimeAxisSnapshot | undefined>;
|
|
390
341
|
writeProfile(input: WriteProfileInput): Promise<ProfileRef>;
|
|
391
342
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* - 返回值**不得**进入任何 Agent 消息、工具结果或模型上下文;调用方只能在宿主进程内
|
|
396
|
-
* 把它变成有界可视化载荷(见 src/chart/series.ts)。
|
|
397
|
-
* - 这是唯一一处把原始 rows 交给 store 之外代码的入口,因此命名、文档与调用点都必须是
|
|
398
|
-
* "呈现"语义。需要数值结论一律走 profile_dataset / query_dataset。
|
|
399
|
-
* - session 作用域与 profile/query 同源(`sessionScopeFor` 取父会话),所以主 Agent
|
|
400
|
-
* 用自己会话即可读到自己子 Agent 采到的 Dataset。
|
|
343
|
+
* Host-internal presentation read (charts). The returned rows must never enter any agent message,
|
|
344
|
+
* tool result or model context; this is the only path that hands raw rows outside the store.
|
|
345
|
+
* Session scope follows the parent session, so the main agent can present a child's Datasets.
|
|
401
346
|
*/
|
|
402
347
|
readPresentationRows(input: {
|
|
403
348
|
session: SessionLike;
|
|
@@ -409,11 +354,8 @@ export declare class WorkspaceDatasetStore {
|
|
|
409
354
|
rows: unknown[];
|
|
410
355
|
}>;
|
|
411
356
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* 这是 workspace 读取的**唯一**实现:`readWorkspaceJson` / `readWorkspaceBytes` 都从
|
|
415
|
-
* `workspaceFileTarget` 取归属与体积闸门,不各自再写一遍路径校验(§9.7——同一个事实
|
|
416
|
-
* 有两份实现就一定会漂移)。
|
|
357
|
+
* Host-internal workspace text read; with readWorkspaceBytes it shares containment and size
|
|
358
|
+
* gates via workspaceFileTarget.
|
|
417
359
|
*/
|
|
418
360
|
readWorkspaceText(input: {
|
|
419
361
|
session: SessionLike;
|
|
@@ -422,11 +364,8 @@ export declare class WorkspaceDatasetStore {
|
|
|
422
364
|
maxBytes?: number;
|
|
423
365
|
}): Promise<string>;
|
|
424
366
|
/**
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
* 上限必须显式给出:这道缝后面的调用方要把字节整个装进内存,没有默认值可兜。
|
|
428
|
-
* `fs.readBytes` 缺席(载体未挂载该能力)时**响亮失败**,不回退成 `readText`——
|
|
429
|
-
* 二进制走文本读取会得到被解码污染的字节。
|
|
367
|
+
* Host-internal raw-byte read (local PDF/images). maxBytes is mandatory and fails loudly; never
|
|
368
|
+
* fall back to readText, which would corrupt binary data.
|
|
430
369
|
*/
|
|
431
370
|
readWorkspaceBytes(input: {
|
|
432
371
|
session: SessionLike;
|
|
@@ -435,25 +374,15 @@ export declare class WorkspaceDatasetStore {
|
|
|
435
374
|
maxBytes: number;
|
|
436
375
|
}): Promise<Uint8Array>;
|
|
437
376
|
private workspaceFileTarget;
|
|
438
|
-
/** 宿主内部呈现层读取 workspace 内的 JSON 文件(用户本地数据直接可视化的入口)。 */
|
|
439
377
|
readWorkspaceJson(input: {
|
|
440
378
|
session: SessionLike;
|
|
441
379
|
path: string;
|
|
442
380
|
signal?: AbortSignal;
|
|
443
381
|
}): Promise<unknown>;
|
|
444
382
|
/**
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
* 覆盖已有产物(图表 id 唯一,重复即 bug,应当响亮失败)。
|
|
449
|
-
*
|
|
450
|
-
* `overwrite: true` 是给**内容寻址产物**重跑用的(OCR:上次在两个文件之间被中断,
|
|
451
|
-
* 半件产物必须能被下一次解析自愈)。它不是"跳过幂等"的口子:幂等判断在调用方,
|
|
452
|
-
* 只有已经决定重新生成时才传。
|
|
453
|
-
*
|
|
454
|
-
* 返回值同时给出**相对路径**(可以进 Agent 消息、可以 present 给用户)与**绝对路径**
|
|
455
|
-
* (只供宿主内部使用,例如登记给 host 平面的取数路由)。这个区分是本仓的数据纪律:
|
|
456
|
-
* 绝对路径不出宿主进程。
|
|
383
|
+
* Host-internal artifact write (charts, OCR): same sandbox and containment checks as Datasets,
|
|
384
|
+
* createIfAbsent by default. `overwrite` exists only for content-addressed reruns.
|
|
385
|
+
* Returns a relative path (agent-visible) and an absolute one (host-only).
|
|
457
386
|
*/
|
|
458
387
|
writeWorkspaceFiles(input: {
|
|
459
388
|
session: SessionLike;
|
|
@@ -471,14 +400,8 @@ export declare class WorkspaceDatasetStore {
|
|
|
471
400
|
}>>;
|
|
472
401
|
private requireRef;
|
|
473
402
|
private requireStoredRef;
|
|
474
|
-
/**
|
|
475
|
-
*
|
|
476
|
-
* - `rows`:顶层数组或 manifest 声明的行数组键,可 profile、可 query;
|
|
477
|
-
* - `document`:顶层对象且没有行数组(财务指标、回测结果这类),可 profile
|
|
478
|
-
* (宿主算结构摘要 + 有界内容),不可 query。
|
|
479
|
-
* 判定放在读取时而不是复用 manifest 的 `format`:`format` 只记录写入时的粗略
|
|
480
|
-
* 形状,真正的形状以文件内容为准。
|
|
481
|
-
*/
|
|
403
|
+
/** Read raw Dataset: rows (array/envelope) are queryable, documents are profile-only. Shape is
|
|
404
|
+
* decided from file content, not the manifest's coarse `format`. */
|
|
482
405
|
private readRaw;
|
|
483
406
|
private resolveContained;
|
|
484
407
|
private assertContained;
|
|
@@ -486,7 +409,7 @@ export declare class WorkspaceDatasetStore {
|
|
|
486
409
|
private readRoot;
|
|
487
410
|
private mapWriteError;
|
|
488
411
|
}
|
|
489
|
-
/**
|
|
412
|
+
/** present=false distinguishes a missing column from an explicit null. */
|
|
490
413
|
export interface ProfileCell {
|
|
491
414
|
present: boolean;
|
|
492
415
|
value: unknown;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { DatasetQueryError, executeJsonRowsQuery } from './query.js';
|
|
2
2
|
import { defaultAxisWindows, isoDateFromEpoch, probeTimeAxis, readAxisDates } from './time-axis.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 原始 Dataset 只由宿主写入,Dataset/Profile 都通过 createIfAbsent 发布。
|
|
7
|
-
* Agent 只能使用 dataset_id 和 opaque artifact_ref,不能传入真实路径。
|
|
4
|
+
* Workspace-local Dataset/profile store: immutable artifacts, host-written only. Agents receive
|
|
5
|
+
* dataset_id / opaque artifact_ref, never real paths; raw rows never enter agent messages.
|
|
8
6
|
*/
|
|
9
7
|
export const WORKSPACE_DATA_DIR = 'capital-data';
|
|
10
8
|
export const DATASETS_SUBDIR = `${WORKSPACE_DATA_DIR}/datasets`;
|
|
@@ -15,39 +13,26 @@ export const PROFILE_FILE = 'profile.json';
|
|
|
15
13
|
export const DEFAULT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
16
14
|
export const MAX_RAW_READ_BYTES = 16 * 1024 * 1024;
|
|
17
15
|
export const MAX_PROFILE_BYTES = 64 * 1024;
|
|
18
|
-
/**
|
|
16
|
+
/** Max distinct category values tracked; beyond this distinct_count is null and truncated is set. */
|
|
19
17
|
export const MAX_CATEGORY_DISTINCT = 2_000;
|
|
20
|
-
/** `top_values` 只展示前 N 个高频取值,其余由 truncated 显式披露。 */
|
|
21
18
|
export const MAX_CATEGORY_TOP_VALUES = 5;
|
|
22
19
|
const MAX_CATEGORY_VALUE_LENGTH = 64;
|
|
23
|
-
/**
|
|
20
|
+
/** Structure-digest caps that keep profile.json bounded. */
|
|
24
21
|
export const MAX_STRUCTURE_ENTRIES = 24;
|
|
25
|
-
/**
|
|
26
|
-
* 递归深度上限。行集合里 depth 0 就是列本身,文档里 depth 0 是 `$` 根,
|
|
27
|
-
* 因此文档需要多一层才够到 `abilities[].indicators[]` 这种叶子。
|
|
28
|
-
* 条目上限(24)才是真正的体积闸门,深度只是防止病态深嵌套。
|
|
29
|
-
*/
|
|
22
|
+
/** Depth 0 is the column itself for rows but `$` for documents; the entry cap is the real size gate. */
|
|
30
23
|
const MAX_STRUCTURE_DEPTH = 4;
|
|
31
24
|
const MAX_STRUCTURE_FIELDS = 8;
|
|
32
25
|
const MAX_STRUCTURE_SAMPLED_ELEMENTS = 50;
|
|
33
|
-
/**
|
|
34
|
-
* `time_facts.windows` 的条数上限。默认只给 4 个常用窗口(近 1 月/3 月/1 年/年初至今),
|
|
35
|
-
* 上限是防回涨的闸门:窗口是"现成坐标",不是让宿主把任意期间都算一遍。
|
|
36
|
-
*/
|
|
26
|
+
/** Window cap: windows are ready-made coordinates, not an invitation to precompute arbitrary ranges. */
|
|
37
27
|
const MAX_TIME_WINDOWS = 8;
|
|
38
|
-
/**
|
|
28
|
+
/** Probe only leading rows: the time column appears early and daily data is ordered. */
|
|
39
29
|
const MAX_AXIS_PROBE_ROWS = 500;
|
|
40
30
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* 超过就会被剪掉中间段,而不是被完整读到。长数组按下面的档位逐级截断,截断了什么
|
|
44
|
-
* 一律写进 `omitted`,不做静默省略。
|
|
31
|
+
* Document content budget in code points; must stay under the preset's 8192 prune threshold.
|
|
32
|
+
* Truncated arrays are reported in `omitted`, never silently dropped.
|
|
45
33
|
*/
|
|
46
34
|
export const MAX_DOCUMENT_CHARS = 6_000;
|
|
47
|
-
/**
|
|
48
|
-
* 呈现层(图表)一次性读取行集的上限。超过就拒绝,而不是把几百 MB 拉进宿主内存:
|
|
49
|
-
* 图表是**有界呈现**,不是通用分析通道。
|
|
50
|
-
*/
|
|
35
|
+
/** Presentation rows are capped: charts are bounded rendering, not a general analysis channel. */
|
|
51
36
|
export const MAX_PRESENTATION_ROWS = 200_000;
|
|
52
37
|
const DOCUMENT_ARRAY_KEEP_STEPS = [8, 3, 1, 0];
|
|
53
38
|
const DOCUMENT_MAX_DEPTH = 6;
|
|
@@ -73,11 +58,8 @@ function isAbsolutePath(value) {
|
|
|
73
58
|
return /^([a-zA-Z]:[\\/]|\/)/.test(value);
|
|
74
59
|
}
|
|
75
60
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* 拒绝:空串、绝对路径(含 Windows 盘符)、任何 `..` 段、反斜杠(本仓统一 `/`)。
|
|
79
|
-
* 这里只做形态校验;真正的沙箱归属由 `resolveContained` 证明。两道关卡都要过——
|
|
80
|
-
* 形态校验是为了给出可读错误,归属校验才是安全边界。
|
|
61
|
+
* Validate a workspace-relative path shape. Containment is proven separately by resolveContained;
|
|
62
|
+
* both checks are required, and this one only makes errors readable.
|
|
81
63
|
*/
|
|
82
64
|
export function normalizeWorkspaceRelativePath(value) {
|
|
83
65
|
if (typeof value !== 'string')
|
|
@@ -250,7 +232,7 @@ export class WorkspaceDatasetStore {
|
|
|
250
232
|
profile,
|
|
251
233
|
signal: input.signal,
|
|
252
234
|
});
|
|
253
|
-
//
|
|
235
|
+
// Structure digest is persisted with the profile; bounded content is returned only.
|
|
254
236
|
return { ...profileRef, ...profile, document: boundDocument(raw.value) };
|
|
255
237
|
}
|
|
256
238
|
const profile = buildProfile(raw.rows, input.time_column, input.primary_key, extractRowContracts(ref.schema));
|
|
@@ -275,11 +257,8 @@ export class WorkspaceDatasetStore {
|
|
|
275
257
|
return executeJsonRowsQuery(raw.rows, ref.schema, input.query);
|
|
276
258
|
}
|
|
277
259
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* 只读前 {@link MAX_AXIS_PROBE_ROWS} 行推断形态与偏移:时间列一定在每行都出现,日线数据
|
|
281
|
-
* 的有序性也让前若干行足以定出粒度;这样这个探针**可以随查询逐次调用**,
|
|
282
|
-
* 代价与"读一份 16MB raw.json 只为一个问题"不同量级。
|
|
260
|
+
* Read-only time-axis probe shared by the date normalizer and dataset time-window mode.
|
|
261
|
+
* Samples at most MAX_AXIS_PROBE_ROWS rows, so it is cheap enough to call per query.
|
|
283
262
|
*/
|
|
284
263
|
async describeTimeAxis(input) {
|
|
285
264
|
const stored = await this.requireStoredRef(input.dataset_id, input.session, input.signal);
|
|
@@ -338,15 +317,9 @@ export class WorkspaceDatasetStore {
|
|
|
338
317
|
return profileRef;
|
|
339
318
|
}
|
|
340
319
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* - 返回值**不得**进入任何 Agent 消息、工具结果或模型上下文;调用方只能在宿主进程内
|
|
345
|
-
* 把它变成有界可视化载荷(见 src/chart/series.ts)。
|
|
346
|
-
* - 这是唯一一处把原始 rows 交给 store 之外代码的入口,因此命名、文档与调用点都必须是
|
|
347
|
-
* "呈现"语义。需要数值结论一律走 profile_dataset / query_dataset。
|
|
348
|
-
* - session 作用域与 profile/query 同源(`sessionScopeFor` 取父会话),所以主 Agent
|
|
349
|
-
* 用自己会话即可读到自己子 Agent 采到的 Dataset。
|
|
320
|
+
* Host-internal presentation read (charts). The returned rows must never enter any agent message,
|
|
321
|
+
* tool result or model context; this is the only path that hands raw rows outside the store.
|
|
322
|
+
* Session scope follows the parent session, so the main agent can present a child's Datasets.
|
|
350
323
|
*/
|
|
351
324
|
async readPresentationRows(input) {
|
|
352
325
|
const stored = await this.requireStoredRef(input.dataset_id, input.session, input.signal);
|
|
@@ -362,11 +335,8 @@ export class WorkspaceDatasetStore {
|
|
|
362
335
|
return { ref, rows: raw.rows };
|
|
363
336
|
}
|
|
364
337
|
/**
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* 这是 workspace 读取的**唯一**实现:`readWorkspaceJson` / `readWorkspaceBytes` 都从
|
|
368
|
-
* `workspaceFileTarget` 取归属与体积闸门,不各自再写一遍路径校验(§9.7——同一个事实
|
|
369
|
-
* 有两份实现就一定会漂移)。
|
|
338
|
+
* Host-internal workspace text read; with readWorkspaceBytes it shares containment and size
|
|
339
|
+
* gates via workspaceFileTarget.
|
|
370
340
|
*/
|
|
371
341
|
async readWorkspaceText(input) {
|
|
372
342
|
const maxBytes = input.maxBytes ?? MAX_RAW_READ_BYTES;
|
|
@@ -386,11 +356,8 @@ export class WorkspaceDatasetStore {
|
|
|
386
356
|
return text;
|
|
387
357
|
}
|
|
388
358
|
/**
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* 上限必须显式给出:这道缝后面的调用方要把字节整个装进内存,没有默认值可兜。
|
|
392
|
-
* `fs.readBytes` 缺席(载体未挂载该能力)时**响亮失败**,不回退成 `readText`——
|
|
393
|
-
* 二进制走文本读取会得到被解码污染的字节。
|
|
359
|
+
* Host-internal raw-byte read (local PDF/images). maxBytes is mandatory and fails loudly; never
|
|
360
|
+
* fall back to readText, which would corrupt binary data.
|
|
394
361
|
*/
|
|
395
362
|
async readWorkspaceBytes(input) {
|
|
396
363
|
const { relative, target } = await this.workspaceFileTarget(input.session, input.path, input.signal, input.maxBytes);
|
|
@@ -423,7 +390,6 @@ export class WorkspaceDatasetStore {
|
|
|
423
390
|
}
|
|
424
391
|
return { relative, target };
|
|
425
392
|
}
|
|
426
|
-
/** 宿主内部呈现层读取 workspace 内的 JSON 文件(用户本地数据直接可视化的入口)。 */
|
|
427
393
|
async readWorkspaceJson(input) {
|
|
428
394
|
const text = await this.readWorkspaceText(input);
|
|
429
395
|
try {
|
|
@@ -434,18 +400,9 @@ export class WorkspaceDatasetStore {
|
|
|
434
400
|
}
|
|
435
401
|
}
|
|
436
402
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* 覆盖已有产物(图表 id 唯一,重复即 bug,应当响亮失败)。
|
|
441
|
-
*
|
|
442
|
-
* `overwrite: true` 是给**内容寻址产物**重跑用的(OCR:上次在两个文件之间被中断,
|
|
443
|
-
* 半件产物必须能被下一次解析自愈)。它不是"跳过幂等"的口子:幂等判断在调用方,
|
|
444
|
-
* 只有已经决定重新生成时才传。
|
|
445
|
-
*
|
|
446
|
-
* 返回值同时给出**相对路径**(可以进 Agent 消息、可以 present 给用户)与**绝对路径**
|
|
447
|
-
* (只供宿主内部使用,例如登记给 host 平面的取数路由)。这个区分是本仓的数据纪律:
|
|
448
|
-
* 绝对路径不出宿主进程。
|
|
403
|
+
* Host-internal artifact write (charts, OCR): same sandbox and containment checks as Datasets,
|
|
404
|
+
* createIfAbsent by default. `overwrite` exists only for content-addressed reruns.
|
|
405
|
+
* Returns a relative path (agent-visible) and an absolute one (host-only).
|
|
449
406
|
*/
|
|
450
407
|
async writeWorkspaceFiles(input) {
|
|
451
408
|
const { session, signal } = input;
|
|
@@ -510,14 +467,8 @@ export class WorkspaceDatasetStore {
|
|
|
510
467
|
throw new DatasetStoreError('dataset_expired', `Dataset ${datasetId} has expired`);
|
|
511
468
|
return stored;
|
|
512
469
|
}
|
|
513
|
-
/**
|
|
514
|
-
*
|
|
515
|
-
* - `rows`:顶层数组或 manifest 声明的行数组键,可 profile、可 query;
|
|
516
|
-
* - `document`:顶层对象且没有行数组(财务指标、回测结果这类),可 profile
|
|
517
|
-
* (宿主算结构摘要 + 有界内容),不可 query。
|
|
518
|
-
* 判定放在读取时而不是复用 manifest 的 `format`:`format` 只记录写入时的粗略
|
|
519
|
-
* 形状,真正的形状以文件内容为准。
|
|
520
|
-
*/
|
|
470
|
+
/** Read raw Dataset: rows (array/envelope) are queryable, documents are profile-only. Shape is
|
|
471
|
+
* decided from file content, not the manifest's coarse `format`. */
|
|
521
472
|
async readRaw(ref, session, signal, rowKey) {
|
|
522
473
|
const root = this.readRoot(session);
|
|
523
474
|
if (!root || !this.fs)
|
|
@@ -805,11 +756,7 @@ function buildProfile(rows, requestedTimeColumn, primaryKey, contracts = {}) {
|
|
|
805
756
|
warnings,
|
|
806
757
|
};
|
|
807
758
|
}
|
|
808
|
-
/**
|
|
809
|
-
* 挑时间列:显式指定优先;否则按列名候选,并要求该列的非空值全是数字或日期样式
|
|
810
|
-
* 字符串。只按列名取第一个匹配列会把 `report_type`(季度枚举)误当时间轴,
|
|
811
|
-
* 于是首末值与覆盖范围全是错的口径。
|
|
812
|
-
*/
|
|
759
|
+
/** Prefer an explicit column; otherwise require a name match plus numeric/date-only values (report_type must not win). */
|
|
813
760
|
function pickTimeColumn(rows, columns, requested) {
|
|
814
761
|
if (requested !== undefined)
|
|
815
762
|
return columns.includes(requested) ? requested : requested;
|
|
@@ -830,17 +777,7 @@ function pickTimeColumn(rows, columns, requested) {
|
|
|
830
777
|
return undefined;
|
|
831
778
|
}
|
|
832
779
|
const DATE_LIKE_PATTERN = /^\d{4}(-\d{1,2}(-\d{1,2})?)?$/;
|
|
833
|
-
/**
|
|
834
|
-
* 时间事实:覆盖范围 + 首行/末行的「时间列与数值列」取值(文件顺序)。
|
|
835
|
-
*
|
|
836
|
-
* 只报 min/max 时,模型会把区间极值讲成走势(实测样本里出现过「从 74 涨到 105
|
|
837
|
-
* 以上随后可能回落」这种没有依据的叙述)。首末行给出的是真实首末取值,
|
|
838
|
-
* 区间变化因此可追溯;`ordered_ascending` 说明文件顺序,避免把末行当成最新。
|
|
839
|
-
*/
|
|
840
|
-
/**
|
|
841
|
-
* 时间事实的**唯一实现**(profile 与时间轴探针共用):
|
|
842
|
-
* 覆盖范围(原值 + ISO)、时间轴语义、常用窗口、首末行取值。
|
|
843
|
-
*/
|
|
780
|
+
/** Sole implementation of time facts (profile and probe): coverage, axis, windows, first/last rows. */
|
|
844
781
|
function timeFactsFor(rows, timeColumn, orderedAscending, numericColumns) {
|
|
845
782
|
const project = (row) => {
|
|
846
783
|
const projected = {};
|
|
@@ -896,14 +833,7 @@ function structureAccumulator(entries, path, type) {
|
|
|
896
833
|
entries.set(path, created);
|
|
897
834
|
return created;
|
|
898
835
|
}
|
|
899
|
-
/**
|
|
900
|
-
* 把单元格里的数组/对象展开成有界路径摘要(深度 ≤3、条目 ≤24)。
|
|
901
|
-
*
|
|
902
|
-
* 路径约定:对象字段用 `.name`,数组元素用 `[]`,例如 QDII 额度的三层嵌套会得到
|
|
903
|
-
* `sub_tab`(数组,共 3 项)→ `sub_tab[].fund_list`(数组,共 40 项)。
|
|
904
|
-
* 未抽样时用 `total_elements` 表示完整数量;超过 50 项时改用 `sampled_elements`,明确只统计了实际检查到的部分,
|
|
905
|
-
* 而不是让模型面对一句「sub_tab 的类型是 object」。
|
|
906
|
-
*/
|
|
836
|
+
/** Bounded path digest: `.name` for object fields, `[]` for array elements, depth and count capped. */
|
|
907
837
|
function collectStructure(value, path, depth, entries, sampled = false) {
|
|
908
838
|
if (depth > MAX_STRUCTURE_DEPTH)
|
|
909
839
|
return;
|
|
@@ -985,12 +915,7 @@ function tallyCategoryValue(tally, column, value) {
|
|
|
985
915
|
}
|
|
986
916
|
columnTally.counts.set(key, 1);
|
|
987
917
|
}
|
|
988
|
-
/**
|
|
989
|
-
* 文档型 Dataset 的 profile:没有行,所以不给行列统计,只给结构摘要。
|
|
990
|
-
*
|
|
991
|
-
* `$` 表示文档根,其余路径沿用 structure 的约定(对象字段用 `.name`、数组元素用 `[]`),
|
|
992
|
-
* 例如财务指标会得到 `abilities`(共 5 项)与 `abilities[].indicators`(共 40 项)。
|
|
993
|
-
*/
|
|
918
|
+
/** Document profile: structure digest only, rooted at `$` (no row statistics). */
|
|
994
919
|
function buildDocumentProfile(value) {
|
|
995
920
|
const entries = new Map();
|
|
996
921
|
collectStructure(value, '$', 0, entries);
|
|
@@ -1010,11 +935,7 @@ function buildDocumentProfile(value) {
|
|
|
1010
935
|
warnings,
|
|
1011
936
|
};
|
|
1012
937
|
}
|
|
1013
|
-
/**
|
|
1014
|
-
* 把文档内容裁到预算内:长数组按档位逐级截断(保留前 N 项),每次尝试都重新
|
|
1015
|
-
* 量体积,直到放得下。返回实际采用的档位与被省略的路径,供模型如实披露
|
|
1016
|
-
* 「完整共 N 项,本次只给了前 K 项」。
|
|
1017
|
-
*/
|
|
938
|
+
/** Shrink long arrays in steps until the document fits; report every omitted path. */
|
|
1018
939
|
function boundDocument(value) {
|
|
1019
940
|
const measure = (candidate) => {
|
|
1020
941
|
try {
|