dsh-plugin-show-me-data 0.1.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/LICENSE +27 -0
- package/README.md +96 -0
- package/cordis.patch.yml +40 -0
- package/docs/01-product-effect.md +178 -0
- package/docs/02-architecture.md +275 -0
- package/docs/03-data-contracts.md +291 -0
- package/docs/04-sources.md +342 -0
- package/docs/05-ui-spec.md +167 -0
- package/docs/06-ai-layer.md +194 -0
- package/docs/07-implementation-plan.md +399 -0
- package/docs/08-test-plan.md +133 -0
- package/docs/09-packaging-install.md +249 -0
- package/docs/10-kickoff-prompt.md +94 -0
- package/docs/11-decisions.md +203 -0
- package/docs/12-runtime-verified.md +115 -0
- package/docs/13-acceptance.md +153 -0
- package/docs/14-progress.md +150 -0
- package/docs/15-publish.md +185 -0
- package/lib/app/ai-deterministic.js +327 -0
- package/lib/app/ai-validate.js +284 -0
- package/lib/app/ai.js +440 -0
- package/lib/app/health.js +77 -0
- package/lib/app/overview.js +349 -0
- package/lib/app/propose-indicator.js +122 -0
- package/lib/app/refresh.js +251 -0
- package/lib/app/series-view.js +195 -0
- package/lib/app/watchlist.js +102 -0
- package/lib/client.js +4322 -0
- package/lib/core/ai/prompts.js +213 -0
- package/lib/core/chart/axis.js +133 -0
- package/lib/core/chart/bar.js +58 -0
- package/lib/core/chart/candle.js +216 -0
- package/lib/core/chart/line.js +186 -0
- package/lib/core/chart/scale.js +132 -0
- package/lib/core/format.js +143 -0
- package/lib/core/indicators/catalog.js +1011 -0
- package/lib/core/indicators/resolve.js +196 -0
- package/lib/core/insight/digest.js +250 -0
- package/lib/core/insight/rank.js +115 -0
- package/lib/core/insight/related.js +90 -0
- package/lib/core/insight/rules.js +417 -0
- package/lib/core/stats/derive.js +123 -0
- package/lib/core/stats/series.js +465 -0
- package/lib/core/time/range.js +242 -0
- package/lib/core/types.js +478 -0
- package/lib/host/ai/discussion.js +559 -0
- package/lib/host/ai/dsh-llm-gateway.js +333 -0
- package/lib/host/config.js +194 -0
- package/lib/host/http/respond.js +165 -0
- package/lib/host/http/routes.js +689 -0
- package/lib/host/index.js +293 -0
- package/lib/host/infra/fs-repos.js +179 -0
- package/lib/host/infra/memory-fallback.js +64 -0
- package/lib/host/tools/define-tool.js +295 -0
- package/lib/host/tools/register.js +431 -0
- package/lib/host.js +7 -0
- package/lib/ports/clock.js +57 -0
- package/lib/ports/snapshot-repo.js +48 -0
- package/lib/sources/eastmoney-macro.js +197 -0
- package/lib/sources/eastmoney-quote.js +201 -0
- package/lib/sources/ecb.js +179 -0
- package/lib/sources/fred.js +207 -0
- package/lib/sources/http.js +136 -0
- package/lib/sources/ohlc.js +36 -0
- package/lib/sources/quote-cascade.js +177 -0
- package/lib/sources/registry.js +153 -0
- package/lib/sources/sina-cn.js +197 -0
- package/lib/sources/sina-us.js +187 -0
- package/lib/sources/tencent.js +158 -0
- package/lib/sources/us-treasury-rates.js +275 -0
- package/lib/sources/us-treasury.js +196 -0
- package/lib/sources/worldbank.js +170 -0
- package/package.json +69 -0
- package/src/app/ai-deterministic.js +327 -0
- package/src/app/ai-validate.js +284 -0
- package/src/app/ai.js +440 -0
- package/src/app/health.js +77 -0
- package/src/app/overview.js +349 -0
- package/src/app/propose-indicator.js +122 -0
- package/src/app/refresh.js +251 -0
- package/src/app/series-view.js +195 -0
- package/src/app/watchlist.js +102 -0
- package/src/client/api.js +323 -0
- package/src/client/components.js +1877 -0
- package/src/client/copy.js +368 -0
- package/src/client/index.js +169 -0
- package/src/client/store.js +219 -0
- package/src/core/ai/prompts.js +213 -0
- package/src/core/chart/axis.js +133 -0
- package/src/core/chart/bar.js +58 -0
- package/src/core/chart/candle.js +216 -0
- package/src/core/chart/line.js +186 -0
- package/src/core/chart/scale.js +132 -0
- package/src/core/format.js +143 -0
- package/src/core/indicators/catalog.js +1011 -0
- package/src/core/indicators/resolve.js +196 -0
- package/src/core/insight/digest.js +250 -0
- package/src/core/insight/rank.js +115 -0
- package/src/core/insight/related.js +90 -0
- package/src/core/insight/rules.js +417 -0
- package/src/core/stats/derive.js +123 -0
- package/src/core/stats/series.js +465 -0
- package/src/core/time/range.js +242 -0
- package/src/core/types.js +478 -0
- package/src/host/ai/discussion.js +559 -0
- package/src/host/ai/dsh-llm-gateway.js +333 -0
- package/src/host/config.js +194 -0
- package/src/host/http/respond.js +165 -0
- package/src/host/http/routes.js +689 -0
- package/src/host/index.js +293 -0
- package/src/host/infra/fs-repos.js +179 -0
- package/src/host/infra/memory-fallback.js +64 -0
- package/src/host/tools/define-tool.js +295 -0
- package/src/host/tools/register.js +431 -0
- package/src/ports/clock.js +57 -0
- package/src/ports/snapshot-repo.js +48 -0
- package/src/sources/eastmoney-macro.js +197 -0
- package/src/sources/eastmoney-quote.js +201 -0
- package/src/sources/ecb.js +179 -0
- package/src/sources/fred.js +207 -0
- package/src/sources/http.js +136 -0
- package/src/sources/ohlc.js +36 -0
- package/src/sources/quote-cascade.js +177 -0
- package/src/sources/registry.js +153 -0
- package/src/sources/sina-cn.js +197 -0
- package/src/sources/sina-us.js +187 -0
- package/src/sources/tencent.js +158 -0
- package/src/sources/us-treasury-rates.js +275 -0
- package/src/sources/us-treasury.js +196 -0
- package/src/sources/worldbank.js +170 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# 03 · 数据契约与指标目录
|
|
2
|
+
|
|
3
|
+
所有跨模块的类型都在这里定死。实现时**这些形状不得随意改动**;确需变更走 `11-decisions.md` 追加 ADR。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 基础类型
|
|
8
|
+
|
|
9
|
+
### 1.1 `SourceRef`(溯源单元,强制出现在每个数值上)
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
/** @typedef {Object} SourceRef
|
|
13
|
+
* @property {string} adapterId // 'fred'
|
|
14
|
+
* @property {string} seriesRef // 'PAYEMS' | '1.000012' | 'RPT_ECONOMY_CPI'
|
|
15
|
+
* @property {string} url // 人类可点开的原始地址(必须真实可达)
|
|
16
|
+
* @property {string} label // 'FRED' | '东方财富' | '美国财政部'
|
|
17
|
+
* @property {string} [apiUrl] // 机器可读的取数地址(便于复现)
|
|
18
|
+
*/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 1.2 `IndicatorDef`(指标定义 = 纯数据)
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
/** @typedef {Object} IndicatorDef
|
|
25
|
+
* @property {string} id // 'us.cpi.yoy',全局唯一,点分命名
|
|
26
|
+
* @property {'US'|'CN'|'GLOBAL'|'CUSTOM'} group
|
|
27
|
+
* @property {{zh: string, en: string}} label
|
|
28
|
+
* @property {string} unit // '%' | 'pp' | '点' | '万人' | '亿元' | '指数'
|
|
29
|
+
* @property {'daily'|'weekly'|'monthly'|'quarterly'|'annual'} freq
|
|
30
|
+
* @property {'SA'|'NSA'|'NA'} seasonal
|
|
31
|
+
* @property {1|2|3|4|5} importance // 5 = 最核心(首页默认展示)
|
|
32
|
+
* @property {SourceBinding} source // 取数绑定
|
|
33
|
+
* @property {DisplaySpec} display // 展示变换
|
|
34
|
+
* @property {{zh: string, en?: string}} [notes] // 供 AI 与 tooltip 使用的口径说明
|
|
35
|
+
* @property {string[]} [tags] // 'inflation','rates','labor','equity','bond'
|
|
36
|
+
*/
|
|
37
|
+
/** @typedef {Object} SourceBinding
|
|
38
|
+
* @property {string} adapter // 注册表里的 adapterId
|
|
39
|
+
* @property {string} seriesRef // 上游系列 ID / secid / reportName
|
|
40
|
+
* @property {Object} [params] // 例如 { klt: 101, fqt: 1 } 或 { column: 'NATIONAL_SAME' }
|
|
41
|
+
*/
|
|
42
|
+
/** @typedef {Object} DisplaySpec
|
|
43
|
+
* @property {'raw'|'diff'|'pctChange'|'yoy'|'mom'|'annualize'|'ratio'} transform
|
|
44
|
+
* @property {number} [window] // 派生窗口(如 diff 的月数)
|
|
45
|
+
* @property {number} [decimals]
|
|
46
|
+
* @property {'up-is-good'|'down-is-good'|'neutral'} [polarity] // 影响颜色与规则措辞
|
|
47
|
+
*/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 1.3 `RawSeries`(适配器唯一输出)
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
/** @typedef {Object} RawSeries
|
|
54
|
+
* @property {string} adapterId
|
|
55
|
+
* @property {string} seriesRef
|
|
56
|
+
* @property {{t: string, v: number}[]} points // t = 'YYYY-MM-DD';缺失点不得填 0,直接跳过
|
|
57
|
+
* @property {Object} meta
|
|
58
|
+
* @property {string} meta.name // 上游给的名称(人类可读)
|
|
59
|
+
* @property {string} [meta.unit] // 上游若有则带上
|
|
60
|
+
* @property {string} [meta.freq]
|
|
61
|
+
* @property {string} fetchedAt // ISO 时间(来自注入的 Clock)
|
|
62
|
+
* @property {SourceRef} sourceRef
|
|
63
|
+
* @property {Object} [raw] // 仅供调试与 fixture 对比,不参与业务
|
|
64
|
+
*/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 1.4 `SeriesView`(`app/series-view.js` 的输出)
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
/** @typedef {Object} SeriesView
|
|
71
|
+
* @property {string} indicatorId
|
|
72
|
+
* @property {Range} range
|
|
73
|
+
* @property {{t: string, v: number}[]} points // 已排序、去重、按范围过滤
|
|
74
|
+
* @property {SeriesStats} stats
|
|
75
|
+
* @property {SourceRef} sourceRef
|
|
76
|
+
* @property {'fresh'|'stale'|'error'|'missing'} status
|
|
77
|
+
* @property {string} [lastSuccessAt]
|
|
78
|
+
* @property {string} [errorKind]
|
|
79
|
+
*/
|
|
80
|
+
/** @typedef {Object} SeriesStats
|
|
81
|
+
* @property {number} latest @property {string} latestAt
|
|
82
|
+
* @property {number} [prev] @property {number} [changeAbs] @property {number} [changePct]
|
|
83
|
+
* @property {number} [yoy] @property {number} [mom]
|
|
84
|
+
* @property {number} mean @property {number} min @property {number} max
|
|
85
|
+
* @property {number} stdDev @property {number} [zScoreLatestChange]
|
|
86
|
+
* @property {number} [slope] // 最小二乘斜率(每期)
|
|
87
|
+
* @property {number} [percentile] // 最新值在区间内的分位(0–1)
|
|
88
|
+
* @property {number} missingCount
|
|
89
|
+
*/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 1.5 `Metric`(卡片)与 `Noteworthy`(值得关注项)
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
/** @typedef {Object} Metric
|
|
96
|
+
* @property {string} indicatorId
|
|
97
|
+
* @property {{zh: string, en: string}} label
|
|
98
|
+
* @property {string} unit
|
|
99
|
+
* @property {number} latest @property {string} latestAt
|
|
100
|
+
* @property {number} [changeAbs] @property {number} [changePct]
|
|
101
|
+
* @property {number[]} sparkline // 归一化后的点(供 SVG 直接画)
|
|
102
|
+
* @property {'fresh'|'stale'|'error'|'missing'} status
|
|
103
|
+
* @property {SourceRef} sourceRef
|
|
104
|
+
* @property {Hit[]} hits // 命中的规则(可为空)
|
|
105
|
+
* @property {number} score // 0–100,排序用
|
|
106
|
+
*/
|
|
107
|
+
/** @typedef {Object} Hit
|
|
108
|
+
* @property {string} ruleId
|
|
109
|
+
* @property {number} score
|
|
110
|
+
* @property {{zh: string, en?: string}} reason
|
|
111
|
+
* @property {Object} evidence // 结构化证据:{ value, threshold, window, dates[] }
|
|
112
|
+
*/
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 1.6 `Range`
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
/** @typedef {Object} Range
|
|
119
|
+
* @property {'1M'|'3M'|'6M'|'YTD'|'1Y'|'3Y'|'5Y'|'MAX'|'CUSTOM'} preset
|
|
120
|
+
* @property {string} from // 'YYYY-MM-DD'
|
|
121
|
+
* @property {string} to // 'YYYY-MM-DD'
|
|
122
|
+
*/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`resolveRange(preset, today)` 是 `core/time/range.js` 的纯函数:给定 `today` 返回 `from`/`to`,
|
|
126
|
+
**不读系统时间**(时间由注入的 `Clock` 提供),因此可以精确单测跨年、闰年、YTD 边界。
|
|
127
|
+
|
|
128
|
+
### 1.7 错误与状态
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
/** @typedef {Object} SourceError
|
|
132
|
+
* @property {'network'|'http'|'parse'|'empty'|'unsupported'} kind
|
|
133
|
+
* @property {string} adapterId
|
|
134
|
+
* @property {string} seriesRef
|
|
135
|
+
* @property {string} detail
|
|
136
|
+
* @property {number} [httpStatus]
|
|
137
|
+
* @property {boolean} retryable
|
|
138
|
+
*/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
状态语义(UI 与 AI 都依赖它):
|
|
142
|
+
|
|
143
|
+
| status | 含义 | UI |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `fresh` | 成功取数且在频率容差内 | 正常 |
|
|
146
|
+
| `stale` | 取数失败但有历史快照,或最后观测日超容差 | 黄点 + 「最后成功 …」 |
|
|
147
|
+
| `missing` | 上游成功但没有该范围的数据点 | 灰卡 + 说明 |
|
|
148
|
+
| `error` | 取数失败且无快照 | 红卡 + 重试按钮 |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 2. 归一化与派生规则(`core/stats`)
|
|
153
|
+
|
|
154
|
+
这些规则会被逐条单测,实现时**先写测试再写实现**。
|
|
155
|
+
|
|
156
|
+
1. **排序与去重**:按 `t` 升序;同一 `t` 出现多次时**保留最后一个**(上游修正值优先)。
|
|
157
|
+
2. **缺失处理**:FRED 的 `"."`、空串、`"null"`、`"N/A"`、`"-"` 一律视为缺失 → 跳过,不填 0、不插值。
|
|
158
|
+
3. **同比(yoy)**:与**去年同月**比较;月频按 `t` 减 12 个月,季频减 4 个季度;用「同月对齐」而非「往前数 N 个点」,
|
|
159
|
+
缺失时允许 ±1 个月的容差匹配,并在 `stats` 里记 `yoyAlignedDrift`。
|
|
160
|
+
4. **环比(mom)**:与上一个已存在观测比较(不跳缺口),返回 `changeAbs` 与 `changePct`。
|
|
161
|
+
5. **diff transform**(如非农):`v[i] - v[i-1]`,用于水平值序列;窗口版为 `window` 期和差。
|
|
162
|
+
6. **pctChange / yoy transform**:以百分比为单位输出(`3.4` 表示 3.4%),**不要输出 0.034**。
|
|
163
|
+
7. **年化(annualize)**:仅对季频(×4)与月频(×12)的水平值允许,且必须在 `unit` 上标注 `(年化)`。
|
|
164
|
+
8. **标准差 / z 分数**:样本标准差(n−1);`zScoreLatestChange` 用「最近一次变化」对「同窗口历史变化」标准化;
|
|
165
|
+
**样本数 < 6 时该字段返回 `undefined`**(避免用 3 个点编出"2σ")。
|
|
166
|
+
9. **分位**:线性插值分位;样本 < 12 时返回 `undefined`。
|
|
167
|
+
10. **斜率**:最小二乘对 `(i, v)` 拟合,单位「每期」;指数型序列(指数/点位)同时给出 `slopeRel = slope / mean`。
|
|
168
|
+
11. **派生序列**(`core/stats/derive.js`):`spread(a,b)` = 按日期**内连接**后相减;`ratio(a,b)` 同理;
|
|
169
|
+
内连接后剩余点数 < 3 时返回空并附 `reason: 'insufficient-overlap'`。
|
|
170
|
+
12. **交易日近似**:不引入交易日历库。日频序列的「最近 N 个交易日」= 上游实际返回的最后 N 个观测,
|
|
171
|
+
**不要**用自然日推算交易日。
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 3. 种子指标目录(seriesRef 均为**实测可返回数据**)
|
|
176
|
+
|
|
177
|
+
> 探测时间:本次会话。`fredgraph.csv` **无需 API key**,是本方案的取数主干。
|
|
178
|
+
|
|
179
|
+
### 3.1 美国(FRED,`adapter: 'fred'`)
|
|
180
|
+
|
|
181
|
+
| id | 名称 | seriesRef | 频率 | 单位 | display.transform |
|
|
182
|
+
| --- | --- | --- | --- | --- | --- |
|
|
183
|
+
| `us.cpi` | 美国 CPI 指数 | `CPIAUCSL` | monthly | 指数 | `raw`(同比见下) |
|
|
184
|
+
| `us.cpi.yoy` | 美国 CPI 同比 | `CPIAUCSL` | monthly | % | `yoy` |
|
|
185
|
+
| `us.core.cpi.yoy` | 美国核心 CPI 同比 | `CPILFESL` | monthly | % | `yoy` |
|
|
186
|
+
| `us.pce.index` | 美国 PCE 物价指数 | `PCEPI` | monthly | 指数 | `raw` |
|
|
187
|
+
| `us.pce.yoy` | 美国 PCE 同比 | `PCEPI` | monthly | % | `yoy` |
|
|
188
|
+
| `us.unrate` | 美国失业率 | `UNRATE` | monthly | % | `raw` |
|
|
189
|
+
| `us.payrolls.level` | 美国非农就业(水平) | `PAYEMS` | monthly | 万人 | `raw` |
|
|
190
|
+
| `us.payrolls.change` | 美国非农就业(月增) | `PAYEMS` | monthly | 万人 | `diff` |
|
|
191
|
+
| `us.fedfunds.monthly` | 联邦基金利率(月均) | `FEDFUNDS` | monthly | % | `raw` |
|
|
192
|
+
| `us.fedfunds.daily` | 联邦基金利率(日度) | `DFF` | daily | % | `raw` |
|
|
193
|
+
| `us.dgs10` | 美国 10Y 国债收益率 | `DGS10` | daily | % | `raw` |
|
|
194
|
+
| `us.dgs2` | 美国 2Y 国债收益率 | `DGS2` | daily | % | `raw` |
|
|
195
|
+
| `us.dgs3m` | 美国 3M 国债收益率 | `DGS3MO` | daily | % | `raw` |
|
|
196
|
+
| `us.curve.10y2y` | 10Y−2Y 利差 | `T10Y2Y` | daily | pp | `raw` |
|
|
197
|
+
| `us.breakeven10y` | 10Y 通胀预期 | `T10YIE` | daily | % | `raw` |
|
|
198
|
+
| `us.gdp.real` | 美国实际 GDP | `GDPC1` | quarterly | 十亿美元 | `raw` |
|
|
199
|
+
| `us.retail.sales` | 美国零售销售 | `RSAFS` | monthly | 百万美元 | `raw` |
|
|
200
|
+
| `us.consumer.sentiment` | 密歇根消费者信心 | `UMCSENT` | monthly | 指数 | `raw` |
|
|
201
|
+
| `us.mortgage30` | 30 年期房贷利率 | `MORTGAGE30US` | weekly | % | `raw` |
|
|
202
|
+
| `us.dollar.index` | 美元指数(广义) | `DTWEXBGS` | daily | 指数 | `raw` |
|
|
203
|
+
| `us.oil.wti` | WTI 原油 | `DCOILWTICO` | daily | 美元/桶 | `raw` |
|
|
204
|
+
|
|
205
|
+
> `GDPC1` 返回的是**全历史**(1947 起),适配器必须支持 `cosd`(起始日)参数以避免拉全量。
|
|
206
|
+
|
|
207
|
+
### 3.2 中国(东方财富,`adapter: 'eastmoney-macro'`)
|
|
208
|
+
|
|
209
|
+
| id | 名称 | seriesRef(reportName) | 列(params.column) | 频率 | 单位 |
|
|
210
|
+
| --- | --- | --- | --- | --- | --- |
|
|
211
|
+
| `cn.cpi.yoy` | 中国 CPI 同比 | `RPT_ECONOMY_CPI` | `NATIONAL_SAME` | monthly | % |
|
|
212
|
+
| `cn.cpi.mom` | 中国 CPI 环比 | `RPT_ECONOMY_CPI` | `NATIONAL_SEQUENTIAL` | monthly | % |
|
|
213
|
+
| `cn.ppi.yoy` | 中国 PPI 同比 | `RPT_ECONOMY_PPI` | `BASE_SAME` | monthly | % |
|
|
214
|
+
| `cn.pmi.mfg` | 中国制造业 PMI | `RPT_ECONOMY_PMI` | `MAKE_INDEX` | monthly | 指数(荣枯线 50) |
|
|
215
|
+
| `cn.pmi.nonmfg` | 中国非制造业 PMI | `RPT_ECONOMY_PMI` | `NMAKE_INDEX` | monthly | 指数 |
|
|
216
|
+
| `cn.gdp.yoy` | 中国 GDP 同比 | `RPT_ECONOMY_GDP` | `SUM_SAME` | quarterly | % |
|
|
217
|
+
| `cn.gdp.level` | 中国 GDP 现价累计 | `RPT_ECONOMY_GDP` | `DOMESTICL_PRODUCT_BASE` | quarterly | 亿元 |
|
|
218
|
+
| `cn.m2.yoy` | 中国 M2 同比 | `RPT_ECONOMY_CURRENCY_SUPPLY` | `BASIC_CURRENCY_SAME` | monthly | % |
|
|
219
|
+
| `cn.m1.yoy` | 中国 M1 同比 | `RPT_ECONOMY_CURRENCY_SUPPLY` | `CURRENCY_SAME` | monthly | % |
|
|
220
|
+
|
|
221
|
+
> ⚠️ **口径陷阱(实测确认,务必照抄)**:在 `RPT_ECONOMY_CURRENCY_SUPPLY` 里
|
|
222
|
+
> `BASIC_CURRENCY`(≈355.5 万亿)才是 **M2**,`CURRENCY`(≈115.5 万亿)是 **M1**,
|
|
223
|
+
> `FREE_CASH`(≈14.8 万亿)是 **M0**。列名与我们的直觉相反——**不要把 `CURRENCY_SAME` 当 M2 同比**
|
|
224
|
+
> (实测两者分别为 7.7 与 4,用错会得出完全不同的结论)。目录里必须显式写 `params.column`,
|
|
225
|
+
> 并有一条断言该值的量级测试(M2 同比历史上落在 0–20% 区间)。
|
|
226
|
+
>
|
|
227
|
+
> `MAKE_SAME` / `NMAKE_SAME` 是「较上月的**变化(百分点)**」而**不是同比**,不要当 yoy 用。
|
|
228
|
+
|
|
229
|
+
**不存在的 reportName(实测返回 `code:9501 "报表配置不存在"`,不要用)**:
|
|
230
|
+
`RPT_ECONOMY_LPR`、`RPT_ECONOMY_CALENDAR`、`RPT_ECONOMY_TRADE`、`RPT_ECONOMY_SHIBOR`、
|
|
231
|
+
`RPT_ECONOMY_INTEREST_RATE`。
|
|
232
|
+
→ 若确实需要 LPR / 财经日历 / 社融,**在 M4 用一个独立 spike 任务去找真实 reportName 或替代源**,
|
|
233
|
+
找不到就把该指标标记为 `unsupported` 并在目录里注明,**不允许猜接口**。
|
|
234
|
+
|
|
235
|
+
### 3.3 中国/香港/全球指数(东方财富行情,`adapter: 'eastmoney-quote'`)
|
|
236
|
+
|
|
237
|
+
| id | 名称 | seriesRef(secid) | 频率 |
|
|
238
|
+
| --- | --- | --- | --- |
|
|
239
|
+
| `cn.idx.sse` | 上证指数 | `1.000001` | daily |
|
|
240
|
+
| `cn.idx.szse` | 深证成指 | `0.399001` | daily |
|
|
241
|
+
| `cn.idx.csi300` | 沪深 300 | `1.000300` | daily |
|
|
242
|
+
| `cn.idx.chinext` | 创业板指 | `0.399006` | daily |
|
|
243
|
+
| `cn.bond.govt.index` | 上证国债指数 | `1.000012` | daily |
|
|
244
|
+
| `cn.bond.corp.index` | 上证企债指数 | `1.000013` | daily |
|
|
245
|
+
| `hk.idx.hsi` | 恒生指数 | `100.HSI` | daily |
|
|
246
|
+
| `us.idx.dji` | 道琼斯工业指数 | `100.DJIA` | daily |
|
|
247
|
+
| `us.idx.spx` | 标普 500 | `100.SPX` | daily |
|
|
248
|
+
| `us.idx.ndx` | 纳斯达克 100 | `100.NDX` | daily |
|
|
249
|
+
| `jp.idx.n225` | 日经 225 | `100.N225` | daily |
|
|
250
|
+
|
|
251
|
+
`params`: `{ klt: 101, fqt: 1, lmt: 800 }`(101=日线,1=前复权,lmt=取最近 N 根)。
|
|
252
|
+
|
|
253
|
+
### 3.4 全球(World Bank / ECB / 美国财政部)
|
|
254
|
+
|
|
255
|
+
| id | 名称 | adapter | seriesRef | 频率 |
|
|
256
|
+
| --- | --- | --- | --- | --- |
|
|
257
|
+
| `wb.gdp.us` | 美国 GDP(现价美元) | `worldbank` | `NY.GDP.MKTP.CD` + country `US` | annual |
|
|
258
|
+
| `wb.gdp.cn` | 中国 GDP(现价美元) | `worldbank` | `NY.GDP.MKTP.CD` + country `CN` | annual |
|
|
259
|
+
| `wb.inflation.cn` | 中国通胀(CPI 同比) | `worldbank` | `FP.CPI.TOTL.ZG` + country `CN` | annual |
|
|
260
|
+
| `wb.unemployment.us` | 美国失业率(ILO 口径) | `worldbank` | `SL.UEM.TOTL.ZS` + country `US` | annual |
|
|
261
|
+
| `ecb.exr.usd` | 欧元/美元 日汇率 | `ecb` | `EXR/D.USD.EUR.SP00.A` | daily |
|
|
262
|
+
| `ust.avg.rate` | 美国国债平均利率 | `us-treasury` | `avg_interest_rates` | monthly |
|
|
263
|
+
|
|
264
|
+
中国 CPI 的 FRED 镜像 `CHNCPIALLMINMEI` 也可用(OECD 口径,**指数非同比**,需 `yoy` 变换),
|
|
265
|
+
作为 `cn.cpi.yoy` 的**交叉校验源**——两条独立来源同向才算可信,这是可选的加分项。
|
|
266
|
+
|
|
267
|
+
### 3.5 派生指标(无 source,`display.transform` 或 `derive` 指令)
|
|
268
|
+
|
|
269
|
+
| id | 名称 | 定义 |
|
|
270
|
+
| --- | --- | --- |
|
|
271
|
+
| `us.real10y` | 美国 10Y 实际利率 | `us.dgs10` − `us.breakeven10y` |
|
|
272
|
+
| `us.curve.10y3m` | 10Y−3M 利差 | `us.dgs10` − `us.dgs3m` |
|
|
273
|
+
| `us.payrolls.3m.avg` | 非农 3 月均值 | `diff` + `window: 3` 的移动平均 |
|
|
274
|
+
| `cn.us.10y.spread` | 中美 10Y 利差 | 需要中国 10Y 收益率源(**待 M4 spike**,暂标 `unsupported`) |
|
|
275
|
+
|
|
276
|
+
派生指标在目录里用 `derive: { op: 'spread'|'ratio'|'avg', operands: [...] }` 声明,
|
|
277
|
+
由 `core/stats/derive.js` 解析;**派生指标同样要有单测**,尤其是重叠区间不足的情形。
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 4. 校验器(`core/types.js`)
|
|
282
|
+
|
|
283
|
+
每个契约都配一个 `validateXxx(value)` 纯函数,返回 `{ ok, errors[] }`:
|
|
284
|
+
|
|
285
|
+
- `validateIndicatorDef`:id 格式 `^[a-z0-9]+(\.[a-z0-9]+)+$`、`importance ∈ [1,5]`、
|
|
286
|
+
`display.transform` 在枚举内、有 `source.adapter/seriesRef`。
|
|
287
|
+
- `validateRawSeries`:`points` 升序、`v` 为有限数、`t` 匹配 `^\d{4}-\d{2}-\d{2}$`、`sourceRef.url` 非空。
|
|
288
|
+
- `validateMetric` / `validateSeriesView`:必填字段存在、`status` 在枚举内。
|
|
289
|
+
|
|
290
|
+
**这些校验器在 CI 门禁里对「种子目录 + 每个适配器的 fixture 输出」全量跑一遍**
|
|
291
|
+
(见 `08-test-plan.md`),所以新增指标或适配器若不合规会立刻红灯。
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# 04 · 数据源适配器规格(含实测结果)
|
|
2
|
+
|
|
3
|
+
**本文所有端点、状态码、耗时、响应形状均为本次会话实测**(Node 24 内置 `fetch`,UA `Mozilla/5.0`)。
|
|
4
|
+
未实测的通路一律标注「未验证」,实现时不得当作事实。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 0. 实测矩阵(2026-09 本次会话)
|
|
9
|
+
|
|
10
|
+
| 数据源 | 端点 | HTTP | 耗时 | CORS | 结论 |
|
|
11
|
+
| --- | --- | --- | --- | --- | --- |
|
|
12
|
+
| **FRED CSV** | `https://fred.stlouisfed.org/graph/fredgraph.csv?id=<SERIES>&cosd=<YYYY-MM-DD>` | 200 | 0.3–3.0s | 不可用(`null` 或缺失) | ✅ **主干源,无需 API key**;**取数必须在宿主半** |
|
|
13
|
+
| **东方财富 行情** | `https://push2his.eastmoney.com/api/qt/stock/kline/get?...` | 200 | 23–120ms | 回显 Origin | ✅ 股/债指数 |
|
|
14
|
+
| **东方财富 宏观** | `https://datacenter-web.eastmoney.com/api/data/v1/get?reportName=...` | 200 | 90–225ms | `*` | ✅ 中国宏观 |
|
|
15
|
+
| **美国财政部** | `https://api.fiscaldata.treasury.gov/services/api/fiscal_service/...` | 200 | ~2.5s | `*` | ✅ 国债/财政 |
|
|
16
|
+
| **World Bank** | `https://api.worldbank.org/v2/country/<C>/indicator/<I>?format=json` | 200 | ~2.0s | `*` | ✅ 跨国年度 |
|
|
17
|
+
| **ECB SDW** | `https://data-api.ecb.europa.eu/service/data/<FLOW>?format=csvdata` | 200 | ~1.8s | `*` | ✅ 欧元区 |
|
|
18
|
+
| 腾讯行情 | `https://qt.gtimg.cn/q=sh000001,usDJI` | 200 | ~190ms | — | ⚠️ 可用但 **GBK 编码**,需解码;作为实时快照备选 |
|
|
19
|
+
| BLS 公开 API | `https://api.bls.gov/publicAPI/v2/timeseries/data/...` | **403** | 1.5s | — | ❌ 不可用(改用 FRED 镜像) |
|
|
20
|
+
| Yahoo Finance | `https://query1.finance.yahoo.com/v8/finance/chart/...` | **403** | 0.8s | — | ❌ 需要 crumb/cookie,不采用 |
|
|
21
|
+
| Stooq CSV | `https://stooq.com/q/d/l/?s=...` | 200 但返回 JS 墙 | 1.2s | — | ❌ 反爬,不采用 |
|
|
22
|
+
| 新浪行情 | `https://hq.sinajs.cn/list=...` | **403** | 4.5s | — | ❌ 需要特定 Referer,不采用 |
|
|
23
|
+
|
|
24
|
+
> 上表由 `scripts/probe-sources.mjs` 复现(**现在已经可以跑**:`node scripts/probe-sources.mjs`);
|
|
25
|
+
> M10 的 `scripts/smoke.mjs` 会复用它做巡检。日后源变更时,更新本文档与脚本的探测清单。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 1. `fred` — FRED CSV(主干源)
|
|
30
|
+
|
|
31
|
+
**为什么用它**:覆盖美国几乎所有宏观与利率指标,**无需注册 key**,CSV 极简,稳定性好。
|
|
32
|
+
(注:`api.stlouisfed.org` 的 JSON API **需要 API key**,本方案不用它。)
|
|
33
|
+
|
|
34
|
+
### 请求
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
GET https://fred.stlouisfed.org/graph/fredgraph.csv?id=PAYEMS&cosd=2024-01-01
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
参数:
|
|
41
|
+
|
|
42
|
+
| 参数 | 含义 | 建议 |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `id` | 系列 ID,**多个用逗号分隔可一次取多条** | 单指标一次;批量场景可合并 |
|
|
45
|
+
| `cosd` | 起始日期 | **必须传**,否则 `GDPC1` 之类会返回全历史(实测 6.5KB / 1947 起) |
|
|
46
|
+
| `coed` | 结束日期 | 可选,默认到今天 |
|
|
47
|
+
|
|
48
|
+
### 响应(实测)
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
observation_date,PAYEMS
|
|
52
|
+
2026-06-01,158892
|
|
53
|
+
2026-07-01,158913
|
|
54
|
+
2026-08-01,159075
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- 首行是表头,第一列恒为 `observation_date`。
|
|
58
|
+
- **缺失值写作 `.`**(例如节假日的美债收益率),必须跳过而不是当 0。
|
|
59
|
+
|
|
60
|
+
### 解析规则
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// 伪代码,实现见 src/sources/fred.js
|
|
64
|
+
const [header, ...rows] = text.trim().split('\n')
|
|
65
|
+
const valueCol = header.split(',')[1] // 即 seriesRef
|
|
66
|
+
const points = rows.map(r => r.split(','))
|
|
67
|
+
.filter(([, v]) => v && v !== '.' && v !== '')
|
|
68
|
+
.map(([t, v]) => ({ t, v: Number(v) }))
|
|
69
|
+
.filter(p => Number.isFinite(p.v))
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 溯源 URL
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
https://fred.stlouisfed.org/series/<SERIES> // 人类可读页面(图表 + 元数据 + 发布说明)
|
|
76
|
+
apiUrl: https://fred.stlouisfed.org/graph/fredgraph.csv?id=<SERIES>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 陷阱
|
|
80
|
+
|
|
81
|
+
1. **频率混合**:日频序列里周末/节假日可能完全没有行(`DGS10` 只含交易日),也可能有 `.`
|
|
82
|
+
(`DCOILWTICO` 偶有)。不要假设"每天一行"。
|
|
83
|
+
2. **发布滞后**:最新观测日 ≠ 今天。`PAYEMS` 的 8 月值通常在 9 月初发布,`raw.meta` 里不要臆造发布日期。
|
|
84
|
+
3. **数值精度**:`CHNCPIALLMINMEI` 返回 `115.004000000000005` 这类浮点噪声 → 输出前按 `display.decimals` 收敛。
|
|
85
|
+
4. **不要**把 FRED 页面上的"同比 %"当成系列本身——很多系列只有指数,同比必须由我们算(`display.transform: 'yoy'`)。
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 2. `eastmoney-quote` — 东方财富行情(股/债指数)
|
|
90
|
+
|
|
91
|
+
### 请求(实测)
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
GET https://push2his.eastmoney.com/api/qt/stock/kline/get
|
|
95
|
+
?secid=1.000001
|
|
96
|
+
&fields1=f1,f2,f3
|
|
97
|
+
&fields2=f51,f52,f53,f54,f55,f56
|
|
98
|
+
&klt=101&fqt=1&end=20500101&lmt=800
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| 参数 | 含义 |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `secid` | `<market>.<code>`;`1.`=上交所,`0.`=深交所,`100.`=国际指数 |
|
|
104
|
+
| `klt` | K 线周期:`101`=日,`102`=周,`103`=月 |
|
|
105
|
+
| `fqt` | 复权:`1`=前复权 |
|
|
106
|
+
| `lmt` | 取最近 N 根(避免拉全量) |
|
|
107
|
+
| `end` | `20500101` 表示到最新 |
|
|
108
|
+
|
|
109
|
+
### 响应(实测)
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{"rc":0,"data":{"code":"000001","market":1,"name":"上证指数",
|
|
113
|
+
"klines":["2026-09-09,3912.32,3888.11,3934.40,3852.03,579123145", ...]}}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`fields2` 的顺序即 CSV 列顺序,**标准字段号**:
|
|
117
|
+
|
|
118
|
+
| 字段 | 含义 |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| `f51` | 日期 `YYYY-MM-DD` |
|
|
121
|
+
| `f52` | 开 |
|
|
122
|
+
| `f53` | **收(我们取这个作为 v)** |
|
|
123
|
+
| `f54` | 高 |
|
|
124
|
+
| `f55` | 低 |
|
|
125
|
+
| `f56` | 成交量 |
|
|
126
|
+
| `f57` | 成交额 |
|
|
127
|
+
| `f58` | 振幅 |
|
|
128
|
+
| `f59` | 涨跌幅 |
|
|
129
|
+
| `f60` | 涨跌额 |
|
|
130
|
+
| `f61` | 换手率 |
|
|
131
|
+
|
|
132
|
+
### 溯源 URL
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
https://quote.eastmoney.com/zs000001.html // 上证指数
|
|
136
|
+
https://quote.eastmoney.com/center/gridlist.html#global_globalindex // 国际指数
|
|
137
|
+
apiUrl: 上面的 kline 请求
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 陷阱
|
|
141
|
+
|
|
142
|
+
1. `rc !== 0` 或 `data === null` → 是**业务失败**(secid 错误),归类 `SourceError.kind='unsupported'`,
|
|
143
|
+
**不要**重试。
|
|
144
|
+
2. 指数 `name` 字段是中文,用于 `meta.name`;`unit` 固定「点」。
|
|
145
|
+
3. `secid` 前缀写错时不会报错,会返回**别的标的**——所以必须有"代码↔名称"断言测试
|
|
146
|
+
(例如 `1.000001` 必须解析出名称含「上证」),Fixture 里存住名称做回归。
|
|
147
|
+
4. 单次 `lmt` 建议 ≤ 1000,过大响应变慢;`1Y` 范围用 260 根足够,`5Y` 用 1300 根 → 分页或直接 `lmt=1300`。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 3. `eastmoney-macro` — 东方财富数据中心(中国宏观)
|
|
152
|
+
|
|
153
|
+
### 请求(实测)
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
GET https://datacenter-web.eastmoney.com/api/data/v1/get
|
|
157
|
+
?reportName=RPT_ECONOMY_CPI
|
|
158
|
+
&columns=ALL
|
|
159
|
+
&pageSize=60
|
|
160
|
+
&sortColumns=REPORT_DATE
|
|
161
|
+
&sortTypes=-1
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 响应(实测,节选)
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{"version":"…","result":{"pages":45,"data":[
|
|
168
|
+
{"REPORT_DATE":"2026-08-01 00:00:00","TIME":"2026年08月份","NATIONAL…":"…"}]},
|
|
169
|
+
"success":true}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**已确认存在**的 reportName:`RPT_ECONOMY_CPI`、`RPT_ECONOMY_PPI`、`RPT_ECONOMY_PMI`、
|
|
173
|
+
`RPT_ECONOMY_GDP`、`RPT_ECONOMY_CURRENCY_SUPPLY`。
|
|
174
|
+
|
|
175
|
+
**已确认不存在**(返回 `{"success":false,"code":9501,"message":"报表配置不存在,…"}`):
|
|
176
|
+
`RPT_ECONOMY_LPR`、`RPT_ECONOMY_CALENDAR`、`RPT_ECONOMY_TRADE`、`RPT_ECONOMY_SHIBOR`、
|
|
177
|
+
`RPT_ECONOMY_INTEREST_RATE`。
|
|
178
|
+
|
|
179
|
+
### 已知字段(实测可见)
|
|
180
|
+
|
|
181
|
+
| 报表 | 字段 | 含义 |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| CPI | `REPORT_DATE`, `TIME` | 报告期 与「2026年08月份」文本 |
|
|
184
|
+
| CPI | `NATIONAL_SAME` | **同比** → `cn.cpi.yoy` 取这一列 |
|
|
185
|
+
| CPI | `NATIONAL_SEQUENTIAL` | **环比** → `cn.cpi.mom` 取这一列 |
|
|
186
|
+
| CPI | `NATIONAL_BASE` | 指数(定基) |
|
|
187
|
+
| CPI | `NATIONAL_ACCUMULATE` | 累计同比 |
|
|
188
|
+
| PPI | `BASE: 103.8` / `BASE_SAME: 3.8` / `BASE_ACCUMULATE: 102` | 指数 / **同比** / 累计 |
|
|
189
|
+
| PMI | `MAKE_INDEX: 49.8` / `MAKE_SAME: 0.81` | 制造业 PMI / **较上月变化(pp,不是同比)** |
|
|
190
|
+
| PMI | `NMAKE_INDEX: 49.0` / `NMAKE_SAME: -2.58` | 非制造业 PMI / 同上 |
|
|
191
|
+
| GDP | `DOMESTICL_PRODUCT_BASE: 695704` | 现价累计总量(**亿元**;注意上游拼写 `DOMESTICL`) |
|
|
192
|
+
| GDP | `SUM_SAME: 4.7` | **GDP 同比** → `cn.gdp.yoy` 取这一列 |
|
|
193
|
+
| GDP | `FIRST/SECOND/THIRD_PRODUCT_BASE`、`FIRST/SECOND/THIRD_SAME` | 分产业总量与同比 |
|
|
194
|
+
| 货币供应 | `BASIC_CURRENCY: 3555077.24` / `BASIC_CURRENCY_SAME: 7.7` | **这是 M2**(余额/同比) |
|
|
195
|
+
| 货币供应 | `CURRENCY: 1154623` / `CURRENCY_SAME: 4` | **这是 M1** |
|
|
196
|
+
| 货币供应 | `FREE_CASH: 148202.86` / `FREE_CASH_SAME: 11.6` | **这是 M0** |
|
|
197
|
+
| 货币供应 | `*_SEQUENTIAL` | 环比(%) |
|
|
198
|
+
|
|
199
|
+
> 全表由 `scripts/probe-sources.mjs` + 直接枚举 keys 实测得到(2026-09)。
|
|
200
|
+
> ⚠️ **`BASIC_CURRENCY` = M2、`CURRENCY` = M1**,与直觉相反;而且 `MAKE_SAME`/`NMAKE_SAME`
|
|
201
|
+
> 是"较上月变化(pp)"而**不是同比**。这两点必须有测试覆盖,否则会产出看似合理但完全错误的结论。
|
|
202
|
+
|
|
203
|
+
### 适配器的额外职责
|
|
204
|
+
|
|
205
|
+
`params.column` 指定取哪一列作为 `v`。因此**同一个 reportName 可以产出多个指标**
|
|
206
|
+
(`cn.cpi.yoy` 与 `cn.cpi.mom` 共用 `RPT_ECONOMY_CPI`,只是列不同)。
|
|
207
|
+
|
|
208
|
+
### 溯源 URL
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
https://data.eastmoney.com/cjsj/cpi.html // CPI
|
|
212
|
+
https://data.eastmoney.com/cjsj/ppi.html // PPI
|
|
213
|
+
https://data.eastmoney.com/cjsj/pmi.html // PMI
|
|
214
|
+
https://data.eastmoney.com/cjsj/gdp.html // GDP
|
|
215
|
+
apiUrl: 上面的 datacenter 请求
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 陷阱
|
|
219
|
+
|
|
220
|
+
1. `REPORT_DATE` 是 `"2026-08-01 00:00:00"` 形式 → 归一化时**只取日期部分**。
|
|
221
|
+
2. **数据是倒序**(`sortTypes=-1`);适配器必须按时间升序输出,否则统计全错。
|
|
222
|
+
3. 上游列名存在拼写问题(`DOMESTICL_PRODUCT_BASE`),**照抄不要"纠正"**。
|
|
223
|
+
4. `success:false` 时 HTTP 仍是 200 → 必须检查 body。这是本适配器最容易漏的测试点。
|
|
224
|
+
5. 该站点接口**非官方承诺**,字段可能变更 → 必须有 `parse` 层的**显式字段存在性断言**,
|
|
225
|
+
缺字段时抛 `SourceError.kind='parse'`,而不是静默产出 `undefined`。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 4. `us-treasury` — 美国财政部 fiscaldata
|
|
230
|
+
|
|
231
|
+
### 请求(实测)
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
GET https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/avg_interest_rates
|
|
235
|
+
?page[size]=100&sort=-record_date
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 响应(实测)
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{"data":[{"record_date":"2001-01-31","security_type_desc":"Marketable",
|
|
242
|
+
"security_desc":"Treasury Notes","avg_interest_rate_amt":"6.096", …}]}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 陷阱
|
|
246
|
+
|
|
247
|
+
1. 数值是**字符串**,且可能为空串 → 必须 `Number()` 后过滤 `NaN`。
|
|
248
|
+
2. 数据集按 `security_desc` 分行(Notes / Bonds / Bills…)→ `params.filter` 支撑
|
|
249
|
+
`filter=security_desc:eq:Treasury Notes`;不写 filter 会混入多种证券类型。
|
|
250
|
+
3. 分页是 `page[size]` / `page[number]`,`meta.total-pages` 可用。
|
|
251
|
+
|
|
252
|
+
### 溯源 URL
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
https://fiscaldata.treasury.gov/datasets/average-interest-rates-treasury-securities/
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 5. `worldbank` — 世界银行
|
|
261
|
+
|
|
262
|
+
### 请求(实测)
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
GET https://api.worldbank.org/v2/country/US;CN/indicator/NY.GDP.MKTP.CD?format=json&per_page=100
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 响应(实测)
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
[{"page":1,"pages":27,"per_page":100,"total":132,"lastupdated":"2026-07-13"},
|
|
272
|
+
[{"indicator":{"id":"NY.GDP.MKTP.CD","value":"GDP (current US$)"},
|
|
273
|
+
"country":{"id":"US","value":"United States"},
|
|
274
|
+
"date":"2025","value":30000000000000}, …]]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 陷阱
|
|
278
|
+
|
|
279
|
+
1. 顶层是**二元数组**:`[meta, rows]`;无数据时 `rows` 为 `null`(不是空数组)→ 必须处理。
|
|
280
|
+
2. `date` 只有年份 → 归一化为 `<YYYY>-12-31`(年末),并在 `meta.freq='annual'` 标注;
|
|
281
|
+
不要虚构 `-01-01`,与"年度值"的口径不符。
|
|
282
|
+
3. `value` 可能是 `null`(该国该年无数据)→ 跳过。
|
|
283
|
+
4. **年度数据发布滞后 1–2 年**,卡片必须允许"最后观测日"远早于今天,不能一律判为 stale 报错
|
|
284
|
+
(规则:按 freq 计算陈旧阈值,annual 的阈值是 400 天)。
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 6. `ecb` — 欧洲央行 SDW
|
|
289
|
+
|
|
290
|
+
### 请求(实测)
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
GET https://data-api.ecb.europa.eu/service/data/EXR/D.USD.EUR.SP00.A?format=csvdata&lastNObservations=30
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 响应(实测)
|
|
297
|
+
|
|
298
|
+
CSV,首行是长表头:`KEY,FREQ,CURRENCY,CURRENCY_DENOM,EXR_TYPE,EXR_SUFFIX,TIME_PERIOD,OBS_VALUE,…`
|
|
299
|
+
|
|
300
|
+
- 取 `TIME_PERIOD`(`YYYY-MM-DD`)与 `OBS_VALUE`(数值)。
|
|
301
|
+
- `lastNObservations` 是必须用的参数,否则拉全历史。
|
|
302
|
+
|
|
303
|
+
### 溯源 URL
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
https://data.ecb.europa.eu/data/datasets/EXR
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 7. 适配器契约测试(所有适配器共用)
|
|
312
|
+
|
|
313
|
+
`test/contract/source-adapter.test.js` 遍历 `src/sources/registry.js` 的注册表,对**每个**适配器断言:
|
|
314
|
+
|
|
315
|
+
1. 导出齐全:`id`(小写 kebab)、`label`、`capabilities`、`fetchSeries`、`sourceRef`。
|
|
316
|
+
2. `fetchSeries` 对同一 fixture **可重复调用且结果一致**(纯函数性)。
|
|
317
|
+
3. 输出满足 `validateRawSeries`(升序、有限数、日期格式、`sourceRef.url` 可达格式)。
|
|
318
|
+
4. 缺失值处理:fixture 里含 `.` / `null` / `""` 时,产出中**没有**这些点。
|
|
319
|
+
5. 错误分类:
|
|
320
|
+
- fixture 为 HTML 错误页 → `kind='parse'` 或 `'http'`;
|
|
321
|
+
- fixture 为 `success:false` → `kind='unsupported'`(不重试);
|
|
322
|
+
- fixture 为空 → `kind='empty'`。
|
|
323
|
+
6. `sourceRef(seriesRef).url` 对目录里每个用到该适配器的指标都非空,且是 `https://`。
|
|
324
|
+
7. **不泄漏上游私有字段**:返回值里除 `raw` 外不得出现上游原始键名。
|
|
325
|
+
|
|
326
|
+
只要满足契约,新适配器**零改动**即可接入(见 `02` 文档 §5.2)。
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 8. 取数策略与降级
|
|
331
|
+
|
|
332
|
+
| 策略 | 做法 | 必须的单测 |
|
|
333
|
+
| --- | --- | --- |
|
|
334
|
+
| 超时 | 每个请求 15s(`AbortController`),在用例层注入 `signal` | 超时 → `kind='network'`, `retryable=true` |
|
|
335
|
+
| 重试 | 仅 `network`/`http(5xx)` 重试 1 次,退避 500ms;`parse`/`unsupported` 不重试 | 重试次数断言(用假 fetch 计数) |
|
|
336
|
+
| 并发 | 同一 `key` 的 in-flight 请求合并 | 并发 5 次调用 → 上游只被调 1 次 |
|
|
337
|
+
| 缓存 | TTL 按频率(日 15min / 月 6h / 季 24h) | 过期前不重取、过期后重取 |
|
|
338
|
+
| 降级 | 失败返回上次快照 + `status='stale'` | 无快照 → `status='error'`;有快照 → `stale` 且数值等于快照 |
|
|
339
|
+
| 批量 | `overview` 用 `Promise.allSettled`,单项失败不影响其他 | 5 项里 2 项失败 → 返回 5 项,其中 2 项带状态 |
|
|
340
|
+
|
|
341
|
+
**免责声明**:面板与 AI 输出必须带一行固定文案——「数据来自公开源,仅供研究参考,不构成投资建议;
|
|
342
|
+
口径以原始来源为准」。这句话是 UI 常量,不进 AI 提示词由模型自由发挥。
|