@hedgehog-finance/hedgehog-plugin 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/index.d.ts +4 -3
  2. package/dist/index.js +49 -6
  3. package/dist/index.js.map +1 -1
  4. package/dist/setup-api.d.ts +2 -0
  5. package/dist/setup-api.js +8 -0
  6. package/dist/setup-api.js.map +1 -0
  7. package/dist/src/channel.js +25 -23
  8. package/dist/src/channel.js.map +1 -1
  9. package/dist/src/core/database.js +449 -39
  10. package/dist/src/core/database.js.map +1 -1
  11. package/dist/src/dailyMorningBriefingCron.d.ts +46 -0
  12. package/dist/src/dailyMorningBriefingCron.js +82 -0
  13. package/dist/src/dailyMorningBriefingCron.js.map +1 -0
  14. package/dist/src/features/chartOutput.d.ts +2 -0
  15. package/dist/src/features/chartOutput.js +35 -0
  16. package/dist/src/features/chartOutput.js.map +1 -0
  17. package/dist/src/features/dailyMorningBriefing/schema.d.ts +56 -0
  18. package/dist/src/features/dailyMorningBriefing/schema.js +22 -0
  19. package/dist/src/features/dailyMorningBriefing/schema.js.map +1 -0
  20. package/dist/src/features/dailyMorningBriefing/tools.d.ts +12 -0
  21. package/dist/src/features/dailyMorningBriefing/tools.js +204 -0
  22. package/dist/src/features/dailyMorningBriefing/tools.js.map +1 -0
  23. package/dist/src/features/deepReasoning/schema.d.ts +43 -0
  24. package/dist/src/features/deepReasoning/schema.js +17 -0
  25. package/dist/src/features/deepReasoning/schema.js.map +1 -0
  26. package/dist/src/features/deepReasoning/tools.d.ts +12 -0
  27. package/dist/src/features/deepReasoning/tools.js +163 -0
  28. package/dist/src/features/deepReasoning/tools.js.map +1 -0
  29. package/dist/src/features/index.d.ts +2 -1
  30. package/dist/src/features/index.js +9 -1
  31. package/dist/src/features/index.js.map +1 -1
  32. package/dist/src/features/informationVerification/schema.d.ts +43 -0
  33. package/dist/src/features/informationVerification/schema.js +17 -0
  34. package/dist/src/features/informationVerification/schema.js.map +1 -0
  35. package/dist/src/features/informationVerification/tools.d.ts +12 -0
  36. package/dist/src/features/informationVerification/tools.js +162 -0
  37. package/dist/src/features/informationVerification/tools.js.map +1 -0
  38. package/dist/src/features/notes/schema.d.ts +136 -39
  39. package/dist/src/features/notes/schema.js +13 -10
  40. package/dist/src/features/notes/schema.js.map +1 -1
  41. package/dist/src/features/notes/tools.d.ts +1 -0
  42. package/dist/src/features/notes/tools.js +47 -14
  43. package/dist/src/features/notes/tools.js.map +1 -1
  44. package/dist/src/features/pluginInfo/schema.d.ts +79 -0
  45. package/dist/src/features/pluginInfo/schema.js +14 -0
  46. package/dist/src/features/pluginInfo/schema.js.map +1 -0
  47. package/dist/src/features/pluginInfo/tools.d.ts +1 -0
  48. package/dist/src/features/pluginInfo/tools.js +157 -2
  49. package/dist/src/features/pluginInfo/tools.js.map +1 -1
  50. package/dist/src/features/profileLibrary/schema.d.ts +34 -6
  51. package/dist/src/features/profileLibrary/schema.js +1 -1
  52. package/dist/src/features/profileLibrary/schema.js.map +1 -1
  53. package/dist/src/features/stockAnalysis/schema.d.ts +224 -31
  54. package/dist/src/features/stockAnalysis/schema.js +76 -12
  55. package/dist/src/features/stockAnalysis/schema.js.map +1 -1
  56. package/dist/src/features/stockAnalysis/tools.d.ts +6 -4
  57. package/dist/src/features/stockAnalysis/tools.js +389 -44
  58. package/dist/src/features/stockAnalysis/tools.js.map +1 -1
  59. package/dist/src/features/stockBasic/schema.d.ts +149 -0
  60. package/dist/src/features/stockBasic/schema.js +26 -0
  61. package/dist/src/features/stockBasic/schema.js.map +1 -0
  62. package/dist/src/features/stockBasic/tools.d.ts +12 -0
  63. package/dist/src/features/stockBasic/tools.js +124 -0
  64. package/dist/src/features/stockBasic/tools.js.map +1 -0
  65. package/dist/src/features/watchlist/logic.d.ts +3 -3
  66. package/dist/src/features/watchlist/logic.js +47 -46
  67. package/dist/src/features/watchlist/logic.js.map +1 -1
  68. package/dist/src/features/watchlist/schema.d.ts +89 -54
  69. package/dist/src/features/watchlist/schema.js +7 -4
  70. package/dist/src/features/watchlist/schema.js.map +1 -1
  71. package/dist/src/features/watchlist/tools.d.ts +106 -59
  72. package/dist/src/features/watchlist/tools.js +182 -104
  73. package/dist/src/features/watchlist/tools.js.map +1 -1
  74. package/dist/src/openclawConfig.d.ts +6 -0
  75. package/dist/src/openclawConfig.js +74 -0
  76. package/dist/src/openclawConfig.js.map +1 -0
  77. package/dist/src/openclawConstants.d.ts +4 -0
  78. package/dist/src/openclawConstants.js +10 -0
  79. package/dist/src/openclawConstants.js.map +1 -0
  80. package/dist/src/runtime.js +20 -0
  81. package/dist/src/runtime.js.map +1 -1
  82. package/index.ts +52 -5
  83. package/openclaw.plugin.json +24 -0
  84. package/package.json +20 -5
  85. package/setup-api.ts +10 -0
  86. package/src/channel.ts +26 -25
  87. package/src/core/database.ts +447 -40
  88. package/src/dailyMorningBriefingCron.ts +129 -0
  89. package/src/features/chartOutput.ts +35 -0
  90. package/src/features/dailyMorningBriefing/schema.ts +38 -0
  91. package/src/features/dailyMorningBriefing/tools.ts +246 -0
  92. package/src/features/deepReasoning/schema.ts +22 -0
  93. package/src/features/deepReasoning/tools.ts +182 -0
  94. package/src/features/index.ts +11 -2
  95. package/src/features/informationVerification/schema.ts +22 -0
  96. package/src/features/informationVerification/tools.ts +181 -0
  97. package/src/features/notes/schema.ts +17 -12
  98. package/src/features/notes/tools.ts +54 -17
  99. package/src/features/pluginInfo/schema.ts +19 -0
  100. package/src/features/pluginInfo/tools.ts +173 -2
  101. package/src/features/profileLibrary/schema.ts +1 -1
  102. package/src/features/stockAnalysis/schema.ts +99 -17
  103. package/src/features/stockAnalysis/tools.ts +447 -49
  104. package/src/features/stockBasic/schema.ts +33 -0
  105. package/src/features/stockBasic/tools.ts +157 -0
  106. package/src/features/watchlist/logic.ts +56 -53
  107. package/src/features/watchlist/schema.ts +11 -6
  108. package/src/features/watchlist/tools.ts +191 -106
  109. package/src/openclawConfig.ts +101 -0
  110. package/src/openclawConstants.ts +11 -0
  111. package/src/runtime.ts +19 -0
