@hedgehog-finance/hedgehog-plugin 1.0.21 → 1.0.22
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/dist/index.d.ts +4 -3
- package/dist/index.js +49 -6
- package/dist/index.js.map +1 -1
- package/dist/setup-api.d.ts +2 -0
- package/dist/setup-api.js +8 -0
- package/dist/setup-api.js.map +1 -0
- package/dist/src/channel.js +25 -23
- package/dist/src/channel.js.map +1 -1
- package/dist/src/core/database.js +449 -39
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/dailyMorningBriefingCron.d.ts +46 -0
- package/dist/src/dailyMorningBriefingCron.js +82 -0
- package/dist/src/dailyMorningBriefingCron.js.map +1 -0
- package/dist/src/features/chartOutput.d.ts +2 -0
- package/dist/src/features/chartOutput.js +35 -0
- package/dist/src/features/chartOutput.js.map +1 -0
- package/dist/src/features/dailyMorningBriefing/schema.d.ts +56 -0
- package/dist/src/features/dailyMorningBriefing/schema.js +22 -0
- package/dist/src/features/dailyMorningBriefing/schema.js.map +1 -0
- package/dist/src/features/dailyMorningBriefing/tools.d.ts +12 -0
- package/dist/src/features/dailyMorningBriefing/tools.js +204 -0
- package/dist/src/features/dailyMorningBriefing/tools.js.map +1 -0
- package/dist/src/features/deepReasoning/schema.d.ts +43 -0
- package/dist/src/features/deepReasoning/schema.js +17 -0
- package/dist/src/features/deepReasoning/schema.js.map +1 -0
- package/dist/src/features/deepReasoning/tools.d.ts +12 -0
- package/dist/src/features/deepReasoning/tools.js +163 -0
- package/dist/src/features/deepReasoning/tools.js.map +1 -0
- package/dist/src/features/index.d.ts +2 -1
- package/dist/src/features/index.js +9 -1
- package/dist/src/features/index.js.map +1 -1
- package/dist/src/features/informationVerification/schema.d.ts +43 -0
- package/dist/src/features/informationVerification/schema.js +17 -0
- package/dist/src/features/informationVerification/schema.js.map +1 -0
- package/dist/src/features/informationVerification/tools.d.ts +12 -0
- package/dist/src/features/informationVerification/tools.js +162 -0
- package/dist/src/features/informationVerification/tools.js.map +1 -0
- package/dist/src/features/notes/schema.d.ts +136 -39
- package/dist/src/features/notes/schema.js +13 -10
- package/dist/src/features/notes/schema.js.map +1 -1
- package/dist/src/features/notes/tools.d.ts +1 -0
- package/dist/src/features/notes/tools.js +47 -14
- package/dist/src/features/notes/tools.js.map +1 -1
- package/dist/src/features/pluginInfo/schema.d.ts +79 -0
- package/dist/src/features/pluginInfo/schema.js +14 -0
- package/dist/src/features/pluginInfo/schema.js.map +1 -0
- package/dist/src/features/pluginInfo/tools.d.ts +1 -0
- package/dist/src/features/pluginInfo/tools.js +157 -2
- package/dist/src/features/pluginInfo/tools.js.map +1 -1
- package/dist/src/features/profileLibrary/schema.d.ts +34 -6
- package/dist/src/features/profileLibrary/schema.js +1 -1
- package/dist/src/features/profileLibrary/schema.js.map +1 -1
- package/dist/src/features/stockAnalysis/schema.d.ts +224 -31
- package/dist/src/features/stockAnalysis/schema.js +76 -12
- package/dist/src/features/stockAnalysis/schema.js.map +1 -1
- package/dist/src/features/stockAnalysis/tools.d.ts +6 -4
- package/dist/src/features/stockAnalysis/tools.js +389 -44
- package/dist/src/features/stockAnalysis/tools.js.map +1 -1
- package/dist/src/features/stockBasic/schema.d.ts +149 -0
- package/dist/src/features/stockBasic/schema.js +26 -0
- package/dist/src/features/stockBasic/schema.js.map +1 -0
- package/dist/src/features/stockBasic/tools.d.ts +12 -0
- package/dist/src/features/stockBasic/tools.js +124 -0
- package/dist/src/features/stockBasic/tools.js.map +1 -0
- package/dist/src/features/watchlist/logic.d.ts +3 -3
- package/dist/src/features/watchlist/logic.js +47 -46
- package/dist/src/features/watchlist/logic.js.map +1 -1
- package/dist/src/features/watchlist/schema.d.ts +89 -54
- package/dist/src/features/watchlist/schema.js +7 -4
- package/dist/src/features/watchlist/schema.js.map +1 -1
- package/dist/src/features/watchlist/tools.d.ts +106 -59
- package/dist/src/features/watchlist/tools.js +182 -104
- package/dist/src/features/watchlist/tools.js.map +1 -1
- package/dist/src/openclawConfig.d.ts +6 -0
- package/dist/src/openclawConfig.js +74 -0
- package/dist/src/openclawConfig.js.map +1 -0
- package/dist/src/openclawConstants.d.ts +4 -0
- package/dist/src/openclawConstants.js +10 -0
- package/dist/src/openclawConstants.js.map +1 -0
- package/dist/src/runtime.js +20 -0
- package/dist/src/runtime.js.map +1 -1
- package/index.ts +52 -5
- package/openclaw.plugin.json +24 -0
- package/package.json +21 -6
- package/setup-api.ts +10 -0
- package/src/channel.ts +26 -25
- package/src/core/database.ts +447 -40
- package/src/dailyMorningBriefingCron.ts +129 -0
- package/src/features/chartOutput.ts +35 -0
- package/src/features/dailyMorningBriefing/schema.ts +38 -0
- package/src/features/dailyMorningBriefing/tools.ts +246 -0
- package/src/features/deepReasoning/schema.ts +22 -0
- package/src/features/deepReasoning/tools.ts +182 -0
- package/src/features/index.ts +11 -2
- package/src/features/informationVerification/schema.ts +22 -0
- package/src/features/informationVerification/tools.ts +181 -0
- package/src/features/notes/schema.ts +17 -12
- package/src/features/notes/tools.ts +54 -17
- package/src/features/pluginInfo/schema.ts +19 -0
- package/src/features/pluginInfo/tools.ts +173 -2
- package/src/features/profileLibrary/schema.ts +1 -1
- package/src/features/stockAnalysis/schema.ts +99 -17
- package/src/features/stockAnalysis/tools.ts +447 -49
- package/src/features/stockBasic/schema.ts +33 -0
- package/src/features/stockBasic/tools.ts +157 -0
- package/src/features/watchlist/logic.ts +56 -53
- package/src/features/watchlist/schema.ts +11 -6
- package/src/features/watchlist/tools.ts +191 -106
- package/src/openclawConfig.ts +101 -0
- package/src/openclawConstants.ts +11 -0
- package/src/runtime.ts +19 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { getDB } from "../../core/database.js";
|
|
2
|
+
import {
|
|
3
|
+
GetStockBasicListParamsSchema,
|
|
4
|
+
GetStockBasicInfoParamsSchema,
|
|
5
|
+
StockBasicItem,
|
|
6
|
+
SyncStockBasicParamsSchema
|
|
7
|
+
} from "./schema.js";
|
|
8
|
+
|
|
9
|
+
interface RuntimeTool {
|
|
10
|
+
name: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
description: string;
|
|
13
|
+
parameters: unknown;
|
|
14
|
+
registerTool?: boolean;
|
|
15
|
+
execute(params: unknown, ctx?: { userId: string }): Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const GetStockBasicInfoAgentToolSchema = {
|
|
19
|
+
type: "object",
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
required: ["stock_code"],
|
|
22
|
+
properties: {
|
|
23
|
+
stock_code: { type: "string", description: "股票代码,例如:000001.SZ 或 600000.SH" }
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function normalizeStockBasic(stock: StockBasicItem): StockBasicItem {
|
|
28
|
+
return {
|
|
29
|
+
...stock,
|
|
30
|
+
stock_code: stock.stock_code.trim().toUpperCase().replace(/\.SS$/i, ".SH"),
|
|
31
|
+
symbol: stock.symbol.trim(),
|
|
32
|
+
exchange: stock.exchange.trim().toUpperCase(),
|
|
33
|
+
name: stock.name.trim(),
|
|
34
|
+
fullname: stock.fullname.trim(),
|
|
35
|
+
enname: stock.enname.trim(),
|
|
36
|
+
cnspell: stock.cnspell.trim().toUpperCase()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function escapeLike(value: string): string {
|
|
41
|
+
return value.replace(/[\\%_]/g, "\\$&");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const stockBasicTools: Record<string, RuntimeTool> = {
|
|
45
|
+
sync_stock_basic: {
|
|
46
|
+
name: "sync_stock_basic",
|
|
47
|
+
description: "批量同步证券基础资料主数据。系统以 stock_code 作为唯一标识执行新增或覆盖更新,适用于初始化或定期刷新股票代码、名称、交易所、行业、上市日期等基础字段。",
|
|
48
|
+
parameters: SyncStockBasicParamsSchema,
|
|
49
|
+
registerTool: false,
|
|
50
|
+
async execute(params) {
|
|
51
|
+
const args = SyncStockBasicParamsSchema.parse(params);
|
|
52
|
+
const db = getDB();
|
|
53
|
+
const stmt = db.prepare(`
|
|
54
|
+
INSERT INTO stock_basic (
|
|
55
|
+
stock_code,
|
|
56
|
+
symbol,
|
|
57
|
+
name,
|
|
58
|
+
fullname,
|
|
59
|
+
enname,
|
|
60
|
+
cnspell,
|
|
61
|
+
exchange,
|
|
62
|
+
market,
|
|
63
|
+
industry,
|
|
64
|
+
area,
|
|
65
|
+
curr_type,
|
|
66
|
+
list_date,
|
|
67
|
+
is_hs,
|
|
68
|
+
act_name,
|
|
69
|
+
act_ent_type
|
|
70
|
+
)
|
|
71
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
72
|
+
ON CONFLICT(stock_code) DO UPDATE SET
|
|
73
|
+
symbol = excluded.symbol,
|
|
74
|
+
name = excluded.name,
|
|
75
|
+
fullname = excluded.fullname,
|
|
76
|
+
enname = excluded.enname,
|
|
77
|
+
cnspell = excluded.cnspell,
|
|
78
|
+
exchange = excluded.exchange,
|
|
79
|
+
market = excluded.market,
|
|
80
|
+
industry = excluded.industry,
|
|
81
|
+
area = excluded.area,
|
|
82
|
+
curr_type = excluded.curr_type,
|
|
83
|
+
list_date = excluded.list_date,
|
|
84
|
+
is_hs = excluded.is_hs,
|
|
85
|
+
act_name = excluded.act_name,
|
|
86
|
+
act_ent_type = excluded.act_ent_type,
|
|
87
|
+
updatedAt = STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')
|
|
88
|
+
`);
|
|
89
|
+
|
|
90
|
+
if (db.inTransaction) db.exec("ROLLBACK");
|
|
91
|
+
db.exec("BEGIN TRANSACTION");
|
|
92
|
+
try {
|
|
93
|
+
for (const rawStock of args.stocks) {
|
|
94
|
+
const stock = normalizeStockBasic(rawStock);
|
|
95
|
+
stmt.run(
|
|
96
|
+
stock.stock_code,
|
|
97
|
+
stock.symbol,
|
|
98
|
+
stock.name,
|
|
99
|
+
stock.fullname,
|
|
100
|
+
stock.enname,
|
|
101
|
+
stock.cnspell,
|
|
102
|
+
stock.exchange,
|
|
103
|
+
stock.market,
|
|
104
|
+
stock.industry,
|
|
105
|
+
stock.area,
|
|
106
|
+
stock.curr_type,
|
|
107
|
+
stock.list_date,
|
|
108
|
+
stock.is_hs,
|
|
109
|
+
stock.act_name,
|
|
110
|
+
stock.act_ent_type
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
db.exec("COMMIT");
|
|
114
|
+
return JSON.stringify({ success: true, synced: args.stocks.length });
|
|
115
|
+
} catch (e: any) {
|
|
116
|
+
if (db.inTransaction) db.exec("ROLLBACK");
|
|
117
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
get_stock_basic_list: {
|
|
123
|
+
name: "get_stock_basic_list",
|
|
124
|
+
description: "查询证券基础资料全量列表。返回股票代码、证券简称、公司全称、交易所、市场板块、行业及上市日期等基础字段,用于前端缓存、下拉选择或基础数据校验。",
|
|
125
|
+
parameters: GetStockBasicListParamsSchema,
|
|
126
|
+
registerTool: false,
|
|
127
|
+
async execute(params) {
|
|
128
|
+
GetStockBasicListParamsSchema.parse(params);
|
|
129
|
+
const db = getDB();
|
|
130
|
+
const rows = db.prepare(`
|
|
131
|
+
SELECT stock_code, symbol, name, fullname, enname, cnspell, exchange, market, industry, area, curr_type, list_date, is_hs, act_name, act_ent_type, createdAt, updatedAt
|
|
132
|
+
FROM stock_basic
|
|
133
|
+
ORDER BY exchange ASC, symbol ASC
|
|
134
|
+
`).all();
|
|
135
|
+
return JSON.stringify({ success: true, data: rows });
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
get_stock_basic_info: {
|
|
140
|
+
name: "get_stock_basic_info",
|
|
141
|
+
label: "查询股票基本信息",
|
|
142
|
+
description: "根据股票代码查询证券基础资料。",
|
|
143
|
+
parameters: GetStockBasicInfoAgentToolSchema,
|
|
144
|
+
registerTool: true,
|
|
145
|
+
async execute(params) {
|
|
146
|
+
const args = GetStockBasicInfoParamsSchema.parse(params);
|
|
147
|
+
const db = getDB();
|
|
148
|
+
const code = args.stock_code.trim().toUpperCase().replace(/\.SS$/i, ".SH");
|
|
149
|
+
const row = db.prepare(`
|
|
150
|
+
SELECT stock_code, name, enname, exchange, market, industry, is_hs
|
|
151
|
+
FROM stock_basic
|
|
152
|
+
WHERE stock_code = ?
|
|
153
|
+
`).get(code);
|
|
154
|
+
return JSON.stringify({ success: true, data: row || null });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
@@ -10,10 +10,10 @@ import { logger } from "../../core/logger.js";
|
|
|
10
10
|
import { StockClassification, StockClassificationSchema } from "../../types.js";
|
|
11
11
|
|
|
12
12
|
interface GlobalStockMetadataRow {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
industry_classification: string;
|
|
14
|
+
theme_classification: string;
|
|
15
|
+
stock_name: string;
|
|
16
|
+
last_updated: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const MAIN_AGENT_ID = "hedgehog-finance";
|
|
@@ -32,8 +32,8 @@ const CLASSIFIER_SYSTEM_PROMPT = [
|
|
|
32
32
|
"只允许根据用户消息中提供的行业/主题分类字典和股票列表输出纯 JSON 数组。"
|
|
33
33
|
].join("\n");
|
|
34
34
|
|
|
35
|
-
function normalizeStockCodeForCache(
|
|
36
|
-
const code = String(
|
|
35
|
+
function normalizeStockCodeForCache(stock_code: string, exchange?: string): string {
|
|
36
|
+
const code = String(stock_code || "")
|
|
37
37
|
.trim()
|
|
38
38
|
.toUpperCase();
|
|
39
39
|
if (/\.(SH|SS|SZ|HK|US)$/i.test(code)) {
|
|
@@ -51,19 +51,19 @@ function normalizeStockCodeForCache(stockCode: string, exchange?: string): strin
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function legacyStockCodeWithoutSuffix(
|
|
55
|
-
return String(
|
|
54
|
+
function legacyStockCodeWithoutSuffix(stock_code: string): string {
|
|
55
|
+
return String(stock_code || "")
|
|
56
56
|
.trim()
|
|
57
57
|
.toUpperCase()
|
|
58
58
|
.replace(/\.(SH|SS|SZ|HK|US)$/i, "");
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function getCachedClassificationRow(db: any,
|
|
62
|
-
const cacheCode = normalizeStockCodeForCache(
|
|
63
|
-
const legacyCode = legacyStockCodeWithoutSuffix(
|
|
61
|
+
function getCachedClassificationRow(db: any, stock_code: string, exchange: string): GlobalStockMetadataRow | undefined {
|
|
62
|
+
const cacheCode = normalizeStockCodeForCache(stock_code, exchange);
|
|
63
|
+
const legacyCode = legacyStockCodeWithoutSuffix(stock_code);
|
|
64
64
|
const stmt = db.prepare(`
|
|
65
|
-
SELECT
|
|
66
|
-
WHERE
|
|
65
|
+
SELECT industry_classification, theme_classification FROM stock_classification_cache
|
|
66
|
+
WHERE stock_code = ? AND exchange = ?
|
|
67
67
|
`);
|
|
68
68
|
return (stmt.get(cacheCode, exchange) || (legacyCode !== cacheCode ? stmt.get(legacyCode, exchange) : undefined)) as GlobalStockMetadataRow | undefined;
|
|
69
69
|
}
|
|
@@ -180,27 +180,27 @@ export const watchlistLogic = {
|
|
|
180
180
|
|
|
181
181
|
async getStockClassification(
|
|
182
182
|
rt: PluginRuntime,
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
stock_name: string,
|
|
184
|
+
stock_code: string,
|
|
185
185
|
exchange: string,
|
|
186
186
|
_userId: string
|
|
187
187
|
): Promise<StockClassification | null> {
|
|
188
188
|
const db = getDB();
|
|
189
189
|
|
|
190
|
-
const cached = getCachedClassificationRow(db,
|
|
190
|
+
const cached = getCachedClassificationRow(db, stock_code, exchange);
|
|
191
191
|
|
|
192
|
-
if (cached && cached.
|
|
192
|
+
if (cached && cached.industry_classification) {
|
|
193
193
|
try {
|
|
194
194
|
return watchlistLogic._normalizeCachedClassification({
|
|
195
|
-
industry: JSON.parse(cached.
|
|
196
|
-
theme: JSON.parse(cached.
|
|
195
|
+
industry: JSON.parse(cached.industry_classification),
|
|
196
|
+
theme: JSON.parse(cached.theme_classification || '[]'),
|
|
197
197
|
weight: 50
|
|
198
198
|
});
|
|
199
199
|
} catch (e) {
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const classification = await watchlistLogic._autoClassifyWithAI(rt,
|
|
203
|
+
const classification = await watchlistLogic._autoClassifyWithAI(rt, stock_name, stock_code, exchange);
|
|
204
204
|
|
|
205
205
|
return classification;
|
|
206
206
|
},
|
|
@@ -212,16 +212,16 @@ export const watchlistLogic = {
|
|
|
212
212
|
): Promise<StockClassification[]> {
|
|
213
213
|
const db = getDB();
|
|
214
214
|
const results = new Array<StockClassification | null>(stocks.length).fill(null);
|
|
215
|
-
const pendingStocks: { idx: number,
|
|
215
|
+
const pendingStocks: { idx: number, stock_name: string, stock_code: string, exchange: string }[] = [];
|
|
216
216
|
|
|
217
217
|
stocks.forEach((stock, idx) => {
|
|
218
|
-
const cached = getCachedClassificationRow(db, stock.
|
|
218
|
+
const cached = getCachedClassificationRow(db, stock.stock_code, stock.exchange);
|
|
219
219
|
|
|
220
|
-
if (cached && cached.
|
|
220
|
+
if (cached && cached.industry_classification) {
|
|
221
221
|
try {
|
|
222
222
|
results[idx] = watchlistLogic._normalizeCachedClassification({
|
|
223
|
-
industry: JSON.parse(cached.
|
|
224
|
-
theme: JSON.parse(cached.
|
|
223
|
+
industry: JSON.parse(cached.industry_classification),
|
|
224
|
+
theme: JSON.parse(cached.theme_classification || '[]'),
|
|
225
225
|
weight: 50
|
|
226
226
|
});
|
|
227
227
|
return;
|
|
@@ -231,8 +231,8 @@ export const watchlistLogic = {
|
|
|
231
231
|
|
|
232
232
|
pendingStocks.push({
|
|
233
233
|
idx,
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
stock_name: stock.stock_name,
|
|
235
|
+
stock_code: stock.stock_code,
|
|
236
236
|
exchange: stock.exchange
|
|
237
237
|
});
|
|
238
238
|
});
|
|
@@ -246,7 +246,7 @@ export const watchlistLogic = {
|
|
|
246
246
|
throw new Error("行业分类字典为空,无法分析行业/主题关系");
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
const stocksList = pendingStocks.map(s => `- ${s.
|
|
249
|
+
const stocksList = pendingStocks.map(s => `- ${s.stock_name} (${s.stock_code})`).join("\n");
|
|
250
250
|
const prompt = watchlistLogic._buildAiPrompt(cats.industries, cats.themes, stocksList, true);
|
|
251
251
|
const sessionId = `classify-batch-${randomUUID()}`;
|
|
252
252
|
const aiText = await watchlistLogic._callClassifierAi(rt, sessionId, prompt, resolveBatchClassificationTimeoutMs(pendingStocks.length));
|
|
@@ -256,7 +256,7 @@ export const watchlistLogic = {
|
|
|
256
256
|
} catch (e) {
|
|
257
257
|
logger.warn({
|
|
258
258
|
err: e instanceof Error ? e.message : String(e),
|
|
259
|
-
pendingCodes: pendingStocks.map(stock => stock.
|
|
259
|
+
pendingCodes: pendingStocks.map(stock => stock.stock_code),
|
|
260
260
|
aiTextLength: aiText.length,
|
|
261
261
|
aiText
|
|
262
262
|
}, "[Watchlist] batch classification AI parse failed");
|
|
@@ -271,16 +271,16 @@ export const watchlistLogic = {
|
|
|
271
271
|
let parsedResults: { stock: typeof pendingStocks[number]; data: StockClassification }[];
|
|
272
272
|
try {
|
|
273
273
|
parsedResults = pendingStocks.map((stock, i) => {
|
|
274
|
-
const raw = parsedByCode.get(String(stock.
|
|
274
|
+
const raw = parsedByCode.get(String(stock.stock_code)) || parsed[i];
|
|
275
275
|
return {
|
|
276
276
|
stock,
|
|
277
|
-
data: watchlistLogic._parseClassification(raw, cats, stock.
|
|
277
|
+
data: watchlistLogic._parseClassification(raw, cats, stock.stock_name || stock.stock_code)
|
|
278
278
|
};
|
|
279
279
|
});
|
|
280
280
|
} catch (e) {
|
|
281
281
|
logger.warn({
|
|
282
282
|
err: e instanceof Error ? e.message : String(e),
|
|
283
|
-
pendingCodes: pendingStocks.map(stock => stock.
|
|
283
|
+
pendingCodes: pendingStocks.map(stock => stock.stock_code),
|
|
284
284
|
aiTextLength: aiText.length,
|
|
285
285
|
aiText
|
|
286
286
|
}, "[Watchlist] batch classification AI semantic parse failed");
|
|
@@ -293,7 +293,7 @@ export const watchlistLogic = {
|
|
|
293
293
|
|
|
294
294
|
const missing = stocks.find((_, i) => !results[i]);
|
|
295
295
|
if (missing) {
|
|
296
|
-
throw new Error(`行业/主题关系分析失败: ${missing.
|
|
296
|
+
throw new Error(`行业/主题关系分析失败: ${missing.stock_name || missing.stock_code}`);
|
|
297
297
|
}
|
|
298
298
|
return results as StockClassification[];
|
|
299
299
|
},
|
|
@@ -310,19 +310,19 @@ export const watchlistLogic = {
|
|
|
310
310
|
stocks.forEach((s, i) => {
|
|
311
311
|
const cached = options.forceRefresh
|
|
312
312
|
? undefined
|
|
313
|
-
: getCachedClassificationRow(db, s.
|
|
314
|
-
if (cached && cached.
|
|
313
|
+
: getCachedClassificationRow(db, s.stock_code, s.exchange);
|
|
314
|
+
if (cached && cached.industry_classification) {
|
|
315
315
|
try {
|
|
316
316
|
results[i] = watchlistLogic._normalizeCachedClassification({
|
|
317
|
-
industry: JSON.parse(cached.
|
|
318
|
-
theme: JSON.parse(cached.
|
|
317
|
+
industry: JSON.parse(cached.industry_classification),
|
|
318
|
+
theme: JSON.parse(cached.theme_classification || '[]'),
|
|
319
319
|
weight: 50
|
|
320
320
|
});
|
|
321
321
|
} catch (e) {
|
|
322
|
-
pendingStocks.push({ idx: i, name: s.
|
|
322
|
+
pendingStocks.push({ idx: i, name: s.stock_name, code: s.stock_code, exchange: s.exchange });
|
|
323
323
|
}
|
|
324
324
|
} else {
|
|
325
|
-
pendingStocks.push({ idx: i, name: s.
|
|
325
|
+
pendingStocks.push({ idx: i, name: s.stock_name, code: s.stock_code, exchange: s.exchange });
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
328
|
if (pendingStocks.length > 0) {
|
|
@@ -362,15 +362,15 @@ export const watchlistLogic = {
|
|
|
362
362
|
if (options.requireComplete) {
|
|
363
363
|
const missing = stocks.find((_, i) => !results[i]);
|
|
364
364
|
if (missing) {
|
|
365
|
-
throw new Error(`行业/主题关系分析失败: ${missing.
|
|
365
|
+
throw new Error(`行业/主题关系分析失败: ${missing.stock_name || missing.stock_code}`);
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
return results;
|
|
369
369
|
},
|
|
370
370
|
|
|
371
371
|
_getKnownCategories(db: any) {
|
|
372
|
-
const industries = db.prepare("SELECT name FROM
|
|
373
|
-
const themes = db.prepare("SELECT name FROM
|
|
372
|
+
const industries = db.prepare("SELECT name FROM industry_theme_categories WHERE type = 'industry'").all() as any[];
|
|
373
|
+
const themes = db.prepare("SELECT name FROM industry_theme_categories WHERE type = 'theme'").all() as any[];
|
|
374
374
|
return {
|
|
375
375
|
industries: industries.map(i => i.name),
|
|
376
376
|
themes: themes.map(t => t.name)
|
|
@@ -379,8 +379,8 @@ export const watchlistLogic = {
|
|
|
379
379
|
|
|
380
380
|
async _autoClassifyWithAI(
|
|
381
381
|
rt: PluginRuntime,
|
|
382
|
-
|
|
383
|
-
|
|
382
|
+
stock_name: string,
|
|
383
|
+
stock_code: string,
|
|
384
384
|
exchange: string
|
|
385
385
|
): Promise<StockClassification | null> {
|
|
386
386
|
const db = getDB();
|
|
@@ -390,17 +390,17 @@ export const watchlistLogic = {
|
|
|
390
390
|
return null;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
const prompt = watchlistLogic._buildAiPrompt(cats.industries, cats.themes, `${
|
|
394
|
-
const aiText = await watchlistLogic._callClassifierAi(rt, `classify-${
|
|
393
|
+
const prompt = watchlistLogic._buildAiPrompt(cats.industries, cats.themes, `${stock_name} (${stock_code})`, false);
|
|
394
|
+
const aiText = await watchlistLogic._callClassifierAi(rt, `classify-${stock_code}`, prompt, SINGLE_CLASSIFICATION_TIMEOUT_MS);
|
|
395
395
|
|
|
396
396
|
try {
|
|
397
397
|
const parsed = extractJsonArray(aiText);
|
|
398
398
|
const raw = parsed[0];
|
|
399
399
|
if (raw) {
|
|
400
|
-
return watchlistLogic._parseClassification(raw, cats,
|
|
400
|
+
return watchlistLogic._parseClassification(raw, cats, stock_name || stock_code);
|
|
401
401
|
}
|
|
402
402
|
} catch (e) {
|
|
403
|
-
logger.warn({ err: e instanceof Error ? e.message : String(e),
|
|
403
|
+
logger.warn({ err: e instanceof Error ? e.message : String(e), stock_code, stock_name }, "[Watchlist] AI 分类解析异常");
|
|
404
404
|
}
|
|
405
405
|
return null;
|
|
406
406
|
},
|
|
@@ -418,11 +418,14 @@ export const watchlistLogic = {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
};
|
|
421
|
-
const
|
|
421
|
+
const completionModelParams: Parameters<typeof prepareSimpleCompletionModelForAgent>[0] & {
|
|
422
|
+
allowBundledStaticCatalogFallback?: boolean;
|
|
423
|
+
} = {
|
|
422
424
|
cfg: embeddedCfg,
|
|
423
425
|
agentId: MAIN_AGENT_ID,
|
|
424
426
|
allowBundledStaticCatalogFallback: true
|
|
425
|
-
}
|
|
427
|
+
};
|
|
428
|
+
const prepared = await prepareSimpleCompletionModelForAgent(completionModelParams);
|
|
426
429
|
if ("error" in prepared) {
|
|
427
430
|
throw new Error(prepared.error);
|
|
428
431
|
}
|
|
@@ -472,12 +475,12 @@ export const watchlistLogic = {
|
|
|
472
475
|
},
|
|
473
476
|
|
|
474
477
|
_ensureCategory(db: any, name: string, type: 'industry' | 'theme', userId: string): string {
|
|
475
|
-
const existing = db.prepare("SELECT id FROM
|
|
478
|
+
const existing = db.prepare("SELECT id FROM industry_theme_categories WHERE userId = ? AND name = ? AND type = ?").get(userId, name, type) as { id: string } | undefined;
|
|
476
479
|
if (existing) return existing.id;
|
|
477
|
-
const maxOrderRow = db.prepare("SELECT MAX(sortOrder) as max FROM
|
|
480
|
+
const maxOrderRow = db.prepare("SELECT MAX(sortOrder) as max FROM industry_theme_categories WHERE userId = ?").get(userId) as { max: number } | undefined;
|
|
478
481
|
const nextOrder = (maxOrderRow?.max || 0) + 10;
|
|
479
482
|
const id = randomUUID();
|
|
480
|
-
db.prepare(`INSERT INTO
|
|
483
|
+
db.prepare(`INSERT INTO industry_theme_categories (id, userId, name, type, sortOrder, weight) VALUES (?, ?, ?, ?, ?, 0)`).run(id, userId, name, type, nextOrder);
|
|
481
484
|
return id;
|
|
482
485
|
},
|
|
483
486
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from "
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
export const GetWatchlistParamsSchema = z.object({
|
|
4
4
|
categoryId: z.string().optional().describe("分类 ID,不传返回所有"),
|
|
@@ -21,8 +21,8 @@ const ExchangeEnum = z.enum(["SSE", "SZSE", "NASDAQ", "NYSE", "AMEX", "HKEX"]);
|
|
|
21
21
|
const MarketEnum = z.enum(["A_SHARE", "US_SHARE", "HK_SHARE", "FUTURES", "FUND", "OTHER"]);
|
|
22
22
|
|
|
23
23
|
export const AddToWatchlistParamsSchema = z.object({
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
stock_code: z.string(),
|
|
25
|
+
stock_name: z.string(),
|
|
26
26
|
exchange: ExchangeEnum,
|
|
27
27
|
market: MarketEnum
|
|
28
28
|
});
|
|
@@ -37,15 +37,15 @@ export type BatchAddToWatchlistParams = z.infer<typeof BatchAddToWatchlistParams
|
|
|
37
37
|
|
|
38
38
|
export const UpdateWatchlistItemSchema = z.object({
|
|
39
39
|
id: z.string(),
|
|
40
|
-
|
|
40
|
+
stock_name: z.string().optional(),
|
|
41
41
|
sortOrder: z.number().optional()
|
|
42
42
|
});
|
|
43
43
|
export type UpdateWatchlistItemParams = z.infer<typeof UpdateWatchlistItemSchema>;
|
|
44
44
|
|
|
45
45
|
export interface WatchlistRow {
|
|
46
46
|
id: string;
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
stock_code: string;
|
|
48
|
+
stock_name: string;
|
|
49
49
|
exchange: string;
|
|
50
50
|
market?: string;
|
|
51
51
|
userId: string;
|
|
@@ -60,3 +60,8 @@ export interface CategoryRow {
|
|
|
60
60
|
type?: 'industry' | 'theme';
|
|
61
61
|
weight?: number;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
export const GetIndustryListParamsSchema = z.object({
|
|
65
|
+
type: z.string().optional().describe("分类类型:industry 行业,theme 主题,空字符串或不传表示全部")
|
|
66
|
+
});
|
|
67
|
+
export type GetIndustryListParams = z.infer<typeof GetIndustryListParamsSchema>;
|