@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.
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 +21 -6
  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
@@ -55,8 +55,8 @@ function startDailyBackupJob() {
55
55
  scheduleNextBackup();
56
56
  }
57
57
 
58
- function normalizeMetadataStockCode(stockCode: string, exchange: string): string {
59
- const code = String(stockCode || "").trim().toUpperCase();
58
+ function normalizeMetadataStockCode(stock_code: string, exchange: string): string {
59
+ const code = String(stock_code || "").trim().toUpperCase();
60
60
  if (/\.(SH|SS|SZ|HK|US)$/i.test(code)) {
61
61
  return code.replace(/\.SS$/i, ".SH");
62
62
  }
@@ -72,35 +72,52 @@ function normalizeMetadataStockCode(stockCode: string, exchange: string): string
72
72
  }
73
73
  }
74
74
 
75
+ function runStockColumnNameMigrations(db: DatabaseSync) {
76
+ const legacyCodeColumn = "stock" + "Code";
77
+ const legacyNameColumn = "stock" + "Name";
78
+ const tables = ["watchlist", "global_stock_metadata", "stock_classification_cache", "stock_ai_analysis"];
79
+
80
+ for (const table of tables) {
81
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];
82
+ const columnNames = new Set(columns.map(column => column.name));
83
+ if (columnNames.has(legacyCodeColumn) && !columnNames.has("stock_code")) {
84
+ db.prepare(`ALTER TABLE ${table} RENAME COLUMN ${legacyCodeColumn} TO stock_code`).run();
85
+ }
86
+ if (columnNames.has(legacyNameColumn) && !columnNames.has("stock_name")) {
87
+ db.prepare(`ALTER TABLE ${table} RENAME COLUMN ${legacyNameColumn} TO stock_name`).run();
88
+ }
89
+ }
90
+ }
91
+
75
92
  function runWatchlistDedupMigrations(db: DatabaseSync) {
76
93
  db.exec("BEGIN TRANSACTION");
77
94
  try {
78
95
  const metadataRows = db.prepare(`
79
- SELECT stockCode, exchange FROM global_stock_metadata
80
- `).all() as { stockCode: string; exchange: string }[];
96
+ SELECT stock_code, exchange FROM stock_classification_cache
97
+ `).all() as { stock_code: string; exchange: string }[];
81
98
  const metadataDeleteStmt = db.prepare(`
82
- DELETE FROM global_stock_metadata WHERE stockCode = ? AND exchange = ?
99
+ DELETE FROM stock_classification_cache WHERE stock_code = ? AND exchange = ?
83
100
  `);
84
101
  const metadataUpdateStmt = db.prepare(`
85
- UPDATE global_stock_metadata SET stockCode = ? WHERE stockCode = ? AND exchange = ?
102
+ UPDATE stock_classification_cache SET stock_code = ? WHERE stock_code = ? AND exchange = ?
86
103
  `);
87
104
  const metadataExistsStmt = db.prepare(`
88
- SELECT 1 FROM global_stock_metadata WHERE stockCode = ? AND exchange = ?
105
+ SELECT 1 FROM stock_classification_cache WHERE stock_code = ? AND exchange = ?
89
106
  `);
90
107
  for (const row of metadataRows) {
91
- const normalizedCode = normalizeMetadataStockCode(row.stockCode, row.exchange);
92
- if (!normalizedCode || normalizedCode === row.stockCode) continue;
108
+ const normalizedCode = normalizeMetadataStockCode(row.stock_code, row.exchange);
109
+ if (!normalizedCode || normalizedCode === row.stock_code) continue;
93
110
  const existing = metadataExistsStmt.get(normalizedCode, row.exchange);
94
111
  if (existing) {
95
- metadataDeleteStmt.run(row.stockCode, row.exchange);
112
+ metadataDeleteStmt.run(row.stock_code, row.exchange);
96
113
  } else {
97
- metadataUpdateStmt.run(normalizedCode, row.stockCode, row.exchange);
114
+ metadataUpdateStmt.run(normalizedCode, row.stock_code, row.exchange);
98
115
  }
99
116
  }
100
117
 
101
118
  const duplicateCategories = db.prepare(`
102
119
  SELECT userId, name, type, MIN(id) AS keepId, GROUP_CONCAT(id) AS ids
103
- FROM watchlist_categories
120
+ FROM industry_theme_categories
104
121
  GROUP BY userId, name, type
105
122
  HAVING COUNT(*) > 1
106
123
  `).all() as { userId: string; name: string; type: 'industry' | 'theme'; keepId: string; ids: string }[];
@@ -109,7 +126,7 @@ function runWatchlistDedupMigrations(db: DatabaseSync) {
109
126
  const ids = dup.ids.split(",").filter(id => id && id !== dup.keepId);
110
127
  for (const oldId of ids) {
111
128
  db.prepare(`UPDATE ${table} SET categoryId = ? WHERE userId = ? AND categoryId = ?`).run(dup.keepId, dup.userId, oldId);
112
- db.prepare("DELETE FROM watchlist_categories WHERE id = ?").run(oldId);
129
+ db.prepare("DELETE FROM industry_theme_categories WHERE id = ?").run(oldId);
113
130
  }
114
131
  }
115
132
 
@@ -132,8 +149,8 @@ function runWatchlistDedupMigrations(db: DatabaseSync) {
132
149
  }
133
150
 
134
151
  db.exec(`
135
- CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_categories_user_name_type
136
- ON watchlist_categories(userId, name, type);
152
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_industry_theme_categories_user_name_type
153
+ ON industry_theme_categories(userId, name, type);
137
154
  CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_industry_items_watchlist_category
138
155
  ON watchlist_industry_items(watchlistId, categoryId);
139
156
  CREATE UNIQUE INDEX IF NOT EXISTS idx_watchlist_theme_items_watchlist_category
@@ -141,6 +158,98 @@ function runWatchlistDedupMigrations(db: DatabaseSync) {
141
158
  `);
142
159
  }
143
160
 
161
+ function runClassificationCacheMigrations(db: DatabaseSync) {
162
+ const oldColumns = db.prepare("PRAGMA table_info(global_stock_metadata)").all() as { name: string }[];
163
+ if (oldColumns.length === 0) return;
164
+
165
+ const oldColumnNames = new Set(oldColumns.map(column => column.name));
166
+ const industryColumn = oldColumnNames.has("industry_classification") ? "industry_classification" : "industryJson";
167
+ const themeColumn = oldColumnNames.has("theme_classification") ? "theme_classification" : oldColumnNames.has("theme") ? "theme" : "themeJson";
168
+ const updatedColumn = oldColumnNames.has("last_updated") ? "last_updated" : "lastUpdated";
169
+ if (!oldColumnNames.has(industryColumn) || !oldColumnNames.has(themeColumn)) return;
170
+
171
+ db.exec("BEGIN");
172
+ try {
173
+ db.exec(`
174
+ INSERT OR REPLACE INTO stock_classification_cache (
175
+ stock_code,
176
+ exchange,
177
+ stock_name,
178
+ industry_classification,
179
+ theme_classification,
180
+ last_updated
181
+ )
182
+ SELECT
183
+ stock_code,
184
+ exchange,
185
+ stock_name,
186
+ ${industryColumn},
187
+ ${themeColumn},
188
+ ${updatedColumn}
189
+ FROM global_stock_metadata;
190
+
191
+ DROP TABLE global_stock_metadata;
192
+ `);
193
+ db.exec("COMMIT");
194
+ } catch (e) {
195
+ if (db.inTransaction) db.exec("ROLLBACK");
196
+ throw e;
197
+ }
198
+ }
199
+
200
+ function runClassificationCacheSchemaMigrations(db: DatabaseSync) {
201
+ const columns = db.prepare("PRAGMA table_info(stock_classification_cache)").all() as { name: string }[];
202
+ if (columns.length === 0) return;
203
+ const columnNames = new Set(columns.map(column => column.name));
204
+ if (columnNames.has("theme") && !columnNames.has("theme_classification")) {
205
+ db.prepare("ALTER TABLE stock_classification_cache RENAME COLUMN theme TO theme_classification").run();
206
+ columnNames.delete("theme");
207
+ columnNames.add("theme_classification");
208
+ }
209
+ if (columnNames.has("lastUpdated") && !columnNames.has("last_updated")) {
210
+ db.prepare("ALTER TABLE stock_classification_cache RENAME COLUMN lastUpdated TO last_updated").run();
211
+ }
212
+ }
213
+
214
+ function runIndustryThemeCategoryMigrations(db: DatabaseSync) {
215
+ const oldColumns = db.prepare("PRAGMA table_info(watchlist_categories)").all() as { name: string }[];
216
+ if (oldColumns.length === 0) return;
217
+
218
+ db.exec("BEGIN");
219
+ try {
220
+ db.exec(`
221
+ INSERT OR IGNORE INTO industry_theme_categories (
222
+ id,
223
+ remoteId,
224
+ userId,
225
+ name,
226
+ type,
227
+ weight,
228
+ sortOrder,
229
+ createdAt,
230
+ updatedAt
231
+ )
232
+ SELECT
233
+ id,
234
+ remoteId,
235
+ userId,
236
+ name,
237
+ type,
238
+ weight,
239
+ sortOrder,
240
+ createdAt,
241
+ updatedAt
242
+ FROM watchlist_categories;
243
+
244
+ DROP TABLE watchlist_categories;
245
+ `);
246
+ db.exec("COMMIT");
247
+ } catch (e) {
248
+ if (db.inTransaction) db.exec("ROLLBACK");
249
+ throw e;
250
+ }
251
+ }
252
+
144
253
  function runStockNotesMigrations(db: DatabaseSync) {
145
254
  const columns = db.prepare("PRAGMA table_info(stock_notes)").all() as { name: string }[];
146
255
  if (columns.length > 0 && !columns.some(column => column.name === "watchlistId")) {
@@ -153,13 +262,72 @@ function runStockNotesMigrations(db: DatabaseSync) {
153
262
  db.exec("CREATE INDEX IF NOT EXISTS idx_stock_notes_user_stock ON stock_notes(userId, watchlistId, updatedAt DESC)");
154
263
  }
155
264
 
265
+ function runDailyMorningBriefingMigrations(db: DatabaseSync) {
266
+ const tableColumns = db.prepare("PRAGMA table_info(daily_morning_briefings)").all() as { name: string }[];
267
+ const tableColumnNames = new Set(tableColumns.map(column => column.name));
268
+ const indexes = db.prepare("PRAGMA index_list(daily_morning_briefings)").all() as { name: string; unique: number }[];
269
+ const hasUniqueDateMarketIndex = indexes.some(index => {
270
+ if (index.unique !== 1) return false;
271
+ const columns = db.prepare(`PRAGMA index_info(${index.name})`).all() as { name: string }[];
272
+ const columnNames = columns.map(column => column.name);
273
+ return columnNames.length === 2 && columnNames.includes("briefingDate") && columnNames.includes("market");
274
+ });
275
+ if (!hasUniqueDateMarketIndex) return;
276
+
277
+ const snapshotColumn = tableColumnNames.has("watchlistSnapshot") ? "watchlistSnapshot" : "watchlistSnapshotJson";
278
+ const statusColumn = tableColumnNames.has("status") ? "status" : "'completed'";
279
+ db.exec("BEGIN");
280
+ try {
281
+ db.exec(`
282
+ DROP TABLE IF EXISTS daily_morning_briefings_history;
283
+
284
+ CREATE TABLE daily_morning_briefings_history (
285
+ id TEXT PRIMARY KEY,
286
+ market TEXT NOT NULL DEFAULT 'CN',
287
+ briefingDate TEXT NOT NULL,
288
+ content TEXT NOT NULL,
289
+ status TEXT NOT NULL DEFAULT 'completed',
290
+ watchlistSnapshot TEXT NOT NULL DEFAULT '[]',
291
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
292
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
293
+ );
294
+
295
+ INSERT INTO daily_morning_briefings_history (id, market, briefingDate, content, status, watchlistSnapshot, createdAt, updatedAt)
296
+ SELECT id, market, briefingDate, content, ${statusColumn}, ${snapshotColumn}, createdAt, updatedAt
297
+ FROM daily_morning_briefings;
298
+
299
+ DROP TABLE daily_morning_briefings;
300
+ ALTER TABLE daily_morning_briefings_history RENAME TO daily_morning_briefings;
301
+ CREATE INDEX IF NOT EXISTS idx_daily_morning_briefings_date ON daily_morning_briefings(briefingDate DESC);
302
+ `);
303
+ db.exec("COMMIT");
304
+ } catch (e) {
305
+ if (db.inTransaction) db.exec("ROLLBACK");
306
+ throw e;
307
+ }
308
+ }
309
+
310
+ function runDailyMorningBriefingSchemaMigrations(db: DatabaseSync) {
311
+ const columns = db.prepare("PRAGMA table_info(daily_morning_briefings)").all() as { name: string }[];
312
+ if (columns.length === 0) return;
313
+ const columnNames = new Set(columns.map(column => column.name));
314
+ if (columnNames.has("watchlistSnapshotJson") && !columnNames.has("watchlistSnapshot")) {
315
+ db.prepare("ALTER TABLE daily_morning_briefings RENAME COLUMN watchlistSnapshotJson TO watchlistSnapshot").run();
316
+ columnNames.delete("watchlistSnapshotJson");
317
+ columnNames.add("watchlistSnapshot");
318
+ }
319
+ if (!columnNames.has("status")) {
320
+ db.prepare("ALTER TABLE daily_morning_briefings ADD COLUMN status TEXT NOT NULL DEFAULT 'completed'").run();
321
+ }
322
+ }
323
+
156
324
  function runStockAiAnalysisMigrations(db: DatabaseSync) {
157
325
  const indexes = db.prepare("PRAGMA index_list(stock_ai_analysis)").all() as { name: string; unique: number }[];
158
326
  const hasUniqueStockIndex = indexes.some(index => {
159
327
  if (index.unique !== 1) return false;
160
328
  const columns = db.prepare(`PRAGMA index_info(${index.name})`).all() as { name: string }[];
161
329
  const columnNames = columns.map(column => column.name);
162
- return columnNames.includes("userId") && columnNames.includes("stockCode");
330
+ return columnNames.includes("userId") && columnNames.includes("stock_code");
163
331
  });
164
332
  if (!hasUniqueStockIndex) return;
165
333
 
@@ -171,8 +339,8 @@ function runStockAiAnalysisMigrations(db: DatabaseSync) {
171
339
  CREATE TABLE stock_ai_analysis_history (
172
340
  id TEXT NOT NULL,
173
341
  userId TEXT NOT NULL,
174
- stockCode TEXT NOT NULL,
175
- stockName TEXT NOT NULL,
342
+ stock_code TEXT NOT NULL,
343
+ stock_name TEXT NOT NULL,
176
344
  market TEXT NOT NULL DEFAULT 'CN',
177
345
  content TEXT NOT NULL,
178
346
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
@@ -180,13 +348,13 @@ function runStockAiAnalysisMigrations(db: DatabaseSync) {
180
348
  PRIMARY KEY(id, userId)
181
349
  );
182
350
 
183
- INSERT INTO stock_ai_analysis_history (id, userId, stockCode, stockName, market, content, createdAt, updatedAt)
184
- SELECT id, userId, stockCode, stockName, market, content, createdAt, updatedAt
351
+ INSERT INTO stock_ai_analysis_history (id, userId, stock_code, stock_name, market, content, createdAt, updatedAt)
352
+ SELECT id, userId, stock_code, stock_name, market, content, createdAt, updatedAt
185
353
  FROM stock_ai_analysis;
186
354
 
187
355
  DROP TABLE stock_ai_analysis;
188
356
  ALTER TABLE stock_ai_analysis_history RENAME TO stock_ai_analysis;
189
- CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stockCode, updatedAt DESC);
357
+ CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stock_code, updatedAt DESC);
190
358
  `);
191
359
  db.exec("COMMIT");
192
360
  } catch (e) {
@@ -195,6 +363,13 @@ function runStockAiAnalysisMigrations(db: DatabaseSync) {
195
363
  }
196
364
  }
197
365
 
366
+ function runStockAiAnalysisSchemaMigrations(db: DatabaseSync) {
367
+ const columns = db.prepare("PRAGMA table_info(stock_ai_analysis)").all() as { name: string }[];
368
+ if (columns.length > 0 && !columns.some(column => column.name === "status")) {
369
+ db.prepare("ALTER TABLE stock_ai_analysis ADD COLUMN status TEXT NOT NULL DEFAULT 'completed'").run();
370
+ }
371
+ }
372
+
198
373
  function runArticleAiAnalysisMigrations(db: DatabaseSync) {
199
374
  const columns = db.prepare("PRAGMA table_info(article_ai_analysis)").all() as { name: string }[];
200
375
  if (columns.length === 0 || columns.some(column => column.name === "sourceId")) {
@@ -235,6 +410,160 @@ function runArticleAiAnalysisMigrations(db: DatabaseSync) {
235
410
  }
236
411
  }
237
412
 
413
+ function runNewsAnalysisMigrations(db: DatabaseSync) {
414
+ const columns = db.prepare("PRAGMA table_info(article_ai_analysis)").all() as { name: string }[];
415
+ if (columns.length === 0 || !columns.some(column => column.name === "sourceId")) {
416
+ return;
417
+ }
418
+
419
+ db.exec("BEGIN");
420
+ try {
421
+ db.exec(`
422
+ INSERT OR REPLACE INTO news_fact_check_analysis (id, sourceId, sourceTitle, userId, status, content, createdAt, updatedAt)
423
+ SELECT id, sourceId, '', userId, 'completed', content, createdAt, updatedAt
424
+ FROM article_ai_analysis
425
+ WHERE analysisType = 'verification';
426
+
427
+ INSERT OR REPLACE INTO news_deep_reasoning_analysis (id, sourceId, sourceTitle, userId, market, status, content, createdAt, updatedAt)
428
+ SELECT id, sourceId, '', userId, market, 'completed', content, createdAt, updatedAt
429
+ FROM article_ai_analysis
430
+ WHERE analysisType = 'deduction';
431
+
432
+ DROP TABLE article_ai_analysis;
433
+ `);
434
+ db.exec("COMMIT");
435
+ } catch (e) {
436
+ if (db.inTransaction) db.exec("ROLLBACK");
437
+ throw e;
438
+ }
439
+ }
440
+
441
+ function runNewsFactCheckAnalysisSchemaMigrations(db: DatabaseSync) {
442
+ const columns = db.prepare("PRAGMA table_info(news_fact_check_analysis)").all() as { name: string }[];
443
+ if (columns.length === 0) return;
444
+ const columnNames = new Set(columns.map(column => column.name));
445
+ if (
446
+ !columnNames.has("market") &&
447
+ columnNames.has("sourceTitle")
448
+ ) return;
449
+
450
+ const sourceTitleExpr = columnNames.has("sourceTitle") ? "sourceTitle" : "''";
451
+ const statusExpr = columnNames.has("status") ? "status" : "'completed'";
452
+
453
+ db.exec("BEGIN");
454
+ try {
455
+ db.exec(`
456
+ DROP TABLE IF EXISTS news_fact_check_analysis_v2;
457
+
458
+ CREATE TABLE news_fact_check_analysis_v2 (
459
+ id TEXT NOT NULL,
460
+ sourceId TEXT NOT NULL,
461
+ sourceTitle TEXT NOT NULL DEFAULT '',
462
+ userId TEXT NOT NULL,
463
+ status TEXT NOT NULL DEFAULT 'completed',
464
+ content TEXT NOT NULL,
465
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
466
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
467
+ PRIMARY KEY(id, userId),
468
+ UNIQUE(sourceId, userId)
469
+ );
470
+
471
+ INSERT OR REPLACE INTO news_fact_check_analysis_v2 (id, sourceId, sourceTitle, userId, status, content, createdAt, updatedAt)
472
+ SELECT id, sourceId, ${sourceTitleExpr}, userId, ${statusExpr}, content, createdAt, updatedAt
473
+ FROM news_fact_check_analysis;
474
+
475
+ DROP TABLE news_fact_check_analysis;
476
+ ALTER TABLE news_fact_check_analysis_v2 RENAME TO news_fact_check_analysis;
477
+ CREATE INDEX IF NOT EXISTS idx_news_fact_check_analysis_user_source_updated
478
+ ON news_fact_check_analysis(userId, sourceId, updatedAt DESC);
479
+ `);
480
+ db.exec("COMMIT");
481
+ } catch (e) {
482
+ if (db.inTransaction) db.exec("ROLLBACK");
483
+ throw e;
484
+ }
485
+ }
486
+
487
+ function runNewsDeepReasoningAnalysisSchemaMigrations(db: DatabaseSync) {
488
+ const columns = db.prepare("PRAGMA table_info(news_deep_reasoning_analysis)").all() as { name: string }[];
489
+ if (columns.length === 0) return;
490
+ const columnNames = new Set(columns.map(column => column.name));
491
+ if (columnNames.has("market") && columnNames.has("sourceTitle")) return;
492
+
493
+ const sourceTitleExpr = columnNames.has("sourceTitle") ? "sourceTitle" : "''";
494
+ const marketExpr = columnNames.has("market") ? "market" : "'CN'";
495
+ const statusExpr = columnNames.has("status") ? "status" : "'completed'";
496
+
497
+ db.exec("BEGIN");
498
+ try {
499
+ db.exec(`
500
+ DROP TABLE IF EXISTS news_deep_reasoning_analysis_v2;
501
+
502
+ CREATE TABLE news_deep_reasoning_analysis_v2 (
503
+ id TEXT NOT NULL,
504
+ sourceId TEXT NOT NULL,
505
+ sourceTitle TEXT NOT NULL DEFAULT '',
506
+ userId TEXT NOT NULL,
507
+ market TEXT NOT NULL DEFAULT 'CN',
508
+ status TEXT NOT NULL DEFAULT 'completed',
509
+ content TEXT NOT NULL,
510
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
511
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
512
+ PRIMARY KEY(id, userId),
513
+ UNIQUE(sourceId, userId, market)
514
+ );
515
+
516
+ INSERT OR REPLACE INTO news_deep_reasoning_analysis_v2 (id, sourceId, sourceTitle, userId, market, status, content, createdAt, updatedAt)
517
+ SELECT id, sourceId, ${sourceTitleExpr}, userId, ${marketExpr}, ${statusExpr}, content, createdAt, updatedAt
518
+ FROM news_deep_reasoning_analysis;
519
+
520
+ DROP TABLE news_deep_reasoning_analysis;
521
+ ALTER TABLE news_deep_reasoning_analysis_v2 RENAME TO news_deep_reasoning_analysis;
522
+ CREATE INDEX IF NOT EXISTS idx_news_deep_reasoning_analysis_user_source_updated
523
+ ON news_deep_reasoning_analysis(userId, sourceId, updatedAt DESC);
524
+ `);
525
+ db.exec("COMMIT");
526
+ } catch (e) {
527
+ if (db.inTransaction) db.exec("ROLLBACK");
528
+ throw e;
529
+ }
530
+ }
531
+
532
+ function runNewsFactCheckDropSourceContentMigration(db: DatabaseSync) {
533
+ const columns = db.prepare("PRAGMA table_info(news_fact_check_analysis)").all() as { name: string }[];
534
+ if (columns.length === 0) return;
535
+ if (!columns.some(column => column.name === "sourceContent")) return;
536
+ db.prepare("ALTER TABLE news_fact_check_analysis DROP COLUMN sourceContent").run();
537
+ }
538
+
539
+ function runNewsDeepReasoningDropSourceContentMigration(db: DatabaseSync) {
540
+ const columns = db.prepare("PRAGMA table_info(news_deep_reasoning_analysis)").all() as { name: string }[];
541
+ if (columns.length === 0) return;
542
+ if (!columns.some(column => column.name === "sourceContent")) return;
543
+ db.prepare("ALTER TABLE news_deep_reasoning_analysis DROP COLUMN sourceContent").run();
544
+ }
545
+
546
+ function runProfileLibrariesSchemaMigrations(db: DatabaseSync) {
547
+ const columns = db.prepare("PRAGMA table_info(profile_libraries)").all() as { name: string }[];
548
+ if (columns.length === 0) return;
549
+ const columnNames = new Set(columns.map(column => column.name));
550
+ if (!columnNames.has("knowType")) {
551
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN knowType TEXT").run();
552
+ }
553
+ if (!columnNames.has("recordDate")) {
554
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN recordDate DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))").run();
555
+ }
556
+ if (!columnNames.has("content")) {
557
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN content TEXT").run();
558
+ }
559
+ if (!columnNames.has("vectorValue")) {
560
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN vectorValue BLOB").run();
561
+ }
562
+ if (!columnNames.has("source")) {
563
+ db.prepare("ALTER TABLE profile_libraries ADD COLUMN source TEXT").run();
564
+ }
565
+ }
566
+
238
567
  export function getDB(): DatabaseSync {
239
568
  if (!_db) {
240
569
  const dbPath = getDbPath();
@@ -251,35 +580,58 @@ export function getDB(): DatabaseSync {
251
580
  _db = new DatabaseSync(dbPath);
252
581
  _db.exec("PRAGMA journal_mode = WAL");
253
582
  _db.exec("PRAGMA synchronous = NORMAL");
583
+ runStockColumnNameMigrations(_db);
254
584
 
255
585
  _db.exec(`
256
586
  CREATE TABLE IF NOT EXISTS watchlist (
257
587
  id TEXT PRIMARY KEY,
258
588
  userId TEXT NOT NULL,
259
- stockCode TEXT NOT NULL,
589
+ stock_code TEXT NOT NULL,
260
590
  exchange TEXT NOT NULL,
261
591
  market TEXT NOT NULL,
262
- stockName TEXT NOT NULL,
592
+ stock_name TEXT NOT NULL,
263
593
  sortOrder REAL DEFAULT 0,
264
594
  isDeleted INTEGER DEFAULT 0 CHECK (isDeleted IN (0, 1)),
265
595
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
266
596
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
267
- UNIQUE(userId, stockCode, exchange)
597
+ UNIQUE(userId, stock_code, exchange)
268
598
  );
269
599
 
270
- CREATE TABLE IF NOT EXISTS global_stock_metadata (
271
- stockCode TEXT NOT NULL,
272
- exchange TEXT NOT NULL,
273
- stockName TEXT NOT NULL,
274
- industryJson TEXT,
275
- themeJson TEXT,
276
- lastUpdated DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
277
- PRIMARY KEY(stockCode, exchange)
600
+ CREATE TABLE IF NOT EXISTS stock_classification_cache (
601
+ stock_code TEXT NOT NULL,
602
+ exchange TEXT NOT NULL,
603
+ stock_name TEXT NOT NULL,
604
+ industry_classification TEXT,
605
+ theme_classification TEXT,
606
+ last_updated DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
607
+ PRIMARY KEY(stock_code, exchange)
278
608
  );
279
609
 
280
610
  CREATE INDEX IF NOT EXISTS idx_watchlist_main ON watchlist(userId, isDeleted, sortOrder ASC);
281
611
 
282
- CREATE TABLE IF NOT EXISTS watchlist_categories (
612
+ CREATE TABLE IF NOT EXISTS stock_basic (
613
+ stock_code TEXT PRIMARY KEY,
614
+ symbol TEXT NOT NULL,
615
+ name TEXT NOT NULL,
616
+ fullname TEXT,
617
+ enname TEXT,
618
+ cnspell TEXT,
619
+ exchange TEXT NOT NULL,
620
+ market TEXT,
621
+ industry TEXT,
622
+ area TEXT,
623
+ curr_type TEXT,
624
+ list_date TEXT,
625
+ is_hs TEXT,
626
+ act_name TEXT,
627
+ act_ent_type TEXT,
628
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
629
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
630
+ );
631
+ CREATE INDEX IF NOT EXISTS idx_stock_basic_exchange_symbol ON stock_basic(exchange, symbol);
632
+ CREATE INDEX IF NOT EXISTS idx_stock_basic_name ON stock_basic(name);
633
+
634
+ CREATE TABLE IF NOT EXISTS industry_theme_categories (
283
635
  id TEXT PRIMARY KEY,
284
636
  remoteId TEXT,
285
637
  userId TEXT NOT NULL,
@@ -318,7 +670,12 @@ export function getDB(): DatabaseSync {
318
670
  CREATE TABLE IF NOT EXISTS profile_libraries (
319
671
  id TEXT NOT NULL,
320
672
  userId TEXT NOT NULL,
673
+ knowType TEXT,
321
674
  title TEXT NOT NULL,
675
+ recordDate DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
676
+ content TEXT,
677
+ vectorValue BLOB,
678
+ source TEXT,
322
679
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
323
680
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
324
681
  PRIMARY KEY(id, userId)
@@ -349,35 +706,85 @@ export function getDB(): DatabaseSync {
349
706
  CREATE TABLE IF NOT EXISTS stock_ai_analysis (
350
707
  id TEXT NOT NULL,
351
708
  userId TEXT NOT NULL,
352
- stockCode TEXT NOT NULL,
353
- stockName TEXT NOT NULL,
709
+ stock_code TEXT NOT NULL,
710
+ stock_name TEXT NOT NULL,
354
711
  market TEXT NOT NULL DEFAULT 'CN',
712
+ status TEXT NOT NULL DEFAULT 'completed',
355
713
  content TEXT NOT NULL,
356
714
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
357
715
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
358
716
  PRIMARY KEY(id, userId)
359
717
  );
360
- CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stockCode, updatedAt DESC);
718
+ CREATE INDEX IF NOT EXISTS idx_stock_ai_analysis_user_stock_updated ON stock_ai_analysis(userId, stock_code, updatedAt DESC);
361
719
 
362
- CREATE TABLE IF NOT EXISTS article_ai_analysis (
720
+ CREATE TABLE IF NOT EXISTS news_fact_check_analysis (
363
721
  id TEXT NOT NULL,
364
722
  sourceId TEXT NOT NULL,
723
+ sourceTitle TEXT NOT NULL DEFAULT '',
724
+ userId TEXT NOT NULL,
725
+ status TEXT NOT NULL DEFAULT 'completed',
726
+ content TEXT NOT NULL,
727
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
728
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
729
+ PRIMARY KEY(id, userId),
730
+ UNIQUE(sourceId, userId)
731
+ );
732
+ CREATE INDEX IF NOT EXISTS idx_news_fact_check_analysis_user_source_updated
733
+ ON news_fact_check_analysis(userId, sourceId, updatedAt DESC);
734
+
735
+ CREATE TABLE IF NOT EXISTS news_deep_reasoning_analysis (
736
+ id TEXT NOT NULL,
737
+ sourceId TEXT NOT NULL,
738
+ sourceTitle TEXT NOT NULL DEFAULT '',
365
739
  userId TEXT NOT NULL,
366
- analysisType TEXT NOT NULL,
367
740
  market TEXT NOT NULL DEFAULT 'CN',
741
+ status TEXT NOT NULL DEFAULT 'completed',
368
742
  content TEXT NOT NULL,
369
743
  createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
370
744
  updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
371
745
  PRIMARY KEY(id, userId),
372
- UNIQUE(sourceId, userId, analysisType, market)
746
+ UNIQUE(sourceId, userId, market)
747
+ );
748
+ CREATE INDEX IF NOT EXISTS idx_news_deep_reasoning_analysis_user_source_updated
749
+ ON news_deep_reasoning_analysis(userId, sourceId, updatedAt DESC);
750
+
751
+ CREATE TABLE IF NOT EXISTS daily_morning_briefings (
752
+ id TEXT PRIMARY KEY,
753
+ market TEXT NOT NULL DEFAULT 'CN',
754
+ briefingDate TEXT NOT NULL,
755
+ content TEXT NOT NULL,
756
+ status TEXT NOT NULL DEFAULT 'completed',
757
+ watchlistSnapshot TEXT NOT NULL DEFAULT '[]',
758
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
759
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
760
+ );
761
+ CREATE INDEX IF NOT EXISTS idx_daily_morning_briefings_date ON daily_morning_briefings(briefingDate DESC);
762
+
763
+ CREATE TABLE IF NOT EXISTS skill_versions (
764
+ skillName TEXT PRIMARY KEY,
765
+ version TEXT NOT NULL,
766
+ createdAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW')),
767
+ updatedAt DATETIME DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'NOW'))
373
768
  );
374
- CREATE INDEX IF NOT EXISTS idx_article_ai_analysis_user_source_type_updated ON article_ai_analysis(userId, sourceId, analysisType, updatedAt DESC);
375
769
  `);
376
770
 
771
+ runClassificationCacheSchemaMigrations(_db);
772
+ runClassificationCacheMigrations(_db);
773
+ runClassificationCacheSchemaMigrations(_db);
774
+ runIndustryThemeCategoryMigrations(_db);
377
775
  runWatchlistDedupMigrations(_db);
378
776
  runStockNotesMigrations(_db);
379
777
  runStockAiAnalysisMigrations(_db);
778
+ runStockAiAnalysisSchemaMigrations(_db);
380
779
  runArticleAiAnalysisMigrations(_db);
780
+ runNewsFactCheckAnalysisSchemaMigrations(_db);
781
+ runNewsDeepReasoningAnalysisSchemaMigrations(_db);
782
+ runNewsAnalysisMigrations(_db);
783
+ runDailyMorningBriefingMigrations(_db);
784
+ runDailyMorningBriefingSchemaMigrations(_db);
785
+ runNewsFactCheckDropSourceContentMigration(_db);
786
+ runNewsDeepReasoningDropSourceContentMigration(_db);
787
+ runProfileLibrariesSchemaMigrations(_db);
381
788
  }
382
789
  return _db;
383
790
  }