opencandle 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (678) hide show
  1. package/README.md +50 -186
  2. package/dist/analysts/contracts.js +0 -1
  3. package/dist/analysts/orchestrator.js +0 -1
  4. package/dist/cli-main.js +22 -15
  5. package/dist/cli-options.d.ts +4 -0
  6. package/dist/cli-options.js +33 -0
  7. package/dist/cli.js +8 -3
  8. package/dist/config.d.ts +4 -0
  9. package/dist/config.js +1 -1
  10. package/dist/doctor/cli-command.js +9 -5
  11. package/dist/doctor/render.js +0 -1
  12. package/dist/doctor/report.js +53 -11
  13. package/dist/gui/server/ask-user-bridge.d.ts +19 -0
  14. package/dist/gui/server/ask-user-bridge.js +55 -0
  15. package/dist/gui/server/automation-heartbeat.d.ts +25 -0
  16. package/dist/gui/server/automation-heartbeat.js +61 -0
  17. package/dist/gui/server/background-quotes.d.ts +39 -0
  18. package/dist/gui/server/background-quotes.js +71 -0
  19. package/dist/gui/server/chat-event-adapter.d.ts +16 -0
  20. package/dist/gui/server/chat-event-adapter.js +388 -0
  21. package/dist/gui/server/gui-session-manager.d.ts +2 -0
  22. package/dist/gui/server/gui-session-manager.js +4 -0
  23. package/dist/gui/server/history-snapshot-store.d.ts +8 -0
  24. package/dist/gui/server/history-snapshot-store.js +43 -0
  25. package/dist/gui/server/http-routes.d.ts +71 -0
  26. package/dist/gui/server/http-routes.js +1033 -0
  27. package/dist/gui/server/invoke-tool.d.ts +47 -0
  28. package/dist/gui/server/invoke-tool.js +373 -0
  29. package/dist/gui/server/live-chat-event-adapter.d.ts +21 -0
  30. package/dist/gui/server/live-chat-event-adapter.js +224 -0
  31. package/dist/gui/server/local-session-coordinator.d.ts +26 -0
  32. package/dist/gui/server/local-session-coordinator.js +53 -0
  33. package/dist/gui/server/market-indices-api.d.ts +21 -0
  34. package/dist/gui/server/market-indices-api.js +26 -0
  35. package/dist/gui/server/market-indices-snapshot-store.d.ts +12 -0
  36. package/dist/gui/server/market-indices-snapshot-store.js +56 -0
  37. package/dist/gui/server/market-state-api.d.ts +250 -0
  38. package/dist/gui/server/market-state-api.js +597 -0
  39. package/dist/gui/server/model-setup.d.ts +55 -0
  40. package/dist/gui/server/model-setup.js +154 -0
  41. package/dist/gui/server/private-api-access.d.ts +6 -0
  42. package/dist/gui/server/private-api-access.js +53 -0
  43. package/dist/gui/server/projector.d.ts +49 -0
  44. package/dist/gui/server/projector.js +296 -0
  45. package/dist/gui/server/prompt-observation.d.ts +8 -0
  46. package/dist/gui/server/prompt-observation.js +43 -0
  47. package/dist/gui/server/quote-snapshot-store.d.ts +14 -0
  48. package/dist/gui/server/quote-snapshot-store.js +49 -0
  49. package/dist/gui/server/server.d.ts +1 -0
  50. package/dist/gui/server/server.js +262 -0
  51. package/dist/gui/server/session-actions.d.ts +60 -0
  52. package/dist/gui/server/session-actions.js +218 -0
  53. package/dist/gui/server/session-entry-wait.d.ts +27 -0
  54. package/dist/gui/server/session-entry-wait.js +129 -0
  55. package/dist/gui/server/session-list.d.ts +2 -0
  56. package/dist/gui/server/session-list.js +11 -0
  57. package/dist/gui/server/shutdown.d.ts +10 -0
  58. package/dist/gui/server/shutdown.js +29 -0
  59. package/dist/gui/server/tool-invoke-ack.d.ts +17 -0
  60. package/dist/gui/server/tool-invoke-ack.js +26 -0
  61. package/dist/gui/server/tool-metadata.d.ts +93 -0
  62. package/dist/gui/server/tool-metadata.js +148 -0
  63. package/dist/gui/server/websocket.d.ts +9 -0
  64. package/dist/gui/server/websocket.js +124 -0
  65. package/dist/gui/server/writer-lock.d.ts +1 -0
  66. package/dist/gui/server/writer-lock.js +1 -0
  67. package/dist/gui/server/ws-hub.d.ts +50 -0
  68. package/dist/gui/server/ws-hub.js +284 -0
  69. package/dist/gui/shared/chat-events.d.ts +174 -0
  70. package/dist/gui/shared/chat-events.js +13 -0
  71. package/dist/gui/shared/event-reducer.d.ts +3 -0
  72. package/dist/gui/shared/event-reducer.js +187 -0
  73. package/dist/index.js +0 -1
  74. package/dist/infra/cache.d.ts +4 -0
  75. package/dist/infra/cache.js +4 -1
  76. package/dist/infra/freshness.js +0 -1
  77. package/dist/infra/http-client.js +1 -2
  78. package/dist/infra/index.js +0 -1
  79. package/dist/infra/lse-byte-budget.d.ts +10 -0
  80. package/dist/infra/lse-byte-budget.js +47 -0
  81. package/dist/infra/market-calendar.js +0 -1
  82. package/dist/infra/native-dependencies.js +0 -1
  83. package/dist/infra/node-version.js +0 -1
  84. package/dist/infra/open-url.js +0 -1
  85. package/dist/infra/opencandle-paths.js +0 -1
  86. package/dist/infra/rate-limiter.js +1 -1
  87. package/dist/market-state/alert-conditions.js +0 -1
  88. package/dist/market-state/alert-runner.d.ts +1 -0
  89. package/dist/market-state/alert-runner.js +5 -4
  90. package/dist/market-state/daily-report.js +0 -1
  91. package/dist/market-state/local-automation-service.js +0 -1
  92. package/dist/market-state/notification-delivery.js +0 -1
  93. package/dist/market-state/resolve-for-mutation.js +0 -1
  94. package/dist/market-state/resolve.js +0 -1
  95. package/dist/market-state/service.d.ts +26 -21
  96. package/dist/market-state/service.js +175 -36
  97. package/dist/market-state/summaries.js +1 -13
  98. package/dist/memory/index.js +0 -1
  99. package/dist/memory/manager.js +0 -1
  100. package/dist/memory/preference-extractor.js +0 -1
  101. package/dist/memory/retrieval.js +0 -1
  102. package/dist/memory/sqlite.js +60 -11
  103. package/dist/memory/storage.js +0 -1
  104. package/dist/memory/tool-defaults.js +0 -1
  105. package/dist/memory/types.js +0 -1
  106. package/dist/monitor.js +0 -1
  107. package/dist/onboarding/connect.js +0 -1
  108. package/dist/onboarding/credential-interceptor.js +0 -1
  109. package/dist/onboarding/degradation-accumulator.js +0 -1
  110. package/dist/onboarding/prompt-user.d.ts +1 -1
  111. package/dist/onboarding/prompt-user.js +0 -1
  112. package/dist/onboarding/provider-status.js +0 -1
  113. package/dist/onboarding/providers.d.ts +16 -1
  114. package/dist/onboarding/providers.js +20 -1
  115. package/dist/onboarding/state.js +0 -1
  116. package/dist/onboarding/tool-helpers.js +0 -1
  117. package/dist/onboarding/tool-tags.js +0 -1
  118. package/dist/onboarding/validate-model-key.d.ts +17 -0
  119. package/dist/onboarding/validate-model-key.js +86 -0
  120. package/dist/onboarding/validation.js +8 -1
  121. package/dist/pi/opencandle-extension.d.ts +2 -1
  122. package/dist/pi/opencandle-extension.js +4 -4
  123. package/dist/pi/session-action-dedupe.js +0 -1
  124. package/dist/pi/session-storage.js +0 -1
  125. package/dist/pi/session-writer-lock.js +0 -1
  126. package/dist/pi/session.d.ts +2 -3
  127. package/dist/pi/session.js +7 -6
  128. package/dist/pi/setup.d.ts +4 -2
  129. package/dist/pi/setup.js +60 -61
  130. package/dist/pi/tool-adapter.js +0 -1
  131. package/dist/pi/tui-session-coordinator.js +0 -1
  132. package/dist/prompts/context-builder.js +1 -2
  133. package/dist/prompts/disclaimer.js +0 -1
  134. package/dist/prompts/policy-cards.js +0 -1
  135. package/dist/prompts/sections.js +0 -1
  136. package/dist/prompts/symbol-preflight.js +0 -1
  137. package/dist/prompts/workflow-prompts.js +16 -15
  138. package/dist/providers/alpha-vantage.js +2 -3
  139. package/dist/providers/coingecko.js +0 -1
  140. package/dist/providers/errors.js +0 -1
  141. package/dist/providers/exa-search.js +11 -10
  142. package/dist/providers/external-tool-command.js +0 -1
  143. package/dist/providers/external-tool-error.js +0 -1
  144. package/dist/providers/fear-greed.js +0 -1
  145. package/dist/providers/finnhub.js +0 -1
  146. package/dist/providers/fred.js +0 -1
  147. package/dist/providers/index.d.ts +1 -0
  148. package/dist/providers/index.js +1 -1
  149. package/dist/providers/lse.d.ts +35 -0
  150. package/dist/providers/lse.js +284 -0
  151. package/dist/providers/polymarket.js +22 -6
  152. package/dist/providers/provider-credential-error.js +0 -1
  153. package/dist/providers/reddit-cli.js +0 -1
  154. package/dist/providers/reddit.js +0 -1
  155. package/dist/providers/sec-edgar.js +0 -1
  156. package/dist/providers/tradingview.js +1 -2
  157. package/dist/providers/twitter-cli.js +0 -1
  158. package/dist/providers/twitter.js +0 -1
  159. package/dist/providers/web-search.js +15 -12
  160. package/dist/providers/with-fallback.js +0 -1
  161. package/dist/providers/wrap-provider.js +1 -1
  162. package/dist/providers/yahoo-finance.js +68 -4
  163. package/dist/routing/classify-intent.js +0 -1
  164. package/dist/routing/defaults.js +0 -1
  165. package/dist/routing/entity-extractor.js +2 -3
  166. package/dist/routing/fund-symbols.js +0 -1
  167. package/dist/routing/horizon.js +0 -1
  168. package/dist/routing/index.js +0 -1
  169. package/dist/routing/legacy-rule-router.js +0 -1
  170. package/dist/routing/planning.js +0 -1
  171. package/dist/routing/route-manifest.js +1 -1
  172. package/dist/routing/router-llm-client.d.ts +2 -2
  173. package/dist/routing/router-llm-client.js +0 -1
  174. package/dist/routing/router-prompt.js +0 -1
  175. package/dist/routing/router-types.js +0 -1
  176. package/dist/routing/router.js +9 -3
  177. package/dist/routing/slot-resolver.js +0 -1
  178. package/dist/routing/symbol-disambiguator.js +0 -1
  179. package/dist/routing/turn-context.js +0 -1
  180. package/dist/routing/types.js +0 -1
  181. package/dist/runtime/answer-contracts.js +0 -1
  182. package/dist/runtime/artifact-contracts.js +0 -1
  183. package/dist/runtime/evidence.d.ts +1 -0
  184. package/dist/runtime/evidence.js +1 -2
  185. package/dist/runtime/numeric-claims.js +0 -1
  186. package/dist/runtime/planning-evidence.js +0 -1
  187. package/dist/runtime/prompt-step.js +0 -1
  188. package/dist/runtime/provider-tracker.js +0 -1
  189. package/dist/runtime/run-context.js +0 -1
  190. package/dist/runtime/session-coordinator.d.ts +2 -2
  191. package/dist/runtime/session-coordinator.js +2 -3
  192. package/dist/runtime/session-title.js +0 -1
  193. package/dist/runtime/tool-defaults-wrapper.js +0 -1
  194. package/dist/runtime/validation.js +0 -1
  195. package/dist/runtime/workflow-events.js +0 -1
  196. package/dist/runtime/workflow-runner.d.ts +1 -0
  197. package/dist/runtime/workflow-runner.js +8 -3
  198. package/dist/runtime/workflow-types.js +0 -1
  199. package/dist/sentiment/adapters/finnhub.js +0 -1
  200. package/dist/sentiment/adapters/reddit.js +0 -1
  201. package/dist/sentiment/adapters/twitter.js +0 -1
  202. package/dist/sentiment/adapters/web.js +0 -1
  203. package/dist/sentiment/index.js +4 -2
  204. package/dist/sentiment/insights.js +0 -1
  205. package/dist/sentiment/keywords.js +0 -1
  206. package/dist/sentiment/pipeline.js +0 -1
  207. package/dist/sentiment/scorer.js +0 -1
  208. package/dist/sentiment/store.js +0 -1
  209. package/dist/sentiment/trends.js +0 -1
  210. package/dist/sentiment/types.js +0 -1
  211. package/dist/system-prompt.js +0 -1
  212. package/dist/tool-kit.js +0 -1
  213. package/dist/tools/fundamentals/company-overview.d.ts +1 -1
  214. package/dist/tools/fundamentals/company-overview.js +2 -1
  215. package/dist/tools/fundamentals/comps.js +6 -4
  216. package/dist/tools/fundamentals/dcf.js +37 -9
  217. package/dist/tools/fundamentals/earnings.d.ts +1 -1
  218. package/dist/tools/fundamentals/earnings.js +2 -1
  219. package/dist/tools/fundamentals/financials.js +88 -9
  220. package/dist/tools/fundamentals/sec-filings.js +0 -1
  221. package/dist/tools/index.d.ts +121 -2
  222. package/dist/tools/index.js +3 -1
  223. package/dist/tools/interaction/ask-user.js +11 -1
  224. package/dist/tools/macro/event-probabilities.js +0 -1
  225. package/dist/tools/macro/fear-greed.d.ts +1 -1
  226. package/dist/tools/macro/fear-greed.js +0 -1
  227. package/dist/tools/macro/fred-data.d.ts +1 -1
  228. package/dist/tools/macro/fred-data.js +2 -1
  229. package/dist/tools/market/crypto-history.js +0 -1
  230. package/dist/tools/market/crypto-price.d.ts +2 -2
  231. package/dist/tools/market/crypto-price.js +0 -1
  232. package/dist/tools/market/price-comparison.d.ts +30 -0
  233. package/dist/tools/market/price-comparison.js +202 -0
  234. package/dist/tools/market/screen-stocks.js +0 -1
  235. package/dist/tools/market/search-ticker.js +1 -2
  236. package/dist/tools/market/stock-history.d.ts +18 -3
  237. package/dist/tools/market/stock-history.js +132 -18
  238. package/dist/tools/market/stock-quote.d.ts +2 -2
  239. package/dist/tools/market/stock-quote.js +0 -1
  240. package/dist/tools/options/greeks.js +0 -1
  241. package/dist/tools/options/option-chain.d.ts +2 -2
  242. package/dist/tools/options/option-chain.js +0 -1
  243. package/dist/tools/portfolio/alerts.d.ts +2 -1
  244. package/dist/tools/portfolio/alerts.js +228 -2
  245. package/dist/tools/portfolio/correlation.d.ts +1 -1
  246. package/dist/tools/portfolio/correlation.js +23 -5
  247. package/dist/tools/portfolio/daily-report.js +0 -1
  248. package/dist/tools/portfolio/holdings-overlap.js +0 -1
  249. package/dist/tools/portfolio/notifications.js +0 -1
  250. package/dist/tools/portfolio/risk-analysis.d.ts +2 -2
  251. package/dist/tools/portfolio/risk-analysis.js +0 -1
  252. package/dist/tools/portfolio/tracker.d.ts +3 -1
  253. package/dist/tools/portfolio/tracker.js +57 -10
  254. package/dist/tools/portfolio/watchlist.d.ts +3 -6
  255. package/dist/tools/portfolio/watchlist.js +101 -110
  256. package/dist/tools/sentiment/insight-format.js +0 -1
  257. package/dist/tools/sentiment/query-match.js +0 -1
  258. package/dist/tools/sentiment/reddit-sentiment.js +0 -1
  259. package/dist/tools/sentiment/sentiment-summary.js +8 -4
  260. package/dist/tools/sentiment/sentiment-trend.d.ts +5 -1
  261. package/dist/tools/sentiment/sentiment-trend.js +6 -2
  262. package/dist/tools/sentiment/twitter-sentiment.js +0 -1
  263. package/dist/tools/sentiment/untrusted-text.js +0 -1
  264. package/dist/tools/sentiment/web-search.d.ts +1 -1
  265. package/dist/tools/sentiment/web-search.js +0 -1
  266. package/dist/tools/sentiment/web-sentiment.js +0 -1
  267. package/dist/tools/technical/backtest.js +0 -1
  268. package/dist/tools/technical/indicators.js +0 -1
  269. package/dist/types/fundamentals.js +0 -1
  270. package/dist/types/index.js +0 -1
  271. package/dist/types/macro.js +0 -1
  272. package/dist/types/market.d.ts +8 -0
  273. package/dist/types/market.js +0 -1
  274. package/dist/types/options.js +0 -1
  275. package/dist/types/portfolio.js +0 -1
  276. package/dist/types/prediction-markets.js +0 -1
  277. package/dist/types/sentiment.js +0 -1
  278. package/dist/workflows/compare-assets.js +0 -1
  279. package/dist/workflows/index.js +0 -1
  280. package/dist/workflows/options-screener.js +0 -1
  281. package/dist/workflows/portfolio-builder.js +0 -1
  282. package/gui/web/dist/assets/CatalogOverlay-Cy8Fq0fn.js +1 -0
  283. package/gui/web/dist/assets/allocation-donut-DgBRIKCk.js +52 -0
  284. package/gui/web/dist/assets/index-DIlFyIOO.js +66 -0
  285. package/gui/web/dist/assets/index-DS4jESOB.css +2 -0
  286. package/gui/web/dist/assets/market-chart-DammaCjH.js +1 -0
  287. package/gui/web/dist/assets/utils-CnADgYgh.js +1 -0
  288. package/gui/web/dist/index.html +3 -2
  289. package/package.json +23 -26
  290. package/dist/analysts/contracts.js.map +0 -1
  291. package/dist/analysts/orchestrator.js.map +0 -1
  292. package/dist/cli-main.js.map +0 -1
  293. package/dist/cli.js.map +0 -1
  294. package/dist/config.js.map +0 -1
  295. package/dist/doctor/cli-command.js.map +0 -1
  296. package/dist/doctor/render.js.map +0 -1
  297. package/dist/doctor/report.js.map +0 -1
  298. package/dist/index.js.map +0 -1
  299. package/dist/infra/cache.js.map +0 -1
  300. package/dist/infra/freshness.js.map +0 -1
  301. package/dist/infra/http-client.js.map +0 -1
  302. package/dist/infra/index.js.map +0 -1
  303. package/dist/infra/market-calendar.js.map +0 -1
  304. package/dist/infra/native-dependencies.js.map +0 -1
  305. package/dist/infra/node-version.js.map +0 -1
  306. package/dist/infra/open-url.js.map +0 -1
  307. package/dist/infra/opencandle-paths.js.map +0 -1
  308. package/dist/infra/rate-limiter.js.map +0 -1
  309. package/dist/market-state/alert-conditions.js.map +0 -1
  310. package/dist/market-state/alert-runner.js.map +0 -1
  311. package/dist/market-state/daily-report.js.map +0 -1
  312. package/dist/market-state/local-automation-service.js.map +0 -1
  313. package/dist/market-state/notification-delivery.js.map +0 -1
  314. package/dist/market-state/resolve-for-mutation.js.map +0 -1
  315. package/dist/market-state/resolve.js.map +0 -1
  316. package/dist/market-state/service.js.map +0 -1
  317. package/dist/market-state/summaries.js.map +0 -1
  318. package/dist/memory/index.js.map +0 -1
  319. package/dist/memory/manager.js.map +0 -1
  320. package/dist/memory/preference-extractor.js.map +0 -1
  321. package/dist/memory/retrieval.js.map +0 -1
  322. package/dist/memory/sqlite.js.map +0 -1
  323. package/dist/memory/storage.js.map +0 -1
  324. package/dist/memory/tool-defaults.js.map +0 -1
  325. package/dist/memory/types.js.map +0 -1
  326. package/dist/monitor.js.map +0 -1
  327. package/dist/onboarding/connect.js.map +0 -1
  328. package/dist/onboarding/credential-interceptor.js.map +0 -1
  329. package/dist/onboarding/degradation-accumulator.js.map +0 -1
  330. package/dist/onboarding/prompt-user.js.map +0 -1
  331. package/dist/onboarding/provider-status.js.map +0 -1
  332. package/dist/onboarding/providers.js.map +0 -1
  333. package/dist/onboarding/state.js.map +0 -1
  334. package/dist/onboarding/tool-helpers.js.map +0 -1
  335. package/dist/onboarding/tool-tags.js.map +0 -1
  336. package/dist/onboarding/validation.js.map +0 -1
  337. package/dist/pi/opencandle-extension.js.map +0 -1
  338. package/dist/pi/session-action-dedupe.js.map +0 -1
  339. package/dist/pi/session-storage.js.map +0 -1
  340. package/dist/pi/session-writer-lock.js.map +0 -1
  341. package/dist/pi/session.js.map +0 -1
  342. package/dist/pi/setup.js.map +0 -1
  343. package/dist/pi/tool-adapter.js.map +0 -1
  344. package/dist/pi/tui-session-coordinator.js.map +0 -1
  345. package/dist/prompts/context-builder.js.map +0 -1
  346. package/dist/prompts/disclaimer.js.map +0 -1
  347. package/dist/prompts/policy-cards.js.map +0 -1
  348. package/dist/prompts/sections.js.map +0 -1
  349. package/dist/prompts/symbol-preflight.js.map +0 -1
  350. package/dist/prompts/workflow-prompts.js.map +0 -1
  351. package/dist/providers/alpha-vantage.js.map +0 -1
  352. package/dist/providers/coingecko.js.map +0 -1
  353. package/dist/providers/errors.js.map +0 -1
  354. package/dist/providers/exa-search.js.map +0 -1
  355. package/dist/providers/external-tool-command.js.map +0 -1
  356. package/dist/providers/external-tool-error.js.map +0 -1
  357. package/dist/providers/fear-greed.js.map +0 -1
  358. package/dist/providers/finnhub.js.map +0 -1
  359. package/dist/providers/fred.js.map +0 -1
  360. package/dist/providers/index.js.map +0 -1
  361. package/dist/providers/polymarket.js.map +0 -1
  362. package/dist/providers/provider-credential-error.js.map +0 -1
  363. package/dist/providers/reddit-cli.js.map +0 -1
  364. package/dist/providers/reddit.js.map +0 -1
  365. package/dist/providers/sec-edgar.js.map +0 -1
  366. package/dist/providers/tradingview.js.map +0 -1
  367. package/dist/providers/twitter-cli.js.map +0 -1
  368. package/dist/providers/twitter.js.map +0 -1
  369. package/dist/providers/web-search.js.map +0 -1
  370. package/dist/providers/with-fallback.js.map +0 -1
  371. package/dist/providers/wrap-provider.js.map +0 -1
  372. package/dist/providers/yahoo-finance.js.map +0 -1
  373. package/dist/routing/classify-intent.js.map +0 -1
  374. package/dist/routing/defaults.js.map +0 -1
  375. package/dist/routing/entity-extractor.js.map +0 -1
  376. package/dist/routing/fund-symbols.js.map +0 -1
  377. package/dist/routing/horizon.js.map +0 -1
  378. package/dist/routing/index.js.map +0 -1
  379. package/dist/routing/legacy-rule-router.js.map +0 -1
  380. package/dist/routing/planning.js.map +0 -1
  381. package/dist/routing/route-manifest.js.map +0 -1
  382. package/dist/routing/router-llm-client.js.map +0 -1
  383. package/dist/routing/router-prompt.js.map +0 -1
  384. package/dist/routing/router-types.js.map +0 -1
  385. package/dist/routing/router.js.map +0 -1
  386. package/dist/routing/slot-resolver.js.map +0 -1
  387. package/dist/routing/symbol-disambiguator.js.map +0 -1
  388. package/dist/routing/turn-context.js.map +0 -1
  389. package/dist/routing/types.js.map +0 -1
  390. package/dist/runtime/answer-contracts.js.map +0 -1
  391. package/dist/runtime/artifact-contracts.js.map +0 -1
  392. package/dist/runtime/evidence.js.map +0 -1
  393. package/dist/runtime/numeric-claims.js.map +0 -1
  394. package/dist/runtime/planning-evidence.js.map +0 -1
  395. package/dist/runtime/prompt-step.js.map +0 -1
  396. package/dist/runtime/provider-tracker.js.map +0 -1
  397. package/dist/runtime/run-context.js.map +0 -1
  398. package/dist/runtime/session-coordinator.js.map +0 -1
  399. package/dist/runtime/session-title.js.map +0 -1
  400. package/dist/runtime/tool-defaults-wrapper.js.map +0 -1
  401. package/dist/runtime/validation.js.map +0 -1
  402. package/dist/runtime/workflow-events.js.map +0 -1
  403. package/dist/runtime/workflow-runner.js.map +0 -1
  404. package/dist/runtime/workflow-types.js.map +0 -1
  405. package/dist/sentiment/adapters/finnhub.js.map +0 -1
  406. package/dist/sentiment/adapters/reddit.js.map +0 -1
  407. package/dist/sentiment/adapters/twitter.js.map +0 -1
  408. package/dist/sentiment/adapters/web.js.map +0 -1
  409. package/dist/sentiment/index.js.map +0 -1
  410. package/dist/sentiment/insights.js.map +0 -1
  411. package/dist/sentiment/keywords.js.map +0 -1
  412. package/dist/sentiment/pipeline.js.map +0 -1
  413. package/dist/sentiment/scorer.js.map +0 -1
  414. package/dist/sentiment/store.js.map +0 -1
  415. package/dist/sentiment/trends.js.map +0 -1
  416. package/dist/sentiment/types.js.map +0 -1
  417. package/dist/system-prompt.js.map +0 -1
  418. package/dist/tool-kit.js.map +0 -1
  419. package/dist/tools/fundamentals/company-overview.js.map +0 -1
  420. package/dist/tools/fundamentals/comps.js.map +0 -1
  421. package/dist/tools/fundamentals/dcf.js.map +0 -1
  422. package/dist/tools/fundamentals/earnings.js.map +0 -1
  423. package/dist/tools/fundamentals/financials.js.map +0 -1
  424. package/dist/tools/fundamentals/sec-filings.js.map +0 -1
  425. package/dist/tools/index.js.map +0 -1
  426. package/dist/tools/interaction/ask-user.js.map +0 -1
  427. package/dist/tools/macro/event-probabilities.js.map +0 -1
  428. package/dist/tools/macro/fear-greed.js.map +0 -1
  429. package/dist/tools/macro/fred-data.js.map +0 -1
  430. package/dist/tools/market/crypto-history.js.map +0 -1
  431. package/dist/tools/market/crypto-price.js.map +0 -1
  432. package/dist/tools/market/screen-stocks.js.map +0 -1
  433. package/dist/tools/market/search-ticker.js.map +0 -1
  434. package/dist/tools/market/stock-history.js.map +0 -1
  435. package/dist/tools/market/stock-quote.js.map +0 -1
  436. package/dist/tools/options/greeks.js.map +0 -1
  437. package/dist/tools/options/option-chain.js.map +0 -1
  438. package/dist/tools/portfolio/alerts.js.map +0 -1
  439. package/dist/tools/portfolio/correlation.js.map +0 -1
  440. package/dist/tools/portfolio/daily-report.js.map +0 -1
  441. package/dist/tools/portfolio/holdings-overlap.js.map +0 -1
  442. package/dist/tools/portfolio/notifications.js.map +0 -1
  443. package/dist/tools/portfolio/risk-analysis.js.map +0 -1
  444. package/dist/tools/portfolio/tracker.js.map +0 -1
  445. package/dist/tools/portfolio/watchlist.js.map +0 -1
  446. package/dist/tools/sentiment/insight-format.js.map +0 -1
  447. package/dist/tools/sentiment/query-match.js.map +0 -1
  448. package/dist/tools/sentiment/reddit-sentiment.js.map +0 -1
  449. package/dist/tools/sentiment/sentiment-summary.js.map +0 -1
  450. package/dist/tools/sentiment/sentiment-trend.js.map +0 -1
  451. package/dist/tools/sentiment/twitter-sentiment.js.map +0 -1
  452. package/dist/tools/sentiment/untrusted-text.js.map +0 -1
  453. package/dist/tools/sentiment/web-search.js.map +0 -1
  454. package/dist/tools/sentiment/web-sentiment.js.map +0 -1
  455. package/dist/tools/technical/backtest.js.map +0 -1
  456. package/dist/tools/technical/indicators.js.map +0 -1
  457. package/dist/types/fundamentals.js.map +0 -1
  458. package/dist/types/index.js.map +0 -1
  459. package/dist/types/macro.js.map +0 -1
  460. package/dist/types/market.js.map +0 -1
  461. package/dist/types/options.js.map +0 -1
  462. package/dist/types/portfolio.js.map +0 -1
  463. package/dist/types/prediction-markets.js.map +0 -1
  464. package/dist/types/sentiment.js.map +0 -1
  465. package/dist/workflows/compare-assets.js.map +0 -1
  466. package/dist/workflows/index.js.map +0 -1
  467. package/dist/workflows/options-screener.js.map +0 -1
  468. package/dist/workflows/portfolio-builder.js.map +0 -1
  469. package/gui/server/ask-user-bridge.ts +0 -89
  470. package/gui/server/automation-heartbeat.ts +0 -97
  471. package/gui/server/background-quotes.ts +0 -127
  472. package/gui/server/chat-event-adapter.ts +0 -294
  473. package/gui/server/gui-session-manager.ts +0 -5
  474. package/gui/server/http-routes.ts +0 -1201
  475. package/gui/server/invoke-tool.ts +0 -490
  476. package/gui/server/live-chat-event-adapter.ts +0 -243
  477. package/gui/server/local-session-coordinator.ts +0 -97
  478. package/gui/server/market-state-api.ts +0 -331
  479. package/gui/server/model-setup.ts +0 -254
  480. package/gui/server/package.json +0 -5
  481. package/gui/server/private-api-access.ts +0 -62
  482. package/gui/server/projector.ts +0 -392
  483. package/gui/server/prompt-observation.ts +0 -58
  484. package/gui/server/quote-snapshot-store.ts +0 -50
  485. package/gui/server/server.ts +0 -289
  486. package/gui/server/session-actions.ts +0 -323
  487. package/gui/server/session-entry-wait.ts +0 -179
  488. package/gui/server/shutdown.ts +0 -47
  489. package/gui/server/tool-invoke-ack.ts +0 -49
  490. package/gui/server/tool-metadata.ts +0 -167
  491. package/gui/server/websocket.ts +0 -138
  492. package/gui/server/writer-lock.ts +0 -1
  493. package/gui/server/ws-hub.ts +0 -403
  494. package/gui/shared/chat-events.ts +0 -165
  495. package/gui/shared/event-reducer.ts +0 -220
  496. package/gui/web/dist/assets/CatalogOverlay-DZ1niyQm.js +0 -1
  497. package/gui/web/dist/assets/index-D4F9AJnn.css +0 -2
  498. package/gui/web/dist/assets/index-DtqMMBTr.js +0 -65
  499. package/src/analysts/contracts.ts +0 -176
  500. package/src/analysts/orchestrator.ts +0 -244
  501. package/src/cli-main.ts +0 -510
  502. package/src/cli.ts +0 -17
  503. package/src/config.ts +0 -272
  504. package/src/doctor/cli-command.ts +0 -83
  505. package/src/doctor/render.ts +0 -37
  506. package/src/doctor/report.ts +0 -638
  507. package/src/index.ts +0 -5
  508. package/src/infra/cache.ts +0 -127
  509. package/src/infra/freshness.ts +0 -165
  510. package/src/infra/http-client.ts +0 -134
  511. package/src/infra/index.ts +0 -14
  512. package/src/infra/market-calendar.ts +0 -193
  513. package/src/infra/native-dependencies.ts +0 -84
  514. package/src/infra/node-version.ts +0 -23
  515. package/src/infra/open-url.ts +0 -28
  516. package/src/infra/opencandle-paths.ts +0 -53
  517. package/src/infra/rate-limiter.ts +0 -77
  518. package/src/market-state/alert-conditions.ts +0 -82
  519. package/src/market-state/alert-runner.ts +0 -863
  520. package/src/market-state/daily-report.ts +0 -237
  521. package/src/market-state/local-automation-service.ts +0 -162
  522. package/src/market-state/notification-delivery.ts +0 -158
  523. package/src/market-state/resolve-for-mutation.ts +0 -24
  524. package/src/market-state/resolve.ts +0 -112
  525. package/src/market-state/service.ts +0 -2208
  526. package/src/market-state/summaries.ts +0 -75
  527. package/src/memory/index.ts +0 -10
  528. package/src/memory/manager.ts +0 -190
  529. package/src/memory/preference-extractor.ts +0 -106
  530. package/src/memory/retrieval.ts +0 -71
  531. package/src/memory/sqlite.ts +0 -577
  532. package/src/memory/storage.ts +0 -195
  533. package/src/memory/tool-defaults.ts +0 -108
  534. package/src/memory/types.ts +0 -71
  535. package/src/monitor.ts +0 -123
  536. package/src/onboarding/connect.ts +0 -177
  537. package/src/onboarding/credential-interceptor.ts +0 -122
  538. package/src/onboarding/degradation-accumulator.ts +0 -77
  539. package/src/onboarding/prompt-user.ts +0 -85
  540. package/src/onboarding/provider-status.ts +0 -382
  541. package/src/onboarding/providers.ts +0 -442
  542. package/src/onboarding/state.ts +0 -216
  543. package/src/onboarding/tool-helpers.ts +0 -104
  544. package/src/onboarding/tool-tags.ts +0 -244
  545. package/src/onboarding/validation.ts +0 -152
  546. package/src/pi/opencandle-extension.ts +0 -1294
  547. package/src/pi/session-action-dedupe.ts +0 -155
  548. package/src/pi/session-storage.ts +0 -5
  549. package/src/pi/session-writer-lock.ts +0 -426
  550. package/src/pi/session.ts +0 -84
  551. package/src/pi/setup.ts +0 -399
  552. package/src/pi/tool-adapter.ts +0 -49
  553. package/src/pi/tui-session-coordinator.ts +0 -351
  554. package/src/prompts/context-builder.ts +0 -326
  555. package/src/prompts/disclaimer.ts +0 -9
  556. package/src/prompts/policy-cards.ts +0 -228
  557. package/src/prompts/sections.ts +0 -46
  558. package/src/prompts/symbol-preflight.ts +0 -80
  559. package/src/prompts/workflow-prompts.ts +0 -504
  560. package/src/providers/alpha-vantage.ts +0 -340
  561. package/src/providers/coingecko.ts +0 -95
  562. package/src/providers/errors.ts +0 -9
  563. package/src/providers/exa-search.ts +0 -375
  564. package/src/providers/external-tool-command.ts +0 -164
  565. package/src/providers/external-tool-error.ts +0 -20
  566. package/src/providers/fear-greed.ts +0 -45
  567. package/src/providers/finnhub.ts +0 -125
  568. package/src/providers/fred.ts +0 -83
  569. package/src/providers/index.ts +0 -17
  570. package/src/providers/polymarket.ts +0 -214
  571. package/src/providers/provider-credential-error.ts +0 -23
  572. package/src/providers/reddit-cli.ts +0 -286
  573. package/src/providers/reddit.ts +0 -106
  574. package/src/providers/sec-edgar.ts +0 -326
  575. package/src/providers/tradingview.ts +0 -399
  576. package/src/providers/twitter-cli.ts +0 -202
  577. package/src/providers/twitter.ts +0 -102
  578. package/src/providers/web-search.ts +0 -303
  579. package/src/providers/with-fallback.ts +0 -42
  580. package/src/providers/wrap-provider.ts +0 -105
  581. package/src/providers/yahoo-finance.ts +0 -984
  582. package/src/routing/classify-intent.ts +0 -380
  583. package/src/routing/defaults.ts +0 -29
  584. package/src/routing/entity-extractor.ts +0 -557
  585. package/src/routing/fund-symbols.ts +0 -58
  586. package/src/routing/horizon.ts +0 -7
  587. package/src/routing/index.ts +0 -70
  588. package/src/routing/legacy-rule-router.ts +0 -13
  589. package/src/routing/planning.ts +0 -829
  590. package/src/routing/route-manifest.ts +0 -308
  591. package/src/routing/router-llm-client.ts +0 -64
  592. package/src/routing/router-prompt.ts +0 -159
  593. package/src/routing/router-types.ts +0 -84
  594. package/src/routing/router.ts +0 -1332
  595. package/src/routing/slot-resolver.ts +0 -213
  596. package/src/routing/symbol-disambiguator.ts +0 -72
  597. package/src/routing/turn-context.ts +0 -108
  598. package/src/routing/types.ts +0 -77
  599. package/src/runtime/answer-contracts.ts +0 -693
  600. package/src/runtime/artifact-contracts.ts +0 -77
  601. package/src/runtime/evidence.ts +0 -78
  602. package/src/runtime/numeric-claims.ts +0 -108
  603. package/src/runtime/planning-evidence.ts +0 -597
  604. package/src/runtime/prompt-step.ts +0 -185
  605. package/src/runtime/provider-tracker.ts +0 -40
  606. package/src/runtime/run-context.ts +0 -32
  607. package/src/runtime/session-coordinator.ts +0 -1037
  608. package/src/runtime/session-title.ts +0 -60
  609. package/src/runtime/tool-defaults-wrapper.ts +0 -43
  610. package/src/runtime/validation.ts +0 -211
  611. package/src/runtime/workflow-events.ts +0 -77
  612. package/src/runtime/workflow-runner.ts +0 -182
  613. package/src/runtime/workflow-types.ts +0 -105
  614. package/src/sentiment/adapters/finnhub.ts +0 -49
  615. package/src/sentiment/adapters/reddit.ts +0 -65
  616. package/src/sentiment/adapters/twitter.ts +0 -36
  617. package/src/sentiment/adapters/web.ts +0 -44
  618. package/src/sentiment/index.ts +0 -49
  619. package/src/sentiment/insights.ts +0 -269
  620. package/src/sentiment/keywords.ts +0 -31
  621. package/src/sentiment/pipeline.ts +0 -91
  622. package/src/sentiment/scorer.ts +0 -89
  623. package/src/sentiment/store.ts +0 -260
  624. package/src/sentiment/trends.ts +0 -96
  625. package/src/sentiment/types.ts +0 -112
  626. package/src/system-prompt.ts +0 -115
  627. package/src/tool-kit.ts +0 -69
  628. package/src/tools/AGENTS.md +0 -36
  629. package/src/tools/fundamentals/company-overview.ts +0 -64
  630. package/src/tools/fundamentals/comps.ts +0 -180
  631. package/src/tools/fundamentals/dcf.ts +0 -447
  632. package/src/tools/fundamentals/earnings.ts +0 -57
  633. package/src/tools/fundamentals/financials.ts +0 -63
  634. package/src/tools/fundamentals/sec-filings.ts +0 -105
  635. package/src/tools/index.ts +0 -104
  636. package/src/tools/interaction/ask-user.ts +0 -93
  637. package/src/tools/macro/event-probabilities.ts +0 -141
  638. package/src/tools/macro/fear-greed.ts +0 -41
  639. package/src/tools/macro/fred-data.ts +0 -92
  640. package/src/tools/market/crypto-history.ts +0 -72
  641. package/src/tools/market/crypto-price.ts +0 -65
  642. package/src/tools/market/screen-stocks.ts +0 -290
  643. package/src/tools/market/search-ticker.ts +0 -254
  644. package/src/tools/market/stock-history.ts +0 -104
  645. package/src/tools/market/stock-quote.ts +0 -73
  646. package/src/tools/options/greeks.ts +0 -81
  647. package/src/tools/options/option-chain.ts +0 -134
  648. package/src/tools/portfolio/alerts.ts +0 -457
  649. package/src/tools/portfolio/correlation.ts +0 -189
  650. package/src/tools/portfolio/daily-report.ts +0 -107
  651. package/src/tools/portfolio/holdings-overlap.ts +0 -139
  652. package/src/tools/portfolio/notifications.ts +0 -45
  653. package/src/tools/portfolio/risk-analysis.ts +0 -173
  654. package/src/tools/portfolio/tracker.ts +0 -308
  655. package/src/tools/portfolio/watchlist.ts +0 -288
  656. package/src/tools/sentiment/insight-format.ts +0 -50
  657. package/src/tools/sentiment/query-match.ts +0 -117
  658. package/src/tools/sentiment/reddit-sentiment.ts +0 -403
  659. package/src/tools/sentiment/sentiment-summary.ts +0 -383
  660. package/src/tools/sentiment/sentiment-trend.ts +0 -75
  661. package/src/tools/sentiment/twitter-sentiment.ts +0 -287
  662. package/src/tools/sentiment/untrusted-text.ts +0 -21
  663. package/src/tools/sentiment/web-search.ts +0 -186
  664. package/src/tools/sentiment/web-sentiment.ts +0 -96
  665. package/src/tools/technical/backtest.ts +0 -336
  666. package/src/tools/technical/indicators.ts +0 -281
  667. package/src/types/fundamentals.ts +0 -47
  668. package/src/types/index.ts +0 -26
  669. package/src/types/macro.ts +0 -27
  670. package/src/types/market.ts +0 -46
  671. package/src/types/options.ts +0 -53
  672. package/src/types/portfolio.ts +0 -83
  673. package/src/types/prediction-markets.ts +0 -13
  674. package/src/types/sentiment.ts +0 -129
  675. package/src/workflows/compare-assets.ts +0 -89
  676. package/src/workflows/index.ts +0 -3
  677. package/src/workflows/options-screener.ts +0 -125
  678. package/src/workflows/portfolio-builder.ts +0 -63
