gloomberb 0.15.7 → 0.16.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/README.md +11 -2
- package/package.json +1 -1
- package/src/api-client/app-attention.ts +147 -0
- package/src/api-client/attention.ts +26 -0
- package/src/api-client/awards.ts +215 -0
- package/src/api-client/catalysts.ts +160 -0
- package/src/api-client/chat.ts +137 -14
- package/src/api-client/company-kpis.ts +153 -0
- package/src/api-client/cpi.ts +52 -0
- package/src/api-client/credit-documents.ts +107 -0
- package/src/api-client/data.ts +192 -3
- package/src/api-client/distress.ts +164 -0
- package/src/api-client/doe.ts +75 -0
- package/src/api-client/equity-screener.ts +11 -0
- package/src/api-client/exposure.ts +202 -0
- package/src/api-client/gpu.ts +93 -0
- package/src/api-client/hiring.ts +110 -0
- package/src/api-client/index.ts +78 -3
- package/src/api-client/normalizers.ts +31 -1
- package/src/api-client/paths.ts +3 -0
- package/src/api-client/perps.ts +127 -0
- package/src/api-client/power.ts +148 -0
- package/src/api-client/request.ts +58 -1
- package/src/api-client/research-activity.ts +172 -13
- package/src/api-client/socket.ts +75 -27
- package/src/api-client/supply-chain-graph.ts +40 -0
- package/src/api-client/supply-chain.ts +98 -0
- package/src/api-client/telemetry.ts +29 -0
- package/src/api-client/themes.ts +28 -0
- package/src/api-client/types.ts +33 -1
- package/src/api-client/wall-experiments.ts +100 -0
- package/src/api-client/wall-summary.ts +49 -0
- package/src/api-client/web-experiments.ts +136 -0
- package/src/app/pane-runtime/plugin-bindings.ts +5 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +8 -2
- package/src/app/runtime/update.ts +13 -1
- package/src/cli/commands/system.ts +2 -0
- package/src/cli/desktop-pane-shot.ts +3 -4
- package/src/cli/pane-functions/data.ts +78 -0
- package/src/cli/pane-functions/financial-ratio-shot.ts +160 -0
- package/src/cli/pane-functions/function-help.ts +220 -8
- package/src/cli/pane-functions/headless.ts +4 -0
- package/src/cli/pane-functions/screenshot-evidence.ts +21 -0
- package/src/cli/pane-functions/screenshot.ts +49 -5
- package/src/components/chart/composite/composite-chart.tsx +15 -2
- package/src/components/chart/composite/format.ts +24 -11
- package/src/components/chart/composite/rasterizer.ts +67 -16
- package/src/components/chart/composite/scene.ts +53 -2
- package/src/components/chart/composite/session-shading.ts +3 -2
- package/src/components/chart/composite/text-renderer.ts +52 -1
- package/src/components/chart/composite/time-axis.ts +100 -30
- package/src/components/chart/composite/tools.ts +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart-table/index.ts +1 -0
- package/src/components/chart-table/layout.ts +15 -0
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/commands/direct/index.ts +32 -0
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/pane-templates/items.ts +11 -0
- package/src/components/command-bar/pane-templates/workflow.ts +19 -4
- package/src/components/command-bar/routes/root/command-items.ts +6 -0
- package/src/components/command-bar/routes/root/provider-search.ts +2 -0
- package/src/components/command-bar/routes/root/results.ts +64 -0
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +89 -24
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -0
- package/src/components/command-bar/surface/index.tsx +15 -0
- package/src/components/command-bar/view-model.ts +3 -0
- package/src/components/command-bar/workflow/collection-actions.ts +9 -7
- package/src/components/command-bar/workflow/ops.ts +19 -0
- package/src/components/command-bar/workflow/tickers.ts +5 -0
- package/src/components/company-logo.tsx +15 -2
- package/src/components/data-table/table-pane.ts +2 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/detached-pane-shell.tsx +12 -1
- package/src/components/layout/missing-pane.tsx +87 -23
- package/src/components/layout/pane/footer/index.tsx +64 -18
- package/src/components/layout/pane/footer/model.ts +85 -0
- package/src/components/layout/pane/sidebar.tsx +19 -1
- package/src/components/layout/shell/index.tsx +38 -28
- package/src/components/layout/shell/pane/management-shortcuts.ts +13 -1
- package/src/components/layout/status-bar.tsx +93 -28
- package/src/components/layout/status-widget-space.ts +72 -0
- package/src/components/onboarding/first-run-workspace.ts +8 -2
- package/src/components/onboarding/onboarding-wizard.tsx +35 -16
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -5
- package/src/components/onboarding/wizard-model.ts +13 -5
- package/src/components/onboarding/wizard-positions.ts +56 -7
- package/src/components/price-performance.tsx +31 -34
- package/src/components/ui/data-table/index.tsx +11 -8
- package/src/components/ui/display.tsx +15 -6
- package/src/components/ui/fields.tsx +5 -0
- package/src/components/ui/figure.tsx +94 -0
- package/src/components/ui/header-case.ts +25 -3
- package/src/components/ui/icon.tsx +3 -1
- package/src/components/ui/message-composer.tsx +8 -1
- package/src/components/ui/query-bar.tsx +8 -1
- package/src/components/ui/ratio-bar.tsx +84 -0
- package/src/components/ui/toggle.tsx +3 -1
- package/src/components/use-scrollbox-layout.ts +17 -0
- package/src/core/state/app/layout.ts +35 -1
- package/src/core/state/app/state.ts +15 -0
- package/src/core/state/app/types.ts +1 -0
- package/src/data/config/store/normalize.ts +24 -0
- package/src/i18n/es.ts +2 -0
- package/src/i18n/ja.ts +2 -0
- package/src/i18n/ko.ts +2 -0
- package/src/i18n/zh-cn.ts +2 -0
- package/src/i18n/zh-tw.ts +2 -0
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/market-data/coordinator/entries.ts +13 -0
- package/src/market-data/coordinator/financials.ts +33 -8
- package/src/market-data/market/freshness.ts +16 -5
- package/src/market-data/market/trading-sessions.ts +65 -1
- package/src/market-data/published-cn-sessions.ts +25 -0
- package/src/market-data/published-nse-sessions.ts +20 -0
- package/src/news/aggregator.ts +4 -0
- package/src/plugins/builtin/account-management/model.ts +6 -1
- package/src/plugins/builtin/account-management/pane.tsx +36 -25
- package/src/plugins/builtin/alerts/catalysts.ts +77 -0
- package/src/plugins/builtin/alerts/events-pane.tsx +3 -0
- package/src/plugins/builtin/alerts/events.ts +10 -4
- package/src/plugins/builtin/alerts/history-pane.tsx +1 -1
- package/src/plugins/builtin/alerts/history.ts +1 -0
- package/src/plugins/builtin/alerts/index.tsx +6 -0
- package/src/plugins/builtin/alerts/research-builder.ts +10 -3
- package/src/plugins/builtin/alerts/research-rules.ts +15 -1
- package/src/plugins/builtin/analytics/risk-client.ts +15 -3
- package/src/plugins/builtin/analytics/risk-headless.ts +123 -18
- package/src/plugins/builtin/attention/client.ts +80 -0
- package/src/plugins/builtin/attention/evidence.ts +30 -0
- package/src/plugins/builtin/attention/headless.ts +33 -0
- package/src/plugins/builtin/attention/index.tsx +18 -0
- package/src/plugins/builtin/attention/model.ts +59 -0
- package/src/plugins/builtin/attention/pane.tsx +211 -0
- package/src/plugins/builtin/attention/trending.tsx +59 -0
- package/src/plugins/builtin/awards/alerts.ts +32 -0
- package/src/plugins/builtin/awards/client.ts +82 -0
- package/src/plugins/builtin/awards/detail.tsx +107 -0
- package/src/plugins/builtin/awards/evidence.ts +36 -0
- package/src/plugins/builtin/awards/headless.ts +55 -0
- package/src/plugins/builtin/awards/index.tsx +20 -0
- package/src/plugins/builtin/awards/model.ts +42 -0
- package/src/plugins/builtin/awards/pane.tsx +263 -0
- package/src/plugins/builtin/catalysts/client.ts +28 -0
- package/src/plugins/builtin/catalysts/detail.tsx +88 -0
- package/src/plugins/builtin/catalysts/evidence.ts +47 -0
- package/src/plugins/builtin/catalysts/headless.ts +42 -0
- package/src/plugins/builtin/catalysts/history.ts +26 -0
- package/src/plugins/builtin/catalysts/index.tsx +29 -0
- package/src/plugins/builtin/catalysts/model.ts +135 -0
- package/src/plugins/builtin/catalysts/pane.tsx +159 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +6 -0
- package/src/plugins/builtin/chart-composer/index.tsx +8 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +28 -1
- package/src/plugins/builtin/chat/attachments/desktop.tsx +610 -0
- package/src/plugins/builtin/chat/attachments/files.ts +45 -0
- package/src/plugins/builtin/chat/attachments/model.ts +164 -0
- package/src/plugins/builtin/chat/attachments/terminal.tsx +72 -0
- package/src/plugins/builtin/chat/attachments/transfer.ts +165 -0
- package/src/plugins/builtin/chat/content/channel-navigation.ts +13 -12
- package/src/plugins/builtin/chat/content/composer-runtime.ts +25 -4
- package/src/plugins/builtin/chat/content/composer.tsx +46 -4
- package/src/plugins/builtin/chat/content/footer.tsx +46 -8
- package/src/plugins/builtin/chat/content/index.tsx +263 -62
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -2
- package/src/plugins/builtin/chat/content/open-targets.ts +17 -3
- package/src/plugins/builtin/chat/content/scroll.ts +7 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +36 -11
- package/src/plugins/builtin/chat/content/snapshot.ts +3 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +17 -1
- package/src/plugins/builtin/chat/content/types.ts +7 -0
- package/src/plugins/builtin/chat/controller/attachments.ts +213 -0
- package/src/plugins/builtin/chat/controller/connections.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +114 -2
- package/src/plugins/builtin/chat/controller/lifecycle.ts +9 -0
- package/src/plugins/builtin/chat/controller/messages.ts +35 -2
- package/src/plugins/builtin/chat/controller/send.ts +128 -25
- package/src/plugins/builtin/chat/controller/state.ts +12 -1
- package/src/plugins/builtin/chat/controller/utils.ts +7 -5
- package/src/plugins/builtin/chat/controller/view.ts +2 -0
- package/src/plugins/builtin/chat/index.tsx +21 -0
- package/src/plugins/builtin/chat/layout.ts +9 -4
- package/src/plugins/builtin/chat/message/desktop.tsx +55 -20
- package/src/plugins/builtin/chat/message/header.tsx +30 -5
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +9 -0
- package/src/plugins/builtin/chat/message/pending-send.ts +35 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
- package/src/plugins/builtin/chat/message/render-state.ts +12 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +51 -4
- package/src/plugins/builtin/chat/message/types.ts +6 -1
- package/src/plugins/builtin/chat/pane-state.ts +59 -1
- package/src/plugins/builtin/chat/profile-popover.ts +50 -1
- package/src/plugins/builtin/chat/sidebar.tsx +8 -3
- package/src/plugins/builtin/chat/status-widget.tsx +29 -12
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +143 -0
- package/src/plugins/builtin/chat/unread-inbox.ts +91 -0
- package/src/plugins/builtin/clinical-trials/client.ts +237 -0
- package/src/plugins/builtin/clinical-trials/index.tsx +74 -0
- package/src/plugins/builtin/clinical-trials/pane.tsx +225 -0
- package/src/plugins/builtin/clinical-trials/types.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +37 -6
- package/src/plugins/builtin/cloud/askg/executor.ts +59 -44
- package/src/plugins/builtin/cloud/askg/host.ts +8 -1
- package/src/plugins/builtin/cloud/askg/manifest.ts +29 -2
- package/src/plugins/builtin/cloud/askg/model.ts +47 -13
- package/src/plugins/builtin/cloud/askg/notes.ts +100 -0
- package/src/plugins/builtin/cloud/askg/pane.tsx +50 -14
- package/src/plugins/builtin/cloud/askg/protocol.ts +39 -1
- package/src/plugins/builtin/cloud/askg/result-budget.ts +156 -0
- package/src/plugins/builtin/cloud/askg/user-data.ts +89 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +55 -31
- package/src/plugins/builtin/cloud/plugin.tsx +4 -1
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -1
- package/src/plugins/builtin/cloud/team/pane.tsx +3 -2
- package/src/plugins/builtin/cloud/thesis/board-pane.tsx +2 -2
- package/src/plugins/builtin/cloud/thesis/detail.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -2
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +202 -0
- package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +29 -3
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +1 -1
- package/src/plugins/builtin/cloud-tweets/table.tsx +1 -1
- package/src/plugins/builtin/comment-letters/client.ts +342 -0
- package/src/plugins/builtin/comment-letters/index.tsx +74 -0
- package/src/plugins/builtin/comment-letters/pane.tsx +295 -0
- package/src/plugins/builtin/comment-letters/types.ts +43 -0
- package/src/plugins/builtin/company-attention/app-rank.tsx +79 -0
- package/src/plugins/builtin/company-attention/client.ts +69 -0
- package/src/plugins/builtin/company-attention/evidence.ts +45 -0
- package/src/plugins/builtin/company-attention/headless.ts +47 -0
- package/src/plugins/builtin/company-attention/index.tsx +30 -0
- package/src/plugins/builtin/company-attention/model.ts +129 -0
- package/src/plugins/builtin/company-attention/pane.tsx +183 -0
- package/src/plugins/builtin/company-kpis/client.ts +92 -0
- package/src/plugins/builtin/company-kpis/headless.ts +32 -0
- package/src/plugins/builtin/company-kpis/index.tsx +26 -0
- package/src/plugins/builtin/company-kpis/model.ts +124 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +226 -0
- package/src/plugins/builtin/company-kpis/related.ts +11 -0
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +44 -0
- package/src/plugins/builtin/composite-plugins.ts +34 -0
- package/src/plugins/builtin/correlation/history.ts +4 -1
- package/src/plugins/builtin/correlation/index.tsx +139 -80
- package/src/plugins/builtin/correlation/matrix/pair-chart.tsx +48 -0
- package/src/plugins/builtin/correlation/matrix/selection.ts +77 -0
- package/src/plugins/builtin/correlation/relationship/model.ts +2 -2
- package/src/plugins/builtin/cpi/client.ts +66 -0
- package/src/plugins/builtin/cpi/headless.ts +78 -0
- package/src/plugins/builtin/cpi/index.tsx +31 -0
- package/src/plugins/builtin/cpi/model.ts +198 -0
- package/src/plugins/builtin/cpi/pane.tsx +157 -0
- package/src/plugins/builtin/credit-documents/client.ts +99 -0
- package/src/plugins/builtin/credit-documents/detail.tsx +109 -0
- package/src/plugins/builtin/credit-documents/evidence.ts +64 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +55 -0
- package/src/plugins/builtin/credit-documents/headless.ts +40 -0
- package/src/plugins/builtin/credit-documents/index.tsx +19 -0
- package/src/plugins/builtin/credit-documents/model.ts +336 -0
- package/src/plugins/builtin/credit-documents/pane.tsx +185 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +14 -2
- package/src/plugins/builtin/debt-maturities/pane.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/doe/client.ts +73 -0
- package/src/plugins/builtin/doe/headless.ts +93 -0
- package/src/plugins/builtin/doe/index.tsx +41 -0
- package/src/plugins/builtin/doe/model.ts +215 -0
- package/src/plugins/builtin/doe/pane.tsx +148 -0
- package/src/plugins/builtin/doe/range-bar.tsx +45 -0
- package/src/plugins/builtin/earnings/board.tsx +3 -0
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +7 -1
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +7 -7
- package/src/plugins/builtin/earnings-ripple/client.ts +60 -0
- package/src/plugins/builtin/earnings-ripple/headless.ts +35 -0
- package/src/plugins/builtin/earnings-ripple/index.tsx +35 -0
- package/src/plugins/builtin/earnings-ripple/model.ts +98 -0
- package/src/plugins/builtin/earnings-ripple/pane.tsx +103 -0
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/criterion-editor.tsx +1 -1
- package/src/plugins/builtin/equity-screener/model.ts +74 -9
- package/src/plugins/builtin/equity-screener/pane.tsx +2 -0
- package/src/plugins/builtin/estimate-revisions/pane.tsx +3 -0
- package/src/plugins/builtin/executives/pane.tsx +7 -5
- package/src/plugins/builtin/exposure/charts.tsx +66 -0
- package/src/plugins/builtin/exposure/client.ts +53 -0
- package/src/plugins/builtin/exposure/drivers.ts +29 -0
- package/src/plugins/builtin/exposure/editor.tsx +61 -0
- package/src/plugins/builtin/exposure/evidence.ts +39 -0
- package/src/plugins/builtin/exposure/headless.ts +51 -0
- package/src/plugins/builtin/exposure/holdings.ts +56 -0
- package/src/plugins/builtin/exposure/index.tsx +13 -0
- package/src/plugins/builtin/exposure/model.ts +104 -0
- package/src/plugins/builtin/exposure/pane.tsx +247 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +1 -1
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -9
- package/src/plugins/builtin/filing-events/pane.tsx +2 -1
- package/src/plugins/builtin/gpu/board.tsx +163 -0
- package/src/plugins/builtin/gpu/changes.tsx +94 -0
- package/src/plugins/builtin/gpu/client.ts +130 -0
- package/src/plugins/builtin/gpu/equities.tsx +112 -0
- package/src/plugins/builtin/gpu/headless.ts +67 -0
- package/src/plugins/builtin/gpu/history.tsx +140 -0
- package/src/plugins/builtin/gpu/index.tsx +18 -0
- package/src/plugins/builtin/gpu/ladder.tsx +127 -0
- package/src/plugins/builtin/gpu/model.ts +317 -0
- package/src/plugins/builtin/gpu/pane.tsx +63 -0
- package/src/plugins/builtin/help/index.tsx +19 -2
- package/src/plugins/builtin/holders/pane.tsx +5 -9
- package/src/plugins/builtin/insider/detail.tsx +119 -0
- package/src/plugins/builtin/insider/display.ts +221 -0
- package/src/plugins/builtin/insider/headless.ts +24 -3
- package/src/plugins/builtin/insider/index.tsx +206 -108
- package/src/plugins/builtin/insider/insider-data.ts +31 -3
- package/src/plugins/builtin/insider/model.ts +53 -4
- package/src/plugins/builtin/insider/table-model.ts +226 -0
- package/src/plugins/builtin/insider/table.tsx +79 -0
- package/src/plugins/builtin/iv-history/screen-pane.tsx +1 -1
- package/src/plugins/builtin/jobs/pane.tsx +14 -7
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/macro-day/client.ts +50 -0
- package/src/plugins/builtin/macro-day/headless.ts +51 -0
- package/src/plugins/builtin/macro-day/index.tsx +21 -0
- package/src/plugins/builtin/macro-day/model.ts +137 -0
- package/src/plugins/builtin/macro-day/pane.tsx +118 -0
- package/src/plugins/builtin/macro-day/releases.ts +58 -0
- package/src/plugins/builtin/market-halts/model.ts +30 -17
- package/src/plugins/builtin/market-halts/pane.tsx +15 -21
- package/src/plugins/builtin/market-heatmap/data.ts +1 -1
- package/src/plugins/builtin/market-heatmap/index.tsx +198 -50
- package/src/plugins/builtin/market-heatmap/portfolio.ts +220 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +5 -2
- package/src/plugins/builtin/mna/distress/client.ts +326 -0
- package/src/plugins/builtin/mna/distress/detail.tsx +247 -0
- package/src/plugins/builtin/mna/distress/headless.ts +257 -0
- package/src/plugins/builtin/mna/distress/model.ts +427 -0
- package/src/plugins/builtin/mna/distress/pane.tsx +606 -0
- package/src/plugins/builtin/mna/index.tsx +15 -0
- package/src/plugins/builtin/mna/pane.tsx +42 -1
- package/src/plugins/builtin/news/index.tsx +62 -16
- package/src/plugins/builtin/news/wire/filter-articles.ts +54 -0
- package/src/plugins/builtin/news/wire/index.tsx +37 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +29 -13
- package/src/plugins/builtin/news/wire/mutes.ts +253 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +117 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +4 -0
- package/src/plugins/builtin/news/wire/news/list-search.ts +34 -0
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +158 -0
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +64 -19
- package/src/plugins/builtin/openfda/client.ts +300 -0
- package/src/plugins/builtin/openfda/index.tsx +71 -0
- package/src/plugins/builtin/openfda/pane.tsx +382 -0
- package/src/plugins/builtin/openfda/types.ts +76 -0
- package/src/plugins/builtin/options/footer.ts +1 -0
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/options-calculator/pane.tsx +0 -1
- package/src/plugins/builtin/options-positioning/index.tsx +4 -2
- package/src/plugins/builtin/options-positioning/pane.tsx +1 -1
- package/src/plugins/builtin/pe-band/client.ts +61 -0
- package/src/plugins/builtin/pe-band/headless.ts +51 -0
- package/src/plugins/builtin/pe-band/index.tsx +20 -0
- package/src/plugins/builtin/pe-band/model.ts +267 -0
- package/src/plugins/builtin/pe-band/pane.tsx +159 -0
- package/src/plugins/builtin/perps/client.ts +66 -0
- package/src/plugins/builtin/perps/equity-row.tsx +28 -0
- package/src/plugins/builtin/perps/headless.ts +34 -0
- package/src/plugins/builtin/perps/index.tsx +14 -0
- package/src/plugins/builtin/perps/model.ts +58 -0
- package/src/plugins/builtin/perps/pane.tsx +185 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +0 -1
- package/src/plugins/builtin/portfolio-list/cli/render.ts +177 -87
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +6 -20
- package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +237 -0
- package/src/plugins/builtin/portfolio-list/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +70 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +88 -5
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +85 -0
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +18 -12
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -0
- package/src/plugins/builtin/power/client.ts +100 -0
- package/src/plugins/builtin/power/detail.tsx +82 -0
- package/src/plugins/builtin/power/evidence.ts +56 -0
- package/src/plugins/builtin/power/generation.tsx +37 -0
- package/src/plugins/builtin/power/headless.ts +43 -0
- package/src/plugins/builtin/power/index.tsx +16 -0
- package/src/plugins/builtin/power/model.ts +89 -0
- package/src/plugins/builtin/power/pane.tsx +209 -0
- package/src/plugins/builtin/power/query.ts +25 -0
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -3
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +13 -4
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +11 -9
- package/src/plugins/builtin/research-search/pane.tsx +21 -7
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +1 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +35 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +44 -0
- package/src/plugins/builtin/reverse-dcf/index.tsx +20 -0
- package/src/plugins/builtin/reverse-dcf/model.ts +87 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +107 -0
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/scanner/denied.tsx +4 -2
- package/src/plugins/builtin/scanner/feed.ts +1 -0
- package/src/plugins/builtin/scanner/flow-pane.tsx +3 -2
- package/src/plugins/builtin/scanner/hilo-pane.tsx +1 -1
- package/src/plugins/builtin/seasonality/client.ts +4 -1
- package/src/plugins/builtin/sec/command-bar-search.ts +217 -0
- package/src/plugins/builtin/sec/forms.ts +54 -0
- package/src/plugins/builtin/sec/headless.ts +31 -5
- package/src/plugins/builtin/sec/index.tsx +67 -11
- package/src/plugins/builtin/sec/model.ts +35 -5
- package/src/plugins/builtin/sectors/headless.ts +1 -0
- package/src/plugins/builtin/sectors/index.tsx +72 -26
- package/src/plugins/builtin/shared/cloud-upgrade.ts +143 -39
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +8 -4
- package/src/plugins/builtin/shared/research-cells.tsx +107 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +5 -1
- package/src/plugins/builtin/shared/ticker-request.ts +1 -1
- package/src/plugins/builtin/shared/wall-teaser-catalog.ts +80 -0
- package/src/plugins/builtin/shared/wall-teaser.tsx +192 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +43 -0
- package/src/plugins/builtin/short-interest/index.tsx +31 -0
- package/src/plugins/builtin/short-interest/watch-client.ts +70 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +49 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +185 -0
- package/src/plugins/builtin/short-interest/watch-pane.tsx +169 -0
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/client.ts +104 -0
- package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +17 -0
- package/src/plugins/builtin/supply-chain/evidence.ts +88 -0
- package/src/plugins/builtin/supply-chain/flow-layout.ts +133 -0
- package/src/plugins/builtin/supply-chain/flow.tsx +323 -0
- package/src/plugins/builtin/supply-chain/graph-client.ts +83 -0
- package/src/plugins/builtin/supply-chain/graph-evidence.ts +25 -0
- package/src/plugins/builtin/supply-chain/graph-model.ts +45 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +218 -0
- package/src/plugins/builtin/supply-chain/graph.tsx +163 -0
- package/src/plugins/builtin/supply-chain/headless.ts +105 -0
- package/src/plugins/builtin/supply-chain/index.tsx +17 -0
- package/src/plugins/builtin/supply-chain/model.ts +145 -0
- package/src/plugins/builtin/supply-chain/pane.tsx +276 -0
- package/src/plugins/builtin/supply-chain/row-evidence.tsx +96 -0
- package/src/plugins/builtin/supply-chain/trust.ts +34 -0
- package/src/plugins/builtin/themes/client.ts +39 -0
- package/src/plugins/builtin/themes/headless.ts +38 -0
- package/src/plugins/builtin/themes/model.ts +92 -0
- package/src/plugins/builtin/themes/pane.tsx +125 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +6 -15
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +201 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +327 -0
- package/src/plugins/builtin/ticker-detail/financials/period-end-history.ts +5 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +60 -12
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +22 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +53 -30
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +112 -47
- package/src/plugins/builtin/ticker-detail/pane.tsx +50 -6
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +21 -42
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +3 -0
- package/src/plugins/builtin/time-sales/pane.tsx +1 -1
- package/src/plugins/builtin/world-indices/indices.ts +1 -0
- package/src/plugins/catalog-browser.ts +40 -0
- package/src/plugins/catalog-manifest.ts +3 -0
- package/src/plugins/catalog-ui.ts +6 -0
- package/src/plugins/current-target.ts +0 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/renderers/browser/cloud-transport.ts +66 -1
- package/src/renderers/browser/main.tsx +2 -0
- package/src/renderers/dom/data-table/row.tsx +2 -0
- package/src/renderers/dom/desktop/controls.tsx +38 -25
- package/src/renderers/dom/desktop/icons.tsx +8 -0
- package/src/renderers/dom/host/chart-surface.tsx +36 -2
- package/src/renderers/dom/host/focus-scope.ts +11 -1
- package/src/renderers/dom/styles.css +28 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +3 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/http-fetch.ts +2 -0
- package/src/renderers/electrobun/view/main.tsx +3 -1
- package/src/renderers/electrobun/view/rpc-timeout.ts +59 -0
- package/src/renderers/opentui/start.tsx +3 -0
- package/src/shares/api.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +47 -13
- package/src/sources/history-result.ts +1 -0
- package/src/sources/history-retention.ts +16 -0
- package/src/sources/provider-router/history.ts +4 -2
- package/src/sources/sec-edgar/identity.ts +36 -0
- package/src/sources/sec-edgar.ts +1 -34
- package/src/state/app/context/index.tsx +8 -0
- package/src/sync/core-contributors.ts +20 -2
- package/src/telemetry/attention-counts.ts +164 -0
- package/src/telemetry/live-config.ts +7 -0
- package/src/test-support/rpc-loopback.ts +7 -3
- package/src/test-support/shell-pane-keys.tsx +34 -0
- package/src/tickers/instrument-kind.ts +12 -6
- package/src/tickers/open-target.ts +1 -1
- package/src/tickers/search/asset-classes.ts +91 -0
- package/src/tickers/search/index.ts +62 -15
- package/src/tickers/search/ranking.ts +22 -3
- package/src/tickers/search/upsert.ts +4 -1
- package/src/time-series/live-bars.ts +8 -3
- package/src/time-series/resolve.ts +224 -17
- package/src/time-series/spec.ts +1 -0
- package/src/time-series/types.ts +9 -1
- package/src/types/config.ts +23 -1
- package/src/types/data-provider.ts +2 -0
- package/src/types/headless.ts +17 -0
- package/src/types/price-history.ts +2 -0
- package/src/ui/host.tsx +6 -0
- package/src/utils/double-escape-close.ts +20 -0
- package/src/utils/exchanges.ts +1 -0
- package/src/utils/html-entities.ts +15 -0
- package/src/utils/http-proxy-response.ts +44 -7
- package/src/utils/plugin-proxy-hosts.json +3 -0
- package/src/utils/price-history.ts +35 -1
- package/src/utils/sec.ts +21 -2
- package/src/utils/text-wrap.ts +13 -8
- package/src/utils/zoned-date-time.ts +67 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { PluginModule } from "../plugin-module";
|
|
2
|
+
import { formatTickerListInput, parseTickerListInput } from "../../../tickers/list";
|
|
3
|
+
import { earningsRippleHeadless } from "./headless";
|
|
4
|
+
import { EarningsRipplePane } from "./pane";
|
|
5
|
+
|
|
6
|
+
function namedSymbols(arg: string | undefined): string[] {
|
|
7
|
+
try { return arg?.trim() ? parseTickerListInput(arg) : []; } catch { return []; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const earningsRippleModule: PluginModule = {
|
|
11
|
+
panes: [{
|
|
12
|
+
id: "earnings-ripple", name: "Earnings Ripple", icon: "E", component: EarningsRipplePane,
|
|
13
|
+
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 96, height: 20 },
|
|
14
|
+
tableExport: true,
|
|
15
|
+
settings: { title: "Earnings Ripple Settings", fields: [
|
|
16
|
+
{ key: "symbols", label: "Holdings", type: "text", placeholder: "Blank for your portfolios and watchlists" },
|
|
17
|
+
] },
|
|
18
|
+
}],
|
|
19
|
+
paneTemplates: [{
|
|
20
|
+
id: "earnings-ripple-pane", paneId: "earnings-ripple", label: "Earnings Ripple",
|
|
21
|
+
description: "Customers of your holdings that report soon, with the share of each holding's revenue they make up.",
|
|
22
|
+
keywords: ["ripl", "ripple", "earnings", "customers", "supply chain", "exposure", "read-through"],
|
|
23
|
+
shortcut: { prefix: "RIPL", argPlaceholder: "tickers", argKind: "ticker-list", argOptional: true, openWithoutArg: true },
|
|
24
|
+
headless: earningsRippleHeadless,
|
|
25
|
+
canCreate: () => true,
|
|
26
|
+
createInstance: (_context, options) => {
|
|
27
|
+
const symbols = options?.symbols?.length ? options.symbols : namedSymbols(options?.arg);
|
|
28
|
+
return {
|
|
29
|
+
title: symbols.length ? `RIPL ${formatTickerListInput(symbols)}` : "Earnings Ripple",
|
|
30
|
+
placement: "floating",
|
|
31
|
+
settings: symbols.length ? { symbols: formatTickerListInput(symbols) } : undefined,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
}],
|
|
35
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { EarningsCalendarReport, EarningsTiming } from "../../../api-client/earnings";
|
|
2
|
+
import type { SupplyChainPayload, SupplyRow } from "../../../api-client/supply-chain";
|
|
3
|
+
import { isUsListingExchange } from "../../../utils/exchanges";
|
|
4
|
+
import { MIN_AVERAGE_REPORTS } from "../earnings/board-model";
|
|
5
|
+
|
|
6
|
+
/** Days ahead the calendar is read. */
|
|
7
|
+
export const RIPPLE_DAYS = 30;
|
|
8
|
+
|
|
9
|
+
export interface RippleRow {
|
|
10
|
+
id: string;
|
|
11
|
+
/** The company that reports. */
|
|
12
|
+
company: string;
|
|
13
|
+
companyName: string;
|
|
14
|
+
/** `customer`: the company buys from the holding. `supplier`: it sells to the holding. */
|
|
15
|
+
link: "customer" | "supplier";
|
|
16
|
+
date: string;
|
|
17
|
+
timing: EarningsTiming | null;
|
|
18
|
+
/** Mean absolute move over the company's last reports, once it has as many as ERN requires. */
|
|
19
|
+
averageMove: number | null;
|
|
20
|
+
holding: string;
|
|
21
|
+
/**
|
|
22
|
+
* Percent of the seller's revenue, as the seller's filing discloses it: the
|
|
23
|
+
* holding's revenue for a customer, the supplier's revenue for a supplier.
|
|
24
|
+
*/
|
|
25
|
+
pctOfRevenue: number;
|
|
26
|
+
pctScope: string | null;
|
|
27
|
+
period: string;
|
|
28
|
+
/** The holding's own next report in the window, when it has one. */
|
|
29
|
+
holdingDate: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Whose revenue the share is of. */
|
|
33
|
+
export const revenueOwner = (row: RippleRow) => row.link === "customer" ? row.holding : row.company;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Listed counterparties in one role with a share of the seller's revenue: one
|
|
37
|
+
* per company, the latest period, then the larger share. Receivables and
|
|
38
|
+
* purchase shares measure something else and are left out.
|
|
39
|
+
*/
|
|
40
|
+
function revenueLinks(rows: readonly SupplyRow[], role: "customer" | "supplier"): SupplyRow[] {
|
|
41
|
+
const best = new Map<string, SupplyRow>();
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
const ticker = row.counterparty.ticker;
|
|
44
|
+
if (row.role !== role || !ticker || row.counterparty.aggregate || row.pctBasis !== "revenue" || row.pctOfRevenue == null) continue;
|
|
45
|
+
// The calendar covers US listings; a foreign code such as Foxconn's 2317 could collide with one.
|
|
46
|
+
if (row.counterparty.exchange && !isUsListingExchange(row.counterparty.exchange)) continue;
|
|
47
|
+
const key = ticker.toUpperCase();
|
|
48
|
+
const current = best.get(key);
|
|
49
|
+
if (!current || row.period > current.period || (row.period === current.period && row.pctOfRevenue > current.pctOfRevenue!)) best.set(key, row);
|
|
50
|
+
}
|
|
51
|
+
return [...best.values()];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Customers a holding names (`says`) and suppliers that name it (`names`), each with its link. */
|
|
55
|
+
function rippleLinks(chain: SupplyChainPayload) {
|
|
56
|
+
return [
|
|
57
|
+
...revenueLinks(chain.says, "customer").map((row) => ({ row, link: "customer" as const })),
|
|
58
|
+
...revenueLinks(chain.names, "supplier").map((row) => ({ row, link: "supplier" as const })),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Calendar symbols use the same eligibility as the displayed rows, in both directions. */
|
|
63
|
+
export function rippleCompanyTickers(chains: ReadonlyMap<string, SupplyChainPayload>): string[] {
|
|
64
|
+
const companies = new Set<string>();
|
|
65
|
+
for (const [holding, chain] of chains) {
|
|
66
|
+
for (const { row } of rippleLinks(chain)) {
|
|
67
|
+
const company = row.counterparty.ticker!.toUpperCase();
|
|
68
|
+
if (company !== holding.toUpperCase()) companies.add(company);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return [...companies];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Customers a holding names in its own filings (`says`), and suppliers whose
|
|
76
|
+
* filings name the holding as a customer (`names`), that report in the
|
|
77
|
+
* calendar: soonest first, then by the share.
|
|
78
|
+
*/
|
|
79
|
+
export function projectRipple(chains: ReadonlyMap<string, SupplyChainPayload>, reports: readonly EarningsCalendarReport[]): RippleRow[] {
|
|
80
|
+
const next = new Map<string, EarningsCalendarReport>();
|
|
81
|
+
for (const report of reports) {
|
|
82
|
+
const key = report.symbol.toUpperCase();
|
|
83
|
+
const current = next.get(key);
|
|
84
|
+
if (!current || report.date < current.date) next.set(key, report);
|
|
85
|
+
}
|
|
86
|
+
const rows: RippleRow[] = [];
|
|
87
|
+
for (const [holding, chain] of chains) {
|
|
88
|
+
for (const { row, link } of rippleLinks(chain)) {
|
|
89
|
+
const company = row.counterparty.ticker!.toUpperCase();
|
|
90
|
+
const report = next.get(company);
|
|
91
|
+
if (!report || company === holding.toUpperCase()) continue;
|
|
92
|
+
rows.push({ id: `${holding}:${link}:${company}`, company, companyName: report.name ?? row.counterparty.name, link, date: report.date,
|
|
93
|
+
timing: report.timing, averageMove: report.averageReports >= MIN_AVERAGE_REPORTS ? report.averageMove : null, holding, pctOfRevenue: row.pctOfRevenue!, pctScope: row.pctScope,
|
|
94
|
+
period: row.period, holdingDate: next.get(holding.toUpperCase())?.date ?? null });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return rows.sort((a, b) => a.date.localeCompare(b.date) || b.pctOfRevenue - a.pctOfRevenue || a.holding.localeCompare(b.holding));
|
|
98
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
import { DataTableView, EmptyState, PaneStatusBody, usePaneFooter, usePaneNoticeFooter,
|
|
3
|
+
type DataTableCell, type DataTableColumn, type PaneHint } from "../../../components";
|
|
4
|
+
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
5
|
+
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
6
|
+
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState, useTickers } from "../../../public/react";
|
|
7
|
+
import type { PaneProps } from "../../../types/plugin";
|
|
8
|
+
import { Box } from "../../../ui";
|
|
9
|
+
import { isUsListingExchange } from "../../../utils/exchanges";
|
|
10
|
+
import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
|
|
11
|
+
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
12
|
+
import { cachedRippleSources, loadRipple } from "./client";
|
|
13
|
+
import { revenueOwner, RIPPLE_DAYS, type RippleRow } from "./model";
|
|
14
|
+
|
|
15
|
+
/** Calls per refresh stay bounded: one disclosure request per holding. */
|
|
16
|
+
const RIPPLE_HOLDINGS_LIMIT = 60;
|
|
17
|
+
|
|
18
|
+
const COLUMNS: DataTableColumn[] = [
|
|
19
|
+
{ id: "date", label: "Date", width: 11, align: "left" },
|
|
20
|
+
{ id: "timing", label: "Time", width: 5, align: "left" },
|
|
21
|
+
{ id: "company", label: "Reports", width: 24, align: "left" },
|
|
22
|
+
{ id: "link", label: "Link", width: 13, align: "left" },
|
|
23
|
+
{ id: "holding", label: "Holding", width: 9, align: "left" },
|
|
24
|
+
{ id: "pct", label: "Revenue share", width: 15, align: "right" },
|
|
25
|
+
{ id: "move", label: "Avg move", width: 9, align: "right" },
|
|
26
|
+
{ id: "holdingDate", label: "Holding reports", width: 16, align: "right" },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function renderCell(row: RippleRow, column: DataTableColumn): DataTableCell {
|
|
30
|
+
switch (column.id) {
|
|
31
|
+
case "date": return { text: row.date };
|
|
32
|
+
case "timing": return { text: row.timing?.toUpperCase() ?? "--" };
|
|
33
|
+
case "company": return { text: `${row.company} ${row.companyName}` };
|
|
34
|
+
case "link": return { text: row.link === "customer" ? "customer of" : "supplier to" };
|
|
35
|
+
case "holding": return { text: row.holding };
|
|
36
|
+
case "pct": return { text: `${row.pctOfRevenue}%${row.pctScope ? "*" : ""} of ${revenueOwner(row)}`, value: row.pctOfRevenue };
|
|
37
|
+
case "move": return { text: row.averageMove == null ? "--" : `±${(row.averageMove * 100).toFixed(1)}%`, value: row.averageMove };
|
|
38
|
+
default: return { text: row.holdingDate ?? "--" };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function EarningsRipplePane({ width, height, focused }: PaneProps) {
|
|
43
|
+
const [symbolsText] = usePaneSettingValue("symbols", "");
|
|
44
|
+
const tickers = useTickers();
|
|
45
|
+
const { createPaneFromTemplate } = usePluginAppActions();
|
|
46
|
+
const [selectedId, setSelectedId] = usePluginPaneState<string | null>("selectedRow", null);
|
|
47
|
+
const session = useResearchCloudSession();
|
|
48
|
+
const access = usePlanAccess();
|
|
49
|
+
const accessKey = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
|
|
50
|
+
// Named tickers, or every US listing in the user's portfolios and watchlists, positions first.
|
|
51
|
+
const scope = useMemo(() => {
|
|
52
|
+
const named = symbolsText.split(/[\s,]+/).map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
|
|
53
|
+
const all = named.length ? [...new Set(named)] : [...new Set([...tickers.values()]
|
|
54
|
+
.filter((ticker) => ticker.metadata.portfolios.length + ticker.metadata.watchlists.length > 0 && isUsListingExchange(ticker.metadata.exchange))
|
|
55
|
+
.sort((a, b) => Number(b.metadata.portfolios.length > 0) - Number(a.metadata.portfolios.length > 0) || a.metadata.ticker.localeCompare(b.metadata.ticker))
|
|
56
|
+
.map((ticker) => ticker.metadata.ticker.toUpperCase()))];
|
|
57
|
+
return { holdings: all.slice(0, RIPPLE_HOLDINGS_LIMIT), total: all.length, named: named.length > 0 };
|
|
58
|
+
}, [symbolsText, tickers]);
|
|
59
|
+
const holdings = scope.holdings;
|
|
60
|
+
const holdingsKey = holdings.join(",");
|
|
61
|
+
const loader = useCallback((force: boolean) => loadRipple(holdings, cachedRippleSources(accessKey, force)), [holdingsKey, accessKey]);
|
|
62
|
+
const ripple = useAsyncResource(holdings.length ? loader : null);
|
|
63
|
+
useAutoRefresh(ripple.updatedAt, ripple.load);
|
|
64
|
+
usePaneRefreshKey(() => { void ripple.reload(); }, { focused });
|
|
65
|
+
|
|
66
|
+
const rows = ripple.data?.rows ?? [];
|
|
67
|
+
const selected = rows.find((row) => row.id === selectedId) ?? rows[0] ?? null;
|
|
68
|
+
const failures = ripple.data?.failures ?? [];
|
|
69
|
+
const truncated = ripple.data?.truncated ?? [];
|
|
70
|
+
const openUpgrade = useCloudUpgradeAction("ripl");
|
|
71
|
+
usePaneNoticeFooter({ registrationId: "earnings-ripple-notices", focused, notices: [
|
|
72
|
+
...(scope.total > holdings.length ? [`Checking ${holdings.length} of ${scope.total} holdings${scope.named ? "" : ", positions first. Name tickers in pane settings to pick others"}`] : []),
|
|
73
|
+
...(failures.length ? [`No disclosures for ${failures.map((failure) => failure.symbol).join(", ")}`] : []),
|
|
74
|
+
...(truncated.length ? [`Free preview: top three customers and suppliers for ${truncated.join(", ")}`] : []),
|
|
75
|
+
] });
|
|
76
|
+
const openSupply = useCallback((row: RippleRow) => createPaneFromTemplate("supply-chain-pane", { symbol: row.holding }), [createPaneFromTemplate]);
|
|
77
|
+
usePaneFooter("earnings-ripple", () => {
|
|
78
|
+
const hints: PaneHint[] = [
|
|
79
|
+
...(selected ? [
|
|
80
|
+
{ id: "supply", key: "s", label: "upply chain", onPress: () => openSupply(selected) },
|
|
81
|
+
{ id: "earnings", key: "e", label: "arnings", onPress: () => createPaneFromTemplate("earnings-calendar-pane", { arg: selected.company }) },
|
|
82
|
+
] : []),
|
|
83
|
+
...(truncated.length ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade }] : []),
|
|
84
|
+
];
|
|
85
|
+
return { info: [
|
|
86
|
+
...(ripple.loading ? [{ id: "loading", parts: [{ text: "loading disclosures", tone: "muted" as const }] }] : []),
|
|
87
|
+
...(ripple.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
88
|
+
], hints };
|
|
89
|
+
}, [ripple.loading, ripple.data?.stale, selected, openSupply, createPaneFromTemplate, truncated.length, openUpgrade]);
|
|
90
|
+
|
|
91
|
+
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
92
|
+
{!holdings.length ? <EmptyState title="Add tickers to a portfolio or watchlist, or name them in pane settings." />
|
|
93
|
+
: <PaneStatusBody subject="earnings ripple" loading={ripple.loading && !ripple.data} error={!ripple.data ? ripple.error : null}
|
|
94
|
+
empty={!!ripple.data && !rows.length} emptyTitle={`No disclosed customer or supplier of these holdings reports in the next ${RIPPLE_DAYS} days.`}>
|
|
95
|
+
<DataTableView<RippleRow> focused={focused} columns={COLUMNS} items={rows} rootWidth={width} rootHeight={height}
|
|
96
|
+
getItemKey={(row) => row.id} emptyStateTitle="No reports." sortColumnId={null} sortDirection="asc"
|
|
97
|
+
selection={{ kind: "id", selectedId: selected?.id ?? null, getId: (row) => row.id, onChange: setSelectedId }}
|
|
98
|
+
onActivate={openSupply}
|
|
99
|
+
getExportMetadata={() => [["window", `${ripple.data?.from} to ${ripple.data?.to}`], ["revenue share", "percent of the seller's revenue, as its filing discloses"]]}
|
|
100
|
+
renderCell={renderCell} />
|
|
101
|
+
</PaneStatusBody>}
|
|
102
|
+
</Box>;
|
|
103
|
+
}
|
|
@@ -19,7 +19,7 @@ const savedPath = (id: string) => `saved/${encodeURIComponent(id)}`;
|
|
|
19
19
|
export const screenerApi = {
|
|
20
20
|
query: (query: ScreenQuery, signal?: AbortSignal, client: ScreenerClient = apiClient) =>
|
|
21
21
|
client.equityScreener<ScreenPayload>("query", json("POST", query, { signal })),
|
|
22
|
-
fields: (signal?: AbortSignal) => apiClient.equityScreener<ScreenFieldsResponse>("fields?include=social,research", { signal }),
|
|
22
|
+
fields: (signal?: AbortSignal) => apiClient.equityScreener<ScreenFieldsResponse>("fields?include=social,research,attention,hiring-apps,kpis", { signal }),
|
|
23
23
|
export: (definition: ScreenDefinition, snapshotId: string) =>
|
|
24
24
|
apiClient.equityScreener<ScreenExportResponse>("export", json("POST", { ...definition, snapshotId }, { headers: { Accept: "application/json" } })),
|
|
25
25
|
saved: async () => (await apiClient.equityScreener<{ screens: SavedScreen[] }>("saved")).screens,
|
|
@@ -100,7 +100,7 @@ export function CriterionEditor({
|
|
|
100
100
|
label="Field"
|
|
101
101
|
emphasized={focused && active === "field"}
|
|
102
102
|
value={fieldId}
|
|
103
|
-
options={fields.map((row) => ({ value: row.id, label: row.label }))}
|
|
103
|
+
options={fields.map((row) => ({ value: row.id, label: row.access === "pro" ? `${row.label} · Pro` : row.label }))}
|
|
104
104
|
variant="field"
|
|
105
105
|
width={Math.min(40, width - 4)}
|
|
106
106
|
onFocus={() => setActive("field")}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ATTENTION_FIELDS,
|
|
3
|
+
HIRING_APP_FIELDS,
|
|
2
4
|
CATEGORY_FIELDS,
|
|
3
5
|
NUMERIC_FIELDS,
|
|
4
6
|
RESEARCH_FIELDS,
|
|
7
|
+
KPI_FIELDS,
|
|
5
8
|
SOCIAL_FIELDS,
|
|
6
9
|
type NumericField,
|
|
7
10
|
type ScreenMetric,
|
|
@@ -20,6 +23,27 @@ import { formatCompact, formatNumber } from "../../../utils/format";
|
|
|
20
23
|
* Growth and margins come from the latest fiscal-year statement, so they say FY.
|
|
21
24
|
*/
|
|
22
25
|
export const SHORT_LABELS: Record<NumericField, string> = {
|
|
26
|
+
kpiArr: "ARR",
|
|
27
|
+
kpiNrrPercent: "NRR%",
|
|
28
|
+
kpiRpo: "RPO",
|
|
29
|
+
kpiCrpo: "CRPO",
|
|
30
|
+
kpiSubscribers: "SUBSCRIBERS",
|
|
31
|
+
kpiDau: "DAU",
|
|
32
|
+
kpiMau: "MAU",
|
|
33
|
+
kpiGmv: "GMV",
|
|
34
|
+
kpiTakeRatePercent: "TAKE RATE%",
|
|
35
|
+
kpiSameStoreSalesPercent: "SAME STORE%",
|
|
36
|
+
kpiStores: "STORES",
|
|
37
|
+
kpiBacklog: "BACKLOG",
|
|
38
|
+
kpiBookToBill: "BOOK/BILL",
|
|
39
|
+
kpiNimPercent: "NIM%",
|
|
40
|
+
kpiCet1Percent: "CET1%",
|
|
41
|
+
kpiCombinedRatioPercent: "COMBINED%",
|
|
42
|
+
kpiOccupancyPercent: "OCCUPANCY%",
|
|
43
|
+
kpiLoadFactorPercent: "LOAD FACTOR%",
|
|
44
|
+
guideRevenueChangePercent: "REV GUIDE CHG%",
|
|
45
|
+
guideEpsChangePercent: "EPS GUIDE CHG%",
|
|
46
|
+
|
|
23
47
|
price: "PRICE",
|
|
24
48
|
changePercent: "CHG%",
|
|
25
49
|
volume: "VOLUME",
|
|
@@ -47,6 +71,8 @@ export const SHORT_LABELS: Record<NumericField, string> = {
|
|
|
47
71
|
xPostsVsMedian: "X VS MED",
|
|
48
72
|
wikiViewsPerDay: "WIKI/D",
|
|
49
73
|
wikiViewsVsMedian: "WIKI VS MED",
|
|
74
|
+
attentionResearchUnits: "ATTN HRS",
|
|
75
|
+
attentionZScore: "ATTN Z",
|
|
50
76
|
return1WPercent: "1W%",
|
|
51
77
|
return1MPercent: "1M%",
|
|
52
78
|
return3MPercent: "3M%",
|
|
@@ -61,20 +87,29 @@ export const SHORT_LABELS: Record<NumericField, string> = {
|
|
|
61
87
|
analystUpsidePercent: "UPSIDE%",
|
|
62
88
|
ivRank: "IV RANK",
|
|
63
89
|
ivToHv: "IV/HV",
|
|
90
|
+
hiringOpenRoles: "OPEN ROLES",
|
|
91
|
+
hiringNetAddsWeek: "NET ROLES/WK",
|
|
92
|
+
hiringRemotePercent: "REMOTE%",
|
|
93
|
+
hiringZScore: "HIRING Z",
|
|
94
|
+
appAttentionScore: "APP SCORE",
|
|
95
|
+
appRankVelocity7d: "APP RANK/D",
|
|
96
|
+
appRatingDrift7d: "RATING 7D",
|
|
97
|
+
appRatingCountGrowth7d: "RATINGS 7D",
|
|
64
98
|
};
|
|
65
|
-
const COMPACT = new Set<NumericField>(["marketCap", "volume", "averageVolume20d", "shortInterestShares"]);
|
|
99
|
+
const COMPACT = new Set<NumericField>(["kpiArr", "kpiRpo", "kpiCrpo", "kpiSubscribers", "kpiDau", "kpiMau", "kpiGmv", "kpiStores", "kpiBacklog", "marketCap", "volume", "averageVolume20d", "shortInterestShares"]);
|
|
66
100
|
/** Daily counts: whole numbers below a thousand, compact above. */
|
|
67
101
|
const DAILY_COUNTS = new Set<NumericField>(["xPostsPerDay", "wikiViewsPerDay"]);
|
|
68
|
-
const RATIOS = new Set<NumericField>(["xPostsVsMedian", "wikiViewsVsMedian", "priceToBook", "evToEbitda", "ivToHv"]);
|
|
102
|
+
const RATIOS = new Set<NumericField>(["kpiBookToBill", "xPostsVsMedian", "wikiViewsVsMedian", "priceToBook", "evToEbitda", "ivToHv"]);
|
|
69
103
|
const RETURNS: NumericField[] = ["return1WPercent", "return1MPercent", "return3MPercent", "returnYtdPercent", "return1YPercent"];
|
|
70
|
-
const SIGNED = new Set<NumericField>([
|
|
104
|
+
const SIGNED = new Set<NumericField>(["hiringNetAddsWeek", "hiringZScore", "appRankVelocity7d", "appRatingDrift7d", "appRatingCountGrowth7d",
|
|
105
|
+
"guideRevenueChangePercent", "guideEpsChangePercent", "kpiSameStoreSalesPercent",
|
|
71
106
|
"changePercent", "revenueGrowthPercent", "earningsGrowthPercent", "shortInterestChangePercent",
|
|
72
107
|
...RETURNS, "fromHigh52WPercent", "epsRevision30dPercent", "analystUpsidePercent",
|
|
73
108
|
]);
|
|
74
109
|
/** Moves whose sign is the news: they take the sign colour. */
|
|
75
110
|
export const SIGN_COLORED = new Set<NumericField>(["changePercent", ...RETURNS, "epsRevision30dPercent", "analystUpsidePercent"]);
|
|
76
|
-
const COUNTS = new Set<NumericField>(["insiderPurchases90d", "insiderSales90d", "institutionalHolders", "institutionalNewHolders", "institutionalExits", "ivRank"]);
|
|
77
|
-
const MONETARY = new Set<NumericField>(["price", "marketCap"]);
|
|
111
|
+
const COUNTS = new Set<NumericField>(["appRatingCountGrowth7d", "hiringNetAddsWeek", "hiringOpenRoles", "attentionResearchUnits", "insiderPurchases90d", "insiderSales90d", "institutionalHolders", "institutionalNewHolders", "institutionalExits", "ivRank"]);
|
|
112
|
+
const MONETARY = new Set<NumericField>(["price", "marketCap", "kpiArr", "kpiRpo", "kpiCrpo", "kpiGmv", "kpiBacklog"]);
|
|
78
113
|
/** Shown after the screen's own criteria when the pane has room. */
|
|
79
114
|
const CONTEXT_FIELDS: NumericField[] = ["marketCap", "price", "changePercent", "trailingPE", "revenueGrowthPercent", "operatingMarginPercent", "dividendYieldPercent"];
|
|
80
115
|
|
|
@@ -117,6 +152,26 @@ export const screenRowId = (row: Pick<ScreenRow, "symbol" | "exchange">) =>
|
|
|
117
152
|
`${canonicalExchange(row.exchange)}:${row.symbol}`;
|
|
118
153
|
export const screenLabel = (field: string) =>
|
|
119
154
|
({
|
|
155
|
+
kpiArr: "Annual recurring revenue",
|
|
156
|
+
kpiNrrPercent: "Net revenue retention %",
|
|
157
|
+
kpiRpo: "Remaining performance obligations",
|
|
158
|
+
kpiCrpo: "Current RPO",
|
|
159
|
+
kpiSubscribers: "Subscribers",
|
|
160
|
+
kpiDau: "Daily active users",
|
|
161
|
+
kpiMau: "Monthly active users",
|
|
162
|
+
kpiGmv: "Gross merchandise value",
|
|
163
|
+
kpiTakeRatePercent: "Take rate %",
|
|
164
|
+
kpiSameStoreSalesPercent: "Same-store sales growth %",
|
|
165
|
+
kpiStores: "Stores",
|
|
166
|
+
kpiBacklog: "Backlog",
|
|
167
|
+
kpiBookToBill: "Book-to-bill",
|
|
168
|
+
kpiNimPercent: "Net interest margin %",
|
|
169
|
+
kpiCet1Percent: "CET1 ratio %",
|
|
170
|
+
kpiCombinedRatioPercent: "Combined ratio %",
|
|
171
|
+
kpiOccupancyPercent: "Occupancy %",
|
|
172
|
+
kpiLoadFactorPercent: "Load factor %",
|
|
173
|
+
guideRevenueChangePercent: "Revenue guide change %",
|
|
174
|
+
guideEpsChangePercent: "EPS guide change %",
|
|
120
175
|
trailingPE: "Trailing P/E",
|
|
121
176
|
forwardPE: "Forward P/E",
|
|
122
177
|
enterpriseToRevenue: "EV / revenue",
|
|
@@ -142,6 +197,8 @@ export const screenLabel = (field: string) =>
|
|
|
142
197
|
xPostsVsMedian: "X posts vs median",
|
|
143
198
|
wikiViewsPerDay: "Wikipedia views per day",
|
|
144
199
|
wikiViewsVsMedian: "Wikipedia views vs median",
|
|
200
|
+
attentionResearchUnits: "Research attention hours (Pro)",
|
|
201
|
+
attentionZScore: "Abnormal research attention (Pro)",
|
|
145
202
|
return1WPercent: "1W return %",
|
|
146
203
|
return1MPercent: "1M return %",
|
|
147
204
|
return3MPercent: "3M return %",
|
|
@@ -156,6 +213,14 @@ export const screenLabel = (field: string) =>
|
|
|
156
213
|
analystUpsidePercent: "Analyst upside %",
|
|
157
214
|
ivRank: "IV rank",
|
|
158
215
|
ivToHv: "IV / HV",
|
|
216
|
+
hiringOpenRoles: "Hiring open roles",
|
|
217
|
+
hiringNetAddsWeek: "Net open roles per week",
|
|
218
|
+
hiringRemotePercent: "Remote roles %",
|
|
219
|
+
hiringZScore: "Hiring z-score",
|
|
220
|
+
appAttentionScore: "App attention score / 100",
|
|
221
|
+
appRankVelocity7d: "App rank places per day, 7D",
|
|
222
|
+
appRatingDrift7d: "App rating drift, 7D",
|
|
223
|
+
appRatingCountGrowth7d: "App ratings added, 7D",
|
|
159
224
|
})[field] ??
|
|
160
225
|
field
|
|
161
226
|
.replace(/([A-Z])/g, " $1")
|
|
@@ -300,14 +365,14 @@ export function parseScreenDefinition(value: unknown): ScreenDefinition {
|
|
|
300
365
|
});
|
|
301
366
|
if (
|
|
302
367
|
!item.currency &&
|
|
303
|
-
(
|
|
368
|
+
(MONETARY.has(item.sort.field as NumericField) ||
|
|
304
369
|
item.criteria.some(
|
|
305
370
|
(criterion) =>
|
|
306
|
-
|
|
371
|
+
MONETARY.has(criterion.field as NumericField) &&
|
|
307
372
|
!["present", "missing"].includes(criterion.op),
|
|
308
373
|
))
|
|
309
374
|
)
|
|
310
|
-
throw new Error("Select one currency for
|
|
375
|
+
throw new Error("Select one currency for monetary comparisons.");
|
|
311
376
|
return { version: 1, currency: item.currency, criteria,
|
|
312
377
|
sort: { field: item.sort.field, direction: item.sort.direction } };
|
|
313
378
|
}
|
|
@@ -363,7 +428,7 @@ export function validateScreenPayload(value: unknown): ScreenPayload {
|
|
|
363
428
|
ids.add(screenRowId(row));
|
|
364
429
|
for (const field of NUMERIC_FIELDS) {
|
|
365
430
|
// A server from before social or research fields omits them; they read as unavailable.
|
|
366
|
-
if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS] as readonly string[]).includes(field))
|
|
431
|
+
if (!row.metrics[field] && ([...SOCIAL_FIELDS, ...RESEARCH_FIELDS, ...ATTENTION_FIELDS, ...HIRING_APP_FIELDS, ...KPI_FIELDS] as readonly string[]).includes(field))
|
|
367
432
|
row.metrics[field] = unavailableMetric();
|
|
368
433
|
const metric = row.metrics[field];
|
|
369
434
|
if (
|
|
@@ -634,6 +634,7 @@ function EquityScreenView({
|
|
|
634
634
|
const bodyHeight = Math.max(5, height - 1 - tabRows);
|
|
635
635
|
const saveContent = !access.emailVerified ? (
|
|
636
636
|
<SignInWall
|
|
637
|
+
placement="eqs-save-signin"
|
|
637
638
|
action="save and open your screens"
|
|
638
639
|
needsVerification={session.needsVerification}
|
|
639
640
|
/>
|
|
@@ -787,6 +788,7 @@ function EquityScreenView({
|
|
|
787
788
|
) : mode === "saved" ? (
|
|
788
789
|
!access.emailVerified ? (
|
|
789
790
|
<SignInWall
|
|
791
|
+
placement="eqs-signin"
|
|
790
792
|
action="save and open your screens"
|
|
791
793
|
needsVerification={session.needsVerification}
|
|
792
794
|
/>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useCompanyDisclosureLinks } from "../company-kpis/related";
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
3
|
import { Box, ScrollBox, type ScrollBoxRenderable } from "../../../ui";
|
|
3
4
|
import {
|
|
@@ -242,6 +243,7 @@ export function EstimateRevisionsPane({ width, height, focused }: PaneProps) {
|
|
|
242
243
|
colors = useThemeColors();
|
|
243
244
|
const identity = listingIdentity(boundSymbol, ticker?.metadata.exchange ?? "");
|
|
244
245
|
const symbol = identity?.symbol ?? null;
|
|
246
|
+
useCompanyDisclosureLinks(boundSymbol);
|
|
245
247
|
const exchange = canonicalExchange(identity?.exchange ?? "");
|
|
246
248
|
const loader = useCallback(
|
|
247
249
|
(force: boolean) => loadEstimates(symbol!, exchange, force),
|
|
@@ -386,6 +388,7 @@ export function EstimateRevisionsPane({ width, height, focused }: PaneProps) {
|
|
|
386
388
|
if (signInWall)
|
|
387
389
|
return (
|
|
388
390
|
<SignInWall
|
|
391
|
+
placement="estimate-revisions-signin"
|
|
389
392
|
action="view estimate revisions"
|
|
390
393
|
needsVerification={session.needsVerification}
|
|
391
394
|
/>
|
|
@@ -230,6 +230,7 @@ function figuresOf(statement: CloudProxyStatementPayload) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
const EXEC_WALL: ProFeatureWallCopy = {
|
|
233
|
+
placement: "exec-wall",
|
|
233
234
|
action: "see executive pay",
|
|
234
235
|
title: "Executive pay is part of Gloom Cloud Pro.",
|
|
235
236
|
message: "Named executive officers and how they were paid, read from each proxy statement and checked against the filing.",
|
|
@@ -238,6 +239,7 @@ const EXEC_WALL: ProFeatureWallCopy = {
|
|
|
238
239
|
export function ExecutivesPane({
|
|
239
240
|
focused,
|
|
240
241
|
width,
|
|
242
|
+
height,
|
|
241
243
|
nested = false,
|
|
242
244
|
}: {
|
|
243
245
|
focused: boolean;
|
|
@@ -246,7 +248,7 @@ export function ExecutivesPane({
|
|
|
246
248
|
/** Inside Ticker Research, whose own tab strip keeps h/l and the arrows. */
|
|
247
249
|
nested?: boolean;
|
|
248
250
|
}) {
|
|
249
|
-
const { wall, guard } = useProFeatureWall(EXEC_WALL);
|
|
251
|
+
const { wall, guard } = useProFeatureWall(EXEC_WALL, { width, height });
|
|
250
252
|
const { symbol } = useBoundTicker();
|
|
251
253
|
const ticker = symbol ? symbol.toUpperCase() : null;
|
|
252
254
|
if (wall) return wall;
|
|
@@ -373,21 +375,21 @@ function ExecutiveResearch({ ticker, focused, width, nested, guard }: { ticker:
|
|
|
373
375
|
>
|
|
374
376
|
{figures.length > 0 && (
|
|
375
377
|
<Box flexDirection="column">
|
|
376
|
-
<SectionHeading title="
|
|
378
|
+
<SectionHeading title="Key Figures" />
|
|
377
379
|
<FigureList figures={figures} width={proseWidth} minValueWidth={4} maxValueWidth={14} />
|
|
378
380
|
</Box>
|
|
379
381
|
)}
|
|
380
382
|
{statement.ceo && (
|
|
381
383
|
<Box flexDirection="column">
|
|
382
384
|
<SectionHeading marginTop={figures.length > 0 ? 1 : 0}
|
|
383
|
-
title={`
|
|
385
|
+
title={`How ${statement.ceo.name.trim().split(/\s+/).pop() || "the CEO"} Was Paid`}
|
|
384
386
|
/>
|
|
385
387
|
<PayMixBar row={statement.ceo} width={proseWidth} />
|
|
386
388
|
</Box>
|
|
387
389
|
)}
|
|
388
390
|
{statement.namedExecutives.length > 0 && (
|
|
389
391
|
<Box flexDirection="column">
|
|
390
|
-
<SectionHeading marginTop={1} title="
|
|
392
|
+
<SectionHeading marginTop={1} title="Named Executive Officers" />
|
|
391
393
|
<ExecutiveRows
|
|
392
394
|
rows={statement.namedExecutives}
|
|
393
395
|
width={proseWidth}
|
|
@@ -396,7 +398,7 @@ function ExecutiveResearch({ ticker, focused, width, nested, guard }: { ticker:
|
|
|
396
398
|
)}
|
|
397
399
|
{statement.highlights && (
|
|
398
400
|
<Box flexDirection="column">
|
|
399
|
-
<SectionHeading marginTop={1} title="
|
|
401
|
+
<SectionHeading marginTop={1} title="What Changed" />
|
|
400
402
|
<BulletList items={statement.highlights.split("\n")} width={proseWidth} color={colors.text} />
|
|
401
403
|
</Box>
|
|
402
404
|
)}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ActionRow } from "../../../components";
|
|
2
|
+
import { Box, ChartSurface, Text, useUiCapabilities } from "../../../ui";
|
|
3
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
4
|
+
import type { ChartVectorShape } from "../../../ui/host";
|
|
5
|
+
import type { ExposureComponent } from "../../../api-client/exposure";
|
|
6
|
+
import { basisLabel, rangeText, type ExposureRow } from "./model";
|
|
7
|
+
import { truncateToDisplayWidth } from "../../../utils/format";
|
|
8
|
+
|
|
9
|
+
/** A disclosure-bound interval, not a probability band. Same numerical domain on both renderers. */
|
|
10
|
+
export function ExposureRanges({ rows, selectedId, width, height, onSelect, onOpen, showTargets = false }: {
|
|
11
|
+
rows: ExposureRow[]; selectedId: string | null; width: number; height: number; onSelect: (id: string) => void; onOpen: (id: string) => void; showTargets?: boolean;
|
|
12
|
+
}) {
|
|
13
|
+
const colors = useThemeColors();
|
|
14
|
+
const desktop = !!useUiCapabilities().nativePaneChrome;
|
|
15
|
+
const selected = rows.find(r => r.id === selectedId) ?? rows[0];
|
|
16
|
+
const eligible = rows.filter(r => r.shockId === selected?.shockId && r.basis === selected?.basis && r.period === selected?.period && r.exposure);
|
|
17
|
+
const shown = eligible.slice(0, Math.max(1, height - 1));
|
|
18
|
+
const lower = Math.min(0, ...eligible.map(r => r.exposure!.low));
|
|
19
|
+
// A fifth of headroom past the largest bound, so no interval or point sits on the edge of its track.
|
|
20
|
+
const upper = Math.max(1, ...eligible.map(r => r.exposure!.high)) * 1.2;
|
|
21
|
+
const span = upper - lower;
|
|
22
|
+
const labelWidth = Math.max(10, Math.min(showTargets ? 42 : 22, Math.floor(width * .32)));
|
|
23
|
+
const plotWidth = Math.max(8, width - labelWidth - 24);
|
|
24
|
+
return <Box flexDirection="column" flexGrow={1} minHeight={0} data-gloom-role="exposure-ranges">
|
|
25
|
+
{shown.map(row => {
|
|
26
|
+
const low = (row.exposure!.low - lower) / span, high = (row.exposure!.high - lower) / span;
|
|
27
|
+
const color = row.classification === "disclosed" ? colors.borderFocused : colors.warning;
|
|
28
|
+
// A disclosed point is a tick; an estimated interval is a band between two ticks.
|
|
29
|
+
const vectors: ChartVectorShape[] = [
|
|
30
|
+
{ id: "zero", points: [{ x: (0 - lower) / span, y: .2 }, { x: (0 - lower) / span, y: .8 }], color: colors.border, strokeWidth: 1 },
|
|
31
|
+
{ id: "track", points: [{ x: 0, y: .5 }, { x: 1, y: .5 }], color: colors.border, strokeWidth: 1 },
|
|
32
|
+
...(high - low > 0.004 ? [{ id: "range", points: [{ x: low, y: .5 }, { x: high, y: .5 }], color, strokeWidth: 5 }] : []),
|
|
33
|
+
{ id: "low", points: [{ x: low, y: .15 }, { x: low, y: .85 }], color, strokeWidth: 3 },
|
|
34
|
+
{ id: "high", points: [{ x: high, y: .15 }, { x: high, y: .85 }], color, strokeWidth: 3 },
|
|
35
|
+
];
|
|
36
|
+
const start = Math.floor(low * (plotWidth - 1)), end = Math.floor(high * (plotWidth - 1));
|
|
37
|
+
return <Box key={row.id} flexDirection="row" height={1} minHeight={1} paddingX={1}>
|
|
38
|
+
<Box width={labelWidth}><ActionRow label={truncateToDisplayWidth(showTargets ? `${row.label} · ${row.symbol}` : row.symbol || row.label, labelWidth - 1)} active={selectedId === row.id} onPress={() => { onSelect(row.id); onOpen(row.id); }} /></Box>
|
|
39
|
+
{desktop ? <ChartSurface width={plotWidth} height={1} vectors={vectors} /> : <Text fg={color}>{" ".repeat(start)}{"─".repeat(Math.max(0, end - start))}│{" ".repeat(Math.max(0, plotWidth - end - 1))}</Text>}
|
|
40
|
+
<Box width={20} alignItems="flex-end"><Text fg={color}>{rangeText(row.exposure)}</Text></Box>
|
|
41
|
+
</Box>;
|
|
42
|
+
})}
|
|
43
|
+
</Box>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function ExposurePathDiagram({ component, width, height }: { component: ExposureComponent | null; width: number; height: number }) {
|
|
47
|
+
const colors = useThemeColors();
|
|
48
|
+
const desktop = !!useUiCapabilities().nativePaneChrome;
|
|
49
|
+
if (!component?.path.length) return <Box paddingX={1} flexGrow={1}><Text fg={colors.textDim}>{component ? `${component.label} · ${basisLabel(component.basis)}` : "Select an evidence path."}</Text></Box>;
|
|
50
|
+
const hops = component.path;
|
|
51
|
+
const nodes = [hops[0]!.from, ...hops.map(h => h.to)];
|
|
52
|
+
const nodeWidth = Math.max(8, Math.floor((width - 2) / nodes.length));
|
|
53
|
+
const vectors: ChartVectorShape[] = hops.map((hop, i) => ({ id: `${i}`, points: [{ x: (i + .5) / nodes.length, y: .5 }, { x: (i + 1.5) / nodes.length, y: .5 }], color: hop.pct === null ? colors.textDim : colors.warning, strokeWidth: 2, handles: true }));
|
|
54
|
+
return <Box flexDirection="column" height={height} overflow="hidden">
|
|
55
|
+
<Box paddingX={1} height={1} flexShrink={0}><Text fg={colors.textDim}>{hops.length} hop{hops.length === 1 ? "" : "s"} · {component.classification}{component.exposurePct ? ` · ${rangeText(component.exposurePct)}` : ""}</Text></Box>
|
|
56
|
+
<Box flexDirection="row" height={1} flexShrink={0} paddingX={1}>{nodes.map((node, i) => <Box key={`${node.id}:${i}`} width={nodeWidth} alignItems="center"><Text fg={colors.textBright}>{truncateToDisplayWidth(node.symbol ?? node.name, nodeWidth - 2)}</Text></Box>)}</Box>
|
|
57
|
+
<Box height={1} flexShrink={0} paddingX={1}>
|
|
58
|
+
{desktop ? <ChartSurface width={width - 2} height={1} vectors={vectors} /> : <Text fg={colors.textDim}>{nodes.map((_, i) => i < hops.length ? "─".repeat(Math.max(1, nodeWidth - 2)) + "→ " : "").join("")}</Text>}
|
|
59
|
+
</Box>
|
|
60
|
+
{height >= 4 ? <Box flexDirection="row" height={1} flexShrink={0} paddingX={1}>{nodes.map((_, i) => <Box key={i} width={nodeWidth} alignItems="center"><Text fg={colors.warning}>{i === 0 ? "" : truncateToDisplayWidth(hops[i - 1]!.pct === null ? "Undisclosed share" : `${hops[i - 1]!.pct}% ${basisLabel(hops[i - 1]!.basis)}`, nodeWidth - 1)}</Text></Box>)}</Box> : null}
|
|
61
|
+
{height >= 5 ? <Box flexDirection="row" height={1} flexShrink={0} paddingX={1}>{nodes.map((_, i) => {
|
|
62
|
+
const evidence = i > 0 ? hops[i - 1]!.evidence[0] : null;
|
|
63
|
+
return <Box key={i} width={nodeWidth} alignItems="center"><Text fg={colors.textDim}>{evidence ? truncateToDisplayWidth(`${evidence.tier} · ${evidence.asOf ?? "undated"}`, nodeWidth - 1) : ""}</Text></Box>;
|
|
64
|
+
})}</Box> : null}
|
|
65
|
+
</Box>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { apiClient } from "../../../api-client";
|
|
2
|
+
import type { ExposurePayload, ExposureRequest, ExposureScenario } from "../../../api-client/exposure";
|
|
3
|
+
import { isRecord } from "../../../utils/guards";
|
|
4
|
+
import { validRange, parseCustomScenario } from "./model";
|
|
5
|
+
|
|
6
|
+
const finite = (v: unknown) => typeof v === "number" && Number.isFinite(v);
|
|
7
|
+
const nullableRange = (v: unknown) => v === null || validRange(v);
|
|
8
|
+
const strings = (v: unknown) => Array.isArray(v) && v.every(x => typeof x === "string");
|
|
9
|
+
function evidence(v: unknown): boolean {
|
|
10
|
+
if (!isRecord(v)) return false;
|
|
11
|
+
const safeUrl = v.url === null || (typeof v.url === "string" && /^https?:\/\//.test(v.url));
|
|
12
|
+
return typeof v.id === "string" && safeUrl && (v.quote === null || typeof v.quote === "string")
|
|
13
|
+
&& ["structured", "primary", "secondary", "imported", "profile", "user"].includes(String(v.tier))
|
|
14
|
+
&& (v.confidence === null || (finite(v.confidence) && Number(v.confidence) >= 0 && Number(v.confidence) <= 1));
|
|
15
|
+
}
|
|
16
|
+
/** Reject malformed percentages and untrusted source links before displaying any result. */
|
|
17
|
+
export function validateExposure(value: ExposurePayload): ExposurePayload {
|
|
18
|
+
const data = value as ExposurePayload;
|
|
19
|
+
let valid = !!data && data.version === 1 && ["full", "preview"].includes(data.access)
|
|
20
|
+
&& Number.isInteger(data.depth) && data.depth >= 1 && data.depth <= 4
|
|
21
|
+
&& typeof data.evaluatedAt === "string" && Number.isFinite(Date.parse(data.evaluatedAt))
|
|
22
|
+
&& Number.isInteger(data.requestedHoldings) && Number.isInteger(data.lockedHoldings) && data.lockedHoldings >= 0
|
|
23
|
+
&& Array.isArray(data.holdings) && strings(data.unknowns) && strings(data.methodology);
|
|
24
|
+
try { parseCustomScenario(JSON.stringify(data.scenario)); } catch { valid = false; }
|
|
25
|
+
valid &&= data.holdings.every(h => typeof h.symbol === "string" && finite(h.weight) && Array.isArray(h.unknowns)
|
|
26
|
+
&& ["quantified", "partial", "unknown"].includes(h.status) && !!h.coverage
|
|
27
|
+
&& Array.isArray(h.measures) && h.measures.every(m => validRange(m.exposurePct) && nullableRange(m.impactPct) && strings(m.componentIds))
|
|
28
|
+
&& Array.isArray(h.extensions) && h.extensions.every(row => isRecord(row) && typeof row.id === "string" && typeof row.label === "string"
|
|
29
|
+
&& ["kpi", "guidance", "credit"].includes(row.kind) && (row.value === null || finite(row.value)) && typeof row.units === "string"
|
|
30
|
+
&& (row.range === undefined || isRecord(row.range) && (row.range.low === null || finite(row.range.low)) && (row.range.high === null || finite(row.range.high))
|
|
31
|
+
&& (row.range.low === null || row.range.high === null || row.range.low <= row.range.high))
|
|
32
|
+
&& [row.currency, row.period, row.asOf, row.basis, row.qualifier, row.status].every(v => v == null || typeof v === "string")
|
|
33
|
+
&& (row.valueText === undefined || typeof row.valueText === "string")
|
|
34
|
+
&& (!row.dimensions || isRecord(row.dimensions) && Object.values(row.dimensions).every(v => typeof v === "string"))
|
|
35
|
+
&& (!row.notes || strings(row.notes)) && Array.isArray(row.evidence) && row.evidence.every(evidence))
|
|
36
|
+
&& Array.isArray(h.components) && h.components.every(c => nullableRange(c.exposurePct) && nullableRange(c.impactPct)
|
|
37
|
+
&& (c.proportionalEstimatePct == null || finite(c.proportionalEstimatePct))
|
|
38
|
+
&& ["disclosed", "estimated", "unknown"].includes(c.classification) && Array.isArray(c.evidence) && c.evidence.every(evidence)
|
|
39
|
+
&& Array.isArray(c.path) && c.path.every(hop => !!hop.from && !!hop.to && Array.isArray(hop.evidence) && hop.evidence.every(evidence)) && strings(c.unknowns)));
|
|
40
|
+
valid &&= !!data.portfolio && finite(data.portfolio.netWeight) && finite(data.portfolio.grossWeight) && finite(data.portfolio.residualWeight)
|
|
41
|
+
&& Array.isArray(data.portfolio.measures) && data.portfolio.measures.every(m => validRange(m.signedExposurePct) && validRange(m.grossExposurePct) && nullableRange(m.impactPct))
|
|
42
|
+
&& Array.isArray(data.portfolio.concentrations) && data.portfolio.concentrations.every(c => nullableRange(c.grossExposurePct) && nullableRange(c.signedExposurePct) && Array.isArray(c.evidence) && c.evidence.every(evidence));
|
|
43
|
+
if (!valid) throw new Error("The server returned unreadable exposure data.");
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
export async function fetchExposure(request: ExposureRequest, client: Pick<typeof apiClient, "analyzeCloudExposure"> = apiClient) {
|
|
47
|
+
return validateExposure(await client.analyzeCloudExposure(request));
|
|
48
|
+
}
|
|
49
|
+
export async function fetchScenarios(client: Pick<typeof apiClient, "getCloudExposureScenarios"> = apiClient): Promise<ExposureScenario[]> {
|
|
50
|
+
const data = await client.getCloudExposureScenarios();
|
|
51
|
+
if (!Array.isArray(data.scenarios)) throw new Error("Scenario library is unavailable.");
|
|
52
|
+
return data.scenarios.map(s => parseCustomScenario(JSON.stringify(s)));
|
|
53
|
+
}
|