@@ -47,8 +47,8 @@ function startDailyBackupJob() {
47
47
  };
48
48
  scheduleNextBackup();
49
49
  }
50
- function normalizeMetadataStockCode(stockCode, exchange) {
51
- const code = String(stockCode || "").trim().toUpperCase();
50
+ function normalizeMetadataStockCode(stock_code, exchange) {
51
+ const code = String(stock_code || "").trim().toUpperCase();
52
52
  if (/\.(SH|SS|SZ|HK|US)$/i.test(code)) {
53
53
  return code.replace(/\.SS$/i, ".SH");
54
54
  }
@@ -63,36 +63,51 @@ function normalizeMetadataStockCode(stockCode, exchange) {
63
63
  return code;
64
64
  }
65
65
  }
66
+ function runStockColumnNameMigrations(db) {
67
+ const legacyCodeColumn = "stock" + "Code";
68
+ const legacyNameColumn = "stock" + "Name";
69
+ const tables = ["watchlist", "global_stock_metadata", "stock_classification_cache", "stock_ai_analysis"];
70
+ for (const table of tables) {
71
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all();
72
+ const columnNames = new Set(columns.map(column => column.name));
73
+ if (columnNames.has(legacyCodeColumn) && !columnNames.has("stock_code")) {
74
+ db.prepare(`ALTER TABLE ${table} RENAME COLUMN ${legacyCodeColumn} TO stock_code`).run();
75
+ }
76
+ if (columnNames.has(legacyNameColumn) && !columnNames.has("stock_name")) {
77
+ db.prepare(`ALTER TABLE ${table} RENAME COLUMN ${legacyNameColumn} TO stock_name`).run();
78
+ }
79
+ }
80
+ }
66
81
  function runWatchlistDedupMigrations(db) {
67
82
  db.exec("BEGIN TRANSACTION");
68
83
  try {
69
84
  const metadataRows = db.prepare(`
70
- SELECT stockCode, exchange FROM global_stock_metadata
85
+ SELECT stock_code, exchange FROM stock_classification_cache
71
86
  `).all();
72
87
  const metadataDeleteStmt = db.prepare(`
73
- DELETE FROM global_stock_metadata WHERE stockCode = ? AND exchange = ?
88
+ DELETE FROM stock_classification_cache WHERE stock_code = ? AND exchange = ?
74
89
  `);
75
90
  const metadataUpdateStmt = db.prepare(`
76
- UPDATE global_stock_metadata SET stockCode = ? WHERE stockCode = ? AND exchange = ?
91
+ UPDATE stock_classification_cache SET stock_code = ? WHERE stock_code = ? AND exchange = ?
77
92
  `);
78
93
  const metadataExistsStmt = db.prepare(`
79
- SELECT 1 FROM global_stock_metadata WHERE stockCode = ? AND exchange = ?
94
+ SELECT 1 FROM stock_classification_cache WHERE stock_code = ? AND exchange = ?
80
95
  `);
81
96
  for (const row of metadataRows) {
82
- const normalizedCode = normalizeMetadataStockCode(row.stockCode, row.exchange);
83
- if (!normalizedCode || normalizedCode === row.stockCode)
97
+ const normalizedCode = normalizeMetadataStockCode(row.stock_code, row.exchange);
98
+ if (!normalizedCode || normalizedCode === row.stock_code)
84
99
  continue;
85
100
  const existing = metadataExistsStmt.get(normalizedCode, row.exchange);
86
101
  if (existing) {
87
- metadataDeleteStmt.run(row.stockCode, row.exchange);
102
+ metadataDeleteStmt.run(row.stock_code, row.exchange);
88
103
  }
89
104
  else {
90
- metadataUpdateStmt.run(normalizedCode, row.stockCode, row.exchange);
105
+ metadataUpdateStmt.run(normalizedCode, row.stock_code, row.exchange);
91
106
  }
92
107
  }
93
108
  const duplicateCategories = db.prepare(`
94
109
  SELECT userId, name, type, MIN(id) AS keepId, GROUP_CONCAT(id) AS ids
95
- FROM watchlist_categories
110
+ FROM industry_theme_categories
96
111
  GROUP BY userId, name, type
97
112
  HAVING COUNT(*) > 1
98
113
  `).all();
@@ -101,7 +116,7 @@ function runWatchlistDedupMigrations(db) {
101
116
  const ids = dup.ids.split(",").filter(id => id && id !== dup.keepId);
102
117
  for (const oldId of ids) {
103
118
  db.prepare(`UPDATE ${table} SET categoryId = ? WHERE userId = ? AND categoryId = ?`).run(dup.keepId, dup.userId, oldId);
104
- db.prepare("DELETE FROM watchlist_categories WHERE id = ?").run(oldId);
119
+ db.prepare("DELETE FROM industry_theme_categories WHERE id = ?").run(oldId);
105
120
  }
106
121
  }
107
122
  db.exec(`
@@ -123,14 +138,108 @@ function runWatchlistDedupMigrations(db) {
123
138
  throw e;
124
139
  }
125
140
  db.exec(`
126
- CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_categories_user_name_type
127
- ON watchlist_categories(userId, name, type);
141
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_industry_theme_categories_user_name_type
142
+ ON industry_theme_categories(userId, name, type);
128
143
  CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_industry_items_watchlist_category
129
144
  ON watchlist_industry_items(watchlistId, categoryId);
130
145
  CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_theme_items_watchlist_category
131
146
  ON watchlist_theme_items(watchlistId, categoryId);
132
147
  `);
133
148
  }
149
+ function runClassificationCacheMigrations(db) {
150
+ const oldColumns = db.prepare("PRAGMA table_info(global_stock_metadata)").all();
151
+ if (oldColumns.length === 0)
152
+ return;
153
+ const oldColumnNames = new Set(oldColumns.map(column => column.name));
154
+ const industryColumn = oldColumnNames.has("industry_classification") ? "industry_classification" : "industryJson";
155
+ const themeColumn = oldColumnNames.has("theme_classification") ? "theme_classification" : oldColumnNames.has("theme") ? "theme" : "themeJson";
156
+ const updatedColumn = oldColumnNames.has("last_updated") ? "last_updated" : "lastUpdated";
157
+ if (!oldColumnNames.has(industryColumn) || !oldColumnNames.has(themeColumn))
158
+ return;
159
+ db.exec("BEGIN");
160
+ try {
161
+ db.exec(`
162
+ INSERT OR REPLACE INTO stock_classification_cache (
163
+ stock_code,
164
+ exchange,
165
+ stock_name,
166
+ industry_classification,
167
+ theme_classification,
168
+ last_updated
169
+ )
170
+ SELECT
171
+ stock_code,
172
+ exchange,
173
+ stock_name,
174
+ ${industryColumn},
175
+ ${themeColumn},
176
+ ${updatedColumn}
177
+ FROM global_stock_metadata;
178
+
179
+ DROP TABLE global_stock_metadata;
180
+ `);
181
+ db.exec("COMMIT");
182
+ }
183
+ catch (e) {
184
+ if (db.inTransaction)
185
+ db.exec("ROLLBACK");
186
+ throw e;
187
+ }
188
+ }
189
+ function runClassificationCacheSchemaMigrations(db) {
190
+ const columns = db.prepare("PRAGMA table_info(stock_classification_cache)").all();
191
+ if (columns.length === 0)
192
+ return;
193
+ const columnNames = new Set(columns.map(column => column.name));
194
+ if (columnNames.has("theme") && !columnNames.has("theme_classification")) {
195
+ db.prepare("ALTER TABLE stock_classification_cache RENAME COLUMN theme TO theme_classification").run();
196
+ columnNames.delete("theme");
197
+ columnNames.add("theme_classification");
198
+ }
199
+ if (columnNames.has("lastUpdated") && !columnNames.has("last_updated")) {
200
+ db.prepare("ALTER TABLE stock_classification_cache RENAME COLUMN lastUpdated TO last_updated").run();
201
+ }
202
+ }
203
+ function runIndustryThemeCategoryMigrations(db) {
204
+ const oldColumns = db.prepare("PRAGMA table_info(watchlist_categories)").all();
205
+ if (oldColumns.length === 0)
206
+ return;
207
+ db.exec("BEGIN");
208
+ try {
209
+ db.exec(`
210
+ INSERT OR IGNORE INTO industry_theme_categories (
211
+ id,
212
+ remoteId,
213
+ userId,
214
+ name,
215
+ type,
216
+ weight,
217
+ sortOrder,
218
+ createdAt,
219
+ updatedAt
220
+ )
221
+ SELECT
222
+ id,
223
+ remoteId,
224
+ userId,
225
+ name,
226
+ type,
227
+ weight,
228
+ sortOrder,
229
+ createdAt,
230
+ updatedAt
231
+ FROM watchlist_categories;
232
+
233
+ DROP TABLE watchlist_categories;
234
+ `);
235
+ db.exec("COMMIT");
236
+ }
237
+ catch (e) {
238
+ if (db.inTransaction)
239
+ db.exec("ROLLBACK");
240
+ throw e;
241
+ }
242
+ }
134
243
  function runStockNotesMigrations(db) {
135
244
  const columns = db.prepare("PRAGMA table_info(stock_notes)").all();
136
245
  if (columns.length > 0 && !columns.some(column => column.name === "watchlistId")) {
@@ -142,6 +251,67 @@ function runStockNotesMigrations(db) {
142
251
  }
143
252
  db.exec("CREATE INDEX IF NOT EXISTS idx_stock_notes_user_stock ON stock_notes(userId, watchlistId, updatedAt DESC)");
144
253
  }
254
+ function runDailyMorningBriefingMigrations(db) {
255
+ const tableColumns = db.prepare("PRAGMA table_info(daily_morning_briefings)").all();
256
+ const tableColumnNames = new Set(tableColumns.map(column => column.name));
257
+ const indexes = db.prepare("PRAGMA index_list(daily_morning_briefings)").all();
258
+ const hasUniqueDateMarketIndex = indexes.some(index => {
259
+ if (index.unique !== 1)
260
+ return false;
261
+ const columns = db.prepare(`PRAGMA index_info(${index.name})`).all();
262
+ const columnNames = columns.map(column => column.name);
263
+ return columnNames.length === 2 && columnNames.includes("briefingDate") && columnNames.includes("market");
264
+ });
265
+ if (!hasUniqueDateMarketIndex)
266
+ return;
267
+ const snapshotColumn = tableColumnNames.has("watchlistSnapshot") ? "watchlistSnapshot" : "watchlistSnapshotJson";
268
+ const statusColumn = tableColumnNames.has("status") ? "status" : "'completed'";
269
+ db.exec("BEGIN");
270
+ try {
271
+ db.exec(`
272
+ DROP TABLE IF EXISTS daily_morning_briefings_history;
273
+
274
+ CREATE TABLE daily_morning_briefings_history (
275
+ id TEXT PRIMARY KEY,
276
+ market TEXT NOT NULL DEFAULT 'CN',
277
+ briefingDate TEXT NOT NULL,
278
+ content TEXT NOT NULL,
279
+ status TEXT NOT NULL DEFAULT 'completed',
280
+ watchlistSnapshot TEXT NOT NULL DEFAULT '[]',
281
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
282
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
283
+ );
284
+
285
+ INSERT INTO daily_morning_briefings_history (id, market, briefingDate, content, status, watchlistSnapshot, createdAt, updatedAt)
286
+ SELECT id, market, briefingDate, content, ${statusColumn}, ${snapshotColumn}, createdAt, updatedAt
287
+ FROM daily_morning_briefings;
288
+
289
+ DROP TABLE daily_morning_briefings;
290
+ ALTER TABLE daily_morning_briefings_history RENAME TO daily_morning_briefings;
291
+ CREATE INDEX IF NOT EXISTS idx_daily_morning_briefings_date ON daily_morning_briefings(briefingDate DESC);
292
+ `);
293
+ db.exec("COMMIT");
294
+ }
295
+ catch (e) {
296
+ if (db.inTransaction)
297
+ db.exec("ROLLBACK");
298
+ throw e;
299
+ }
300
+ }
301
+ function runDailyMorningBriefingSchemaMigrations(db) {
302
+ const columns = db.prepare("PRAGMA table_info(daily_morning_briefings)").all();
303
+ if (columns.length === 0)
304
+ return;
305
+ const columnNames = new Set(columns.map(column => column.name));
306
+ if (columnNames.has("watchlistSnapshotJson") && !columnNames.has("watchlistSnapshot")) {
307
+ db.prepare("ALTER TABLE daily_morning_briefings RENAME COLUMN watchlistSnapshotJson TO watchlistSnapshot").run();
308
+ columnNames.delete("watchlistSnapshotJson");
309
+ columnNames.add("watchlistSnapshot");
310
+ }
311
+ if (!columnNames.has("status")) {
312
+ db.prepare("ALTER TABLE daily_morning_briefings ADD COLUMN status TEXT NOT NULL DEFAULT 'completed'").run();
313
+ }
314
+ }
145
315
  function runStockAiAnalysisMigrations(db) {
146
316
  const indexes = db.prepare("PRAGMA index_list(stock_ai_analysis)").all();
147
317
  const hasUniqueStockIndex = indexes.some(index => {
@@ -149,7 +319,7 @@ function runStockAiAnalysisMigrations(db) {
149
319
  return false;
150
320
  const columns = db.prepare(`PRAGMA index_info(${index.name})`).all();
151
321
  const columnNames = columns.map(column => column.name);
152
- return columnNames.includes("userId") && columnNames.includes("stockCode");
322
+ return columnNames.includes("userId") && columnNames.includes("stock_code");
153
323
  });
154
324
  if (!hasUniqueStockIndex)
155
325
  return;
@@ -161,8 +331,8 @@ function runStockAiAnalysisMigrations(db) {
161
331
  CREATE TABLE stock_ai_analysis_history (
162
332
  id TEXT NOT NULL,
163
333
  userId TEXT NOT NULL,
164
- stockCode TEXT NOT NULL,
165
- stockName TEXT NOT NULL,
334
+ stock_code TEXT NOT NULL,
335
+ stock_name TEXT NOT NULL,
166
336
  market TEXT NOT NULL DEFAULT 'CN',
167
337
  content TEXT NOT NULL,
168
338
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
@@ -170,13 +340,13 @@ function runStockAiAnalysisMigrations(db) {
170
340
  PRIMARY KEY(id, userId)
171
341
  );
172
342
 
173
- INSERT INTO stock_ai_analysis_history (id, userId, stockCode, stockName, market, content, createdAt, updatedAt)
174
- SELECT id, userId, stockCode, stockName, market, content, createdAt, updatedAt
343
+ INSERT INTO stock_ai_analysis_history (id, userId, stock_code, stock_name, market, content, createdAt, updatedAt)
344
+ SELECT id, userId, stock_code, stock_name, market, content, createdAt, updatedAt
175
345
  FROM stock_ai_analysis;
176
346
 
177
347
  DROP TABLE stock_ai_analysis;
178
348
  ALTER TABLE stock_ai_analysis_history RENAME TO stock_ai_analysis;
179
- CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stockCode, updatedAt DESC);
349
+ CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stock_code, updatedAt DESC);
180
350
  `);
181
351
  db.exec("COMMIT");
182
352
  }
@@ -186,6 +356,12 @@ function runStockAiAnalysisMigrations(db) {
186
356
  throw e;
187
357
  }
188
358
  }
359
+ function runStockAiAnalysisSchemaMigrations(db) {
360
+ const columns = db.prepare("PRAGMA table_info(stock_ai_analysis)").all();
361
+ if (columns.length > 0 && !columns.some(column => column.name === "status")) {
362
+ db.prepare("ALTER TABLE stock_ai_analysis ADD COLUMN status TEXT NOT NULL DEFAULT 'completed'").run();
363
+ }
364
+ }
189
365
  function runArticleAiAnalysisMigrations(db) {
190
366
  const columns = db.prepare("PRAGMA table_info(article_ai_analysis)").all();
191
367
  if (columns.length === 0 || columns.some(column => column.name === "sourceId")) {
@@ -226,6 +402,162 @@ function runArticleAiAnalysisMigrations(db) {
226
402
  throw e;
227
403
  }
228
404
  }
405
+ function runNewsAnalysisMigrations(db) {
406
+ const columns = db.prepare("PRAGMA table_info(article_ai_analysis)").all();
407
+ if (columns.length === 0 || !columns.some(column => column.name === "sourceId")) {
408
+ return;
409
+ }
410
+ db.exec("BEGIN");
411
+ try {
412
+ db.exec(`
413
+ INSERT OR REPLACE INTO news_fact_check_analysis (id, sourceId, sourceTitle, userId, status, content, createdAt, updatedAt)
414
+ SELECT id, sourceId, '', userId, 'completed', content, createdAt, updatedAt
415
+ FROM article_ai_analysis
416
+ WHERE analysisType = 'verification';
417
+
418
+ INSERT OR REPLACE INTO news_deep_reasoning_analysis (id, sourceId, sourceTitle, userId, market, status, content, createdAt, updatedAt)
419
+ SELECT id, sourceId, '', userId, market, 'completed', content, createdAt, updatedAt
420
+ FROM article_ai_analysis
421
+ WHERE analysisType = 'deduction';
422
+
423
+ DROP TABLE article_ai_analysis;
424
+ `);
425
+ db.exec("COMMIT");
426
+ }
427
+ catch (e) {
428
+ if (db.inTransaction)
429
+ db.exec("ROLLBACK");
430
+ throw e;
431
+ }
432
+ }
433
+ function runNewsFactCheckAnalysisSchemaMigrations(db) {
434
+ const columns = db.prepare("PRAGMA table_info(news_fact_check_analysis)").all();
435
+ if (columns.length === 0)
436
+ return;
437
+ const columnNames = new Set(columns.map(column => column.name));
438
+ if (!columnNames.has("market") &&
439
+ columnNames.has("sourceTitle"))
440
+ return;
441
+ const sourceTitleExpr = columnNames.has("sourceTitle") ? "sourceTitle" : "''";
442
+ const statusExpr = columnNames.has("status") ? "status" : "'completed'";
443
+ db.exec("BEGIN");
444
+ try {
445
+ db.exec(`
446
+ DROP TABLE IF EXISTS news_fact_check_analysis_v2;
447
+
448
+ CREATE TABLE news_fact_check_analysis_v2 (
449
+ id TEXT NOT NULL,
450
+ sourceId TEXT NOT NULL,
451
+ sourceTitle TEXT NOT NULL DEFAULT '',
452
+ userId TEXT NOT NULL,
453
+ status TEXT NOT NULL DEFAULT 'completed',
454
+ content TEXT NOT NULL,
455
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
456
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
457
+ PRIMARY KEY(id, userId),
458
+ UNIQUE(sourceId, userId)
459
+ );
460
+
461
+ INSERT OR REPLACE INTO news_fact_check_analysis_v2 (id, sourceId, sourceTitle, userId, status, content, createdAt, updatedAt)
462
+ SELECT id, sourceId, ${sourceTitleExpr}, userId, ${statusExpr}, content, createdAt, updatedAt
463
+ FROM news_fact_check_analysis;
464
+
465
+ DROP TABLE news_fact_check_analysis;
466
+ ALTER TABLE news_fact_check_analysis_v2 RENAME TO news_fact_check_analysis;
467
+ CREATE INDEX IF NOT EXISTS idx_news_fact_check_analysis_user_source_updated
468
+ ON news_fact_check_analysis(userId, sourceId, updatedAt DESC);
469
+ `);
470
+ db.exec("COMMIT");
471
+ }
472
+ catch (e) {
473
+ if (db.inTransaction)
474
+ db.exec("ROLLBACK");
475
+ throw e;
476
+ }
477
+ }
478
+ function runNewsDeepReasoningAnalysisSchemaMigrations(db) {
479
+ const columns = db.prepare("PRAGMA table_info(news_deep_reasoning_analysis)").all();
480
+ if (columns.length === 0)
481
+ return;
482
+ const columnNames = new Set(columns.map(column => column.name));
483
+ if (columnNames.has("market") && columnNames.has("sourceTitle"))
484
+ return;
485
+ const sourceTitleExpr = columnNames.has("sourceTitle") ? "sourceTitle" : "''";
486
+ const marketExpr = columnNames.has("market") ? "market" : "'CN'";
487
+ const statusExpr = columnNames.has("status") ? "status" : "'completed'";
488
+ db.exec("BEGIN");
489
+ try {
490
+ db.exec(`
491
+ DROP TABLE IF EXISTS news_deep_reasoning_analysis_v2;
492
+
493
+ CREATE TABLE news_deep_reasoning_analysis_v2 (
494
+ id TEXT NOT NULL,
495
+ sourceId TEXT NOT NULL,
496
+ sourceTitle TEXT NOT NULL DEFAULT '',
497
+ userId TEXT NOT NULL,
498
+ market TEXT NOT NULL DEFAULT 'CN',
499
+ status TEXT NOT NULL DEFAULT 'completed',
500
+ content TEXT NOT NULL,
501
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
502
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
503
+ PRIMARY KEY(id, userId),
504
+ UNIQUE(sourceId, userId, market)
505
+ );
506
+
507
+ INSERT OR REPLACE INTO news_deep_reasoning_analysis_v2 (id, sourceId, sourceTitle, userId, market, status, content, createdAt, updatedAt)
508
+ SELECT id, sourceId, ${sourceTitleExpr}, userId, ${marketExpr}, ${statusExpr}, content, createdAt, updatedAt
509
+ FROM news_deep_reasoning_analysis;
510
+
511
+ DROP TABLE news_deep_reasoning_analysis;
512
+ ALTER TABLE news_deep_reasoning_analysis_v2 RENAME TO news_deep_reasoning_analysis;
513
+ CREATE INDEX IF NOT EXISTS idx_news_deep_reasoning_analysis_user_source_updated
514
+ ON news_deep_reasoning_analysis(userId, sourceId, updatedAt DESC);
515
+ `);
516
+ db.exec("COMMIT");
517
+ }
518
+ catch (e) {
519
+ if (db.inTransaction)
520
+ db.exec("ROLLBACK");
521
+ throw e;
522
+ }
523
+ }
524
+ function runNewsFactCheckDropSourceContentMigration(db) {
525
+ const columns = db.prepare("PRAGMA table_info(news_fact_check_analysis)").all();
526
+ if (columns.length === 0)
527
+ return;
528
+ if (!columns.some(column => column.name === "sourceContent"))
529
+ return;
530
+ db.prepare("ALTER TABLE news_fact_check_analysis DROP COLUMN sourceContent").run();
531
+ }
532
+ function runNewsDeepReasoningDropSourceContentMigration(db) {
533
+ const columns = db.prepare("PRAGMA table_info(news_deep_reasoning_analysis)").all();
534
+ if (columns.length === 0)
535
+ return;
536
+ if (!columns.some(column => column.name === "sourceContent"))
537
+ return;
538
+ db.prepare("ALTER TABLE news_deep_reasoning_analysis DROP COLUMN sourceContent").run();
539
+ }
540
+ function runProfileLibrariesSchemaMigrations(db) {
541
+ const columns = db.prepare("PRAGMA table_info(profile_libraries)").all();
542
+ if (columns.length === 0)
543
+ return;
544
+ const columnNames = new Set(columns.map(column => column.name));
545
+ if (!columnNames.has("knowType")) {
546
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN knowType TEXT").run();
547
+ }
548
+ if (!columnNames.has("recordDate")) {
549
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN recordDate DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))").run();
550
+ }
551
+ if (!columnNames.has("content")) {
552
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN content TEXT").run();
553
+ }
554
+ if (!columnNames.has("vectorValue")) {
555
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN vectorValue BLOB").run();
556
+ }
557
+ if (!columnNames.has("source")) {
558
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN source TEXT").run();
559
+ }
560
+ }
229
561
  export function getDB() {
230
562
  if (!_db) {
231
563
  const dbPath = getDbPath();
@@ -240,34 +572,57 @@ export function getDB() {
240
572
  _db = new DatabaseSync(dbPath);
241
573
  _db.exec("PRAGMA journal_mode = WAL");
242
574
  _db.exec("PRAGMA synchronous = NORMAL");
575
+ runStockColumnNameMigrations(_db);
243
576
  _db.exec(`
244
577
  CREATE TABLE IF NOT EXISTS watchlist (
245
578
  id TEXT PRIMARY KEY,
246
579
  userId TEXT NOT NULL,
247
- stockCode TEXT NOT NULL,
580
+ stock_code TEXT NOT NULL,
248
581
  exchange TEXT NOT NULL,
249
582
  market TEXT NOT NULL,
250
- stockName TEXT NOT NULL,
583
+ stock_name TEXT NOT NULL,
251
584
  sortOrder REAL DEFAULT 0,
252
585
  isDeleted INTEGER DEFAULT 0 CHECK (isDeleted IN (0, 1)),
253
586
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
254
587
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
255
- UNIQUE(userId, stockCode, exchange)
588
+ UNIQUE(userId, stock_code, exchange)
256
589
  );
257
590
 
258
- CREATE TABLE IF NOT EXISTS global_stock_metadata (
259
- stockCode TEXT NOT NULL,
260
- exchange TEXT NOT NULL,
261
- stockName TEXT NOT NULL,
262
- industryJson TEXT,
263
- themeJson TEXT,
264
- lastUpdated DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
265
- PRIMARY KEY(stockCode, exchange)
591
+ CREATE TABLE IF NOT EXISTS stock_classification_cache (
592
+ stock_code TEXT NOT NULL,
593
+ exchange TEXT NOT NULL,
594
+ stock_name TEXT NOT NULL,
595
+ industry_classification TEXT,
596
+ theme_classification TEXT,
597
+ last_updated DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
598
+ PRIMARY KEY(stock_code, exchange)
266
599
  );
267
600
 
268
601
  CREATE INDEX IF NOT EXISTS idx_watchlist_main ON watchlist(userId, isDeleted, sortOrder ASC);
269
602
 
270
- CREATE TABLE IF NOT EXISTS watchlist_categories (
603
+ CREATE TABLE IF NOT EXISTS stock_basic (
604
+ stock_code TEXT PRIMARY KEY,
605
+ symbol TEXT NOT NULL,
606
+ name TEXT NOT NULL,
607
+ fullname TEXT,
608
+ enname TEXT,
609
+ cnspell TEXT,
610
+ exchange TEXT NOT NULL,
611
+ market TEXT,
612
+ industry TEXT,
613
+ area TEXT,
614
+ curr_type TEXT,
615
+ list_date TEXT,
616
+ is_hs TEXT,
617
+ act_name TEXT,
618
+ act_ent_type TEXT,
619
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
620
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
621
+ );
622
+ CREATE INDEX IF NOT EXISTS idx_stock_basic_exchange_symbol ON stock_basic(exchange, symbol);
623
+ CREATE INDEX IF NOT EXISTS idx_stock_basic_name ON stock_basic(name);
624
+
625
+ CREATE TABLE IF NOT EXISTS industry_theme_categories (
271
626
  id TEXT PRIMARY KEY,
272
627
  remoteId TEXT,
273
628
  userId TEXT NOT NULL,
@@ -306,7 +661,12 @@ export function getDB() {
306
661
  CREATE TABLE IF NOT EXISTS profile_libraries (
307
662
  id TEXT NOT NULL,
308
663
  userId TEXT NOT NULL,
664
+ knowType TEXT,
309
665
  title TEXT NOT NULL,
666
+ recordDate DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
667
+ content TEXT,
668
+ vectorValue BLOB,
669
+ source TEXT,
310
670
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
311
671
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
312
672
  PRIMARY KEY(id, userId)
@@ -337,34 +697,84 @@ export function getDB() {
337
697
  CREATE TABLE IF NOT EXISTS stock_ai_analysis (
338
698
  id TEXT NOT NULL,
339
699
  userId TEXT NOT NULL,
340
- stockCode TEXT NOT NULL,
341
- stockName TEXT NOT NULL,
700
+ stock_code TEXT NOT NULL,
701
+ stock_name TEXT NOT NULL,
342
702
  market TEXT NOT NULL DEFAULT 'CN',
703
+ status TEXT NOT NULL DEFAULT 'completed',
343
704
  content TEXT NOT NULL,
344
705
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
345
706
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
346
707
  PRIMARY KEY(id, userId)
347
708
  );
348
- CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stockCode, updatedAt DESC);
709
+ CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stock_code, updatedAt DESC);
710
+
711
+ CREATE TABLE IF NOT EXISTS news_fact_check_analysis (
712
+ id TEXT NOT NULL,
713
+ sourceId TEXT NOT NULL,
714
+ sourceTitle TEXT NOT NULL DEFAULT '',
715
+ userId TEXT NOT NULL,
716
+ status TEXT NOT NULL DEFAULT 'completed',
717
+ content TEXT NOT NULL,
718
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
719
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
720
+ PRIMARY KEY(id, userId),
721
+ UNIQUE(sourceId, userId)
722
+ );
723
+ CREATE INDEX IF NOT EXISTS idx_news_fact_check_analysis_user_source_updated
724
+ ON news_fact_check_analysis(userId, sourceId, updatedAt DESC);
349
725
 
350
- CREATE TABLE IF NOT EXISTS article_ai_analysis (
726
+ CREATE TABLE IF NOT EXISTS news_deep_reasoning_analysis (
351
727
  id TEXT NOT NULL,
352
728
  sourceId TEXT NOT NULL,
729
+ sourceTitle TEXT NOT NULL DEFAULT '',
353
730
  userId TEXT NOT NULL,
354
- analysisType TEXT NOT NULL,
355
731
  market TEXT NOT NULL DEFAULT 'CN',
732
+ status TEXT NOT NULL DEFAULT 'completed',
356
733
  content TEXT NOT NULL,
357
734
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
358
735
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
359
736
  PRIMARY KEY(id, userId),
360
- UNIQUE(sourceId, userId, analysisType, market)
737
+ UNIQUE(sourceId, userId, market)
738
+ );
739
+ CREATE INDEX IF NOT EXISTS idx_news_deep_reasoning_analysis_user_source_updated
740
+ ON news_deep_reasoning_analysis(userId, sourceId, updatedAt DESC);
741
+
742
+ CREATE TABLE IF NOT EXISTS daily_morning_briefings (
743
+ id TEXT PRIMARY KEY,
744
+ market TEXT NOT NULL DEFAULT 'CN',
745
+ briefingDate TEXT NOT NULL,
746
+ content TEXT NOT NULL,
747
+ status TEXT NOT NULL DEFAULT 'completed',
748
+ watchlistSnapshot TEXT NOT NULL DEFAULT '[]',
749
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
750
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
751
+ );
752
+ CREATE INDEX IF NOT EXISTS idx_daily_morning_briefings_date ON daily_morning_briefings(briefingDate DESC);
753
+
754
+ CREATE TABLE IF NOT EXISTS skill_versions (
755
+ skillName TEXT PRIMARY KEY,
756
+ version TEXT NOT NULL,
757
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
758
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
361
759
  );
362
- CREATE INDEX IF NOT EXISTS idx_article_ai_analysis_user_source_type_updated ON article_ai_analysis(userId, sourceId, analysisType, updatedAt DESC);
363
760
  `);
761
+ runClassificationCacheSchemaMigrations(_db);
762
+ runClassificationCacheMigrations(_db);
763
+ runClassificationCacheSchemaMigrations(_db);
764
+ runIndustryThemeCategoryMigrations(_db);
364
765
  runWatchlistDedupMigrations(_db);
365
766
  runStockNotesMigrations(_db);
366
767
  runStockAiAnalysisMigrations(_db);
768
+ runStockAiAnalysisSchemaMigrations(_db);
367
769
  runArticleAiAnalysisMigrations(_db);
770
+ runNewsFactCheckAnalysisSchemaMigrations(_db);
771
+ runNewsDeepReasoningAnalysisSchemaMigrations(_db);
772
+ runNewsAnalysisMigrations(_db);
773
+ runDailyMorningBriefingMigrations(_db);
774
+ runDailyMorningBriefingSchemaMigrations(_db);
775
+ runNewsFactCheckDropSourceContentMigration(_db);
776
+ runNewsDeepReasoningDropSourceContentMigration(_db);
777
+ runProfileLibrariesSchemaMigrations(_db);
368
778
  }
369
779
  return _db;
370
780
  }