@@ -1,2208 +0,0 @@
1
- import type Database from "better-sqlite3";
2
-
3
- export type AssetType = "equity" | "etf" | "fund" | "crypto" | "index" | "option" | "unknown";
4
- export type AlertScopeType = "instrument" | "watchlist" | "portfolio";
5
-
6
- export interface InstrumentInput {
7
- symbol: string;
8
- assetType: AssetType | string;
9
- name?: string | null;
10
- exchange?: string | null;
11
- currency?: string | null;
12
- provider: string;
13
- providerMetadata?: unknown;
14
- resolvedAt?: Date;
15
- aliases?: InstrumentAliasInput[];
16
- }
17
-
18
- export interface InstrumentAliasInput {
19
- source: string;
20
- sourceSymbol: string;
21
- sourceExchange?: string | null;
22
- sourceAssetType?: string | null;
23
- sourceId?: string | null;
24
- raw?: unknown;
25
- }
26
-
27
- export interface InstrumentAliasLookup {
28
- source: string;
29
- sourceSymbol?: string;
30
- sourceExchange?: string | null;
31
- sourceAssetType?: string | null;
32
- sourceId?: string | null;
33
- }
34
-
35
- export interface CollectionRecord {
36
- id: number;
37
- name: string;
38
- isDefault: boolean;
39
- baseCurrency?: string;
40
- createdAt: string;
41
- updatedAt: string;
42
- }
43
-
44
- export interface InstrumentRecord {
45
- id: number;
46
- symbol: string;
47
- assetType: string;
48
- name: string | null;
49
- exchange: string | null;
50
- currency: string | null;
51
- provider: string;
52
- createdAt: string;
53
- updatedAt: string;
54
- }
55
-
56
- export interface WatchlistItemRecord {
57
- id: number;
58
- watchlistId: number;
59
- instrumentId: number;
60
- symbol: string;
61
- name: string | null;
62
- assetType: string;
63
- exchange: string | null;
64
- currency: string | null;
65
- targetPrice: number | null;
66
- stopPrice: number | null;
67
- priceCurrency: string | null;
68
- thesis: string | null;
69
- notes: string | null;
70
- tags: string[] | null;
71
- source: string | null;
72
- sourceRowId: string | null;
73
- sourceMetadata: unknown;
74
- createdAt: string;
75
- updatedAt: string;
76
- }
77
-
78
- export interface PortfolioLotRecord {
79
- id: number;
80
- portfolioId: number;
81
- instrumentId: number;
82
- symbol: string;
83
- name: string | null;
84
- assetType: string;
85
- exchange: string | null;
86
- instrumentCurrency: string | null;
87
- quantity: number;
88
- avgCost: number;
89
- currency: string;
90
- openedAt: string | null;
91
- notes: string | null;
92
- source: string | null;
93
- sourceAccountRef: string | null;
94
- sourceLotId: string | null;
95
- sourceRowId: string | null;
96
- sourceMetadata: unknown;
97
- createdAt: string;
98
- updatedAt: string;
99
- }
100
-
101
- export interface AlertRuleRecord {
102
- id: number;
103
- scopeType: AlertScopeType;
104
- scopeId: number | null;
105
- instrumentId: number | null;
106
- conditionType: string;
107
- conditionVersion: number;
108
- conditionJson: unknown;
109
- timeframe: string;
110
- enabled: boolean;
111
- checkIntervalSeconds: number | null;
112
- nextCheckAt: string | null;
113
- lastCheckedAt: string | null;
114
- lastObservedJson: unknown;
115
- status: string;
116
- retriggerMode: string;
117
- lastConditionState: string;
118
- ruleRevision: number;
119
- armCycleId: number;
120
- cooldownSeconds: number | null;
121
- lastTriggeredAt: string | null;
122
- createdAt: string;
123
- updatedAt: string;
124
- }
125
-
126
- export interface AlertEventRecord {
127
- id: number;
128
- alertRuleId: number;
129
- instrumentId: number | null;
130
- observedValueJson: unknown;
131
- triggeredAt: string;
132
- observedAt: string;
133
- providerDataAt: string | null;
134
- sourceProvider: string | null;
135
- cacheStatus: string;
136
- dataDelayMs: number | null;
137
- triggerSource: string;
138
- dedupeKey: string | null;
139
- status: string;
140
- message: string | null;
141
- }
142
-
143
- export interface AutomationRunnerLeaseRecord {
144
- ownerId: string;
145
- ownerKind: string;
146
- acquiredAt: string;
147
- heartbeatAt: string;
148
- expiresAt: string;
149
- }
150
-
151
- export interface AutomationRunnerLeaseResult extends AutomationRunnerLeaseRecord {
152
- acquired: boolean;
153
- }
154
-
155
- export interface AlertCheckRunRecord {
156
- id: number;
157
- startedAt: string;
158
- completedAt: string | null;
159
- status: string;
160
- triggerType: string;
161
- checkedCount: number;
162
- triggeredCount: number;
163
- unavailableCount: number;
164
- ownerId: string | null;
165
- errorJson: unknown;
166
- providerStatusJson: unknown;
167
- }
168
-
169
- export interface NotificationEventRecord {
170
- id: number;
171
- sourceType: string;
172
- sourceId: number | null;
173
- severity: string;
174
- title: string;
175
- body: string;
176
- payloadJson: unknown;
177
- status: string;
178
- createdAt: string;
179
- acknowledgedAt: string | null;
180
- }
181
-
182
- export interface NotificationDeliveryAttemptRecord {
183
- id: number;
184
- notificationEventId: number;
185
- channel: string;
186
- status: string;
187
- attemptedAt: string;
188
- completedAt: string | null;
189
- responseJson: unknown;
190
- error: string | null;
191
- }
192
-
193
- export interface ReportTemplateRecord {
194
- id: number;
195
- name: string;
196
- reportType: string;
197
- cadence: string;
198
- timezone: string;
199
- localTime: string;
200
- configJson: unknown;
201
- enabled: boolean;
202
- lastRunAt: string | null;
203
- nextRunAt: string | null;
204
- createdAt: string;
205
- updatedAt: string;
206
- }
207
-
208
- export interface ReportRunRecord {
209
- id: number;
210
- templateId: number | null;
211
- startedAt: string;
212
- completedAt: string | null;
213
- status: string;
214
- triggerType: string;
215
- scheduledFor: string | null;
216
- ownerId: string | null;
217
- artifactPath: string | null;
218
- summaryJson: unknown;
219
- errorsJson: unknown;
220
- }
221
-
222
- export interface ImportBatchRecord {
223
- id: number;
224
- source: string;
225
- sourceLabel: string | null;
226
- importedAt: string;
227
- status: string;
228
- rawMetadata: unknown;
229
- }
230
-
231
- export interface ImportRowRecord {
232
- id: number;
233
- batchId: number;
234
- rowType: string;
235
- sourceSymbol: string | null;
236
- sourceRowId: string | null;
237
- sourceAccountRef: string | null;
238
- normalizedInstrumentId: number | null;
239
- status: string;
240
- error: string | null;
241
- sourceMetadata: unknown;
242
- raw: unknown;
243
- }
244
-
245
- interface WatchlistRow {
246
- id: number;
247
- name: string;
248
- is_default: number;
249
- created_at: string;
250
- updated_at: string;
251
- }
252
-
253
- interface PortfolioRow extends WatchlistRow {
254
- base_currency: string;
255
- }
256
-
257
- interface InstrumentRow {
258
- id: number;
259
- symbol: string;
260
- asset_type: string;
261
- name: string | null;
262
- exchange: string | null;
263
- currency: string | null;
264
- provider: string;
265
- provider_metadata_json: string | null;
266
- last_resolved_at: string;
267
- created_at: string;
268
- updated_at: string;
269
- }
270
-
271
- interface InstrumentAliasRow {
272
- id: number;
273
- instrument_id: number;
274
- }
275
-
276
- type WatchlistItemRow = {
277
- id: number;
278
- watchlist_id: number;
279
- instrument_id: number;
280
- symbol: string;
281
- name: string | null;
282
- asset_type: string;
283
- exchange: string | null;
284
- currency: string | null;
285
- target_price: number | null;
286
- stop_price: number | null;
287
- price_currency: string | null;
288
- thesis: string | null;
289
- notes: string | null;
290
- tags_json: string | null;
291
- source: string | null;
292
- source_row_id: string | null;
293
- source_metadata_json: string | null;
294
- created_at: string;
295
- updated_at: string;
296
- };
297
-
298
- type PortfolioLotRow = {
299
- id: number;
300
- portfolio_id: number;
301
- instrument_id: number;
302
- symbol: string;
303
- name: string | null;
304
- asset_type: string;
305
- exchange: string | null;
306
- instrument_currency: string | null;
307
- quantity: number;
308
- avg_cost: number;
309
- currency: string;
310
- opened_at: string | null;
311
- notes: string | null;
312
- source: string | null;
313
- source_account_ref: string | null;
314
- source_lot_id: string | null;
315
- source_row_id: string | null;
316
- source_metadata_json: string | null;
317
- created_at: string;
318
- updated_at: string;
319
- };
320
-
321
- type AlertRuleRow = {
322
- id: number;
323
- scope_type: AlertScopeType;
324
- scope_id: number | null;
325
- instrument_id: number | null;
326
- condition_type: string;
327
- condition_version: number;
328
- condition_json: string;
329
- timeframe: string;
330
- enabled: number;
331
- check_interval_seconds: number | null;
332
- next_check_at: string | null;
333
- last_checked_at: string | null;
334
- last_observed_json: string | null;
335
- status: string;
336
- retrigger_mode: string;
337
- last_condition_state: string;
338
- rule_revision: number;
339
- arm_cycle_id: number;
340
- cooldown_seconds: number | null;
341
- last_triggered_at: string | null;
342
- created_at: string;
343
- updated_at: string;
344
- };
345
-
346
- type AlertEventRow = {
347
- id: number;
348
- alert_rule_id: number;
349
- instrument_id: number | null;
350
- observed_value_json: string | null;
351
- triggered_at: string;
352
- observed_at: string;
353
- provider_data_at: string | null;
354
- source_provider: string | null;
355
- cache_status: string;
356
- data_delay_ms: number | null;
357
- trigger_source: string;
358
- dedupe_key: string | null;
359
- status: string;
360
- message: string | null;
361
- };
362
-
363
- type AutomationRunnerLeaseRow = {
364
- owner_id: string;
365
- owner_kind: string;
366
- acquired_at: string;
367
- heartbeat_at: string;
368
- expires_at: string;
369
- };
370
-
371
- type AlertCheckRunRow = {
372
- id: number;
373
- started_at: string;
374
- completed_at: string | null;
375
- status: string;
376
- trigger_type: string;
377
- checked_count: number;
378
- triggered_count: number;
379
- unavailable_count: number;
380
- owner_id: string | null;
381
- error_json: string | null;
382
- provider_status_json: string | null;
383
- };
384
-
385
- type NotificationEventRow = {
386
- id: number;
387
- source_type: string;
388
- source_id: number | null;
389
- severity: string;
390
- title: string;
391
- body: string;
392
- payload_json: string | null;
393
- status: string;
394
- created_at: string;
395
- acknowledged_at: string | null;
396
- };
397
-
398
- type NotificationDeliveryAttemptRow = {
399
- id: number;
400
- notification_event_id: number;
401
- channel: string;
402
- status: string;
403
- attempted_at: string;
404
- completed_at: string | null;
405
- response_json: string | null;
406
- error: string | null;
407
- };
408
-
409
- type ReportTemplateRow = {
410
- id: number;
411
- name: string;
412
- report_type: string;
413
- cadence: string;
414
- timezone: string;
415
- local_time: string;
416
- config_json: string;
417
- enabled: number;
418
- last_run_at: string | null;
419
- next_run_at: string | null;
420
- created_at: string;
421
- updated_at: string;
422
- };
423
-
424
- type ReportRunRow = {
425
- id: number;
426
- template_id: number | null;
427
- started_at: string;
428
- completed_at: string | null;
429
- status: string;
430
- trigger_type: string;
431
- scheduled_for: string | null;
432
- owner_id: string | null;
433
- artifact_path: string | null;
434
- summary_json: string | null;
435
- errors_json: string | null;
436
- };
437
-
438
- type ImportBatchRow = {
439
- id: number;
440
- source: string;
441
- source_label: string | null;
442
- imported_at: string;
443
- status: string;
444
- raw_metadata_json: string | null;
445
- };
446
-
447
- type ImportRowRow = {
448
- id: number;
449
- batch_id: number;
450
- row_type: string;
451
- source_symbol: string | null;
452
- source_row_id: string | null;
453
- source_account_ref: string | null;
454
- normalized_instrument_id: number | null;
455
- status: string;
456
- error: string | null;
457
- source_metadata_json: string | null;
458
- raw_json: string | null;
459
- };
460
-
461
- export class MarketStateService {
462
- constructor(private readonly db: Database.Database) {}
463
-
464
- getDefaultWatchlist(): CollectionRecord {
465
- const now = new Date().toISOString();
466
- this.db
467
- .prepare(
468
- `INSERT OR IGNORE INTO watchlists (name, is_default, created_at, updated_at)
469
- SELECT 'Default', 1, ?, ?
470
- WHERE NOT EXISTS (SELECT 1 FROM watchlists WHERE is_default = 1)`,
471
- )
472
- .run(now, now);
473
-
474
- const row = this.db
475
- .prepare("SELECT * FROM watchlists WHERE is_default = 1 LIMIT 1")
476
- .get() as WatchlistRow;
477
- return mapCollection(row);
478
- }
479
-
480
- getDefaultPortfolio(): CollectionRecord {
481
- const now = new Date().toISOString();
482
- this.db
483
- .prepare(
484
- `INSERT OR IGNORE INTO portfolios (name, base_currency, is_default, created_at, updated_at)
485
- SELECT 'Default', 'USD', 1, ?, ?
486
- WHERE NOT EXISTS (SELECT 1 FROM portfolios WHERE is_default = 1)`,
487
- )
488
- .run(now, now);
489
-
490
- const row = this.db
491
- .prepare("SELECT * FROM portfolios WHERE is_default = 1 LIMIT 1")
492
- .get() as PortfolioRow;
493
- return {
494
- ...mapCollection(row),
495
- baseCurrency: row.base_currency,
496
- };
497
- }
498
-
499
- findInstrumentByAlias(lookup: InstrumentAliasLookup): InstrumentRecord | null {
500
- const source = normalizeSource(lookup.source);
501
- const sourceId = normalizeNullable(lookup.sourceId);
502
- const alias =
503
- sourceId == null
504
- ? (this.db
505
- .prepare(
506
- `SELECT id, instrument_id FROM instrument_aliases
507
- WHERE source = ?
508
- AND source_symbol = ?
509
- AND IFNULL(source_exchange, '') = IFNULL(?, '')
510
- AND IFNULL(source_asset_type, '') = IFNULL(?, '')
511
- LIMIT 1`,
512
- )
513
- .get(
514
- source,
515
- normalizeSourceSymbol(lookup.sourceSymbol ?? ""),
516
- normalizeExchange(lookup.sourceExchange),
517
- normalizeAssetType(lookup.sourceAssetType),
518
- ) as InstrumentAliasRow | undefined)
519
- : (this.db
520
- .prepare(
521
- `SELECT id, instrument_id FROM instrument_aliases
522
- WHERE source = ? AND source_id = ?
523
- LIMIT 1`,
524
- )
525
- .get(source, sourceId) as InstrumentAliasRow | undefined);
526
-
527
- if (alias == null) return null;
528
-
529
- const row = this.db
530
- .prepare("SELECT * FROM instruments WHERE id = ?")
531
- .get(alias.instrument_id) as InstrumentRow | undefined;
532
- return row == null ? null : mapInstrument(row);
533
- }
534
-
535
- addWatchlistItem(params: {
536
- instrument: InstrumentInput;
537
- watchlistId?: number;
538
- targetPrice?: number | null;
539
- stopPrice?: number | null;
540
- priceCurrency?: string | null;
541
- thesis?: string | null;
542
- notes?: string | null;
543
- tags?: string[];
544
- source?: string;
545
- sourceRowId?: string;
546
- sourceMetadata?: unknown;
547
- }): WatchlistItemRecord {
548
- const tx = this.db.transaction(() => {
549
- const watchlistId = params.watchlistId ?? this.getDefaultWatchlist().id;
550
- const instrument = this.upsertInstrument(params.instrument);
551
- const now = new Date().toISOString();
552
- const existing = this.db
553
- .prepare(
554
- `SELECT * FROM watchlist_items
555
- WHERE watchlist_id = ? AND instrument_id = ?`,
556
- )
557
- .get(watchlistId, instrument.id) as WatchlistItemRow | undefined;
558
-
559
- if (existing) {
560
- this.db
561
- .prepare(
562
- `UPDATE watchlist_items
563
- SET target_price = ?, stop_price = ?, price_currency = ?, thesis = ?,
564
- notes = ?, tags_json = ?, source = ?, source_row_id = ?,
565
- source_metadata_json = ?, updated_at = ?
566
- WHERE id = ?`,
567
- )
568
- .run(
569
- params.targetPrice === undefined ? existing.target_price : params.targetPrice,
570
- params.stopPrice === undefined ? existing.stop_price : params.stopPrice,
571
- params.priceCurrency === undefined ? existing.price_currency : params.priceCurrency,
572
- params.thesis === undefined ? existing.thesis : params.thesis,
573
- params.notes === undefined ? existing.notes : params.notes,
574
- params.tags == null ? existing.tags_json : JSON.stringify(params.tags),
575
- params.source === undefined ? existing.source : normalizeNullable(params.source),
576
- params.sourceRowId === undefined
577
- ? existing.source_row_id
578
- : normalizeNullable(params.sourceRowId),
579
- params.sourceMetadata === undefined
580
- ? existing.source_metadata_json
581
- : params.sourceMetadata == null
582
- ? null
583
- : JSON.stringify(params.sourceMetadata),
584
- now,
585
- existing.id,
586
- );
587
- return existing.id;
588
- }
589
-
590
- const result = this.db
591
- .prepare(
592
- `INSERT INTO watchlist_items (
593
- watchlist_id, instrument_id, thesis, notes, tags_json,
594
- target_price, stop_price, price_currency, source, source_row_id,
595
- source_metadata_json, created_at, updated_at
596
- )
597
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
598
- )
599
- .run(
600
- watchlistId,
601
- instrument.id,
602
- params.thesis ?? null,
603
- params.notes ?? null,
604
- params.tags == null ? null : JSON.stringify(params.tags),
605
- params.targetPrice ?? null,
606
- params.stopPrice ?? null,
607
- params.priceCurrency ?? params.instrument.currency ?? null,
608
- normalizeNullable(params.source),
609
- normalizeNullable(params.sourceRowId),
610
- params.sourceMetadata == null ? null : JSON.stringify(params.sourceMetadata),
611
- now,
612
- now,
613
- );
614
- return Number(result.lastInsertRowid);
615
- });
616
-
617
- return this.getWatchlistItem(tx());
618
- }
619
-
620
- listWatchlistItems(watchlistId = this.getDefaultWatchlist().id): WatchlistItemRecord[] {
621
- const rows = this.db
622
- .prepare(
623
- `SELECT wi.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency
624
- FROM watchlist_items wi
625
- JOIN instruments i ON i.id = wi.instrument_id
626
- WHERE wi.watchlist_id = ?
627
- ORDER BY i.symbol`,
628
- )
629
- .all(watchlistId) as WatchlistItemRow[];
630
- return rows.map(mapWatchlistItem);
631
- }
632
-
633
- removeWatchlistItemBySymbol(
634
- symbol: string,
635
- watchlistId = this.getDefaultWatchlist().id,
636
- ): boolean {
637
- const result = this.db
638
- .prepare(
639
- `DELETE FROM watchlist_items
640
- WHERE watchlist_id = ?
641
- AND instrument_id IN (SELECT id FROM instruments WHERE symbol = ?)`,
642
- )
643
- .run(watchlistId, symbol.trim().toUpperCase());
644
- return result.changes > 0;
645
- }
646
-
647
- updateWatchlistItemBySymbol(
648
- symbol: string,
649
- params: {
650
- watchlistId?: number;
651
- targetPrice?: number | null;
652
- stopPrice?: number | null;
653
- priceCurrency?: string | null;
654
- thesis?: string | null;
655
- notes?: string | null;
656
- tags?: string[];
657
- },
658
- ): WatchlistItemRecord | null {
659
- const watchlistId = params.watchlistId ?? this.getDefaultWatchlist().id;
660
- const existing = this.db
661
- .prepare(
662
- `SELECT wi.*
663
- FROM watchlist_items wi
664
- JOIN instruments i ON i.id = wi.instrument_id
665
- WHERE wi.watchlist_id = ? AND i.symbol = ?
666
- LIMIT 1`,
667
- )
668
- .get(watchlistId, symbol.trim().toUpperCase()) as WatchlistItemRow | undefined;
669
- if (existing == null) return null;
670
-
671
- const now = new Date().toISOString();
672
- this.db
673
- .prepare(
674
- `UPDATE watchlist_items
675
- SET target_price = ?, stop_price = ?, price_currency = ?, thesis = ?,
676
- notes = ?, tags_json = ?, updated_at = ?
677
- WHERE id = ?`,
678
- )
679
- .run(
680
- params.targetPrice === undefined ? existing.target_price : params.targetPrice,
681
- params.stopPrice === undefined ? existing.stop_price : params.stopPrice,
682
- params.priceCurrency === undefined ? existing.price_currency : params.priceCurrency,
683
- params.thesis === undefined ? existing.thesis : params.thesis,
684
- params.notes === undefined ? existing.notes : params.notes,
685
- params.tags == null ? existing.tags_json : JSON.stringify(params.tags),
686
- now,
687
- existing.id,
688
- );
689
- return this.getWatchlistItem(existing.id);
690
- }
691
-
692
- addPortfolioLot(params: {
693
- instrument: InstrumentInput;
694
- portfolioId?: number;
695
- quantity: number;
696
- avgCost: number;
697
- currency: string;
698
- openedAt?: string;
699
- notes?: string;
700
- source?: string;
701
- sourceAccountRef?: string;
702
- sourceLotId?: string;
703
- sourceRowId?: string;
704
- sourceMetadata?: unknown;
705
- }): PortfolioLotRecord {
706
- assertPositiveFinitePortfolioLotNumber(params.quantity, "quantity");
707
- assertPositiveFinitePortfolioLotNumber(params.avgCost, "average cost");
708
- const tx = this.db.transaction(() => {
709
- const portfolioId = params.portfolioId ?? this.getDefaultPortfolio().id;
710
- const instrument = this.upsertInstrument(params.instrument);
711
- const now = new Date().toISOString();
712
- const result = this.db
713
- .prepare(
714
- `INSERT INTO portfolio_lots (
715
- portfolio_id, instrument_id, quantity, avg_cost, currency,
716
- opened_at, notes, source, source_account_ref, source_lot_id,
717
- source_row_id, source_metadata_json, created_at, updated_at
718
- )
719
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
720
- )
721
- .run(
722
- portfolioId,
723
- instrument.id,
724
- params.quantity,
725
- params.avgCost,
726
- params.currency.toUpperCase(),
727
- params.openedAt ?? now,
728
- params.notes ?? null,
729
- normalizeNullable(params.source),
730
- normalizeNullable(params.sourceAccountRef),
731
- normalizeNullable(params.sourceLotId),
732
- normalizeNullable(params.sourceRowId),
733
- params.sourceMetadata == null ? null : JSON.stringify(params.sourceMetadata),
734
- now,
735
- now,
736
- );
737
- return Number(result.lastInsertRowid);
738
- });
739
-
740
- return this.getPortfolioLot(tx());
741
- }
742
-
743
- listPortfolioLots(portfolioId = this.getDefaultPortfolio().id): PortfolioLotRecord[] {
744
- const rows = this.db
745
- .prepare(
746
- `SELECT pl.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency AS instrument_currency
747
- FROM portfolio_lots pl
748
- JOIN instruments i ON i.id = pl.instrument_id
749
- WHERE pl.portfolio_id = ?
750
- ORDER BY pl.id`,
751
- )
752
- .all(portfolioId) as PortfolioLotRow[];
753
- return rows.map(mapPortfolioLot);
754
- }
755
-
756
- removePortfolioLotsBySymbol(
757
- symbol: string,
758
- portfolioId = this.getDefaultPortfolio().id,
759
- ): boolean {
760
- const result = this.db
761
- .prepare(
762
- `DELETE FROM portfolio_lots
763
- WHERE portfolio_id = ?
764
- AND instrument_id IN (SELECT id FROM instruments WHERE symbol = ?)`,
765
- )
766
- .run(portfolioId, symbol.trim().toUpperCase());
767
- return result.changes > 0;
768
- }
769
-
770
- removePortfolioLot(id: number): PortfolioLotRecord | null {
771
- const existing = this.getPortfolioLotOrNull(id);
772
- if (existing == null) return null;
773
- this.db.prepare("DELETE FROM portfolio_lots WHERE id = ?").run(id);
774
- return existing;
775
- }
776
-
777
- updatePortfolioLot(
778
- id: number,
779
- params: {
780
- quantity?: number;
781
- avgCost?: number;
782
- currency?: string;
783
- openedAt?: string;
784
- notes?: string;
785
- },
786
- ): PortfolioLotRecord | null {
787
- if (params.quantity != null) {
788
- assertPositiveFinitePortfolioLotNumber(params.quantity, "quantity");
789
- }
790
- if (params.avgCost != null) {
791
- assertPositiveFinitePortfolioLotNumber(params.avgCost, "average cost");
792
- }
793
- const existing = this.db.prepare("SELECT * FROM portfolio_lots WHERE id = ?").get(id) as
794
- | PortfolioLotRow
795
- | undefined;
796
- if (existing == null) return null;
797
-
798
- const now = new Date().toISOString();
799
- this.db
800
- .prepare(
801
- `UPDATE portfolio_lots
802
- SET quantity = ?, avg_cost = ?, currency = ?, opened_at = ?, notes = ?, updated_at = ?
803
- WHERE id = ?`,
804
- )
805
- .run(
806
- params.quantity ?? existing.quantity,
807
- params.avgCost ?? existing.avg_cost,
808
- params.currency == null ? existing.currency : params.currency.toUpperCase(),
809
- params.openedAt ?? existing.opened_at,
810
- params.notes ?? existing.notes,
811
- now,
812
- id,
813
- );
814
- return this.getPortfolioLot(id);
815
- }
816
-
817
- updatePortfolioLotsBySymbol(
818
- symbol: string,
819
- params: {
820
- portfolioId?: number;
821
- quantity?: number;
822
- avgCost?: number;
823
- currency?: string;
824
- openedAt?: string;
825
- notes?: string;
826
- },
827
- ): PortfolioLotRecord[] {
828
- const portfolioId = params.portfolioId ?? this.getDefaultPortfolio().id;
829
- const rows = this.db
830
- .prepare(
831
- `SELECT pl.*
832
- FROM portfolio_lots pl
833
- JOIN instruments i ON i.id = pl.instrument_id
834
- WHERE pl.portfolio_id = ? AND i.symbol = ?
835
- ORDER BY pl.id`,
836
- )
837
- .all(portfolioId, symbol.trim().toUpperCase()) as PortfolioLotRow[];
838
- return rows.flatMap((row) => {
839
- const updated = this.updatePortfolioLot(row.id, params);
840
- return updated == null ? [] : [updated];
841
- });
842
- }
843
-
844
- createAlertRule(params: {
845
- scopeType: AlertScopeType;
846
- scopeId?: number;
847
- instrumentId?: number;
848
- conditionType: string;
849
- conditionVersion: number;
850
- condition: unknown;
851
- timeframe: string;
852
- enabled?: boolean;
853
- checkIntervalSeconds?: number;
854
- nextCheckAt?: string;
855
- cooldownSeconds?: number;
856
- retriggerMode?: string;
857
- }): AlertRuleRecord {
858
- const now = new Date().toISOString();
859
- const result = this.db
860
- .prepare(
861
- `INSERT INTO alert_rules (
862
- scope_type, scope_id, instrument_id, condition_type, condition_version,
863
- condition_json, timeframe, enabled, check_interval_seconds, next_check_at,
864
- retrigger_mode, cooldown_seconds, created_at, updated_at
865
- )
866
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
867
- )
868
- .run(
869
- params.scopeType,
870
- params.scopeId ?? null,
871
- params.instrumentId ?? null,
872
- params.conditionType,
873
- params.conditionVersion,
874
- JSON.stringify(params.condition),
875
- params.timeframe,
876
- params.enabled === false ? 0 : 1,
877
- params.checkIntervalSeconds ?? null,
878
- params.nextCheckAt ?? null,
879
- params.retriggerMode ?? "recurring",
880
- params.cooldownSeconds ?? null,
881
- now,
882
- now,
883
- );
884
- return this.getAlertRule(Number(result.lastInsertRowid));
885
- }
886
-
887
- listAlertRules(): AlertRuleRecord[] {
888
- const rows = this.db
889
- .prepare("SELECT * FROM alert_rules ORDER BY created_at, id")
890
- .all() as AlertRuleRow[];
891
- return rows.map(mapAlertRule);
892
- }
893
-
894
- setAlertRuleEnabled(id: number, enabled: boolean): AlertRuleRecord | null {
895
- const now = new Date().toISOString();
896
- const result = this.db
897
- .prepare(
898
- `UPDATE alert_rules
899
- SET enabled = ?, updated_at = ?
900
- WHERE id = ?`,
901
- )
902
- .run(enabled ? 1 : 0, now, id);
903
- if (result.changes === 0) return null;
904
- return this.getAlertRule(id);
905
- }
906
-
907
- getInstrument(id: number): InstrumentRecord | null {
908
- const row = this.db.prepare("SELECT * FROM instruments WHERE id = ?").get(id) as
909
- | InstrumentRow
910
- | undefined;
911
- return row == null ? null : mapInstrument(row);
912
- }
913
-
914
- upsertInstrumentRecord(input: InstrumentInput): InstrumentRecord {
915
- return mapInstrument(this.upsertInstrument(input));
916
- }
917
-
918
- acquireAutomationRunnerLease(params: {
919
- ownerId: string;
920
- ownerKind: string;
921
- now?: string;
922
- ttlSeconds: number;
923
- }): AutomationRunnerLeaseResult {
924
- const now = params.now ?? new Date().toISOString();
925
- const nowMs = new Date(now).getTime();
926
- const expiresAt = new Date(nowMs + params.ttlSeconds * 1000).toISOString();
927
- const tx = this.db.transaction(() => {
928
- const current = this.db
929
- .prepare(
930
- "SELECT owner_id, owner_kind, acquired_at, heartbeat_at, expires_at FROM automation_runner_leases WHERE id = 1",
931
- )
932
- .get() as AutomationRunnerLeaseRow | undefined;
933
-
934
- if (
935
- current != null &&
936
- current.owner_id !== params.ownerId &&
937
- new Date(current.expires_at).getTime() > nowMs
938
- ) {
939
- return { acquired: false, ...mapAutomationRunnerLease(current) };
940
- }
941
-
942
- this.db
943
- .prepare(
944
- `INSERT INTO automation_runner_leases (
945
- id, owner_id, owner_kind, acquired_at, heartbeat_at, expires_at
946
- )
947
- VALUES (1, ?, ?, ?, ?, ?)
948
- ON CONFLICT(id) DO UPDATE SET
949
- owner_id = excluded.owner_id,
950
- owner_kind = excluded.owner_kind,
951
- acquired_at = excluded.acquired_at,
952
- heartbeat_at = excluded.heartbeat_at,
953
- expires_at = excluded.expires_at`,
954
- )
955
- .run(params.ownerId, params.ownerKind, now, now, expiresAt);
956
-
957
- return {
958
- acquired: true,
959
- ownerId: params.ownerId,
960
- ownerKind: params.ownerKind,
961
- acquiredAt: now,
962
- heartbeatAt: now,
963
- expiresAt,
964
- };
965
- });
966
- return tx();
967
- }
968
-
969
- getAutomationRunnerLease(now = new Date().toISOString()): AutomationRunnerLeaseRecord | null {
970
- const row = this.db
971
- .prepare(
972
- "SELECT owner_id, owner_kind, acquired_at, heartbeat_at, expires_at FROM automation_runner_leases WHERE id = 1",
973
- )
974
- .get() as AutomationRunnerLeaseRow | undefined;
975
- if (row == null) return null;
976
- if (new Date(row.expires_at).getTime() <= new Date(now).getTime()) return null;
977
- return mapAutomationRunnerLease(row);
978
- }
979
-
980
- releaseAutomationRunnerLease(ownerId: string): boolean {
981
- const result = this.db
982
- .prepare("DELETE FROM automation_runner_leases WHERE id = 1 AND owner_id = ?")
983
- .run(ownerId);
984
- return result.changes > 0;
985
- }
986
-
987
- startAlertCheckRun(params: {
988
- ownerId?: string | null;
989
- startedAt?: string;
990
- triggerType: string;
991
- }): AlertCheckRunRecord {
992
- const startedAt = params.startedAt ?? new Date().toISOString();
993
- const result = this.db
994
- .prepare(
995
- `INSERT INTO alert_check_runs (
996
- started_at, status, trigger_type, owner_id
997
- )
998
- VALUES (?, 'running', ?, ?)`,
999
- )
1000
- .run(startedAt, params.triggerType, params.ownerId ?? null);
1001
- return this.getAlertCheckRun(Number(result.lastInsertRowid));
1002
- }
1003
-
1004
- completeAlertCheckRun(
1005
- id: number,
1006
- params: {
1007
- completedAt?: string;
1008
- status: string;
1009
- checkedCount: number;
1010
- triggeredCount: number;
1011
- unavailableCount: number;
1012
- error?: unknown;
1013
- providerStatus?: unknown;
1014
- },
1015
- ): AlertCheckRunRecord {
1016
- const completedAt = params.completedAt ?? new Date().toISOString();
1017
- this.db
1018
- .prepare(
1019
- `UPDATE alert_check_runs
1020
- SET completed_at = ?,
1021
- status = ?,
1022
- checked_count = ?,
1023
- triggered_count = ?,
1024
- unavailable_count = ?,
1025
- error_json = ?,
1026
- provider_status_json = ?
1027
- WHERE id = ?`,
1028
- )
1029
- .run(
1030
- completedAt,
1031
- params.status,
1032
- params.checkedCount,
1033
- params.triggeredCount,
1034
- params.unavailableCount,
1035
- params.error == null ? null : JSON.stringify(params.error),
1036
- params.providerStatus == null ? null : JSON.stringify(params.providerStatus),
1037
- id,
1038
- );
1039
- return this.getAlertCheckRun(id);
1040
- }
1041
-
1042
- getAlertCheckRun(id: number): AlertCheckRunRecord {
1043
- const row = this.db.prepare("SELECT * FROM alert_check_runs WHERE id = ?").get(id) as
1044
- | AlertCheckRunRow
1045
- | undefined;
1046
- if (row == null) throw new Error(`alert check run ${id} not found`);
1047
- return mapAlertCheckRun(row);
1048
- }
1049
-
1050
- listAlertCheckRuns(): AlertCheckRunRecord[] {
1051
- const rows = this.db
1052
- .prepare("SELECT * FROM alert_check_runs ORDER BY started_at DESC, id DESC")
1053
- .all() as AlertCheckRunRow[];
1054
- return rows.map(mapAlertCheckRun);
1055
- }
1056
-
1057
- markStaleAutomationRunsLost(params: { now?: string; graceSeconds: number }): {
1058
- alertCheckRuns: number;
1059
- reportRuns: number;
1060
- } {
1061
- const now = params.now ?? new Date().toISOString();
1062
- const cutoff = new Date(new Date(now).getTime() - params.graceSeconds * 1000).toISOString();
1063
- const tx = this.db.transaction(() => {
1064
- const alertResult = this.db
1065
- .prepare(
1066
- `UPDATE alert_check_runs
1067
- SET status = 'lost',
1068
- completed_at = COALESCE(completed_at, ?),
1069
- error_json = COALESCE(error_json, ?)
1070
- WHERE status = 'running' AND started_at < ?`,
1071
- )
1072
- .run(now, JSON.stringify({ message: "runner lease expired before completion" }), cutoff);
1073
- const reportResult = this.db
1074
- .prepare(
1075
- `UPDATE report_runs
1076
- SET status = 'lost',
1077
- completed_at = COALESCE(completed_at, ?),
1078
- errors_json = COALESCE(errors_json, ?)
1079
- WHERE status = 'running' AND started_at < ?`,
1080
- )
1081
- .run(now, JSON.stringify(["runner lease expired before completion"]), cutoff);
1082
- return {
1083
- alertCheckRuns: alertResult.changes,
1084
- reportRuns: reportResult.changes,
1085
- };
1086
- });
1087
- return tx();
1088
- }
1089
-
1090
- recordNotificationEvent(params: {
1091
- sourceType: string;
1092
- sourceId?: number | null;
1093
- severity: string;
1094
- title: string;
1095
- body: string;
1096
- payload?: unknown;
1097
- status?: string;
1098
- createdAt?: string;
1099
- }): NotificationEventRecord {
1100
- const createdAt = params.createdAt ?? new Date().toISOString();
1101
- const result = this.db
1102
- .prepare(
1103
- `INSERT INTO notification_events (
1104
- source_type, source_id, severity, title, body, payload_json, status, created_at
1105
- )
1106
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1107
- )
1108
- .run(
1109
- params.sourceType,
1110
- params.sourceId ?? null,
1111
- params.severity,
1112
- params.title,
1113
- params.body,
1114
- params.payload == null ? null : JSON.stringify(params.payload),
1115
- params.status ?? "unread",
1116
- createdAt,
1117
- );
1118
- return this.getNotificationEvent(Number(result.lastInsertRowid));
1119
- }
1120
-
1121
- listNotificationEvents(): NotificationEventRecord[] {
1122
- const rows = this.db
1123
- .prepare("SELECT * FROM notification_events ORDER BY created_at DESC, id DESC")
1124
- .all() as NotificationEventRow[];
1125
- return rows.map(mapNotificationEvent);
1126
- }
1127
-
1128
- getNotificationEvent(id: number): NotificationEventRecord {
1129
- const row = this.db.prepare("SELECT * FROM notification_events WHERE id = ?").get(id) as
1130
- | NotificationEventRow
1131
- | undefined;
1132
- if (row == null) throw new Error(`notification event ${id} not found`);
1133
- return mapNotificationEvent(row);
1134
- }
1135
-
1136
- acknowledgeNotificationEvent(
1137
- id: number,
1138
- acknowledgedAt = new Date().toISOString(),
1139
- ): NotificationEventRecord {
1140
- this.db
1141
- .prepare(
1142
- `UPDATE notification_events
1143
- SET status = 'acknowledged',
1144
- acknowledged_at = ?
1145
- WHERE id = ?`,
1146
- )
1147
- .run(acknowledgedAt, id);
1148
- return this.getNotificationEvent(id);
1149
- }
1150
-
1151
- recordNotificationDeliveryAttempt(params: {
1152
- notificationEventId: number;
1153
- channel: string;
1154
- status: string;
1155
- attemptedAt?: string;
1156
- completedAt?: string | null;
1157
- response?: unknown;
1158
- error?: string | null;
1159
- }): NotificationDeliveryAttemptRecord {
1160
- const attemptedAt = params.attemptedAt ?? new Date().toISOString();
1161
- const result = this.db
1162
- .prepare(
1163
- `INSERT INTO notification_delivery_attempts (
1164
- notification_event_id, channel, status, attempted_at, completed_at,
1165
- response_json, error
1166
- )
1167
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
1168
- )
1169
- .run(
1170
- params.notificationEventId,
1171
- params.channel,
1172
- params.status,
1173
- attemptedAt,
1174
- params.completedAt ?? null,
1175
- params.response == null ? null : JSON.stringify(params.response),
1176
- params.error ?? null,
1177
- );
1178
- return this.getNotificationDeliveryAttempt(Number(result.lastInsertRowid));
1179
- }
1180
-
1181
- listNotificationDeliveryAttempts(
1182
- notificationEventId?: number,
1183
- ): NotificationDeliveryAttemptRecord[] {
1184
- const rows =
1185
- notificationEventId == null
1186
- ? this.db
1187
- .prepare(
1188
- "SELECT * FROM notification_delivery_attempts ORDER BY attempted_at DESC, id DESC",
1189
- )
1190
- .all()
1191
- : this.db
1192
- .prepare(
1193
- "SELECT * FROM notification_delivery_attempts WHERE notification_event_id = ? ORDER BY attempted_at DESC, id DESC",
1194
- )
1195
- .all(notificationEventId);
1196
- return (rows as NotificationDeliveryAttemptRow[]).map(mapNotificationDeliveryAttempt);
1197
- }
1198
-
1199
- getNotificationDeliveryAttempt(id: number): NotificationDeliveryAttemptRecord {
1200
- const row = this.db
1201
- .prepare("SELECT * FROM notification_delivery_attempts WHERE id = ?")
1202
- .get(id) as NotificationDeliveryAttemptRow | undefined;
1203
- if (row == null) throw new Error(`notification delivery attempt ${id} not found`);
1204
- return mapNotificationDeliveryAttempt(row);
1205
- }
1206
-
1207
- updateAlertObservation(params: {
1208
- ruleId: number;
1209
- observed: unknown;
1210
- checkedAt?: string;
1211
- triggeredAt?: string;
1212
- }): AlertRuleRecord {
1213
- const checkedAt = params.checkedAt ?? new Date().toISOString();
1214
- this.db
1215
- .prepare(
1216
- `UPDATE alert_rules
1217
- SET last_checked_at = ?,
1218
- last_observed_json = ?,
1219
- last_triggered_at = COALESCE(?, last_triggered_at),
1220
- updated_at = ?
1221
- WHERE id = ?`,
1222
- )
1223
- .run(
1224
- checkedAt,
1225
- JSON.stringify(params.observed),
1226
- params.triggeredAt ?? null,
1227
- checkedAt,
1228
- params.ruleId,
1229
- );
1230
- return this.getAlertRule(params.ruleId);
1231
- }
1232
-
1233
- recordAlertEvent(params: {
1234
- alertRuleId: number;
1235
- instrumentId?: number | null;
1236
- observedValue: unknown;
1237
- status: string;
1238
- message: string;
1239
- triggeredAt?: string;
1240
- }): AlertEventRecord {
1241
- const triggeredAt = params.triggeredAt ?? new Date().toISOString();
1242
- const result = this.db
1243
- .prepare(
1244
- `INSERT INTO alert_events (
1245
- alert_rule_id, instrument_id, observed_value_json, triggered_at, status, message
1246
- )
1247
- VALUES (?, ?, ?, ?, ?, ?)`,
1248
- )
1249
- .run(
1250
- params.alertRuleId,
1251
- params.instrumentId ?? null,
1252
- JSON.stringify(params.observedValue),
1253
- triggeredAt,
1254
- params.status,
1255
- params.message,
1256
- );
1257
- return this.getAlertEvent(Number(result.lastInsertRowid));
1258
- }
1259
-
1260
- recordAlertCheckResult(params: {
1261
- ruleId: number;
1262
- observed: unknown;
1263
- checkedAt: string;
1264
- trigger?: {
1265
- expectedPreviousValue: number | null;
1266
- expectedLastTriggeredAt: string | null;
1267
- instrumentId: number | null;
1268
- message: string;
1269
- triggeredAt: string;
1270
- };
1271
- }): { triggered: boolean; rule: AlertRuleRecord } {
1272
- const tx = this.db.transaction(() => {
1273
- const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(params.ruleId) as
1274
- | AlertRuleRow
1275
- | undefined;
1276
- if (row == null) {
1277
- throw new Error(`alert rule ${params.ruleId} not found`);
1278
- }
1279
-
1280
- const currentPrevious = lastObservedValueFromJson(row.last_observed_json);
1281
- const currentLastTriggeredAt = row.last_triggered_at ?? null;
1282
- const canTrigger =
1283
- params.trigger != null &&
1284
- currentPrevious === params.trigger.expectedPreviousValue &&
1285
- currentLastTriggeredAt === params.trigger.expectedLastTriggeredAt;
1286
-
1287
- if (canTrigger && params.trigger) {
1288
- this.db
1289
- .prepare(
1290
- `INSERT INTO alert_events (
1291
- alert_rule_id, instrument_id, observed_value_json, triggered_at, status, message
1292
- )
1293
- VALUES (?, ?, ?, ?, 'triggered', ?)`,
1294
- )
1295
- .run(
1296
- params.ruleId,
1297
- params.trigger.instrumentId,
1298
- JSON.stringify(params.observed),
1299
- params.trigger.triggeredAt,
1300
- params.trigger.message,
1301
- );
1302
- }
1303
-
1304
- this.db
1305
- .prepare(
1306
- `UPDATE alert_rules
1307
- SET last_checked_at = ?,
1308
- last_observed_json = ?,
1309
- last_triggered_at = COALESCE(?, last_triggered_at),
1310
- updated_at = ?
1311
- WHERE id = ?`,
1312
- )
1313
- .run(
1314
- params.checkedAt,
1315
- JSON.stringify(params.observed),
1316
- canTrigger && params.trigger ? params.trigger.triggeredAt : null,
1317
- params.checkedAt,
1318
- params.ruleId,
1319
- );
1320
-
1321
- return canTrigger;
1322
- });
1323
- const triggered = tx();
1324
- return { triggered, rule: this.getAlertRule(params.ruleId) };
1325
- }
1326
-
1327
- recordAlertEvaluationResult(params: {
1328
- ruleId: number;
1329
- observed: unknown;
1330
- checkedAt: string;
1331
- conditionState: "true" | "false" | "unknown";
1332
- trigger?: {
1333
- instrumentId: number | null;
1334
- title?: string;
1335
- message: string;
1336
- triggeredAt: string;
1337
- observedAt: string;
1338
- providerDataAt?: string | null;
1339
- sourceProvider?: string | null;
1340
- cacheStatus?: string;
1341
- dataDelayMs?: number | null;
1342
- triggerSource: string;
1343
- dedupeKey: string;
1344
- status?: string;
1345
- };
1346
- }): { triggered: boolean; event: AlertEventRecord | null; rule: AlertRuleRecord } {
1347
- const tx = this.db.transaction(() => {
1348
- const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(params.ruleId) as
1349
- | AlertRuleRow
1350
- | undefined;
1351
- if (row == null) {
1352
- throw new Error(`alert rule ${params.ruleId} not found`);
1353
- }
1354
-
1355
- const rearmed = params.conditionState === "false" && row.last_condition_state === "true";
1356
- const nextArmCycleId = rearmed ? row.arm_cycle_id + 1 : row.arm_cycle_id;
1357
- let eventId: number | null = null;
1358
- const canInsertTrigger =
1359
- params.trigger != null &&
1360
- row.enabled === 1 &&
1361
- row.status === "active" &&
1362
- row.last_condition_state !== "true";
1363
-
1364
- if (params.trigger != null && canInsertTrigger) {
1365
- const result = this.db
1366
- .prepare(
1367
- `INSERT OR IGNORE INTO alert_events (
1368
- alert_rule_id, instrument_id, observed_value_json, triggered_at,
1369
- observed_at, provider_data_at, source_provider, cache_status,
1370
- data_delay_ms, trigger_source, dedupe_key, status, message
1371
- )
1372
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1373
- )
1374
- .run(
1375
- params.ruleId,
1376
- params.trigger.instrumentId,
1377
- JSON.stringify(params.observed),
1378
- params.trigger.triggeredAt,
1379
- params.trigger.observedAt,
1380
- params.trigger.providerDataAt ?? null,
1381
- params.trigger.sourceProvider ?? null,
1382
- params.trigger.cacheStatus ?? "live",
1383
- params.trigger.dataDelayMs ?? null,
1384
- params.trigger.triggerSource,
1385
- params.trigger.dedupeKey,
1386
- params.trigger.status ?? "triggered",
1387
- params.trigger.message,
1388
- );
1389
-
1390
- if (result.changes > 0) {
1391
- eventId = Number(result.lastInsertRowid);
1392
- this.db
1393
- .prepare(
1394
- `INSERT INTO notification_events (
1395
- source_type, source_id, severity, title, body, payload_json, status, created_at
1396
- )
1397
- VALUES ('alert_event', ?, 'warning', ?, ?, ?, 'unread', ?)`,
1398
- )
1399
- .run(
1400
- eventId,
1401
- params.trigger.title ?? "Alert triggered",
1402
- params.trigger.message,
1403
- JSON.stringify({
1404
- ruleId: params.ruleId,
1405
- observed: params.observed,
1406
- sourceProvider: params.trigger.sourceProvider ?? null,
1407
- providerDataAt: params.trigger.providerDataAt ?? null,
1408
- cacheStatus: params.trigger.cacheStatus ?? "live",
1409
- dataDelayMs: params.trigger.dataDelayMs ?? null,
1410
- }),
1411
- params.trigger.observedAt,
1412
- );
1413
- }
1414
- }
1415
-
1416
- const triggered = eventId != null;
1417
- const completeOneShot = triggered && row.retrigger_mode === "once";
1418
- this.db
1419
- .prepare(
1420
- `UPDATE alert_rules
1421
- SET last_checked_at = ?,
1422
- last_observed_json = ?,
1423
- last_condition_state = ?,
1424
- arm_cycle_id = ?,
1425
- next_check_at = ?,
1426
- last_triggered_at = COALESCE(?, last_triggered_at),
1427
- enabled = CASE WHEN ? THEN 0 ELSE enabled END,
1428
- status = CASE WHEN ? THEN 'completed' ELSE status END,
1429
- updated_at = ?
1430
- WHERE id = ?`,
1431
- )
1432
- .run(
1433
- params.checkedAt,
1434
- JSON.stringify(params.observed),
1435
- params.conditionState,
1436
- nextArmCycleId,
1437
- nextAlertCheckAt(row, params.checkedAt),
1438
- triggered && params.trigger ? params.trigger.triggeredAt : null,
1439
- completeOneShot ? 1 : 0,
1440
- completeOneShot ? 1 : 0,
1441
- params.checkedAt,
1442
- params.ruleId,
1443
- );
1444
-
1445
- return { triggered, eventId };
1446
- });
1447
- const result = tx();
1448
- return {
1449
- triggered: result.triggered,
1450
- event: result.eventId == null ? null : this.getAlertEvent(result.eventId),
1451
- rule: this.getAlertRule(params.ruleId),
1452
- };
1453
- }
1454
-
1455
- recordAlertUnavailable(params: {
1456
- ruleId: number;
1457
- instrumentId?: number | null;
1458
- reason: string;
1459
- checkedAt: string;
1460
- }): { event: AlertEventRecord; rule: AlertRuleRecord } {
1461
- const tx = this.db.transaction(() => {
1462
- const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(params.ruleId) as
1463
- | AlertRuleRow
1464
- | undefined;
1465
- if (row == null) {
1466
- throw new Error(`alert rule ${params.ruleId} not found`);
1467
- }
1468
-
1469
- const result = this.db
1470
- .prepare(
1471
- `INSERT INTO alert_events (
1472
- alert_rule_id, instrument_id, observed_value_json, triggered_at, status, message
1473
- )
1474
- VALUES (?, ?, ?, ?, 'unavailable', ?)`,
1475
- )
1476
- .run(
1477
- params.ruleId,
1478
- params.instrumentId ?? null,
1479
- JSON.stringify({ status: "unavailable", reason: params.reason, at: params.checkedAt }),
1480
- params.checkedAt,
1481
- `Alert unavailable: ${params.reason}`,
1482
- );
1483
-
1484
- this.db
1485
- .prepare(
1486
- `UPDATE alert_rules
1487
- SET last_checked_at = ?,
1488
- next_check_at = ?,
1489
- updated_at = ?
1490
- WHERE id = ?`,
1491
- )
1492
- .run(
1493
- params.checkedAt,
1494
- nextAlertCheckAt(row, params.checkedAt),
1495
- params.checkedAt,
1496
- params.ruleId,
1497
- );
1498
-
1499
- return Number(result.lastInsertRowid);
1500
- });
1501
- const eventId = tx();
1502
- return { event: this.getAlertEvent(eventId), rule: this.getAlertRule(params.ruleId) };
1503
- }
1504
-
1505
- listAlertEvents(): AlertEventRecord[] {
1506
- const rows = this.db
1507
- .prepare("SELECT * FROM alert_events ORDER BY triggered_at, id")
1508
- .all() as AlertEventRow[];
1509
- return rows.map(mapAlertEvent);
1510
- }
1511
-
1512
- createReportTemplate(params: {
1513
- name: string;
1514
- reportType: string;
1515
- cadence: string;
1516
- timezone: string;
1517
- localTime: string;
1518
- config: unknown;
1519
- enabled?: boolean;
1520
- nextRunAt?: string;
1521
- }): ReportTemplateRecord {
1522
- const now = new Date().toISOString();
1523
- const result = this.db
1524
- .prepare(
1525
- `INSERT INTO report_templates (
1526
- name, report_type, cadence, timezone, local_time, config_json,
1527
- enabled, next_run_at, created_at, updated_at
1528
- )
1529
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1530
- )
1531
- .run(
1532
- params.name,
1533
- params.reportType,
1534
- params.cadence,
1535
- params.timezone,
1536
- params.localTime,
1537
- JSON.stringify(params.config),
1538
- params.enabled === false ? 0 : 1,
1539
- params.nextRunAt ?? null,
1540
- now,
1541
- now,
1542
- );
1543
- return this.getReportTemplate(Number(result.lastInsertRowid));
1544
- }
1545
-
1546
- updateReportTemplate(
1547
- id: number,
1548
- params: {
1549
- name?: string;
1550
- reportType?: string;
1551
- cadence?: string;
1552
- timezone?: string;
1553
- localTime?: string;
1554
- config?: unknown;
1555
- enabled?: boolean;
1556
- nextRunAt?: string | null;
1557
- },
1558
- ): ReportTemplateRecord {
1559
- const existing = this.getReportTemplate(id);
1560
- const now = new Date().toISOString();
1561
- this.db
1562
- .prepare(
1563
- `UPDATE report_templates
1564
- SET name = ?, report_type = ?, cadence = ?, timezone = ?, local_time = ?,
1565
- config_json = ?, enabled = ?, next_run_at = ?, updated_at = ?
1566
- WHERE id = ?`,
1567
- )
1568
- .run(
1569
- params.name ?? existing.name,
1570
- params.reportType ?? existing.reportType,
1571
- params.cadence ?? existing.cadence,
1572
- params.timezone ?? existing.timezone,
1573
- params.localTime ?? existing.localTime,
1574
- JSON.stringify(params.config ?? existing.configJson),
1575
- params.enabled == null ? (existing.enabled ? 1 : 0) : params.enabled ? 1 : 0,
1576
- params.nextRunAt === undefined ? existing.nextRunAt : params.nextRunAt,
1577
- now,
1578
- id,
1579
- );
1580
- return this.getReportTemplate(id);
1581
- }
1582
-
1583
- claimDueReportTemplateRun(
1584
- id: number,
1585
- params: {
1586
- scheduledFor: string;
1587
- nextRunAt: string;
1588
- claimedAt?: string;
1589
- },
1590
- ): ReportTemplateRecord | null {
1591
- const result = this.db
1592
- .prepare(
1593
- `UPDATE report_templates
1594
- SET next_run_at = ?, updated_at = ?
1595
- WHERE id = ? AND enabled = 1 AND next_run_at = ?`,
1596
- )
1597
- .run(params.nextRunAt, params.claimedAt ?? new Date().toISOString(), id, params.scheduledFor);
1598
- return result.changes === 0 ? null : this.getReportTemplate(id);
1599
- }
1600
-
1601
- listReportTemplates(): ReportTemplateRecord[] {
1602
- const rows = this.db
1603
- .prepare("SELECT * FROM report_templates ORDER BY created_at, id")
1604
- .all() as ReportTemplateRow[];
1605
- return rows.map(mapReportTemplate);
1606
- }
1607
-
1608
- recordReportRun(params: {
1609
- templateId?: number | null;
1610
- startedAt?: string;
1611
- completedAt?: string | null;
1612
- status: string;
1613
- triggerType?: string;
1614
- scheduledFor?: string | null;
1615
- ownerId?: string | null;
1616
- artifactPath?: string | null;
1617
- summary?: unknown;
1618
- errors?: unknown;
1619
- }): ReportRunRecord {
1620
- const startedAt = params.startedAt ?? new Date().toISOString();
1621
- const tx = this.db.transaction(() => {
1622
- const result = this.db
1623
- .prepare(
1624
- `INSERT INTO report_runs (
1625
- template_id, started_at, completed_at, status, trigger_type, scheduled_for,
1626
- owner_id, artifact_path, summary_json, errors_json
1627
- )
1628
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1629
- )
1630
- .run(
1631
- params.templateId ?? null,
1632
- startedAt,
1633
- params.completedAt ?? null,
1634
- params.status,
1635
- params.triggerType ?? "manual",
1636
- params.scheduledFor ?? null,
1637
- params.ownerId ?? null,
1638
- params.artifactPath ?? null,
1639
- params.summary == null ? null : JSON.stringify(params.summary),
1640
- params.errors == null ? null : JSON.stringify(params.errors),
1641
- );
1642
-
1643
- if (params.templateId != null) {
1644
- this.db
1645
- .prepare("UPDATE report_templates SET last_run_at = ?, updated_at = ? WHERE id = ?")
1646
- .run(startedAt, startedAt, params.templateId);
1647
- }
1648
-
1649
- return Number(result.lastInsertRowid);
1650
- });
1651
- return this.getReportRun(tx());
1652
- }
1653
-
1654
- listReportRuns(): ReportRunRecord[] {
1655
- const rows = this.db
1656
- .prepare("SELECT * FROM report_runs ORDER BY started_at DESC, id DESC")
1657
- .all() as ReportRunRow[];
1658
- return rows.map(mapReportRun);
1659
- }
1660
-
1661
- recordImportBatch(params: {
1662
- source: string;
1663
- sourceLabel?: string;
1664
- importedAt?: string;
1665
- status: string;
1666
- rawMetadata?: unknown;
1667
- }): ImportBatchRecord {
1668
- const importedAt = params.importedAt ?? new Date().toISOString();
1669
- const result = this.db
1670
- .prepare(
1671
- `INSERT INTO import_batches (
1672
- source, source_label, imported_at, status, raw_metadata_json
1673
- )
1674
- VALUES (?, ?, ?, ?, ?)`,
1675
- )
1676
- .run(
1677
- normalizeNullable(params.source) ?? "unknown",
1678
- normalizeNullable(params.sourceLabel),
1679
- importedAt,
1680
- params.status,
1681
- params.rawMetadata == null ? null : JSON.stringify(params.rawMetadata),
1682
- );
1683
- return this.getImportBatch(Number(result.lastInsertRowid));
1684
- }
1685
-
1686
- recordImportRow(params: {
1687
- batchId: number;
1688
- rowType: string;
1689
- sourceSymbol?: string;
1690
- sourceRowId?: string;
1691
- sourceAccountRef?: string;
1692
- normalizedInstrumentId?: number | null;
1693
- status: string;
1694
- error?: string;
1695
- sourceMetadata?: unknown;
1696
- raw?: unknown;
1697
- }): ImportRowRecord {
1698
- const result = this.db
1699
- .prepare(
1700
- `INSERT INTO import_rows (
1701
- batch_id, row_type, source_symbol, source_row_id, source_account_ref,
1702
- normalized_instrument_id, status, error, source_metadata_json, raw_json
1703
- )
1704
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1705
- )
1706
- .run(
1707
- params.batchId,
1708
- params.rowType,
1709
- normalizeNullable(params.sourceSymbol),
1710
- normalizeNullable(params.sourceRowId),
1711
- normalizeNullable(params.sourceAccountRef),
1712
- params.normalizedInstrumentId ?? null,
1713
- params.status,
1714
- normalizeNullable(params.error),
1715
- params.sourceMetadata == null ? null : JSON.stringify(params.sourceMetadata),
1716
- params.raw == null ? null : JSON.stringify(params.raw),
1717
- );
1718
- return this.getImportRow(Number(result.lastInsertRowid));
1719
- }
1720
-
1721
- private upsertInstrument(input: InstrumentInput): InstrumentRow {
1722
- const symbol = input.symbol.trim().toUpperCase();
1723
- const assetType = input.assetType.trim().toLowerCase();
1724
- const provider = input.provider.trim().toLowerCase();
1725
- const exchange = normalizeNullable(input.exchange);
1726
- const now = new Date().toISOString();
1727
- const resolvedAt = (input.resolvedAt ?? new Date()).toISOString();
1728
-
1729
- const existing = this.db
1730
- .prepare(
1731
- `SELECT * FROM instruments
1732
- WHERE provider = ?
1733
- AND symbol = ?
1734
- AND asset_type = ?
1735
- AND IFNULL(exchange, '') = IFNULL(?, '')`,
1736
- )
1737
- .get(provider, symbol, assetType, exchange) as InstrumentRow | undefined;
1738
-
1739
- if (existing) {
1740
- this.db
1741
- .prepare(
1742
- `UPDATE instruments
1743
- SET name = ?, currency = ?, provider_metadata_json = ?,
1744
- last_resolved_at = ?, updated_at = ?
1745
- WHERE id = ?`,
1746
- )
1747
- .run(
1748
- normalizeNullable(input.name),
1749
- normalizeNullable(input.currency)?.toUpperCase() ?? null,
1750
- input.providerMetadata == null ? null : JSON.stringify(input.providerMetadata),
1751
- resolvedAt,
1752
- now,
1753
- existing.id,
1754
- );
1755
- this.upsertInstrumentAliases(existing.id, input.aliases ?? []);
1756
- return this.db
1757
- .prepare("SELECT * FROM instruments WHERE id = ?")
1758
- .get(existing.id) as InstrumentRow;
1759
- }
1760
-
1761
- const result = this.db
1762
- .prepare(
1763
- `INSERT INTO instruments (
1764
- symbol, asset_type, name, exchange, currency, provider,
1765
- provider_metadata_json, last_resolved_at, created_at, updated_at
1766
- )
1767
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1768
- )
1769
- .run(
1770
- symbol,
1771
- assetType,
1772
- normalizeNullable(input.name),
1773
- exchange,
1774
- normalizeNullable(input.currency)?.toUpperCase() ?? null,
1775
- provider,
1776
- input.providerMetadata == null ? null : JSON.stringify(input.providerMetadata),
1777
- resolvedAt,
1778
- now,
1779
- now,
1780
- );
1781
- const instrumentId = Number(result.lastInsertRowid);
1782
- this.upsertInstrumentAliases(instrumentId, input.aliases ?? []);
1783
- return this.db
1784
- .prepare("SELECT * FROM instruments WHERE id = ?")
1785
- .get(instrumentId) as InstrumentRow;
1786
- }
1787
-
1788
- private upsertInstrumentAliases(instrumentId: number, aliases: InstrumentAliasInput[]): void {
1789
- if (aliases.length === 0) return;
1790
-
1791
- const now = new Date().toISOString();
1792
- for (const alias of aliases) {
1793
- const source = normalizeSource(alias.source);
1794
- const sourceSymbol = normalizeSourceSymbol(alias.sourceSymbol);
1795
- const sourceExchange = normalizeExchange(alias.sourceExchange);
1796
- const sourceAssetType = normalizeAssetType(alias.sourceAssetType);
1797
- const sourceId = normalizeNullable(alias.sourceId);
1798
- const rawJson = alias.raw == null ? null : JSON.stringify(alias.raw);
1799
- const existing =
1800
- sourceId == null
1801
- ? (this.db
1802
- .prepare(
1803
- `SELECT id, instrument_id FROM instrument_aliases
1804
- WHERE source = ?
1805
- AND source_symbol = ?
1806
- AND IFNULL(source_exchange, '') = IFNULL(?, '')
1807
- AND IFNULL(source_asset_type, '') = IFNULL(?, '')
1808
- LIMIT 1`,
1809
- )
1810
- .get(source, sourceSymbol, sourceExchange, sourceAssetType) as
1811
- | InstrumentAliasRow
1812
- | undefined)
1813
- : (this.db
1814
- .prepare(
1815
- `SELECT id, instrument_id FROM instrument_aliases
1816
- WHERE source = ? AND source_id = ?
1817
- LIMIT 1`,
1818
- )
1819
- .get(source, sourceId) as InstrumentAliasRow | undefined);
1820
-
1821
- if (existing) {
1822
- this.db
1823
- .prepare(
1824
- `UPDATE instrument_aliases
1825
- SET instrument_id = ?, source_symbol = ?, source_exchange = ?,
1826
- source_asset_type = ?, source_id = ?, raw_json = ?, updated_at = ?
1827
- WHERE id = ?`,
1828
- )
1829
- .run(
1830
- instrumentId,
1831
- sourceSymbol,
1832
- sourceExchange,
1833
- sourceAssetType,
1834
- sourceId,
1835
- rawJson,
1836
- now,
1837
- existing.id,
1838
- );
1839
- continue;
1840
- }
1841
-
1842
- this.db
1843
- .prepare(
1844
- `INSERT INTO instrument_aliases (
1845
- instrument_id, source, source_symbol, source_exchange,
1846
- source_asset_type, source_id, raw_json, created_at, updated_at
1847
- )
1848
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1849
- )
1850
- .run(
1851
- instrumentId,
1852
- source,
1853
- sourceSymbol,
1854
- sourceExchange,
1855
- sourceAssetType,
1856
- sourceId,
1857
- rawJson,
1858
- now,
1859
- now,
1860
- );
1861
- }
1862
- }
1863
-
1864
- private getWatchlistItem(id: number): WatchlistItemRecord {
1865
- const row = this.db
1866
- .prepare(
1867
- `SELECT wi.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency
1868
- FROM watchlist_items wi
1869
- JOIN instruments i ON i.id = wi.instrument_id
1870
- WHERE wi.id = ?`,
1871
- )
1872
- .get(id) as WatchlistItemRow;
1873
- return mapWatchlistItem(row);
1874
- }
1875
-
1876
- private getPortfolioLot(id: number): PortfolioLotRecord {
1877
- const row = this.getPortfolioLotOrNull(id);
1878
- if (row == null) {
1879
- throw new Error(`portfolio lot ${id} not found`);
1880
- }
1881
- return row;
1882
- }
1883
-
1884
- private getPortfolioLotOrNull(id: number): PortfolioLotRecord | null {
1885
- const row = this.db
1886
- .prepare(
1887
- `SELECT pl.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency AS instrument_currency
1888
- FROM portfolio_lots pl
1889
- JOIN instruments i ON i.id = pl.instrument_id
1890
- WHERE pl.id = ?`,
1891
- )
1892
- .get(id) as PortfolioLotRow | undefined;
1893
- return row == null ? null : mapPortfolioLot(row);
1894
- }
1895
-
1896
- getAlertRule(id: number): AlertRuleRecord {
1897
- const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(id) as AlertRuleRow;
1898
- return mapAlertRule(row);
1899
- }
1900
-
1901
- getAlertEvent(id: number): AlertEventRecord {
1902
- const row = this.db.prepare("SELECT * FROM alert_events WHERE id = ?").get(id) as AlertEventRow;
1903
- return mapAlertEvent(row);
1904
- }
1905
-
1906
- private getReportTemplate(id: number): ReportTemplateRecord {
1907
- const row = this.db
1908
- .prepare("SELECT * FROM report_templates WHERE id = ?")
1909
- .get(id) as ReportTemplateRow;
1910
- return mapReportTemplate(row);
1911
- }
1912
-
1913
- private getReportRun(id: number): ReportRunRecord {
1914
- const row = this.db.prepare("SELECT * FROM report_runs WHERE id = ?").get(id) as ReportRunRow;
1915
- return mapReportRun(row);
1916
- }
1917
-
1918
- private getImportBatch(id: number): ImportBatchRecord {
1919
- const row = this.db
1920
- .prepare("SELECT * FROM import_batches WHERE id = ?")
1921
- .get(id) as ImportBatchRow;
1922
- return mapImportBatch(row);
1923
- }
1924
-
1925
- private getImportRow(id: number): ImportRowRecord {
1926
- const row = this.db.prepare("SELECT * FROM import_rows WHERE id = ?").get(id) as ImportRowRow;
1927
- return mapImportRow(row);
1928
- }
1929
- }
1930
-
1931
- function mapCollection(row: WatchlistRow): CollectionRecord {
1932
- return {
1933
- id: row.id,
1934
- name: row.name,
1935
- isDefault: row.is_default === 1,
1936
- createdAt: row.created_at,
1937
- updatedAt: row.updated_at,
1938
- };
1939
- }
1940
-
1941
- function mapInstrument(row: InstrumentRow): InstrumentRecord {
1942
- return {
1943
- id: row.id,
1944
- symbol: row.symbol,
1945
- assetType: row.asset_type,
1946
- name: row.name,
1947
- exchange: row.exchange,
1948
- currency: row.currency,
1949
- provider: row.provider,
1950
- createdAt: row.created_at,
1951
- updatedAt: row.updated_at,
1952
- };
1953
- }
1954
-
1955
- function mapWatchlistItem(row: WatchlistItemRow): WatchlistItemRecord {
1956
- return {
1957
- id: row.id,
1958
- watchlistId: row.watchlist_id,
1959
- instrumentId: row.instrument_id,
1960
- symbol: row.symbol,
1961
- name: row.name,
1962
- assetType: row.asset_type,
1963
- exchange: row.exchange,
1964
- currency: row.currency,
1965
- targetPrice: row.target_price,
1966
- stopPrice: row.stop_price,
1967
- priceCurrency: row.price_currency,
1968
- thesis: row.thesis,
1969
- notes: row.notes,
1970
- tags: row.tags_json == null ? null : (JSON.parse(row.tags_json) as string[]),
1971
- source: row.source,
1972
- sourceRowId: row.source_row_id,
1973
- sourceMetadata: row.source_metadata_json == null ? null : JSON.parse(row.source_metadata_json),
1974
- createdAt: row.created_at,
1975
- updatedAt: row.updated_at,
1976
- };
1977
- }
1978
-
1979
- function mapPortfolioLot(row: PortfolioLotRow): PortfolioLotRecord {
1980
- return {
1981
- id: row.id,
1982
- portfolioId: row.portfolio_id,
1983
- instrumentId: row.instrument_id,
1984
- symbol: row.symbol,
1985
- name: row.name,
1986
- assetType: row.asset_type,
1987
- exchange: row.exchange,
1988
- instrumentCurrency: row.instrument_currency,
1989
- quantity: row.quantity,
1990
- avgCost: row.avg_cost,
1991
- currency: row.currency,
1992
- openedAt: row.opened_at,
1993
- notes: row.notes,
1994
- source: row.source,
1995
- sourceAccountRef: row.source_account_ref,
1996
- sourceLotId: row.source_lot_id,
1997
- sourceRowId: row.source_row_id,
1998
- sourceMetadata: row.source_metadata_json == null ? null : JSON.parse(row.source_metadata_json),
1999
- createdAt: row.created_at,
2000
- updatedAt: row.updated_at,
2001
- };
2002
- }
2003
-
2004
- function mapAlertRule(row: AlertRuleRow): AlertRuleRecord {
2005
- return {
2006
- id: row.id,
2007
- scopeType: row.scope_type,
2008
- scopeId: row.scope_id,
2009
- instrumentId: row.instrument_id,
2010
- conditionType: row.condition_type,
2011
- conditionVersion: row.condition_version,
2012
- conditionJson: JSON.parse(row.condition_json),
2013
- timeframe: row.timeframe,
2014
- enabled: row.enabled === 1,
2015
- checkIntervalSeconds: row.check_interval_seconds,
2016
- nextCheckAt: row.next_check_at,
2017
- lastCheckedAt: row.last_checked_at,
2018
- lastObservedJson: row.last_observed_json == null ? null : JSON.parse(row.last_observed_json),
2019
- status: row.status,
2020
- retriggerMode: row.retrigger_mode,
2021
- lastConditionState: row.last_condition_state,
2022
- ruleRevision: row.rule_revision,
2023
- armCycleId: row.arm_cycle_id,
2024
- cooldownSeconds: row.cooldown_seconds,
2025
- lastTriggeredAt: row.last_triggered_at,
2026
- createdAt: row.created_at,
2027
- updatedAt: row.updated_at,
2028
- };
2029
- }
2030
-
2031
- function mapAlertEvent(row: AlertEventRow): AlertEventRecord {
2032
- return {
2033
- id: row.id,
2034
- alertRuleId: row.alert_rule_id,
2035
- instrumentId: row.instrument_id,
2036
- observedValueJson: row.observed_value_json == null ? null : JSON.parse(row.observed_value_json),
2037
- triggeredAt: row.triggered_at,
2038
- observedAt: row.observed_at,
2039
- providerDataAt: row.provider_data_at,
2040
- sourceProvider: row.source_provider,
2041
- cacheStatus: row.cache_status,
2042
- dataDelayMs: row.data_delay_ms,
2043
- triggerSource: row.trigger_source,
2044
- dedupeKey: row.dedupe_key,
2045
- status: row.status,
2046
- message: row.message,
2047
- };
2048
- }
2049
-
2050
- function mapAutomationRunnerLease(row: AutomationRunnerLeaseRow): AutomationRunnerLeaseRecord {
2051
- return {
2052
- ownerId: row.owner_id,
2053
- ownerKind: row.owner_kind,
2054
- acquiredAt: row.acquired_at,
2055
- heartbeatAt: row.heartbeat_at,
2056
- expiresAt: row.expires_at,
2057
- };
2058
- }
2059
-
2060
- function mapAlertCheckRun(row: AlertCheckRunRow): AlertCheckRunRecord {
2061
- return {
2062
- id: row.id,
2063
- startedAt: row.started_at,
2064
- completedAt: row.completed_at,
2065
- status: row.status,
2066
- triggerType: row.trigger_type,
2067
- checkedCount: row.checked_count,
2068
- triggeredCount: row.triggered_count,
2069
- unavailableCount: row.unavailable_count,
2070
- ownerId: row.owner_id,
2071
- errorJson: row.error_json == null ? null : JSON.parse(row.error_json),
2072
- providerStatusJson:
2073
- row.provider_status_json == null ? null : JSON.parse(row.provider_status_json),
2074
- };
2075
- }
2076
-
2077
- function mapNotificationEvent(row: NotificationEventRow): NotificationEventRecord {
2078
- return {
2079
- id: row.id,
2080
- sourceType: row.source_type,
2081
- sourceId: row.source_id,
2082
- severity: row.severity,
2083
- title: row.title,
2084
- body: row.body,
2085
- payloadJson: row.payload_json == null ? null : JSON.parse(row.payload_json),
2086
- status: row.status,
2087
- createdAt: row.created_at,
2088
- acknowledgedAt: row.acknowledged_at,
2089
- };
2090
- }
2091
-
2092
- function mapNotificationDeliveryAttempt(
2093
- row: NotificationDeliveryAttemptRow,
2094
- ): NotificationDeliveryAttemptRecord {
2095
- return {
2096
- id: row.id,
2097
- notificationEventId: row.notification_event_id,
2098
- channel: row.channel,
2099
- status: row.status,
2100
- attemptedAt: row.attempted_at,
2101
- completedAt: row.completed_at,
2102
- responseJson: row.response_json == null ? null : JSON.parse(row.response_json),
2103
- error: row.error,
2104
- };
2105
- }
2106
-
2107
- function nextAlertCheckAt(row: AlertRuleRow, checkedAt: string): string | null {
2108
- if (row.check_interval_seconds == null) return null;
2109
- const checkedAtMs = new Date(checkedAt).getTime();
2110
- if (!Number.isFinite(checkedAtMs)) return null;
2111
- return new Date(checkedAtMs + row.check_interval_seconds * 1000).toISOString();
2112
- }
2113
-
2114
- function mapReportTemplate(row: ReportTemplateRow): ReportTemplateRecord {
2115
- return {
2116
- id: row.id,
2117
- name: row.name,
2118
- reportType: row.report_type,
2119
- cadence: row.cadence,
2120
- timezone: row.timezone,
2121
- localTime: row.local_time,
2122
- configJson: JSON.parse(row.config_json),
2123
- enabled: row.enabled === 1,
2124
- lastRunAt: row.last_run_at,
2125
- nextRunAt: row.next_run_at,
2126
- createdAt: row.created_at,
2127
- updatedAt: row.updated_at,
2128
- };
2129
- }
2130
-
2131
- function mapReportRun(row: ReportRunRow): ReportRunRecord {
2132
- return {
2133
- id: row.id,
2134
- templateId: row.template_id,
2135
- startedAt: row.started_at,
2136
- completedAt: row.completed_at,
2137
- status: row.status,
2138
- triggerType: row.trigger_type,
2139
- scheduledFor: row.scheduled_for,
2140
- ownerId: row.owner_id,
2141
- artifactPath: row.artifact_path,
2142
- summaryJson: row.summary_json == null ? null : JSON.parse(row.summary_json),
2143
- errorsJson: row.errors_json == null ? null : JSON.parse(row.errors_json),
2144
- };
2145
- }
2146
-
2147
- function mapImportBatch(row: ImportBatchRow): ImportBatchRecord {
2148
- return {
2149
- id: row.id,
2150
- source: row.source,
2151
- sourceLabel: row.source_label,
2152
- importedAt: row.imported_at,
2153
- status: row.status,
2154
- rawMetadata: row.raw_metadata_json == null ? null : JSON.parse(row.raw_metadata_json),
2155
- };
2156
- }
2157
-
2158
- function mapImportRow(row: ImportRowRow): ImportRowRecord {
2159
- return {
2160
- id: row.id,
2161
- batchId: row.batch_id,
2162
- rowType: row.row_type,
2163
- sourceSymbol: row.source_symbol,
2164
- sourceRowId: row.source_row_id,
2165
- sourceAccountRef: row.source_account_ref,
2166
- normalizedInstrumentId: row.normalized_instrument_id,
2167
- status: row.status,
2168
- error: row.error,
2169
- sourceMetadata: row.source_metadata_json == null ? null : JSON.parse(row.source_metadata_json),
2170
- raw: row.raw_json == null ? null : JSON.parse(row.raw_json),
2171
- };
2172
- }
2173
-
2174
- function normalizeNullable(value: string | null | undefined): string | null {
2175
- const normalized = value?.trim();
2176
- return normalized ? normalized : null;
2177
- }
2178
-
2179
- function assertPositiveFinitePortfolioLotNumber(
2180
- value: number,
2181
- label: "quantity" | "average cost",
2182
- ): void {
2183
- if (!Number.isFinite(value) || value <= 0) {
2184
- throw new Error(`Portfolio lot ${label} must be a positive finite number.`);
2185
- }
2186
- }
2187
-
2188
- function lastObservedValueFromJson(value: string | null): number | null {
2189
- if (value == null) return null;
2190
- const parsed = JSON.parse(value) as { value?: unknown } | null;
2191
- return typeof parsed?.value === "number" ? parsed.value : null;
2192
- }
2193
-
2194
- function normalizeSource(value: string): string {
2195
- return value.trim().toLowerCase();
2196
- }
2197
-
2198
- function normalizeSourceSymbol(value: string): string {
2199
- return value.trim().toUpperCase();
2200
- }
2201
-
2202
- function normalizeExchange(value: string | null | undefined): string | null {
2203
- return normalizeNullable(value)?.toUpperCase() ?? null;
2204
- }
2205
-
2206
- function normalizeAssetType(value: string | null | undefined): string | null {
2207
- return normalizeNullable(value)?.toLowerCase() ?? null;
2208
- }