opencandle 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +186 -117
- package/dist/analysts/contracts.d.ts +1 -3
- package/dist/analysts/contracts.js +1 -11
- package/dist/analysts/contracts.js.map +1 -1
- package/dist/analysts/orchestrator.d.ts +1 -3
- package/dist/analysts/orchestrator.js +1 -26
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli.js +32 -8
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +19 -3
- package/dist/config.js +69 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/browser.d.ts +1 -3
- package/dist/infra/browser.js +4 -2
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/cache.d.ts +8 -11
- package/dist/infra/cache.js +17 -15
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/http-client.d.ts +4 -1
- package/dist/infra/http-client.js +59 -6
- package/dist/infra/http-client.js.map +1 -1
- package/dist/infra/index.d.ts +3 -3
- package/dist/infra/index.js +3 -3
- package/dist/infra/index.js.map +1 -1
- package/dist/infra/native-dependencies.js +2 -2
- package/dist/infra/native-dependencies.js.map +1 -1
- package/dist/infra/node-version.js.map +1 -1
- package/dist/infra/opencandle-paths.d.ts +0 -3
- package/dist/infra/opencandle-paths.js +4 -11
- package/dist/infra/opencandle-paths.js.map +1 -1
- package/dist/infra/rate-limiter.d.ts +4 -0
- package/dist/infra/rate-limiter.js +17 -10
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/alert-conditions.d.ts +34 -0
- package/dist/market-state/alert-conditions.js +23 -0
- package/dist/market-state/alert-conditions.js.map +1 -0
- package/dist/market-state/alert-runner.d.ts +55 -0
- package/dist/market-state/alert-runner.js +634 -0
- package/dist/market-state/alert-runner.js.map +1 -0
- package/dist/market-state/daily-report.d.ts +26 -0
- package/dist/market-state/daily-report.js +179 -0
- package/dist/market-state/daily-report.js.map +1 -0
- package/dist/market-state/local-automation-service.d.ts +25 -0
- package/dist/market-state/local-automation-service.js +119 -0
- package/dist/market-state/local-automation-service.js.map +1 -0
- package/dist/market-state/notification-delivery.d.ts +14 -0
- package/dist/market-state/notification-delivery.js +139 -0
- package/dist/market-state/notification-delivery.js.map +1 -0
- package/dist/market-state/resolve-for-mutation.d.ts +10 -0
- package/dist/market-state/resolve-for-mutation.js +15 -0
- package/dist/market-state/resolve-for-mutation.js.map +1 -0
- package/dist/market-state/resolve.d.ts +14 -0
- package/dist/market-state/resolve.js +89 -0
- package/dist/market-state/resolve.js.map +1 -0
- package/dist/market-state/service.d.ts +527 -0
- package/dist/market-state/service.js +1099 -0
- package/dist/market-state/service.js.map +1 -0
- package/dist/memory/index.d.ts +7 -7
- package/dist/memory/index.js +6 -6
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manager.d.ts +9 -0
- package/dist/memory/manager.js +39 -22
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/retrieval.js +7 -4
- package/dist/memory/retrieval.js.map +1 -1
- package/dist/memory/sqlite.js +385 -3
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/storage.d.ts +3 -2
- package/dist/memory/storage.js +1 -2
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/tool-defaults.js +64 -28
- package/dist/memory/tool-defaults.js.map +1 -1
- package/dist/memory/types.js +4 -0
- package/dist/memory/types.js.map +1 -1
- package/dist/monitor.d.ts +2 -0
- package/dist/monitor.js +104 -0
- package/dist/monitor.js.map +1 -0
- package/dist/onboarding/connect.js +4 -6
- package/dist/onboarding/connect.js.map +1 -1
- package/dist/onboarding/credential-interceptor.js +1 -1
- package/dist/onboarding/credential-interceptor.js.map +1 -1
- package/dist/onboarding/degradation-accumulator.js +1 -3
- package/dist/onboarding/degradation-accumulator.js.map +1 -1
- package/dist/onboarding/providers.js +3 -16
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.js +1 -1
- package/dist/onboarding/tool-helpers.js.map +1 -1
- package/dist/onboarding/tool-tags.js +6 -4
- package/dist/onboarding/tool-tags.js.map +1 -1
- package/dist/onboarding/validation.js +1 -1
- package/dist/onboarding/validation.js.map +1 -1
- package/dist/pi/opencandle-extension.d.ts +8 -0
- package/dist/pi/opencandle-extension.js +637 -59
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session.d.ts +1 -1
- package/dist/pi/session.js +3 -1
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.js +17 -2
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tool-adapter.js +5 -2
- package/dist/pi/tool-adapter.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +18 -3
- package/dist/prompts/context-builder.js +117 -18
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/disclaimer.js +1 -1
- package/dist/prompts/disclaimer.js.map +1 -1
- package/dist/prompts/policy-cards.d.ts +13 -0
- package/dist/prompts/policy-cards.js +197 -0
- package/dist/prompts/policy-cards.js.map +1 -0
- package/dist/prompts/sections.d.ts +1 -1
- package/dist/prompts/sections.js +3 -3
- package/dist/prompts/sections.js.map +1 -1
- package/dist/prompts/symbol-preflight.d.ts +20 -0
- package/dist/prompts/symbol-preflight.js +49 -0
- package/dist/prompts/symbol-preflight.js.map +1 -0
- package/dist/prompts/workflow-prompts.d.ts +1 -1
- package/dist/prompts/workflow-prompts.js +209 -19
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.d.ts +1 -1
- package/dist/providers/alpha-vantage.js +49 -8
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -1
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/errors.d.ts +5 -0
- package/dist/providers/errors.js +11 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/exa-search.d.ts +2 -2
- package/dist/providers/exa-search.js +19 -11
- package/dist/providers/exa-search.js.map +1 -1
- package/dist/providers/fear-greed.js +1 -1
- package/dist/providers/fear-greed.js.map +1 -1
- package/dist/providers/finnhub.js +3 -5
- package/dist/providers/finnhub.js.map +1 -1
- package/dist/providers/fred.js +2 -2
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +7 -6
- package/dist/providers/index.js +6 -5
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/reddit.js +2 -2
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/sec-edgar.d.ts +9 -1
- package/dist/providers/sec-edgar.js +181 -6
- package/dist/providers/sec-edgar.js.map +1 -1
- package/dist/providers/tradingview.d.ts +47 -0
- package/dist/providers/tradingview.js +275 -0
- package/dist/providers/tradingview.js.map +1 -0
- package/dist/providers/twitter.js +6 -8
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.js +26 -12
- package/dist/providers/web-search.js.map +1 -1
- package/dist/providers/with-fallback.js +4 -2
- package/dist/providers/with-fallback.js.map +1 -1
- package/dist/providers/wrap-provider.d.ts +2 -3
- package/dist/providers/wrap-provider.js +14 -8
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +3 -1
- package/dist/providers/yahoo-finance.js +226 -11
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +9 -0
- package/dist/routing/classify-intent.js +153 -3
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.d.ts +1 -1
- package/dist/routing/defaults.js +3 -3
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/entity-extractor.d.ts +2 -0
- package/dist/routing/entity-extractor.js +377 -26
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/fund-symbols.d.ts +2 -0
- package/dist/routing/fund-symbols.js +55 -0
- package/dist/routing/fund-symbols.js.map +1 -0
- package/dist/routing/horizon.d.ts +1 -0
- package/dist/routing/horizon.js +10 -0
- package/dist/routing/horizon.js.map +1 -0
- package/dist/routing/index.d.ts +12 -6
- package/dist/routing/index.js +8 -4
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/legacy-rule-router.d.ts +9 -0
- package/dist/routing/legacy-rule-router.js +12 -0
- package/dist/routing/legacy-rule-router.js.map +1 -0
- package/dist/routing/planning.d.ts +54 -0
- package/dist/routing/planning.js +562 -0
- package/dist/routing/planning.js.map +1 -0
- package/dist/routing/route-manifest.d.ts +35 -0
- package/dist/routing/route-manifest.js +242 -0
- package/dist/routing/route-manifest.js.map +1 -0
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +46 -45
- package/dist/routing/router-prompt.js.map +1 -1
- package/dist/routing/router-types.d.ts +10 -0
- package/dist/routing/router.d.ts +1 -0
- package/dist/routing/router.js +572 -13
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -1
- package/dist/routing/slot-resolver.js +45 -7
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/routing/symbol-disambiguator.d.ts +11 -0
- package/dist/routing/symbol-disambiguator.js +52 -0
- package/dist/routing/symbol-disambiguator.js.map +1 -0
- package/dist/routing/turn-context.d.ts +44 -0
- package/dist/routing/turn-context.js +45 -0
- package/dist/routing/turn-context.js.map +1 -0
- package/dist/routing/types.d.ts +15 -1
- package/dist/runtime/answer-contracts.d.ts +82 -0
- package/dist/runtime/answer-contracts.js +442 -0
- package/dist/runtime/answer-contracts.js.map +1 -0
- package/dist/runtime/artifact-contracts.d.ts +14 -0
- package/dist/runtime/artifact-contracts.js +57 -0
- package/dist/runtime/artifact-contracts.js.map +1 -0
- package/dist/runtime/planning-evidence.d.ts +99 -0
- package/dist/runtime/planning-evidence.js +466 -0
- package/dist/runtime/planning-evidence.js.map +1 -0
- package/dist/runtime/prompt-step.d.ts +1 -9
- package/dist/runtime/prompt-step.js +0 -10
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/run-context.d.ts +5 -2
- package/dist/runtime/run-context.js +8 -1
- package/dist/runtime/run-context.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +29 -3
- package/dist/runtime/session-coordinator.js +204 -31
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/session-title.d.ts +14 -0
- package/dist/runtime/session-title.js +50 -0
- package/dist/runtime/session-title.js.map +1 -0
- package/dist/runtime/tool-defaults-wrapper.js +1 -3
- package/dist/runtime/tool-defaults-wrapper.js.map +1 -1
- package/dist/runtime/validation.js.map +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-runner.d.ts +3 -3
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/runtime/workflow-runner.js.map +1 -1
- package/dist/sentiment/adapters/finnhub.d.ts +1 -1
- package/dist/sentiment/adapters/finnhub.js +6 -1
- package/dist/sentiment/adapters/finnhub.js.map +1 -1
- package/dist/sentiment/adapters/reddit.d.ts +2 -2
- package/dist/sentiment/adapters/twitter.d.ts +1 -1
- package/dist/sentiment/adapters/web.d.ts +1 -1
- package/dist/sentiment/index.d.ts +9 -11
- package/dist/sentiment/index.js +9 -20
- package/dist/sentiment/index.js.map +1 -1
- package/dist/sentiment/keywords.js +26 -4
- package/dist/sentiment/keywords.js.map +1 -1
- package/dist/sentiment/pipeline.d.ts +2 -2
- package/dist/sentiment/pipeline.js +1 -1
- package/dist/sentiment/pipeline.js.map +1 -1
- package/dist/sentiment/scorer.js +1 -1
- package/dist/sentiment/store.d.ts +1 -1
- package/dist/sentiment/store.js +1 -1
- package/dist/sentiment/store.js.map +1 -1
- package/dist/sentiment/trends.d.ts +1 -1
- package/dist/sentiment/trends.js.map +1 -1
- package/dist/sentiment/types.js.map +1 -1
- package/dist/system-prompt.js +7 -3
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +7 -7
- package/dist/tool-kit.js +4 -4
- package/dist/tool-kit.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.js +12 -7
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +19 -10
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +24 -12
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.js +9 -4
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.js +9 -4
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +1 -0
- package/dist/tools/fundamentals/sec-filings.js +36 -4
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +23 -18
- package/dist/tools/index.js +53 -38
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.js +15 -3
- package/dist/tools/interaction/ask-user.js.map +1 -1
- package/dist/tools/interaction/twitter-login.js +13 -3
- package/dist/tools/interaction/twitter-login.js.map +1 -1
- package/dist/tools/macro/fear-greed.js +1 -1
- package/dist/tools/macro/fear-greed.js.map +1 -1
- package/dist/tools/macro/fred-data.d.ts +1 -1
- package/dist/tools/macro/fred-data.js +44 -9
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.js +21 -3
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.js +4 -2
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.d.ts +18 -0
- package/dist/tools/market/screen-stocks.js +252 -0
- package/dist/tools/market/screen-stocks.js.map +1 -0
- package/dist/tools/market/search-ticker.js +161 -9
- package/dist/tools/market/search-ticker.js.map +1 -1
- package/dist/tools/market/stock-history.d.ts +2 -2
- package/dist/tools/market/stock-history.js +27 -8
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.js +6 -4
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +1 -2
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.js +27 -9
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/alerts.d.ts +15 -0
- package/dist/tools/portfolio/alerts.js +357 -0
- package/dist/tools/portfolio/alerts.js.map +1 -0
- package/dist/tools/portfolio/correlation.d.ts +1 -1
- package/dist/tools/portfolio/correlation.js +34 -14
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/daily-report.d.ts +8 -0
- package/dist/tools/portfolio/daily-report.js +83 -0
- package/dist/tools/portfolio/daily-report.js.map +1 -0
- package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
- package/dist/tools/portfolio/holdings-overlap.js +112 -0
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
- package/dist/tools/portfolio/notifications.d.ts +7 -0
- package/dist/tools/portfolio/notifications.js +43 -0
- package/dist/tools/portfolio/notifications.js.map +1 -0
- package/dist/tools/portfolio/predictions.d.ts +12 -6
- package/dist/tools/portfolio/predictions.js +338 -88
- package/dist/tools/portfolio/predictions.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
- package/dist/tools/portfolio/risk-analysis.js +46 -7
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.d.ts +4 -3
- package/dist/tools/portfolio/tracker.js +247 -102
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.d.ts +6 -4
- package/dist/tools/portfolio/watchlist.js +209 -101
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js +24 -11
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.js +71 -14
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
- package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
- package/dist/tools/sentiment/sentiment-trend.js +12 -2
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +13 -6
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/untrusted-text.d.ts +2 -0
- package/dist/tools/sentiment/untrusted-text.js +17 -0
- package/dist/tools/sentiment/untrusted-text.js.map +1 -0
- package/dist/tools/sentiment/web-search.js +37 -12
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.js +16 -4
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +3 -3
- package/dist/tools/technical/backtest.js +65 -44
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +24 -8
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/market.d.ts +1 -0
- package/dist/types/options.d.ts +10 -0
- package/dist/types/portfolio.d.ts +41 -4
- package/dist/workflows/compare-assets.d.ts +0 -3
- package/dist/workflows/compare-assets.js +55 -10
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -4
- package/dist/workflows/index.js +3 -3
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/options-screener.d.ts +0 -3
- package/dist/workflows/options-screener.js +88 -14
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.d.ts +0 -3
- package/dist/workflows/portfolio-builder.js +7 -11
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +82 -0
- package/gui/server/automation-heartbeat.ts +97 -0
- package/gui/server/background-quotes.ts +97 -1
- package/gui/server/chat-event-adapter.ts +32 -10
- package/gui/server/chat-run-session.ts +16 -0
- package/gui/server/gui-session-manager.ts +5 -0
- package/gui/server/invoke-tool.ts +144 -1
- package/gui/server/live-chat-event-adapter.ts +21 -6
- package/gui/server/market-state-api.ts +315 -0
- package/gui/server/model-setup.ts +149 -2
- package/gui/server/private-api-access.ts +62 -0
- package/gui/server/projector.ts +58 -11
- package/gui/server/prompt-observation.ts +58 -0
- package/gui/server/quote-snapshot-store.ts +50 -0
- package/gui/server/server.ts +236 -376
- package/gui/server/session-actions.ts +186 -1
- package/gui/server/session-entry-wait.ts +81 -0
- package/gui/server/shutdown.ts +47 -0
- package/gui/server/tool-invoke-ack.ts +49 -0
- package/gui/server/tool-metadata.ts +23 -10
- package/gui/server/websocket.ts +13 -3
- package/gui/server/writer-lock.ts +6 -2
- package/gui/server/ws-hub.ts +292 -0
- package/gui/shared/chat-events.ts +16 -1
- package/gui/shared/event-reducer.ts +24 -6
- package/gui/web/dist/assets/CatalogOverlay-eJ2cBk33.js +1 -0
- package/gui/web/dist/assets/index-2KZtKBmu.css +1 -0
- package/gui/web/dist/assets/index-CveNgtDg.js +69 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +22 -12
- package/src/analysts/contracts.ts +10 -23
- package/src/analysts/orchestrator.ts +8 -43
- package/src/cli.ts +37 -13
- package/src/config.ts +99 -7
- package/src/index.ts +1 -1
- package/src/infra/browser.ts +4 -2
- package/src/infra/cache.ts +41 -30
- package/src/infra/http-client.ts +72 -6
- package/src/infra/index.ts +7 -10
- package/src/infra/native-dependencies.ts +8 -3
- package/src/infra/node-version.ts +3 -1
- package/src/infra/opencandle-paths.ts +3 -14
- package/src/infra/rate-limiter.ts +32 -20
- package/src/market-state/alert-conditions.ts +82 -0
- package/src/market-state/alert-runner.ts +863 -0
- package/src/market-state/daily-report.ts +247 -0
- package/src/market-state/local-automation-service.ts +162 -0
- package/src/market-state/notification-delivery.ts +158 -0
- package/src/market-state/resolve-for-mutation.ts +24 -0
- package/src/market-state/resolve.ts +112 -0
- package/src/market-state/service.ts +2344 -0
- package/src/memory/index.ts +7 -7
- package/src/memory/manager.ts +57 -26
- package/src/memory/retrieval.ts +8 -7
- package/src/memory/sqlite.ts +407 -6
- package/src/memory/storage.ts +8 -17
- package/src/memory/tool-defaults.ts +60 -39
- package/src/memory/types.ts +7 -3
- package/src/monitor.ts +121 -0
- package/src/onboarding/connect.ts +10 -33
- package/src/onboarding/credential-interceptor.ts +3 -15
- package/src/onboarding/degradation-accumulator.ts +1 -3
- package/src/onboarding/providers.ts +9 -40
- package/src/onboarding/state.ts +4 -15
- package/src/onboarding/tool-helpers.ts +2 -9
- package/src/onboarding/tool-tags.ts +6 -6
- package/src/onboarding/validation.ts +14 -20
- package/src/pi/opencandle-extension.ts +795 -120
- package/src/pi/session.ts +7 -5
- package/src/pi/setup.ts +61 -33
- package/src/pi/tool-adapter.ts +5 -2
- package/src/prompts/context-builder.ts +143 -21
- package/src/prompts/disclaimer.ts +1 -1
- package/src/prompts/policy-cards.ts +220 -0
- package/src/prompts/sections.ts +4 -4
- package/src/prompts/symbol-preflight.ts +80 -0
- package/src/prompts/workflow-prompts.ts +231 -28
- package/src/providers/alpha-vantage.ts +82 -40
- package/src/providers/coingecko.ts +2 -5
- package/src/providers/errors.ts +9 -0
- package/src/providers/exa-search.ts +24 -22
- package/src/providers/fear-greed.ts +1 -1
- package/src/providers/finnhub.ts +7 -6
- package/src/providers/fred.ts +3 -3
- package/src/providers/index.ts +14 -6
- package/src/providers/reddit.ts +17 -6
- package/src/providers/sec-edgar.ts +235 -5
- package/src/providers/tradingview.ts +399 -0
- package/src/providers/twitter.ts +6 -8
- package/src/providers/web-search.ts +30 -20
- package/src/providers/with-fallback.ts +8 -7
- package/src/providers/wrap-provider.ts +15 -10
- package/src/providers/yahoo-finance.ts +292 -20
- package/src/routing/classify-intent.ts +186 -4
- package/src/routing/defaults.ts +4 -4
- package/src/routing/entity-extractor.ts +428 -28
- package/src/routing/fund-symbols.ts +58 -0
- package/src/routing/horizon.ts +7 -0
- package/src/routing/index.ts +60 -16
- package/src/routing/legacy-rule-router.ts +13 -0
- package/src/routing/planning.ts +823 -0
- package/src/routing/route-manifest.ts +309 -0
- package/src/routing/router-llm-client.ts +4 -4
- package/src/routing/router-prompt.ts +52 -52
- package/src/routing/router-types.ts +18 -0
- package/src/routing/router.ts +717 -20
- package/src/routing/slot-resolver.ts +75 -14
- package/src/routing/symbol-disambiguator.ts +72 -0
- package/src/routing/turn-context.ts +108 -0
- package/src/routing/types.ts +15 -1
- package/src/runtime/answer-contracts.ts +672 -0
- package/src/runtime/artifact-contracts.ts +77 -0
- package/src/runtime/planning-evidence.ts +682 -0
- package/src/runtime/prompt-step.ts +1 -16
- package/src/runtime/run-context.ts +12 -2
- package/src/runtime/session-coordinator.ts +297 -56
- package/src/runtime/session-title.ts +60 -0
- package/src/runtime/tool-defaults-wrapper.ts +1 -3
- package/src/runtime/validation.ts +1 -4
- package/src/runtime/workflow-events.ts +7 -7
- package/src/runtime/workflow-runner.ts +5 -11
- package/src/sentiment/adapters/finnhub.ts +7 -2
- package/src/sentiment/adapters/reddit.ts +2 -2
- package/src/sentiment/adapters/twitter.ts +1 -1
- package/src/sentiment/adapters/web.ts +1 -1
- package/src/sentiment/index.ts +16 -26
- package/src/sentiment/keywords.ts +26 -4
- package/src/sentiment/pipeline.ts +15 -4
- package/src/sentiment/scorer.ts +1 -1
- package/src/sentiment/store.ts +2 -2
- package/src/sentiment/trends.ts +9 -3
- package/src/sentiment/types.ts +5 -4
- package/src/system-prompt.ts +7 -3
- package/src/tool-kit.ts +10 -9
- package/src/tools/fundamentals/company-overview.ts +20 -10
- package/src/tools/fundamentals/comps.ts +69 -56
- package/src/tools/fundamentals/dcf.ts +146 -96
- package/src/tools/fundamentals/earnings.ts +17 -7
- package/src/tools/fundamentals/financials.ts +17 -8
- package/src/tools/fundamentals/sec-filings.ts +52 -8
- package/src/tools/index.ts +53 -38
- package/src/tools/interaction/ask-user.ts +22 -10
- package/src/tools/interaction/twitter-login.ts +17 -5
- package/src/tools/macro/fear-greed.ts +2 -2
- package/src/tools/macro/fred-data.ts +80 -42
- package/src/tools/market/crypto-history.ts +25 -4
- package/src/tools/market/crypto-price.ts +7 -7
- package/src/tools/market/screen-stocks.ts +279 -0
- package/src/tools/market/search-ticker.ts +219 -18
- package/src/tools/market/stock-history.ts +38 -13
- package/src/tools/market/stock-quote.ts +11 -8
- package/src/tools/options/greeks.ts +5 -6
- package/src/tools/options/option-chain.ts +47 -18
- package/src/tools/portfolio/alerts.ts +457 -0
- package/src/tools/portfolio/correlation.ts +48 -21
- package/src/tools/portfolio/daily-report.ts +101 -0
- package/src/tools/portfolio/holdings-overlap.ts +139 -0
- package/src/tools/portfolio/notifications.ts +45 -0
- package/src/tools/portfolio/predictions.ts +407 -107
- package/src/tools/portfolio/risk-analysis.ts +47 -8
- package/src/tools/portfolio/tracker.ts +271 -110
- package/src/tools/portfolio/watchlist.ts +251 -116
- package/src/tools/sentiment/reddit-sentiment.ts +51 -25
- package/src/tools/sentiment/sentiment-summary.ts +116 -35
- package/src/tools/sentiment/sentiment-trend.ts +24 -7
- package/src/tools/sentiment/twitter-sentiment.ts +23 -16
- package/src/tools/sentiment/untrusted-text.ts +21 -0
- package/src/tools/sentiment/web-search.ts +52 -16
- package/src/tools/sentiment/web-sentiment.ts +27 -11
- package/src/tools/technical/backtest.ts +78 -47
- package/src/tools/technical/indicators.ts +40 -17
- package/src/types/index.ts +8 -3
- package/src/types/market.ts +1 -0
- package/src/types/options.ts +17 -0
- package/src/types/portfolio.ts +46 -4
- package/src/types/sentiment.ts +2 -2
- package/src/workflows/compare-assets.ts +67 -19
- package/src/workflows/index.ts +3 -4
- package/src/workflows/options-screener.ts +98 -22
- package/src/workflows/portfolio-builder.ts +40 -29
- package/dist/runtime/index.d.ts +0 -16
- package/dist/runtime/index.js +0 -10
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/provider-ids.d.ts +0 -14
- package/dist/runtime/provider-ids.js +0 -14
- package/dist/runtime/provider-ids.js.map +0 -1
- package/dist/workflows/types.d.ts +0 -4
- package/dist/workflows/types.js +0 -2
- package/dist/workflows/types.js.map +0 -1
- package/gui/web/dist/assets/CatalogOverlay-D1ImSJTe.js +0 -1
- package/gui/web/dist/assets/index-DBrWq43L.css +0 -1
- package/gui/web/dist/assets/index-RflHaj0y.js +0 -67
- package/src/runtime/index.ts +0 -55
- package/src/runtime/provider-ids.ts +0 -15
- package/src/workflows/types.ts +0 -4
|
@@ -0,0 +1,1099 @@
|
|
|
1
|
+
export class MarketStateService {
|
|
2
|
+
db;
|
|
3
|
+
constructor(db) {
|
|
4
|
+
this.db = db;
|
|
5
|
+
}
|
|
6
|
+
getDefaultWatchlist() {
|
|
7
|
+
const now = new Date().toISOString();
|
|
8
|
+
this.db
|
|
9
|
+
.prepare(`INSERT OR IGNORE INTO watchlists (name, is_default, created_at, updated_at)
|
|
10
|
+
SELECT 'Default', 1, ?, ?
|
|
11
|
+
WHERE NOT EXISTS (SELECT 1 FROM watchlists WHERE is_default = 1)`)
|
|
12
|
+
.run(now, now);
|
|
13
|
+
const row = this.db
|
|
14
|
+
.prepare("SELECT * FROM watchlists WHERE is_default = 1 LIMIT 1")
|
|
15
|
+
.get();
|
|
16
|
+
return mapCollection(row);
|
|
17
|
+
}
|
|
18
|
+
getDefaultPortfolio() {
|
|
19
|
+
const now = new Date().toISOString();
|
|
20
|
+
this.db
|
|
21
|
+
.prepare(`INSERT OR IGNORE INTO portfolios (name, base_currency, is_default, created_at, updated_at)
|
|
22
|
+
SELECT 'Default', 'USD', 1, ?, ?
|
|
23
|
+
WHERE NOT EXISTS (SELECT 1 FROM portfolios WHERE is_default = 1)`)
|
|
24
|
+
.run(now, now);
|
|
25
|
+
const row = this.db
|
|
26
|
+
.prepare("SELECT * FROM portfolios WHERE is_default = 1 LIMIT 1")
|
|
27
|
+
.get();
|
|
28
|
+
return {
|
|
29
|
+
...mapCollection(row),
|
|
30
|
+
baseCurrency: row.base_currency,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
findInstrumentByAlias(lookup) {
|
|
34
|
+
const source = normalizeSource(lookup.source);
|
|
35
|
+
const sourceId = normalizeNullable(lookup.sourceId);
|
|
36
|
+
const alias = sourceId == null
|
|
37
|
+
? this.db
|
|
38
|
+
.prepare(`SELECT id, instrument_id FROM instrument_aliases
|
|
39
|
+
WHERE source = ?
|
|
40
|
+
AND source_symbol = ?
|
|
41
|
+
AND IFNULL(source_exchange, '') = IFNULL(?, '')
|
|
42
|
+
AND IFNULL(source_asset_type, '') = IFNULL(?, '')
|
|
43
|
+
LIMIT 1`)
|
|
44
|
+
.get(source, normalizeSourceSymbol(lookup.sourceSymbol ?? ""), normalizeExchange(lookup.sourceExchange), normalizeAssetType(lookup.sourceAssetType))
|
|
45
|
+
: this.db
|
|
46
|
+
.prepare(`SELECT id, instrument_id FROM instrument_aliases
|
|
47
|
+
WHERE source = ? AND source_id = ?
|
|
48
|
+
LIMIT 1`)
|
|
49
|
+
.get(source, sourceId);
|
|
50
|
+
if (alias == null)
|
|
51
|
+
return null;
|
|
52
|
+
const row = this.db
|
|
53
|
+
.prepare("SELECT * FROM instruments WHERE id = ?")
|
|
54
|
+
.get(alias.instrument_id);
|
|
55
|
+
return row == null ? null : mapInstrument(row);
|
|
56
|
+
}
|
|
57
|
+
addWatchlistItem(params) {
|
|
58
|
+
const tx = this.db.transaction(() => {
|
|
59
|
+
const watchlistId = params.watchlistId ?? this.getDefaultWatchlist().id;
|
|
60
|
+
const instrument = this.upsertInstrument(params.instrument);
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
const existing = this.db
|
|
63
|
+
.prepare(`SELECT * FROM watchlist_items
|
|
64
|
+
WHERE watchlist_id = ? AND instrument_id = ?`)
|
|
65
|
+
.get(watchlistId, instrument.id);
|
|
66
|
+
if (existing) {
|
|
67
|
+
this.db
|
|
68
|
+
.prepare(`UPDATE watchlist_items
|
|
69
|
+
SET target_price = ?, stop_price = ?, price_currency = ?, thesis = ?,
|
|
70
|
+
notes = ?, tags_json = ?, source = ?, source_row_id = ?,
|
|
71
|
+
source_metadata_json = ?, updated_at = ?
|
|
72
|
+
WHERE id = ?`)
|
|
73
|
+
.run(params.targetPrice === undefined ? existing.target_price : params.targetPrice, params.stopPrice === undefined ? existing.stop_price : params.stopPrice, params.priceCurrency === undefined ? existing.price_currency : params.priceCurrency, params.thesis === undefined ? existing.thesis : params.thesis, params.notes === undefined ? existing.notes : params.notes, params.tags == null ? existing.tags_json : JSON.stringify(params.tags), params.source === undefined ? existing.source : normalizeNullable(params.source), params.sourceRowId === undefined
|
|
74
|
+
? existing.source_row_id
|
|
75
|
+
: normalizeNullable(params.sourceRowId), params.sourceMetadata === undefined
|
|
76
|
+
? existing.source_metadata_json
|
|
77
|
+
: params.sourceMetadata == null
|
|
78
|
+
? null
|
|
79
|
+
: JSON.stringify(params.sourceMetadata), now, existing.id);
|
|
80
|
+
return existing.id;
|
|
81
|
+
}
|
|
82
|
+
const result = this.db
|
|
83
|
+
.prepare(`INSERT INTO watchlist_items (
|
|
84
|
+
watchlist_id, instrument_id, thesis, notes, tags_json,
|
|
85
|
+
target_price, stop_price, price_currency, source, source_row_id,
|
|
86
|
+
source_metadata_json, created_at, updated_at
|
|
87
|
+
)
|
|
88
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
89
|
+
.run(watchlistId, instrument.id, params.thesis ?? null, params.notes ?? null, params.tags == null ? null : JSON.stringify(params.tags), params.targetPrice ?? null, params.stopPrice ?? null, params.priceCurrency ?? params.instrument.currency ?? null, normalizeNullable(params.source), normalizeNullable(params.sourceRowId), params.sourceMetadata == null ? null : JSON.stringify(params.sourceMetadata), now, now);
|
|
90
|
+
return Number(result.lastInsertRowid);
|
|
91
|
+
});
|
|
92
|
+
return this.getWatchlistItem(tx());
|
|
93
|
+
}
|
|
94
|
+
listWatchlistItems(watchlistId = this.getDefaultWatchlist().id) {
|
|
95
|
+
const rows = this.db
|
|
96
|
+
.prepare(`SELECT wi.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency
|
|
97
|
+
FROM watchlist_items wi
|
|
98
|
+
JOIN instruments i ON i.id = wi.instrument_id
|
|
99
|
+
WHERE wi.watchlist_id = ?
|
|
100
|
+
ORDER BY i.symbol`)
|
|
101
|
+
.all(watchlistId);
|
|
102
|
+
return rows.map(mapWatchlistItem);
|
|
103
|
+
}
|
|
104
|
+
removeWatchlistItemBySymbol(symbol, watchlistId = this.getDefaultWatchlist().id) {
|
|
105
|
+
const result = this.db
|
|
106
|
+
.prepare(`DELETE FROM watchlist_items
|
|
107
|
+
WHERE watchlist_id = ?
|
|
108
|
+
AND instrument_id IN (SELECT id FROM instruments WHERE symbol = ?)`)
|
|
109
|
+
.run(watchlistId, symbol.trim().toUpperCase());
|
|
110
|
+
return result.changes > 0;
|
|
111
|
+
}
|
|
112
|
+
updateWatchlistItemBySymbol(symbol, params) {
|
|
113
|
+
const watchlistId = params.watchlistId ?? this.getDefaultWatchlist().id;
|
|
114
|
+
const existing = this.db
|
|
115
|
+
.prepare(`SELECT wi.*
|
|
116
|
+
FROM watchlist_items wi
|
|
117
|
+
JOIN instruments i ON i.id = wi.instrument_id
|
|
118
|
+
WHERE wi.watchlist_id = ? AND i.symbol = ?
|
|
119
|
+
LIMIT 1`)
|
|
120
|
+
.get(watchlistId, symbol.trim().toUpperCase());
|
|
121
|
+
if (existing == null)
|
|
122
|
+
return null;
|
|
123
|
+
const now = new Date().toISOString();
|
|
124
|
+
this.db
|
|
125
|
+
.prepare(`UPDATE watchlist_items
|
|
126
|
+
SET target_price = ?, stop_price = ?, price_currency = ?, thesis = ?,
|
|
127
|
+
notes = ?, tags_json = ?, updated_at = ?
|
|
128
|
+
WHERE id = ?`)
|
|
129
|
+
.run(params.targetPrice === undefined ? existing.target_price : params.targetPrice, params.stopPrice === undefined ? existing.stop_price : params.stopPrice, params.priceCurrency === undefined ? existing.price_currency : params.priceCurrency, params.thesis === undefined ? existing.thesis : params.thesis, params.notes === undefined ? existing.notes : params.notes, params.tags == null ? existing.tags_json : JSON.stringify(params.tags), now, existing.id);
|
|
130
|
+
return this.getWatchlistItem(existing.id);
|
|
131
|
+
}
|
|
132
|
+
addPortfolioLot(params) {
|
|
133
|
+
assertPositiveFinitePortfolioLotNumber(params.quantity, "quantity");
|
|
134
|
+
assertPositiveFinitePortfolioLotNumber(params.avgCost, "average cost");
|
|
135
|
+
const tx = this.db.transaction(() => {
|
|
136
|
+
const portfolioId = params.portfolioId ?? this.getDefaultPortfolio().id;
|
|
137
|
+
const instrument = this.upsertInstrument(params.instrument);
|
|
138
|
+
const now = new Date().toISOString();
|
|
139
|
+
const result = this.db
|
|
140
|
+
.prepare(`INSERT INTO portfolio_lots (
|
|
141
|
+
portfolio_id, instrument_id, quantity, avg_cost, currency,
|
|
142
|
+
opened_at, notes, source, source_account_ref, source_lot_id,
|
|
143
|
+
source_row_id, source_metadata_json, created_at, updated_at
|
|
144
|
+
)
|
|
145
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
146
|
+
.run(portfolioId, instrument.id, params.quantity, params.avgCost, params.currency.toUpperCase(), params.openedAt ?? now, params.notes ?? null, normalizeNullable(params.source), normalizeNullable(params.sourceAccountRef), normalizeNullable(params.sourceLotId), normalizeNullable(params.sourceRowId), params.sourceMetadata == null ? null : JSON.stringify(params.sourceMetadata), now, now);
|
|
147
|
+
return Number(result.lastInsertRowid);
|
|
148
|
+
});
|
|
149
|
+
return this.getPortfolioLot(tx());
|
|
150
|
+
}
|
|
151
|
+
listPortfolioLots(portfolioId = this.getDefaultPortfolio().id) {
|
|
152
|
+
const rows = this.db
|
|
153
|
+
.prepare(`SELECT pl.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency AS instrument_currency
|
|
154
|
+
FROM portfolio_lots pl
|
|
155
|
+
JOIN instruments i ON i.id = pl.instrument_id
|
|
156
|
+
WHERE pl.portfolio_id = ?
|
|
157
|
+
ORDER BY pl.id`)
|
|
158
|
+
.all(portfolioId);
|
|
159
|
+
return rows.map(mapPortfolioLot);
|
|
160
|
+
}
|
|
161
|
+
removePortfolioLotsBySymbol(symbol, portfolioId = this.getDefaultPortfolio().id) {
|
|
162
|
+
const result = this.db
|
|
163
|
+
.prepare(`DELETE FROM portfolio_lots
|
|
164
|
+
WHERE portfolio_id = ?
|
|
165
|
+
AND instrument_id IN (SELECT id FROM instruments WHERE symbol = ?)`)
|
|
166
|
+
.run(portfolioId, symbol.trim().toUpperCase());
|
|
167
|
+
return result.changes > 0;
|
|
168
|
+
}
|
|
169
|
+
removePortfolioLot(id) {
|
|
170
|
+
const existing = this.getPortfolioLotOrNull(id);
|
|
171
|
+
if (existing == null)
|
|
172
|
+
return null;
|
|
173
|
+
this.db.prepare("DELETE FROM portfolio_lots WHERE id = ?").run(id);
|
|
174
|
+
return existing;
|
|
175
|
+
}
|
|
176
|
+
updatePortfolioLot(id, params) {
|
|
177
|
+
if (params.quantity != null) {
|
|
178
|
+
assertPositiveFinitePortfolioLotNumber(params.quantity, "quantity");
|
|
179
|
+
}
|
|
180
|
+
if (params.avgCost != null) {
|
|
181
|
+
assertPositiveFinitePortfolioLotNumber(params.avgCost, "average cost");
|
|
182
|
+
}
|
|
183
|
+
const existing = this.db.prepare("SELECT * FROM portfolio_lots WHERE id = ?").get(id);
|
|
184
|
+
if (existing == null)
|
|
185
|
+
return null;
|
|
186
|
+
const now = new Date().toISOString();
|
|
187
|
+
this.db
|
|
188
|
+
.prepare(`UPDATE portfolio_lots
|
|
189
|
+
SET quantity = ?, avg_cost = ?, currency = ?, opened_at = ?, notes = ?, updated_at = ?
|
|
190
|
+
WHERE id = ?`)
|
|
191
|
+
.run(params.quantity ?? existing.quantity, params.avgCost ?? existing.avg_cost, params.currency == null ? existing.currency : params.currency.toUpperCase(), params.openedAt ?? existing.opened_at, params.notes ?? existing.notes, now, id);
|
|
192
|
+
return this.getPortfolioLot(id);
|
|
193
|
+
}
|
|
194
|
+
updatePortfolioLotsBySymbol(symbol, params) {
|
|
195
|
+
const portfolioId = params.portfolioId ?? this.getDefaultPortfolio().id;
|
|
196
|
+
const rows = this.db
|
|
197
|
+
.prepare(`SELECT pl.*
|
|
198
|
+
FROM portfolio_lots pl
|
|
199
|
+
JOIN instruments i ON i.id = pl.instrument_id
|
|
200
|
+
WHERE pl.portfolio_id = ? AND i.symbol = ?
|
|
201
|
+
ORDER BY pl.id`)
|
|
202
|
+
.all(portfolioId, symbol.trim().toUpperCase());
|
|
203
|
+
return rows.flatMap((row) => {
|
|
204
|
+
const updated = this.updatePortfolioLot(row.id, params);
|
|
205
|
+
return updated == null ? [] : [updated];
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
recordPrediction(params) {
|
|
209
|
+
const tx = this.db.transaction(() => {
|
|
210
|
+
const instrument = this.upsertInstrument(params.instrument);
|
|
211
|
+
const opened = params.now ?? new Date();
|
|
212
|
+
const expires = new Date(opened);
|
|
213
|
+
expires.setDate(expires.getDate() + params.timeframeDays);
|
|
214
|
+
const nowIso = opened.toISOString();
|
|
215
|
+
const result = this.db
|
|
216
|
+
.prepare(`INSERT INTO prediction_records (
|
|
217
|
+
instrument_id, direction, conviction, entry_price, target_price,
|
|
218
|
+
opened_at, expires_at, status, created_at, updated_at
|
|
219
|
+
)
|
|
220
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'open', ?, ?)`)
|
|
221
|
+
.run(instrument.id, params.direction, params.conviction, params.entryPrice, params.targetPrice ?? null, nowIso, expires.toISOString(), nowIso, nowIso);
|
|
222
|
+
return Number(result.lastInsertRowid);
|
|
223
|
+
});
|
|
224
|
+
return this.getPrediction(tx());
|
|
225
|
+
}
|
|
226
|
+
listPredictions() {
|
|
227
|
+
const rows = this.db
|
|
228
|
+
.prepare(`SELECT pr.*, i.symbol
|
|
229
|
+
FROM prediction_records pr
|
|
230
|
+
JOIN instruments i ON i.id = pr.instrument_id
|
|
231
|
+
ORDER BY pr.opened_at, pr.id`)
|
|
232
|
+
.all();
|
|
233
|
+
return rows.map(mapPrediction);
|
|
234
|
+
}
|
|
235
|
+
updatePredictionOutcome(params) {
|
|
236
|
+
const now = new Date().toISOString();
|
|
237
|
+
this.db
|
|
238
|
+
.prepare(`UPDATE prediction_records
|
|
239
|
+
SET status = ?, resolved_at = ?, result_json = ?, updated_at = ?
|
|
240
|
+
WHERE id = ?`)
|
|
241
|
+
.run(params.status, params.resolvedAt, JSON.stringify(params.result), now, params.id);
|
|
242
|
+
return this.getPrediction(params.id);
|
|
243
|
+
}
|
|
244
|
+
createAlertRule(params) {
|
|
245
|
+
const now = new Date().toISOString();
|
|
246
|
+
const result = this.db
|
|
247
|
+
.prepare(`INSERT INTO alert_rules (
|
|
248
|
+
scope_type, scope_id, instrument_id, condition_type, condition_version,
|
|
249
|
+
condition_json, timeframe, enabled, check_interval_seconds, next_check_at,
|
|
250
|
+
retrigger_mode, cooldown_seconds, created_at, updated_at
|
|
251
|
+
)
|
|
252
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
253
|
+
.run(params.scopeType, params.scopeId ?? null, params.instrumentId ?? null, params.conditionType, params.conditionVersion, JSON.stringify(params.condition), params.timeframe, params.enabled === false ? 0 : 1, params.checkIntervalSeconds ?? null, params.nextCheckAt ?? null, params.retriggerMode ?? "recurring", params.cooldownSeconds ?? null, now, now);
|
|
254
|
+
return this.getAlertRule(Number(result.lastInsertRowid));
|
|
255
|
+
}
|
|
256
|
+
listAlertRules() {
|
|
257
|
+
const rows = this.db
|
|
258
|
+
.prepare("SELECT * FROM alert_rules ORDER BY created_at, id")
|
|
259
|
+
.all();
|
|
260
|
+
return rows.map(mapAlertRule);
|
|
261
|
+
}
|
|
262
|
+
setAlertRuleEnabled(id, enabled) {
|
|
263
|
+
const now = new Date().toISOString();
|
|
264
|
+
const result = this.db
|
|
265
|
+
.prepare(`UPDATE alert_rules
|
|
266
|
+
SET enabled = ?, updated_at = ?
|
|
267
|
+
WHERE id = ?`)
|
|
268
|
+
.run(enabled ? 1 : 0, now, id);
|
|
269
|
+
if (result.changes === 0)
|
|
270
|
+
return null;
|
|
271
|
+
return this.getAlertRule(id);
|
|
272
|
+
}
|
|
273
|
+
getInstrument(id) {
|
|
274
|
+
const row = this.db.prepare("SELECT * FROM instruments WHERE id = ?").get(id);
|
|
275
|
+
return row == null ? null : mapInstrument(row);
|
|
276
|
+
}
|
|
277
|
+
upsertInstrumentRecord(input) {
|
|
278
|
+
return mapInstrument(this.upsertInstrument(input));
|
|
279
|
+
}
|
|
280
|
+
acquireAutomationRunnerLease(params) {
|
|
281
|
+
const now = params.now ?? new Date().toISOString();
|
|
282
|
+
const nowMs = new Date(now).getTime();
|
|
283
|
+
const expiresAt = new Date(nowMs + params.ttlSeconds * 1000).toISOString();
|
|
284
|
+
const tx = this.db.transaction(() => {
|
|
285
|
+
const current = this.db
|
|
286
|
+
.prepare("SELECT owner_id, owner_kind, acquired_at, heartbeat_at, expires_at FROM automation_runner_leases WHERE id = 1")
|
|
287
|
+
.get();
|
|
288
|
+
if (current != null &&
|
|
289
|
+
current.owner_id !== params.ownerId &&
|
|
290
|
+
new Date(current.expires_at).getTime() > nowMs) {
|
|
291
|
+
return { acquired: false, ...mapAutomationRunnerLease(current) };
|
|
292
|
+
}
|
|
293
|
+
this.db
|
|
294
|
+
.prepare(`INSERT INTO automation_runner_leases (
|
|
295
|
+
id, owner_id, owner_kind, acquired_at, heartbeat_at, expires_at
|
|
296
|
+
)
|
|
297
|
+
VALUES (1, ?, ?, ?, ?, ?)
|
|
298
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
299
|
+
owner_id = excluded.owner_id,
|
|
300
|
+
owner_kind = excluded.owner_kind,
|
|
301
|
+
acquired_at = excluded.acquired_at,
|
|
302
|
+
heartbeat_at = excluded.heartbeat_at,
|
|
303
|
+
expires_at = excluded.expires_at`)
|
|
304
|
+
.run(params.ownerId, params.ownerKind, now, now, expiresAt);
|
|
305
|
+
return {
|
|
306
|
+
acquired: true,
|
|
307
|
+
ownerId: params.ownerId,
|
|
308
|
+
ownerKind: params.ownerKind,
|
|
309
|
+
acquiredAt: now,
|
|
310
|
+
heartbeatAt: now,
|
|
311
|
+
expiresAt,
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
return tx();
|
|
315
|
+
}
|
|
316
|
+
getAutomationRunnerLease(now = new Date().toISOString()) {
|
|
317
|
+
const row = this.db
|
|
318
|
+
.prepare("SELECT owner_id, owner_kind, acquired_at, heartbeat_at, expires_at FROM automation_runner_leases WHERE id = 1")
|
|
319
|
+
.get();
|
|
320
|
+
if (row == null)
|
|
321
|
+
return null;
|
|
322
|
+
if (new Date(row.expires_at).getTime() <= new Date(now).getTime())
|
|
323
|
+
return null;
|
|
324
|
+
return mapAutomationRunnerLease(row);
|
|
325
|
+
}
|
|
326
|
+
releaseAutomationRunnerLease(ownerId) {
|
|
327
|
+
const result = this.db
|
|
328
|
+
.prepare("DELETE FROM automation_runner_leases WHERE id = 1 AND owner_id = ?")
|
|
329
|
+
.run(ownerId);
|
|
330
|
+
return result.changes > 0;
|
|
331
|
+
}
|
|
332
|
+
startAlertCheckRun(params) {
|
|
333
|
+
const startedAt = params.startedAt ?? new Date().toISOString();
|
|
334
|
+
const result = this.db
|
|
335
|
+
.prepare(`INSERT INTO alert_check_runs (
|
|
336
|
+
started_at, status, trigger_type, owner_id
|
|
337
|
+
)
|
|
338
|
+
VALUES (?, 'running', ?, ?)`)
|
|
339
|
+
.run(startedAt, params.triggerType, params.ownerId ?? null);
|
|
340
|
+
return this.getAlertCheckRun(Number(result.lastInsertRowid));
|
|
341
|
+
}
|
|
342
|
+
completeAlertCheckRun(id, params) {
|
|
343
|
+
const completedAt = params.completedAt ?? new Date().toISOString();
|
|
344
|
+
this.db
|
|
345
|
+
.prepare(`UPDATE alert_check_runs
|
|
346
|
+
SET completed_at = ?,
|
|
347
|
+
status = ?,
|
|
348
|
+
checked_count = ?,
|
|
349
|
+
triggered_count = ?,
|
|
350
|
+
unavailable_count = ?,
|
|
351
|
+
error_json = ?,
|
|
352
|
+
provider_status_json = ?
|
|
353
|
+
WHERE id = ?`)
|
|
354
|
+
.run(completedAt, params.status, params.checkedCount, params.triggeredCount, params.unavailableCount, params.error == null ? null : JSON.stringify(params.error), params.providerStatus == null ? null : JSON.stringify(params.providerStatus), id);
|
|
355
|
+
return this.getAlertCheckRun(id);
|
|
356
|
+
}
|
|
357
|
+
getAlertCheckRun(id) {
|
|
358
|
+
const row = this.db.prepare("SELECT * FROM alert_check_runs WHERE id = ?").get(id);
|
|
359
|
+
if (row == null)
|
|
360
|
+
throw new Error(`alert check run ${id} not found`);
|
|
361
|
+
return mapAlertCheckRun(row);
|
|
362
|
+
}
|
|
363
|
+
listAlertCheckRuns() {
|
|
364
|
+
const rows = this.db
|
|
365
|
+
.prepare("SELECT * FROM alert_check_runs ORDER BY started_at DESC, id DESC")
|
|
366
|
+
.all();
|
|
367
|
+
return rows.map(mapAlertCheckRun);
|
|
368
|
+
}
|
|
369
|
+
markStaleAutomationRunsLost(params) {
|
|
370
|
+
const now = params.now ?? new Date().toISOString();
|
|
371
|
+
const cutoff = new Date(new Date(now).getTime() - params.graceSeconds * 1000).toISOString();
|
|
372
|
+
const tx = this.db.transaction(() => {
|
|
373
|
+
const alertResult = this.db
|
|
374
|
+
.prepare(`UPDATE alert_check_runs
|
|
375
|
+
SET status = 'lost',
|
|
376
|
+
completed_at = COALESCE(completed_at, ?),
|
|
377
|
+
error_json = COALESCE(error_json, ?)
|
|
378
|
+
WHERE status = 'running' AND started_at < ?`)
|
|
379
|
+
.run(now, JSON.stringify({ message: "runner lease expired before completion" }), cutoff);
|
|
380
|
+
const reportResult = this.db
|
|
381
|
+
.prepare(`UPDATE report_runs
|
|
382
|
+
SET status = 'lost',
|
|
383
|
+
completed_at = COALESCE(completed_at, ?),
|
|
384
|
+
errors_json = COALESCE(errors_json, ?)
|
|
385
|
+
WHERE status = 'running' AND started_at < ?`)
|
|
386
|
+
.run(now, JSON.stringify(["runner lease expired before completion"]), cutoff);
|
|
387
|
+
return {
|
|
388
|
+
alertCheckRuns: alertResult.changes,
|
|
389
|
+
reportRuns: reportResult.changes,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
return tx();
|
|
393
|
+
}
|
|
394
|
+
recordNotificationEvent(params) {
|
|
395
|
+
const createdAt = params.createdAt ?? new Date().toISOString();
|
|
396
|
+
const result = this.db
|
|
397
|
+
.prepare(`INSERT INTO notification_events (
|
|
398
|
+
source_type, source_id, severity, title, body, payload_json, status, created_at
|
|
399
|
+
)
|
|
400
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
401
|
+
.run(params.sourceType, params.sourceId ?? null, params.severity, params.title, params.body, params.payload == null ? null : JSON.stringify(params.payload), params.status ?? "unread", createdAt);
|
|
402
|
+
return this.getNotificationEvent(Number(result.lastInsertRowid));
|
|
403
|
+
}
|
|
404
|
+
listNotificationEvents() {
|
|
405
|
+
const rows = this.db
|
|
406
|
+
.prepare("SELECT * FROM notification_events ORDER BY created_at DESC, id DESC")
|
|
407
|
+
.all();
|
|
408
|
+
return rows.map(mapNotificationEvent);
|
|
409
|
+
}
|
|
410
|
+
getNotificationEvent(id) {
|
|
411
|
+
const row = this.db.prepare("SELECT * FROM notification_events WHERE id = ?").get(id);
|
|
412
|
+
if (row == null)
|
|
413
|
+
throw new Error(`notification event ${id} not found`);
|
|
414
|
+
return mapNotificationEvent(row);
|
|
415
|
+
}
|
|
416
|
+
acknowledgeNotificationEvent(id, acknowledgedAt = new Date().toISOString()) {
|
|
417
|
+
this.db
|
|
418
|
+
.prepare(`UPDATE notification_events
|
|
419
|
+
SET status = 'acknowledged',
|
|
420
|
+
acknowledged_at = ?
|
|
421
|
+
WHERE id = ?`)
|
|
422
|
+
.run(acknowledgedAt, id);
|
|
423
|
+
return this.getNotificationEvent(id);
|
|
424
|
+
}
|
|
425
|
+
recordNotificationDeliveryAttempt(params) {
|
|
426
|
+
const attemptedAt = params.attemptedAt ?? new Date().toISOString();
|
|
427
|
+
const result = this.db
|
|
428
|
+
.prepare(`INSERT INTO notification_delivery_attempts (
|
|
429
|
+
notification_event_id, channel, status, attempted_at, completed_at,
|
|
430
|
+
response_json, error
|
|
431
|
+
)
|
|
432
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
433
|
+
.run(params.notificationEventId, params.channel, params.status, attemptedAt, params.completedAt ?? null, params.response == null ? null : JSON.stringify(params.response), params.error ?? null);
|
|
434
|
+
return this.getNotificationDeliveryAttempt(Number(result.lastInsertRowid));
|
|
435
|
+
}
|
|
436
|
+
listNotificationDeliveryAttempts(notificationEventId) {
|
|
437
|
+
const rows = notificationEventId == null
|
|
438
|
+
? this.db
|
|
439
|
+
.prepare("SELECT * FROM notification_delivery_attempts ORDER BY attempted_at DESC, id DESC")
|
|
440
|
+
.all()
|
|
441
|
+
: this.db
|
|
442
|
+
.prepare("SELECT * FROM notification_delivery_attempts WHERE notification_event_id = ? ORDER BY attempted_at DESC, id DESC")
|
|
443
|
+
.all(notificationEventId);
|
|
444
|
+
return rows.map(mapNotificationDeliveryAttempt);
|
|
445
|
+
}
|
|
446
|
+
getNotificationDeliveryAttempt(id) {
|
|
447
|
+
const row = this.db
|
|
448
|
+
.prepare("SELECT * FROM notification_delivery_attempts WHERE id = ?")
|
|
449
|
+
.get(id);
|
|
450
|
+
if (row == null)
|
|
451
|
+
throw new Error(`notification delivery attempt ${id} not found`);
|
|
452
|
+
return mapNotificationDeliveryAttempt(row);
|
|
453
|
+
}
|
|
454
|
+
updateAlertObservation(params) {
|
|
455
|
+
const checkedAt = params.checkedAt ?? new Date().toISOString();
|
|
456
|
+
this.db
|
|
457
|
+
.prepare(`UPDATE alert_rules
|
|
458
|
+
SET last_checked_at = ?,
|
|
459
|
+
last_observed_json = ?,
|
|
460
|
+
last_triggered_at = COALESCE(?, last_triggered_at),
|
|
461
|
+
updated_at = ?
|
|
462
|
+
WHERE id = ?`)
|
|
463
|
+
.run(checkedAt, JSON.stringify(params.observed), params.triggeredAt ?? null, checkedAt, params.ruleId);
|
|
464
|
+
return this.getAlertRule(params.ruleId);
|
|
465
|
+
}
|
|
466
|
+
recordAlertEvent(params) {
|
|
467
|
+
const triggeredAt = params.triggeredAt ?? new Date().toISOString();
|
|
468
|
+
const result = this.db
|
|
469
|
+
.prepare(`INSERT INTO alert_events (
|
|
470
|
+
alert_rule_id, instrument_id, observed_value_json, triggered_at, status, message
|
|
471
|
+
)
|
|
472
|
+
VALUES (?, ?, ?, ?, ?, ?)`)
|
|
473
|
+
.run(params.alertRuleId, params.instrumentId ?? null, JSON.stringify(params.observedValue), triggeredAt, params.status, params.message);
|
|
474
|
+
return this.getAlertEvent(Number(result.lastInsertRowid));
|
|
475
|
+
}
|
|
476
|
+
recordAlertCheckResult(params) {
|
|
477
|
+
const tx = this.db.transaction(() => {
|
|
478
|
+
const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(params.ruleId);
|
|
479
|
+
if (row == null) {
|
|
480
|
+
throw new Error(`alert rule ${params.ruleId} not found`);
|
|
481
|
+
}
|
|
482
|
+
const currentPrevious = lastObservedValueFromJson(row.last_observed_json);
|
|
483
|
+
const currentLastTriggeredAt = row.last_triggered_at ?? null;
|
|
484
|
+
const canTrigger = params.trigger != null &&
|
|
485
|
+
currentPrevious === params.trigger.expectedPreviousValue &&
|
|
486
|
+
currentLastTriggeredAt === params.trigger.expectedLastTriggeredAt;
|
|
487
|
+
if (canTrigger && params.trigger) {
|
|
488
|
+
this.db
|
|
489
|
+
.prepare(`INSERT INTO alert_events (
|
|
490
|
+
alert_rule_id, instrument_id, observed_value_json, triggered_at, status, message
|
|
491
|
+
)
|
|
492
|
+
VALUES (?, ?, ?, ?, 'triggered', ?)`)
|
|
493
|
+
.run(params.ruleId, params.trigger.instrumentId, JSON.stringify(params.observed), params.trigger.triggeredAt, params.trigger.message);
|
|
494
|
+
}
|
|
495
|
+
this.db
|
|
496
|
+
.prepare(`UPDATE alert_rules
|
|
497
|
+
SET last_checked_at = ?,
|
|
498
|
+
last_observed_json = ?,
|
|
499
|
+
last_triggered_at = COALESCE(?, last_triggered_at),
|
|
500
|
+
updated_at = ?
|
|
501
|
+
WHERE id = ?`)
|
|
502
|
+
.run(params.checkedAt, JSON.stringify(params.observed), canTrigger && params.trigger ? params.trigger.triggeredAt : null, params.checkedAt, params.ruleId);
|
|
503
|
+
return canTrigger;
|
|
504
|
+
});
|
|
505
|
+
const triggered = tx();
|
|
506
|
+
return { triggered, rule: this.getAlertRule(params.ruleId) };
|
|
507
|
+
}
|
|
508
|
+
recordAlertEvaluationResult(params) {
|
|
509
|
+
const tx = this.db.transaction(() => {
|
|
510
|
+
const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(params.ruleId);
|
|
511
|
+
if (row == null) {
|
|
512
|
+
throw new Error(`alert rule ${params.ruleId} not found`);
|
|
513
|
+
}
|
|
514
|
+
const rearmed = params.conditionState === "false" && row.last_condition_state === "true";
|
|
515
|
+
const nextArmCycleId = rearmed ? row.arm_cycle_id + 1 : row.arm_cycle_id;
|
|
516
|
+
let eventId = null;
|
|
517
|
+
const canInsertTrigger = params.trigger != null &&
|
|
518
|
+
row.enabled === 1 &&
|
|
519
|
+
row.status === "active" &&
|
|
520
|
+
row.last_condition_state !== "true";
|
|
521
|
+
if (params.trigger != null && canInsertTrigger) {
|
|
522
|
+
const result = this.db
|
|
523
|
+
.prepare(`INSERT OR IGNORE INTO alert_events (
|
|
524
|
+
alert_rule_id, instrument_id, observed_value_json, triggered_at,
|
|
525
|
+
observed_at, provider_data_at, source_provider, cache_status,
|
|
526
|
+
data_delay_ms, trigger_source, dedupe_key, status, message
|
|
527
|
+
)
|
|
528
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
529
|
+
.run(params.ruleId, params.trigger.instrumentId, JSON.stringify(params.observed), params.trigger.triggeredAt, params.trigger.observedAt, params.trigger.providerDataAt ?? null, params.trigger.sourceProvider ?? null, params.trigger.cacheStatus ?? "live", params.trigger.dataDelayMs ?? null, params.trigger.triggerSource, params.trigger.dedupeKey, params.trigger.status ?? "triggered", params.trigger.message);
|
|
530
|
+
if (result.changes > 0) {
|
|
531
|
+
eventId = Number(result.lastInsertRowid);
|
|
532
|
+
this.db
|
|
533
|
+
.prepare(`INSERT INTO notification_events (
|
|
534
|
+
source_type, source_id, severity, title, body, payload_json, status, created_at
|
|
535
|
+
)
|
|
536
|
+
VALUES ('alert_event', ?, 'warning', ?, ?, ?, 'unread', ?)`)
|
|
537
|
+
.run(eventId, params.trigger.title ?? "Alert triggered", params.trigger.message, JSON.stringify({
|
|
538
|
+
ruleId: params.ruleId,
|
|
539
|
+
observed: params.observed,
|
|
540
|
+
sourceProvider: params.trigger.sourceProvider ?? null,
|
|
541
|
+
providerDataAt: params.trigger.providerDataAt ?? null,
|
|
542
|
+
cacheStatus: params.trigger.cacheStatus ?? "live",
|
|
543
|
+
dataDelayMs: params.trigger.dataDelayMs ?? null,
|
|
544
|
+
}), params.trigger.observedAt);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const triggered = eventId != null;
|
|
548
|
+
const completeOneShot = triggered && row.retrigger_mode === "once";
|
|
549
|
+
this.db
|
|
550
|
+
.prepare(`UPDATE alert_rules
|
|
551
|
+
SET last_checked_at = ?,
|
|
552
|
+
last_observed_json = ?,
|
|
553
|
+
last_condition_state = ?,
|
|
554
|
+
arm_cycle_id = ?,
|
|
555
|
+
next_check_at = ?,
|
|
556
|
+
last_triggered_at = COALESCE(?, last_triggered_at),
|
|
557
|
+
enabled = CASE WHEN ? THEN 0 ELSE enabled END,
|
|
558
|
+
status = CASE WHEN ? THEN 'completed' ELSE status END,
|
|
559
|
+
updated_at = ?
|
|
560
|
+
WHERE id = ?`)
|
|
561
|
+
.run(params.checkedAt, JSON.stringify(params.observed), params.conditionState, nextArmCycleId, nextAlertCheckAt(row, params.checkedAt), triggered && params.trigger ? params.trigger.triggeredAt : null, completeOneShot ? 1 : 0, completeOneShot ? 1 : 0, params.checkedAt, params.ruleId);
|
|
562
|
+
return { triggered, eventId };
|
|
563
|
+
});
|
|
564
|
+
const result = tx();
|
|
565
|
+
return {
|
|
566
|
+
triggered: result.triggered,
|
|
567
|
+
event: result.eventId == null ? null : this.getAlertEvent(result.eventId),
|
|
568
|
+
rule: this.getAlertRule(params.ruleId),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
recordAlertUnavailable(params) {
|
|
572
|
+
const tx = this.db.transaction(() => {
|
|
573
|
+
const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(params.ruleId);
|
|
574
|
+
if (row == null) {
|
|
575
|
+
throw new Error(`alert rule ${params.ruleId} not found`);
|
|
576
|
+
}
|
|
577
|
+
const result = this.db
|
|
578
|
+
.prepare(`INSERT INTO alert_events (
|
|
579
|
+
alert_rule_id, instrument_id, observed_value_json, triggered_at, status, message
|
|
580
|
+
)
|
|
581
|
+
VALUES (?, ?, ?, ?, 'unavailable', ?)`)
|
|
582
|
+
.run(params.ruleId, params.instrumentId ?? null, JSON.stringify({ status: "unavailable", reason: params.reason, at: params.checkedAt }), params.checkedAt, `Alert unavailable: ${params.reason}`);
|
|
583
|
+
this.db
|
|
584
|
+
.prepare(`UPDATE alert_rules
|
|
585
|
+
SET last_checked_at = ?,
|
|
586
|
+
next_check_at = ?,
|
|
587
|
+
updated_at = ?
|
|
588
|
+
WHERE id = ?`)
|
|
589
|
+
.run(params.checkedAt, nextAlertCheckAt(row, params.checkedAt), params.checkedAt, params.ruleId);
|
|
590
|
+
return Number(result.lastInsertRowid);
|
|
591
|
+
});
|
|
592
|
+
const eventId = tx();
|
|
593
|
+
return { event: this.getAlertEvent(eventId), rule: this.getAlertRule(params.ruleId) };
|
|
594
|
+
}
|
|
595
|
+
listAlertEvents() {
|
|
596
|
+
const rows = this.db
|
|
597
|
+
.prepare("SELECT * FROM alert_events ORDER BY triggered_at, id")
|
|
598
|
+
.all();
|
|
599
|
+
return rows.map(mapAlertEvent);
|
|
600
|
+
}
|
|
601
|
+
createReportTemplate(params) {
|
|
602
|
+
const now = new Date().toISOString();
|
|
603
|
+
const result = this.db
|
|
604
|
+
.prepare(`INSERT INTO report_templates (
|
|
605
|
+
name, report_type, cadence, timezone, local_time, config_json,
|
|
606
|
+
enabled, next_run_at, created_at, updated_at
|
|
607
|
+
)
|
|
608
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
609
|
+
.run(params.name, params.reportType, params.cadence, params.timezone, params.localTime, JSON.stringify(params.config), params.enabled === false ? 0 : 1, params.nextRunAt ?? null, now, now);
|
|
610
|
+
return this.getReportTemplate(Number(result.lastInsertRowid));
|
|
611
|
+
}
|
|
612
|
+
updateReportTemplate(id, params) {
|
|
613
|
+
const existing = this.getReportTemplate(id);
|
|
614
|
+
const now = new Date().toISOString();
|
|
615
|
+
this.db
|
|
616
|
+
.prepare(`UPDATE report_templates
|
|
617
|
+
SET name = ?, report_type = ?, cadence = ?, timezone = ?, local_time = ?,
|
|
618
|
+
config_json = ?, enabled = ?, next_run_at = ?, updated_at = ?
|
|
619
|
+
WHERE id = ?`)
|
|
620
|
+
.run(params.name ?? existing.name, params.reportType ?? existing.reportType, params.cadence ?? existing.cadence, params.timezone ?? existing.timezone, params.localTime ?? existing.localTime, JSON.stringify(params.config ?? existing.configJson), params.enabled == null ? (existing.enabled ? 1 : 0) : params.enabled ? 1 : 0, params.nextRunAt === undefined ? existing.nextRunAt : params.nextRunAt, now, id);
|
|
621
|
+
return this.getReportTemplate(id);
|
|
622
|
+
}
|
|
623
|
+
claimDueReportTemplateRun(id, params) {
|
|
624
|
+
const result = this.db
|
|
625
|
+
.prepare(`UPDATE report_templates
|
|
626
|
+
SET next_run_at = ?, updated_at = ?
|
|
627
|
+
WHERE id = ? AND enabled = 1 AND next_run_at = ?`)
|
|
628
|
+
.run(params.nextRunAt, params.claimedAt ?? new Date().toISOString(), id, params.scheduledFor);
|
|
629
|
+
return result.changes === 0 ? null : this.getReportTemplate(id);
|
|
630
|
+
}
|
|
631
|
+
listReportTemplates() {
|
|
632
|
+
const rows = this.db
|
|
633
|
+
.prepare("SELECT * FROM report_templates ORDER BY created_at, id")
|
|
634
|
+
.all();
|
|
635
|
+
return rows.map(mapReportTemplate);
|
|
636
|
+
}
|
|
637
|
+
recordReportRun(params) {
|
|
638
|
+
const startedAt = params.startedAt ?? new Date().toISOString();
|
|
639
|
+
const tx = this.db.transaction(() => {
|
|
640
|
+
const result = this.db
|
|
641
|
+
.prepare(`INSERT INTO report_runs (
|
|
642
|
+
template_id, started_at, completed_at, status, trigger_type, scheduled_for,
|
|
643
|
+
owner_id, artifact_path, summary_json, errors_json
|
|
644
|
+
)
|
|
645
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
646
|
+
.run(params.templateId ?? null, startedAt, params.completedAt ?? null, params.status, params.triggerType ?? "manual", params.scheduledFor ?? null, params.ownerId ?? null, params.artifactPath ?? null, params.summary == null ? null : JSON.stringify(params.summary), params.errors == null ? null : JSON.stringify(params.errors));
|
|
647
|
+
if (params.templateId != null) {
|
|
648
|
+
this.db
|
|
649
|
+
.prepare("UPDATE report_templates SET last_run_at = ?, updated_at = ? WHERE id = ?")
|
|
650
|
+
.run(startedAt, startedAt, params.templateId);
|
|
651
|
+
}
|
|
652
|
+
return Number(result.lastInsertRowid);
|
|
653
|
+
});
|
|
654
|
+
return this.getReportRun(tx());
|
|
655
|
+
}
|
|
656
|
+
listReportRuns() {
|
|
657
|
+
const rows = this.db
|
|
658
|
+
.prepare("SELECT * FROM report_runs ORDER BY started_at DESC, id DESC")
|
|
659
|
+
.all();
|
|
660
|
+
return rows.map(mapReportRun);
|
|
661
|
+
}
|
|
662
|
+
recordImportBatch(params) {
|
|
663
|
+
const importedAt = params.importedAt ?? new Date().toISOString();
|
|
664
|
+
const result = this.db
|
|
665
|
+
.prepare(`INSERT INTO import_batches (
|
|
666
|
+
source, source_label, imported_at, status, raw_metadata_json
|
|
667
|
+
)
|
|
668
|
+
VALUES (?, ?, ?, ?, ?)`)
|
|
669
|
+
.run(normalizeNullable(params.source) ?? "unknown", normalizeNullable(params.sourceLabel), importedAt, params.status, params.rawMetadata == null ? null : JSON.stringify(params.rawMetadata));
|
|
670
|
+
return this.getImportBatch(Number(result.lastInsertRowid));
|
|
671
|
+
}
|
|
672
|
+
recordImportRow(params) {
|
|
673
|
+
const result = this.db
|
|
674
|
+
.prepare(`INSERT INTO import_rows (
|
|
675
|
+
batch_id, row_type, source_symbol, source_row_id, source_account_ref,
|
|
676
|
+
normalized_instrument_id, status, error, source_metadata_json, raw_json
|
|
677
|
+
)
|
|
678
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
679
|
+
.run(params.batchId, params.rowType, normalizeNullable(params.sourceSymbol), normalizeNullable(params.sourceRowId), normalizeNullable(params.sourceAccountRef), params.normalizedInstrumentId ?? null, params.status, normalizeNullable(params.error), params.sourceMetadata == null ? null : JSON.stringify(params.sourceMetadata), params.raw == null ? null : JSON.stringify(params.raw));
|
|
680
|
+
return this.getImportRow(Number(result.lastInsertRowid));
|
|
681
|
+
}
|
|
682
|
+
upsertInstrument(input) {
|
|
683
|
+
const symbol = input.symbol.trim().toUpperCase();
|
|
684
|
+
const assetType = input.assetType.trim().toLowerCase();
|
|
685
|
+
const provider = input.provider.trim().toLowerCase();
|
|
686
|
+
const exchange = normalizeNullable(input.exchange);
|
|
687
|
+
const now = new Date().toISOString();
|
|
688
|
+
const resolvedAt = (input.resolvedAt ?? new Date()).toISOString();
|
|
689
|
+
const existing = this.db
|
|
690
|
+
.prepare(`SELECT * FROM instruments
|
|
691
|
+
WHERE provider = ?
|
|
692
|
+
AND symbol = ?
|
|
693
|
+
AND asset_type = ?
|
|
694
|
+
AND IFNULL(exchange, '') = IFNULL(?, '')`)
|
|
695
|
+
.get(provider, symbol, assetType, exchange);
|
|
696
|
+
if (existing) {
|
|
697
|
+
this.db
|
|
698
|
+
.prepare(`UPDATE instruments
|
|
699
|
+
SET name = ?, currency = ?, provider_metadata_json = ?,
|
|
700
|
+
last_resolved_at = ?, updated_at = ?
|
|
701
|
+
WHERE id = ?`)
|
|
702
|
+
.run(normalizeNullable(input.name), normalizeNullable(input.currency)?.toUpperCase() ?? null, input.providerMetadata == null ? null : JSON.stringify(input.providerMetadata), resolvedAt, now, existing.id);
|
|
703
|
+
this.upsertInstrumentAliases(existing.id, input.aliases ?? []);
|
|
704
|
+
return this.db
|
|
705
|
+
.prepare("SELECT * FROM instruments WHERE id = ?")
|
|
706
|
+
.get(existing.id);
|
|
707
|
+
}
|
|
708
|
+
const result = this.db
|
|
709
|
+
.prepare(`INSERT INTO instruments (
|
|
710
|
+
symbol, asset_type, name, exchange, currency, provider,
|
|
711
|
+
provider_metadata_json, last_resolved_at, created_at, updated_at
|
|
712
|
+
)
|
|
713
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
714
|
+
.run(symbol, assetType, normalizeNullable(input.name), exchange, normalizeNullable(input.currency)?.toUpperCase() ?? null, provider, input.providerMetadata == null ? null : JSON.stringify(input.providerMetadata), resolvedAt, now, now);
|
|
715
|
+
const instrumentId = Number(result.lastInsertRowid);
|
|
716
|
+
this.upsertInstrumentAliases(instrumentId, input.aliases ?? []);
|
|
717
|
+
return this.db
|
|
718
|
+
.prepare("SELECT * FROM instruments WHERE id = ?")
|
|
719
|
+
.get(instrumentId);
|
|
720
|
+
}
|
|
721
|
+
upsertInstrumentAliases(instrumentId, aliases) {
|
|
722
|
+
if (aliases.length === 0)
|
|
723
|
+
return;
|
|
724
|
+
const now = new Date().toISOString();
|
|
725
|
+
for (const alias of aliases) {
|
|
726
|
+
const source = normalizeSource(alias.source);
|
|
727
|
+
const sourceSymbol = normalizeSourceSymbol(alias.sourceSymbol);
|
|
728
|
+
const sourceExchange = normalizeExchange(alias.sourceExchange);
|
|
729
|
+
const sourceAssetType = normalizeAssetType(alias.sourceAssetType);
|
|
730
|
+
const sourceId = normalizeNullable(alias.sourceId);
|
|
731
|
+
const rawJson = alias.raw == null ? null : JSON.stringify(alias.raw);
|
|
732
|
+
const existing = sourceId == null
|
|
733
|
+
? this.db
|
|
734
|
+
.prepare(`SELECT id, instrument_id FROM instrument_aliases
|
|
735
|
+
WHERE source = ?
|
|
736
|
+
AND source_symbol = ?
|
|
737
|
+
AND IFNULL(source_exchange, '') = IFNULL(?, '')
|
|
738
|
+
AND IFNULL(source_asset_type, '') = IFNULL(?, '')
|
|
739
|
+
LIMIT 1`)
|
|
740
|
+
.get(source, sourceSymbol, sourceExchange, sourceAssetType)
|
|
741
|
+
: this.db
|
|
742
|
+
.prepare(`SELECT id, instrument_id FROM instrument_aliases
|
|
743
|
+
WHERE source = ? AND source_id = ?
|
|
744
|
+
LIMIT 1`)
|
|
745
|
+
.get(source, sourceId);
|
|
746
|
+
if (existing) {
|
|
747
|
+
this.db
|
|
748
|
+
.prepare(`UPDATE instrument_aliases
|
|
749
|
+
SET instrument_id = ?, source_symbol = ?, source_exchange = ?,
|
|
750
|
+
source_asset_type = ?, source_id = ?, raw_json = ?, updated_at = ?
|
|
751
|
+
WHERE id = ?`)
|
|
752
|
+
.run(instrumentId, sourceSymbol, sourceExchange, sourceAssetType, sourceId, rawJson, now, existing.id);
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
this.db
|
|
756
|
+
.prepare(`INSERT INTO instrument_aliases (
|
|
757
|
+
instrument_id, source, source_symbol, source_exchange,
|
|
758
|
+
source_asset_type, source_id, raw_json, created_at, updated_at
|
|
759
|
+
)
|
|
760
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
761
|
+
.run(instrumentId, source, sourceSymbol, sourceExchange, sourceAssetType, sourceId, rawJson, now, now);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
getWatchlistItem(id) {
|
|
765
|
+
const row = this.db
|
|
766
|
+
.prepare(`SELECT wi.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency
|
|
767
|
+
FROM watchlist_items wi
|
|
768
|
+
JOIN instruments i ON i.id = wi.instrument_id
|
|
769
|
+
WHERE wi.id = ?`)
|
|
770
|
+
.get(id);
|
|
771
|
+
return mapWatchlistItem(row);
|
|
772
|
+
}
|
|
773
|
+
getPortfolioLot(id) {
|
|
774
|
+
const row = this.getPortfolioLotOrNull(id);
|
|
775
|
+
if (row == null) {
|
|
776
|
+
throw new Error(`portfolio lot ${id} not found`);
|
|
777
|
+
}
|
|
778
|
+
return row;
|
|
779
|
+
}
|
|
780
|
+
getPortfolioLotOrNull(id) {
|
|
781
|
+
const row = this.db
|
|
782
|
+
.prepare(`SELECT pl.*, i.symbol, i.name, i.asset_type, i.exchange, i.currency AS instrument_currency
|
|
783
|
+
FROM portfolio_lots pl
|
|
784
|
+
JOIN instruments i ON i.id = pl.instrument_id
|
|
785
|
+
WHERE pl.id = ?`)
|
|
786
|
+
.get(id);
|
|
787
|
+
return row == null ? null : mapPortfolioLot(row);
|
|
788
|
+
}
|
|
789
|
+
getPrediction(id) {
|
|
790
|
+
const row = this.db
|
|
791
|
+
.prepare(`SELECT pr.*, i.symbol
|
|
792
|
+
FROM prediction_records pr
|
|
793
|
+
JOIN instruments i ON i.id = pr.instrument_id
|
|
794
|
+
WHERE pr.id = ?`)
|
|
795
|
+
.get(id);
|
|
796
|
+
return mapPrediction(row);
|
|
797
|
+
}
|
|
798
|
+
getAlertRule(id) {
|
|
799
|
+
const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(id);
|
|
800
|
+
return mapAlertRule(row);
|
|
801
|
+
}
|
|
802
|
+
getAlertEvent(id) {
|
|
803
|
+
const row = this.db.prepare("SELECT * FROM alert_events WHERE id = ?").get(id);
|
|
804
|
+
return mapAlertEvent(row);
|
|
805
|
+
}
|
|
806
|
+
getReportTemplate(id) {
|
|
807
|
+
const row = this.db
|
|
808
|
+
.prepare("SELECT * FROM report_templates WHERE id = ?")
|
|
809
|
+
.get(id);
|
|
810
|
+
return mapReportTemplate(row);
|
|
811
|
+
}
|
|
812
|
+
getReportRun(id) {
|
|
813
|
+
const row = this.db.prepare("SELECT * FROM report_runs WHERE id = ?").get(id);
|
|
814
|
+
return mapReportRun(row);
|
|
815
|
+
}
|
|
816
|
+
getImportBatch(id) {
|
|
817
|
+
const row = this.db
|
|
818
|
+
.prepare("SELECT * FROM import_batches WHERE id = ?")
|
|
819
|
+
.get(id);
|
|
820
|
+
return mapImportBatch(row);
|
|
821
|
+
}
|
|
822
|
+
getImportRow(id) {
|
|
823
|
+
const row = this.db.prepare("SELECT * FROM import_rows WHERE id = ?").get(id);
|
|
824
|
+
return mapImportRow(row);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function mapCollection(row) {
|
|
828
|
+
return {
|
|
829
|
+
id: row.id,
|
|
830
|
+
name: row.name,
|
|
831
|
+
isDefault: row.is_default === 1,
|
|
832
|
+
createdAt: row.created_at,
|
|
833
|
+
updatedAt: row.updated_at,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
function mapInstrument(row) {
|
|
837
|
+
return {
|
|
838
|
+
id: row.id,
|
|
839
|
+
symbol: row.symbol,
|
|
840
|
+
assetType: row.asset_type,
|
|
841
|
+
name: row.name,
|
|
842
|
+
exchange: row.exchange,
|
|
843
|
+
currency: row.currency,
|
|
844
|
+
provider: row.provider,
|
|
845
|
+
createdAt: row.created_at,
|
|
846
|
+
updatedAt: row.updated_at,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function mapWatchlistItem(row) {
|
|
850
|
+
return {
|
|
851
|
+
id: row.id,
|
|
852
|
+
watchlistId: row.watchlist_id,
|
|
853
|
+
instrumentId: row.instrument_id,
|
|
854
|
+
symbol: row.symbol,
|
|
855
|
+
name: row.name,
|
|
856
|
+
assetType: row.asset_type,
|
|
857
|
+
exchange: row.exchange,
|
|
858
|
+
currency: row.currency,
|
|
859
|
+
targetPrice: row.target_price,
|
|
860
|
+
stopPrice: row.stop_price,
|
|
861
|
+
priceCurrency: row.price_currency,
|
|
862
|
+
thesis: row.thesis,
|
|
863
|
+
notes: row.notes,
|
|
864
|
+
tags: row.tags_json == null ? null : JSON.parse(row.tags_json),
|
|
865
|
+
source: row.source,
|
|
866
|
+
sourceRowId: row.source_row_id,
|
|
867
|
+
sourceMetadata: row.source_metadata_json == null ? null : JSON.parse(row.source_metadata_json),
|
|
868
|
+
createdAt: row.created_at,
|
|
869
|
+
updatedAt: row.updated_at,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function mapPortfolioLot(row) {
|
|
873
|
+
return {
|
|
874
|
+
id: row.id,
|
|
875
|
+
portfolioId: row.portfolio_id,
|
|
876
|
+
instrumentId: row.instrument_id,
|
|
877
|
+
symbol: row.symbol,
|
|
878
|
+
name: row.name,
|
|
879
|
+
assetType: row.asset_type,
|
|
880
|
+
exchange: row.exchange,
|
|
881
|
+
instrumentCurrency: row.instrument_currency,
|
|
882
|
+
quantity: row.quantity,
|
|
883
|
+
avgCost: row.avg_cost,
|
|
884
|
+
currency: row.currency,
|
|
885
|
+
openedAt: row.opened_at,
|
|
886
|
+
notes: row.notes,
|
|
887
|
+
source: row.source,
|
|
888
|
+
sourceAccountRef: row.source_account_ref,
|
|
889
|
+
sourceLotId: row.source_lot_id,
|
|
890
|
+
sourceRowId: row.source_row_id,
|
|
891
|
+
sourceMetadata: row.source_metadata_json == null ? null : JSON.parse(row.source_metadata_json),
|
|
892
|
+
createdAt: row.created_at,
|
|
893
|
+
updatedAt: row.updated_at,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
function mapPrediction(row) {
|
|
897
|
+
return {
|
|
898
|
+
id: row.id,
|
|
899
|
+
instrumentId: row.instrument_id,
|
|
900
|
+
symbol: row.symbol,
|
|
901
|
+
direction: row.direction,
|
|
902
|
+
conviction: row.conviction,
|
|
903
|
+
entryPrice: row.entry_price,
|
|
904
|
+
targetPrice: row.target_price,
|
|
905
|
+
openedAt: row.opened_at,
|
|
906
|
+
expiresAt: row.expires_at,
|
|
907
|
+
status: row.status,
|
|
908
|
+
resolvedAt: row.resolved_at,
|
|
909
|
+
resultJson: row.result_json,
|
|
910
|
+
createdAt: row.created_at,
|
|
911
|
+
updatedAt: row.updated_at,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function mapAlertRule(row) {
|
|
915
|
+
return {
|
|
916
|
+
id: row.id,
|
|
917
|
+
scopeType: row.scope_type,
|
|
918
|
+
scopeId: row.scope_id,
|
|
919
|
+
instrumentId: row.instrument_id,
|
|
920
|
+
conditionType: row.condition_type,
|
|
921
|
+
conditionVersion: row.condition_version,
|
|
922
|
+
conditionJson: JSON.parse(row.condition_json),
|
|
923
|
+
timeframe: row.timeframe,
|
|
924
|
+
enabled: row.enabled === 1,
|
|
925
|
+
checkIntervalSeconds: row.check_interval_seconds,
|
|
926
|
+
nextCheckAt: row.next_check_at,
|
|
927
|
+
lastCheckedAt: row.last_checked_at,
|
|
928
|
+
lastObservedJson: row.last_observed_json == null ? null : JSON.parse(row.last_observed_json),
|
|
929
|
+
status: row.status,
|
|
930
|
+
retriggerMode: row.retrigger_mode,
|
|
931
|
+
lastConditionState: row.last_condition_state,
|
|
932
|
+
ruleRevision: row.rule_revision,
|
|
933
|
+
armCycleId: row.arm_cycle_id,
|
|
934
|
+
cooldownSeconds: row.cooldown_seconds,
|
|
935
|
+
lastTriggeredAt: row.last_triggered_at,
|
|
936
|
+
createdAt: row.created_at,
|
|
937
|
+
updatedAt: row.updated_at,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function mapAlertEvent(row) {
|
|
941
|
+
return {
|
|
942
|
+
id: row.id,
|
|
943
|
+
alertRuleId: row.alert_rule_id,
|
|
944
|
+
instrumentId: row.instrument_id,
|
|
945
|
+
observedValueJson: row.observed_value_json == null ? null : JSON.parse(row.observed_value_json),
|
|
946
|
+
triggeredAt: row.triggered_at,
|
|
947
|
+
observedAt: row.observed_at,
|
|
948
|
+
providerDataAt: row.provider_data_at,
|
|
949
|
+
sourceProvider: row.source_provider,
|
|
950
|
+
cacheStatus: row.cache_status,
|
|
951
|
+
dataDelayMs: row.data_delay_ms,
|
|
952
|
+
triggerSource: row.trigger_source,
|
|
953
|
+
dedupeKey: row.dedupe_key,
|
|
954
|
+
status: row.status,
|
|
955
|
+
message: row.message,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function mapAutomationRunnerLease(row) {
|
|
959
|
+
return {
|
|
960
|
+
ownerId: row.owner_id,
|
|
961
|
+
ownerKind: row.owner_kind,
|
|
962
|
+
acquiredAt: row.acquired_at,
|
|
963
|
+
heartbeatAt: row.heartbeat_at,
|
|
964
|
+
expiresAt: row.expires_at,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
function mapAlertCheckRun(row) {
|
|
968
|
+
return {
|
|
969
|
+
id: row.id,
|
|
970
|
+
startedAt: row.started_at,
|
|
971
|
+
completedAt: row.completed_at,
|
|
972
|
+
status: row.status,
|
|
973
|
+
triggerType: row.trigger_type,
|
|
974
|
+
checkedCount: row.checked_count,
|
|
975
|
+
triggeredCount: row.triggered_count,
|
|
976
|
+
unavailableCount: row.unavailable_count,
|
|
977
|
+
ownerId: row.owner_id,
|
|
978
|
+
errorJson: row.error_json == null ? null : JSON.parse(row.error_json),
|
|
979
|
+
providerStatusJson: row.provider_status_json == null ? null : JSON.parse(row.provider_status_json),
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
function mapNotificationEvent(row) {
|
|
983
|
+
return {
|
|
984
|
+
id: row.id,
|
|
985
|
+
sourceType: row.source_type,
|
|
986
|
+
sourceId: row.source_id,
|
|
987
|
+
severity: row.severity,
|
|
988
|
+
title: row.title,
|
|
989
|
+
body: row.body,
|
|
990
|
+
payloadJson: row.payload_json == null ? null : JSON.parse(row.payload_json),
|
|
991
|
+
status: row.status,
|
|
992
|
+
createdAt: row.created_at,
|
|
993
|
+
acknowledgedAt: row.acknowledged_at,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function mapNotificationDeliveryAttempt(row) {
|
|
997
|
+
return {
|
|
998
|
+
id: row.id,
|
|
999
|
+
notificationEventId: row.notification_event_id,
|
|
1000
|
+
channel: row.channel,
|
|
1001
|
+
status: row.status,
|
|
1002
|
+
attemptedAt: row.attempted_at,
|
|
1003
|
+
completedAt: row.completed_at,
|
|
1004
|
+
responseJson: row.response_json == null ? null : JSON.parse(row.response_json),
|
|
1005
|
+
error: row.error,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function nextAlertCheckAt(row, checkedAt) {
|
|
1009
|
+
if (row.check_interval_seconds == null)
|
|
1010
|
+
return null;
|
|
1011
|
+
const checkedAtMs = new Date(checkedAt).getTime();
|
|
1012
|
+
if (!Number.isFinite(checkedAtMs))
|
|
1013
|
+
return null;
|
|
1014
|
+
return new Date(checkedAtMs + row.check_interval_seconds * 1000).toISOString();
|
|
1015
|
+
}
|
|
1016
|
+
function mapReportTemplate(row) {
|
|
1017
|
+
return {
|
|
1018
|
+
id: row.id,
|
|
1019
|
+
name: row.name,
|
|
1020
|
+
reportType: row.report_type,
|
|
1021
|
+
cadence: row.cadence,
|
|
1022
|
+
timezone: row.timezone,
|
|
1023
|
+
localTime: row.local_time,
|
|
1024
|
+
configJson: JSON.parse(row.config_json),
|
|
1025
|
+
enabled: row.enabled === 1,
|
|
1026
|
+
lastRunAt: row.last_run_at,
|
|
1027
|
+
nextRunAt: row.next_run_at,
|
|
1028
|
+
createdAt: row.created_at,
|
|
1029
|
+
updatedAt: row.updated_at,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function mapReportRun(row) {
|
|
1033
|
+
return {
|
|
1034
|
+
id: row.id,
|
|
1035
|
+
templateId: row.template_id,
|
|
1036
|
+
startedAt: row.started_at,
|
|
1037
|
+
completedAt: row.completed_at,
|
|
1038
|
+
status: row.status,
|
|
1039
|
+
triggerType: row.trigger_type,
|
|
1040
|
+
scheduledFor: row.scheduled_for,
|
|
1041
|
+
ownerId: row.owner_id,
|
|
1042
|
+
artifactPath: row.artifact_path,
|
|
1043
|
+
summaryJson: row.summary_json == null ? null : JSON.parse(row.summary_json),
|
|
1044
|
+
errorsJson: row.errors_json == null ? null : JSON.parse(row.errors_json),
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function mapImportBatch(row) {
|
|
1048
|
+
return {
|
|
1049
|
+
id: row.id,
|
|
1050
|
+
source: row.source,
|
|
1051
|
+
sourceLabel: row.source_label,
|
|
1052
|
+
importedAt: row.imported_at,
|
|
1053
|
+
status: row.status,
|
|
1054
|
+
rawMetadata: row.raw_metadata_json == null ? null : JSON.parse(row.raw_metadata_json),
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function mapImportRow(row) {
|
|
1058
|
+
return {
|
|
1059
|
+
id: row.id,
|
|
1060
|
+
batchId: row.batch_id,
|
|
1061
|
+
rowType: row.row_type,
|
|
1062
|
+
sourceSymbol: row.source_symbol,
|
|
1063
|
+
sourceRowId: row.source_row_id,
|
|
1064
|
+
sourceAccountRef: row.source_account_ref,
|
|
1065
|
+
normalizedInstrumentId: row.normalized_instrument_id,
|
|
1066
|
+
status: row.status,
|
|
1067
|
+
error: row.error,
|
|
1068
|
+
sourceMetadata: row.source_metadata_json == null ? null : JSON.parse(row.source_metadata_json),
|
|
1069
|
+
raw: row.raw_json == null ? null : JSON.parse(row.raw_json),
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function normalizeNullable(value) {
|
|
1073
|
+
const normalized = value?.trim();
|
|
1074
|
+
return normalized ? normalized : null;
|
|
1075
|
+
}
|
|
1076
|
+
function assertPositiveFinitePortfolioLotNumber(value, label) {
|
|
1077
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1078
|
+
throw new Error(`Portfolio lot ${label} must be a positive finite number.`);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function lastObservedValueFromJson(value) {
|
|
1082
|
+
if (value == null)
|
|
1083
|
+
return null;
|
|
1084
|
+
const parsed = JSON.parse(value);
|
|
1085
|
+
return typeof parsed?.value === "number" ? parsed.value : null;
|
|
1086
|
+
}
|
|
1087
|
+
function normalizeSource(value) {
|
|
1088
|
+
return value.trim().toLowerCase();
|
|
1089
|
+
}
|
|
1090
|
+
function normalizeSourceSymbol(value) {
|
|
1091
|
+
return value.trim().toUpperCase();
|
|
1092
|
+
}
|
|
1093
|
+
function normalizeExchange(value) {
|
|
1094
|
+
return normalizeNullable(value)?.toUpperCase() ?? null;
|
|
1095
|
+
}
|
|
1096
|
+
function normalizeAssetType(value) {
|
|
1097
|
+
return normalizeNullable(value)?.toLowerCase() ?? null;
|
|
1098
|
+
}
|
|
1099
|
+
//# sourceMappingURL=service.js.map
|