gloomberb 0.16.1 → 0.16.2
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 +2 -1
- package/package.json +2 -1
- package/src/api-client/beneficial-owners.ts +92 -0
- package/src/api-client/data.ts +26 -8
- package/src/api-client/errors.ts +12 -0
- package/src/api-client/geo.ts +363 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/paths.ts +30 -1
- package/src/api-client/perps.ts +37 -0
- package/src/api-client/request.ts +5 -2
- package/src/api-client/types.ts +2 -0
- package/src/brokers/account-cache.ts +24 -0
- package/src/cli/commands/command-utils.ts +8 -0
- package/src/cli/commands/crypto-hints.ts +75 -0
- package/src/cli/commands/market.ts +414 -106
- package/src/cli/commands/overview.ts +77 -32
- package/src/cli/commands/ticker.ts +101 -43
- package/src/cli/desktop-pane-shot.ts +34 -4
- package/src/cli/freshness.ts +61 -0
- package/src/cli/help.ts +1 -0
- package/src/cli/helpers.ts +4 -8
- package/src/cli/index.ts +12 -2
- package/src/cli/listing-arg.ts +321 -0
- package/src/cli/pane-functions/access-gate.ts +42 -0
- package/src/cli/pane-functions/catalog.ts +28 -1
- package/src/cli/pane-functions/data.ts +3 -1
- package/src/cli/pane-functions/dom.ts +20 -2
- package/src/cli/pane-functions/freshness.ts +400 -0
- package/src/cli/pane-functions/function-help.ts +27 -20
- package/src/cli/pane-functions/headless.ts +39 -11
- package/src/cli/pane-functions/index.ts +15 -4
- package/src/cli/pane-functions/options.ts +3 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +35 -7
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +7 -3
- package/src/cli/result.ts +41 -27
- package/src/cli/ticker-resolution.ts +7 -5
- package/src/components/chart/composite/composite-chart.tsx +88 -2536
- package/src/components/chart/composite/drawing-store.ts +59 -0
- package/src/components/chart/composite/legend.tsx +319 -0
- package/src/components/chart/composite/panel-bitmap.ts +139 -0
- package/src/components/chart/composite/panel-gestures.ts +59 -0
- package/src/components/chart/composite/panel-labels.ts +107 -0
- package/src/components/chart/composite/panel-surface.tsx +876 -0
- package/src/components/chart/composite/tool-catalog.ts +98 -0
- package/src/components/chart/composite/toolbar.tsx +186 -0
- package/src/components/chart/composite/tools.ts +8 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
- package/src/components/chart/composite/use-composite-viewport.ts +143 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/command-bar/workflow/tickers.ts +6 -0
- package/src/components/data-table/view.tsx +29 -10
- package/src/components/feedback-dialog.tsx +2 -1
- package/src/components/form-modal/field-row.tsx +2 -2
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/pane/content.tsx +23 -10
- package/src/components/layout/pane/error-boundary.tsx +103 -0
- package/src/components/layout/pane/footer/registration.tsx +68 -23
- package/src/components/layout/pane/header.tsx +11 -11
- package/src/components/layout/shell/menu.ts +2 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +14 -3
- package/src/components/metric-treemap/heat-surface.tsx +43 -16
- package/src/components/metric-treemap/heat-tiles.ts +18 -0
- package/src/components/metric-treemap/layout.ts +2 -0
- package/src/components/onboarding/onboarding-keyboard.ts +311 -0
- package/src/components/onboarding/onboarding-wizard.tsx +101 -788
- package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
- package/src/components/onboarding/use-onboarding-progress.ts +298 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
- package/src/components/ticker/list-table-view.tsx +13 -1
- package/src/components/ui/fields.tsx +8 -0
- package/src/components/ui/range-track.tsx +10 -3
- package/src/components/ui/status.tsx +8 -2
- package/src/components/ui/tabs.tsx +7 -4
- package/src/components/ui/text-prompt-dialog.tsx +4 -2
- package/src/data/config/store/normalize.ts +17 -1
- package/src/i18n/es.ts +19 -1
- package/src/i18n/ja.ts +19 -1
- package/src/i18n/ko.ts +19 -1
- package/src/i18n/zh-cn.ts +19 -1
- package/src/i18n/zh-tw.ts +19 -1
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/quotes.ts +23 -4
- package/src/market-data/fx-closes.ts +72 -0
- package/src/market-data/market/freshness.ts +78 -6
- package/src/market-data/market/session-close-note.ts +57 -0
- package/src/market-data/market/status.ts +151 -0
- package/src/market-data/published-apac-sessions.ts +66 -0
- package/src/market-data/quotes/contributions.ts +2 -0
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +49 -4
- package/src/plugins/builtin/account-management/pane.tsx +4 -0
- package/src/plugins/builtin/alerts/cli.ts +10 -5
- package/src/plugins/builtin/alerts/index.tsx +1 -0
- package/src/plugins/builtin/analytics/metrics.ts +8 -3
- package/src/plugins/builtin/analytics/risk-client.ts +191 -66
- package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
- package/src/plugins/builtin/analytics/risk-model.ts +45 -17
- package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
- package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
- package/src/plugins/builtin/attention/headless.ts +6 -4
- package/src/plugins/builtin/attention/index.tsx +1 -1
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/awards/headless.ts +1 -0
- package/src/plugins/builtin/awards/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/headless.ts +1 -0
- package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
- package/src/plugins/builtin/buildout/index.tsx +1 -0
- package/src/plugins/builtin/catalysts/headless.ts +3 -1
- package/src/plugins/builtin/cds/headless.ts +2 -0
- package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
- package/src/plugins/builtin/chart-composer/headless.ts +38 -3
- package/src/plugins/builtin/chart-composer/index.tsx +3 -1
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
- package/src/plugins/builtin/chat/channels.ts +19 -3
- package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
- package/src/plugins/builtin/chat/content/footer.tsx +15 -6
- package/src/plugins/builtin/chat/content/index.tsx +120 -21
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
- package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
- package/src/plugins/builtin/chat/direct-messages.ts +200 -0
- package/src/plugins/builtin/chat/index.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
- package/src/plugins/builtin/chat/message/header.tsx +16 -6
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
- package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
- package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
- package/src/plugins/builtin/chat/message/types.ts +14 -2
- package/src/plugins/builtin/chat/profile-popover.ts +18 -13
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/cloud/team/module.tsx +1 -0
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
- package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
- package/src/plugins/builtin/comment-letters/index.tsx +1 -0
- package/src/plugins/builtin/company-attention/headless.ts +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/headless.ts +1 -0
- package/src/plugins/builtin/congress-trades/headless.ts +1 -0
- package/src/plugins/builtin/correlation/compute.ts +59 -16
- package/src/plugins/builtin/correlation/geo.ts +34 -0
- package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
- package/src/plugins/builtin/correlation/headless.ts +42 -8
- package/src/plugins/builtin/correlation/history.ts +2 -0
- package/src/plugins/builtin/correlation/index.tsx +68 -14
- package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
- package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
- package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
- package/src/plugins/builtin/correlation/settings.ts +1 -1
- package/src/plugins/builtin/cot/headless.ts +6 -3
- package/src/plugins/builtin/cot/index.tsx +3 -3
- package/src/plugins/builtin/cot/model.ts +36 -3
- package/src/plugins/builtin/cpi/headless.ts +4 -0
- package/src/plugins/builtin/credit-boards/headless.ts +2 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
- package/src/plugins/builtin/credit-documents/headless.ts +2 -0
- package/src/plugins/builtin/crypto-board/headless.ts +3 -0
- package/src/plugins/builtin/custom-view/loader.ts +1 -1
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
- package/src/plugins/builtin/doe/headless.ts +7 -0
- package/src/plugins/builtin/doe/range-bar.tsx +7 -37
- package/src/plugins/builtin/earnings/headless.ts +5 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
- package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
- package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
- package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
- package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
- package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
- package/src/plugins/builtin/econ/index.tsx +1 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
- package/src/plugins/builtin/equity-screener/headless.ts +2 -0
- package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
- package/src/plugins/builtin/executives/index.tsx +1 -0
- package/src/plugins/builtin/exposure/headless.ts +3 -1
- package/src/plugins/builtin/fear-greed/headless.ts +2 -0
- package/src/plugins/builtin/filing-events/index.tsx +1 -0
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +39 -10
- package/src/plugins/builtin/futures-curve/index.tsx +3 -3
- package/src/plugins/builtin/futures-curve/model.ts +188 -4
- package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
- package/src/plugins/builtin/gpu/headless.ts +7 -3
- package/src/plugins/builtin/gpu/ladder.tsx +26 -30
- package/src/plugins/builtin/gpu/model.ts +27 -1
- package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
- package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
- package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
- package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
- package/src/plugins/builtin/holders/headless.ts +139 -5
- package/src/plugins/builtin/holders/index.tsx +10 -3
- package/src/plugins/builtin/holders/pane.tsx +20 -3
- package/src/plugins/builtin/holders/report-header.ts +95 -0
- package/src/plugins/builtin/holders/table-model.ts +20 -5
- package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
- package/src/plugins/builtin/holders/types.ts +2 -1
- package/src/plugins/builtin/insider/headless.ts +2 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
- package/src/plugins/builtin/iv-history/headless.ts +4 -1
- package/src/plugins/builtin/jobs/headless.ts +1 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
- package/src/plugins/builtin/macro-day/headless.ts +2 -0
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-heatmap/data.ts +2 -0
- package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
- package/src/plugins/builtin/market-heatmap/model.ts +146 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
- package/src/plugins/builtin/market-movers/headless.ts +2 -0
- package/src/plugins/builtin/market-valuation/headless.ts +2 -0
- package/src/plugins/builtin/members/client.ts +20 -3
- package/src/plugins/builtin/members/headless.ts +5 -3
- package/src/plugins/builtin/members/model.ts +10 -2
- package/src/plugins/builtin/members/pane.tsx +13 -6
- package/src/plugins/builtin/mna/distress/headless.ts +1 -0
- package/src/plugins/builtin/mna/headless.ts +1 -0
- package/src/plugins/builtin/money-markets/headless.ts +1 -0
- package/src/plugins/builtin/news/headless.ts +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +8 -1
- package/src/plugins/builtin/notes/cli.ts +14 -6
- package/src/plugins/builtin/notes/index.tsx +1 -0
- package/src/plugins/builtin/openfda/index.tsx +1 -0
- package/src/plugins/builtin/options-calculator/headless.ts +2 -0
- package/src/plugins/builtin/options-positioning/headless.ts +3 -0
- package/src/plugins/builtin/options-scenario/headless.ts +5 -0
- package/src/plugins/builtin/pe-band/client.ts +48 -3
- package/src/plugins/builtin/pe-band/headless.ts +25 -7
- package/src/plugins/builtin/pe-band/model.ts +92 -16
- package/src/plugins/builtin/pe-band/pane.tsx +33 -16
- package/src/plugins/builtin/perps/client.ts +50 -3
- package/src/plugins/builtin/perps/headless.ts +144 -16
- package/src/plugins/builtin/perps/index.tsx +6 -6
- package/src/plugins/builtin/perps/model.ts +189 -12
- package/src/plugins/builtin/perps/pane.tsx +248 -47
- package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
- package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
- package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
- package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
- package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
- package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
- package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
- package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
- package/src/plugins/builtin/power/headless.ts +3 -1
- package/src/plugins/builtin/rate-path/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
- package/src/plugins/builtin/research/analyst-headless.ts +3 -0
- package/src/plugins/builtin/research/events-headless.ts +2 -0
- package/src/plugins/builtin/research/headless.ts +2 -0
- package/src/plugins/builtin/research/index.tsx +1 -0
- package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
- package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/research-search/index.tsx +1 -0
- package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
- package/src/plugins/builtin/risk-factors/headless.ts +2 -0
- package/src/plugins/builtin/scanner/flow-history.ts +2 -13
- package/src/plugins/builtin/scanner/headless.ts +14 -0
- package/src/plugins/builtin/seasonality/headless.ts +2 -0
- package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
- package/src/plugins/builtin/sec/filing-content.ts +42 -17
- package/src/plugins/builtin/sec/forms.ts +12 -0
- package/src/plugins/builtin/sec/headless.ts +2 -0
- package/src/plugins/builtin/sectors/client.ts +1 -0
- package/src/plugins/builtin/sectors/headless.ts +9 -1
- package/src/plugins/builtin/sectors/sector-model.ts +2 -0
- package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/report-freshness.ts +57 -0
- package/src/plugins/builtin/shared/research-cells.tsx +4 -9
- package/src/plugins/builtin/short-interest/headless.ts +2 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -0
- package/src/plugins/builtin/supply-chain/headless.ts +7 -1
- package/src/plugins/builtin/supply-chain/index.tsx +10 -1
- package/src/plugins/builtin/supply-chain/model.ts +27 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
- package/src/plugins/builtin/themes/headless.ts +3 -0
- package/src/plugins/builtin/thirteenf/headless.ts +110 -25
- package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
- package/src/plugins/builtin/time-sales/headless.ts +5 -1
- package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
- package/src/plugins/builtin/volatility/headless.ts +6 -1
- package/src/plugins/builtin/world-indices/headless.ts +2 -0
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
- package/src/plugins/builtin/world-venue-map/client.ts +45 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
- package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
- package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
- package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
- package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
- package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
- package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +67 -4
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
- package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
- package/src/plugins/builtin/world-venue-map/model.ts +7 -4
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
- package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
- package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
- package/src/plugins/builtin/yield-curve/headless.ts +16 -0
- package/src/plugins/loader.ts +13 -1
- package/src/plugins/registry/pane-settings.ts +1 -0
- package/src/renderers/dom/build-assets.ts +81 -3
- package/src/renderers/dom/data-table/row.tsx +2 -0
- package/src/renderers/dom/desktop/controls.tsx +7 -3
- package/src/renderers/dom/desktop/list-view.tsx +35 -10
- package/src/renderers/dom/desktop/popover.tsx +73 -14
- package/src/renderers/dom/host/input.tsx +14 -3
- package/src/renderers/dom/styles.css +6 -7
- package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
- package/src/renderers/opentui/host.tsx +5 -0
- package/src/sources/gloomberb-cloud/index.ts +25 -4
- package/src/sources/gloomberb-cloud/normalizers.ts +27 -3
- package/src/sources/provider-router/cached-routes.ts +4 -1
- package/src/sources/provider-router/financials.ts +8 -7
- package/src/sources/provider-router/history.ts +15 -10
- package/src/sources/sec-registrant.ts +75 -0
- package/src/state/app/context/index.tsx +24 -8
- package/src/state/hooks/quote-streaming.ts +41 -26
- package/src/sync/core-contributors.ts +4 -0
- package/src/telemetry/crash-reports.ts +4 -1
- package/src/test-support/cli-context.ts +10 -2
- package/src/theme/heat-colors.ts +266 -84
- package/src/time-series/resolve.ts +1 -1
- package/src/time-series/use-chart-resolution.ts +1 -1
- package/src/types/context-menu.ts +6 -0
- package/src/types/data-provider.ts +2 -0
- package/src/types/financials.ts +3 -0
- package/src/types/headless.ts +79 -2
- package/src/types/plugin.ts +20 -1
- package/src/types/ticker.ts +15 -0
- package/src/ui/host.tsx +14 -0
- package/src/updater/index.ts +9 -11
- package/src/utils/async-deadline.ts +21 -0
- package/src/utils/cli-output.ts +5 -1
- package/src/utils/crypto-pair.ts +41 -0
- package/src/utils/currency-units.ts +14 -0
- package/src/utils/depositary-receipt.ts +26 -0
- package/src/utils/exchanges.ts +72 -0
- package/src/utils/fire-and-forget.ts +24 -0
- package/src/utils/format.ts +13 -0
- package/src/utils/price-earnings.ts +70 -3
- package/src/utils/price-history.ts +58 -13
- package/src/utils/rpc-timeout-error.ts +27 -0
- package/src/utils/sec.ts +13 -1
- package/src/utils/utc-time.ts +79 -0
|
@@ -1,60 +1,133 @@
|
|
|
1
1
|
import type { EarningsCalendarPayload, EarningsCalendarQuery } from "../../../api-client/earnings";
|
|
2
2
|
import type { SupplyChainPayload } from "../../../api-client/supply-chain";
|
|
3
|
+
import { DEFAULT_GRAPH_OPTIONS, type GraphOptions, type GraphPayload } from "../../../api-client/supply-chain-graph";
|
|
3
4
|
import { errorMessage } from "../../../utils/errors";
|
|
4
5
|
import { addDays, newYorkToday } from "../earnings/board-model";
|
|
5
6
|
import { loadEarningsBoard } from "../earnings/client";
|
|
6
7
|
import { loadSupplyChain } from "../supply-chain/client";
|
|
7
|
-
import {
|
|
8
|
+
import { cachedGraph, loadGraph } from "../supply-chain/graph-client";
|
|
9
|
+
import { projectRipple, projectSecondHop, rippleCompanyTickers, RIPPLE_DAYS, secondHopLinks, type RippleRow, type SecondHopRow } from "./model";
|
|
8
10
|
|
|
9
11
|
export interface RippleSources {
|
|
10
12
|
supplyChain(symbol: string): Promise<SupplyChainPayload>;
|
|
11
13
|
calendar(query: EarningsCalendarQuery): Promise<EarningsCalendarPayload>;
|
|
14
|
+
/** SPLC's graph, which returns both hops around a holding in one call. Absent when the account sees one hop only. */
|
|
15
|
+
graph?(symbol: string, options: GraphOptions): Promise<GraphPayload>;
|
|
12
16
|
}
|
|
13
17
|
|
|
18
|
+
/**
|
|
19
|
+
* The graph query behind the 2 hops tab. Supplier and customer links only, and
|
|
20
|
+
* only evidence from filings, company announcements and calls (graph classes
|
|
21
|
+
* structured and primary), the tiers the 1 hop tab's disclosures are read with.
|
|
22
|
+
* The server filters before it ranks and caps each direction, so news and
|
|
23
|
+
* partner routes cannot use up the 50 companies a direction returns.
|
|
24
|
+
*/
|
|
25
|
+
const RIPPLE_GRAPH_OPTIONS: GraphOptions = { ...DEFAULT_GRAPH_OPTIONS, depth: 2, roles: ["customer", "supplier"], tiers: ["structured", "primary"] };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What opens SPLC's Path view on a 2 hops row: from the holding to the company
|
|
29
|
+
* that reports, on RIPL's own query in the row's direction, so the route the
|
|
30
|
+
* row shows is the one Path lists.
|
|
31
|
+
*/
|
|
32
|
+
export function rippleRouteSettings(row: Pick<SecondHopRow, "company" | "link">): Record<string, string> {
|
|
33
|
+
return { tab: "path", to: row.company, depth: String(RIPPLE_GRAPH_OPTIONS.depth), direction: row.link === "supplier" ? "upstream" : "downstream",
|
|
34
|
+
roles: RIPPLE_GRAPH_OPTIONS.roles.join(","), tiers: RIPPLE_GRAPH_OPTIONS.tiers.join(",") };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Failure { symbol: string; error: string }
|
|
38
|
+
|
|
14
39
|
export interface RippleSnapshot {
|
|
15
40
|
rows: RippleRow[];
|
|
16
41
|
/** Holdings whose disclosures could not be read, with the reason. */
|
|
17
|
-
failures:
|
|
42
|
+
failures: Failure[];
|
|
18
43
|
/** Holdings whose disclosures the free preview cut short, so a missing link may only be hidden. */
|
|
19
44
|
truncated: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Companies two hops away, when asked for. `locked`: the account sees one hop
|
|
47
|
+
* only. `truncated`: holdings whose graph search stopped at a limit, with the
|
|
48
|
+
* server's reasons, so a company missing there may only be cut off.
|
|
49
|
+
*/
|
|
50
|
+
secondHop: { rows: SecondHopRow[]; failures: Failure[]; truncated: { symbol: string; reasons: string[] }[]; locked: boolean } | null;
|
|
20
51
|
stale: boolean;
|
|
21
52
|
from: string;
|
|
22
53
|
to: string;
|
|
23
54
|
}
|
|
24
55
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
56
|
+
/**
|
|
57
|
+
* The same caches SPLC and ERN fill, so opening either pane after this costs
|
|
58
|
+
* nothing. `graph`: no graph source, graphs read through the cache, or graphs
|
|
59
|
+
* from the cache alone (fetched only when missing) while 2 hops is not on screen.
|
|
60
|
+
*/
|
|
61
|
+
export function cachedRippleSources(accessKey: string, force = false, graph: "off" | "load" | "cached" = "off"): RippleSources & { staleFlags: boolean[] } {
|
|
27
62
|
const staleFlags: boolean[] = [];
|
|
28
63
|
return {
|
|
29
64
|
staleFlags,
|
|
30
65
|
supplyChain: async (symbol) => { const result = await loadSupplyChain(symbol, accessKey, force); staleFlags.push(result.stale); return result.payload; },
|
|
31
66
|
calendar: async (query) => { const result = await loadEarningsBoard(query, force); staleFlags.push(result.stale); return result.payload; },
|
|
67
|
+
// Cached by the full query, apart from SPLC Graph's default one.
|
|
68
|
+
...(graph !== "off" ? { graph: async (symbol: string, options: GraphOptions) => {
|
|
69
|
+
const cached = graph === "cached" ? cachedGraph(symbol, "", options, accessKey) : null;
|
|
70
|
+
const result = cached ?? await loadGraph(symbol, "", options, accessKey, force); staleFlags.push(result.stale); return result.payload;
|
|
71
|
+
} } : {}),
|
|
32
72
|
};
|
|
33
73
|
}
|
|
34
74
|
|
|
35
75
|
const CONCURRENCY = 6;
|
|
36
76
|
|
|
37
|
-
|
|
77
|
+
async function eachLimited(symbols: readonly string[], load: (symbol: string) => Promise<void>) {
|
|
78
|
+
const queue = [...symbols];
|
|
79
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
|
|
80
|
+
for (let symbol = queue.shift(); symbol; symbol = queue.shift()) await load(symbol);
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const locked = () => ({ graphs: new Map<string, GraphPayload>(), failures: [] as Failure[], locked: true });
|
|
85
|
+
const isPreview = (graphs: ReadonlyMap<string, GraphPayload>) => [...graphs.values()].some((graph) => graph.access === "preview");
|
|
86
|
+
|
|
87
|
+
/** One graph call per holding. The first answer says whether the account sees two hops; a one-hop preview skips the rest. */
|
|
88
|
+
async function loadGraphs(holdings: readonly string[], graph: NonNullable<RippleSources["graph"]>) {
|
|
89
|
+
const graphs = new Map<string, GraphPayload>();
|
|
90
|
+
const failures: Failure[] = [];
|
|
91
|
+
const load = async (symbol: string) => {
|
|
92
|
+
try { graphs.set(symbol, await graph(symbol, RIPPLE_GRAPH_OPTIONS)); }
|
|
93
|
+
catch (error) { failures.push({ symbol, error: errorMessage(error) }); }
|
|
94
|
+
};
|
|
95
|
+
const [first, ...rest] = holdings;
|
|
96
|
+
if (first) await load(first);
|
|
97
|
+
if (isPreview(graphs)) return locked();
|
|
98
|
+
await eachLimited(rest, load);
|
|
99
|
+
// Only when the first holding failed can a preview get this far.
|
|
100
|
+
if (isPreview(graphs)) return locked();
|
|
101
|
+
return { graphs, failures: failures.sort((a, b) => a.symbol.localeCompare(b.symbol)), locked: false };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function loadRipple(holdings: readonly string[], sources: RippleSources & { staleFlags?: boolean[] }, now = new Date(), { secondHop = false } = {}): Promise<RippleSnapshot> {
|
|
38
105
|
const from = newYorkToday(now);
|
|
39
106
|
const to = addDays(from, RIPPLE_DAYS);
|
|
107
|
+
const symbols = [...new Set(holdings.map((symbol) => symbol.toUpperCase()))];
|
|
40
108
|
const chains = new Map<string, SupplyChainPayload>();
|
|
41
|
-
const failures:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const companies = rippleCompanyTickers(chains);
|
|
109
|
+
const failures: Failure[] = [];
|
|
110
|
+
await eachLimited(symbols, async (symbol) => {
|
|
111
|
+
try { chains.set(symbol, await sources.supplyChain(symbol)); }
|
|
112
|
+
catch (error) { failures.push({ symbol, error: errorMessage(error) }); }
|
|
113
|
+
});
|
|
114
|
+
const second = !secondHop ? null : sources.graph ? await loadGraphs(symbols, sources.graph) : locked();
|
|
115
|
+
const graphs = second?.graphs ?? new Map<string, GraphPayload>();
|
|
116
|
+
const companies = [...rippleCompanyTickers(chains), ...secondHopLinks(chains, graphs).map((link) => link.company.ticker!.toUpperCase())];
|
|
50
117
|
const reports: EarningsCalendarPayload["reports"] = [];
|
|
51
118
|
if (companies.length) {
|
|
52
|
-
const
|
|
53
|
-
for (let offset = 0; offset <
|
|
54
|
-
const payload = await sources.calendar({ from, to, perDay: 0, symbols:
|
|
119
|
+
const calendarSymbols = [...new Set([...companies, ...symbols])].sort();
|
|
120
|
+
for (let offset = 0; offset < calendarSymbols.length; offset += 200) {
|
|
121
|
+
const payload = await sources.calendar({ from, to, perDay: 0, symbols: calendarSymbols.slice(offset, offset + 200) });
|
|
55
122
|
reports.push(...payload.reports);
|
|
56
123
|
}
|
|
57
124
|
}
|
|
58
125
|
const truncated = [...chains].filter(([, chain]) => chain.truncated).map(([symbol]) => symbol).sort();
|
|
59
|
-
return {
|
|
126
|
+
return {
|
|
127
|
+
rows: projectRipple(chains, reports), failures, truncated,
|
|
128
|
+
secondHop: second ? { rows: projectSecondHop(chains, graphs, reports), failures: second.failures, locked: second.locked,
|
|
129
|
+
truncated: [...graphs].filter(([, graph]) => graph.truncated || !graph.search.complete)
|
|
130
|
+
.map(([symbol, graph]) => ({ symbol, reasons: graph.search.reasons })).sort((a, b) => a.symbol.localeCompare(b.symbol)) } : null,
|
|
131
|
+
stale: !!sources.staleFlags?.some(Boolean), from, to,
|
|
132
|
+
};
|
|
60
133
|
}
|
|
@@ -1,35 +1,55 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
2
|
import { fetchEarningsCalendar } from "../earnings/client";
|
|
3
3
|
import { fetchSupplyChain } from "../supply-chain/client";
|
|
4
|
+
import { fetchSupplyGraph } from "../supply-chain/graph-client";
|
|
4
5
|
import { loadRipple } from "./client";
|
|
5
|
-
import { revenueOwner } from "./model";
|
|
6
|
+
import { hopLabel, revenueOwner } from "./model";
|
|
6
7
|
|
|
7
8
|
const percent = (value: unknown) => typeof value === "number" ? `${(value * 100).toFixed(1)}%` : "--";
|
|
8
9
|
|
|
9
10
|
export const earningsRippleHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
10
11
|
shape: "bundle",
|
|
12
|
+
freshness: { source: "SEC filings and Gloom Cloud", status: "not-a-feed", basis: "filings and earnings calendar" },
|
|
11
13
|
argument: { kind: "symbol-list", placeholder: "tickers", description: "Holdings to check against their customers' and suppliers' report dates.", minimum: 1, maximum: 60 },
|
|
12
|
-
options: [],
|
|
14
|
+
options: [{ key: "tab", type: "enum", settingKey: "tab", defaultValue: "direct", values: [{ value: "direct" }, { value: "two-hop" }],
|
|
15
|
+
description: "Direct customers and suppliers, or also companies two hops away (Pro)." }],
|
|
13
16
|
describe: (args) => `RIPL ${args.symbols.join(", ")}`,
|
|
14
|
-
discovery: { screenshotReadiness: "live-dom", limitations: ["Customers each holding names in its own US filings, and suppliers whose US filings name it, with a share of revenue."
|
|
17
|
+
discovery: { screenshotReadiness: "live-dom", limitations: ["Customers each holding names in its own US filings, and suppliers whose US filings name it, with a share of revenue.",
|
|
18
|
+
"Two hops: a supplier's supplier or a customer's customer from the supply chain graph, read from filings, company announcements and calls only, Pro only; companies already one hop away are left out."] },
|
|
15
19
|
async load(args, ctx) {
|
|
20
|
+
const secondHop = args.options.tab === "two-hop";
|
|
16
21
|
const snapshot = await loadRipple(args.symbols, {
|
|
17
22
|
supplyChain: (symbol) => fetchSupplyChain(symbol, ctx.apiClient),
|
|
18
23
|
calendar: (query) => fetchEarningsCalendar(query, ctx.apiClient),
|
|
19
|
-
|
|
24
|
+
graph: (symbol, options) => fetchSupplyGraph(symbol, options, "", ctx.apiClient),
|
|
25
|
+
}, undefined, { secondHop });
|
|
20
26
|
ctx.signal.throwIfAborted();
|
|
27
|
+
const second = snapshot.secondHop;
|
|
21
28
|
return {
|
|
22
29
|
sections: [{ title: `Customers and suppliers reporting ${snapshot.from} to ${snapshot.to}`, columns: [
|
|
23
30
|
{ key: "date", header: "Date" }, { key: "timing", header: "Time" }, { key: "company", header: "Reports" }, { key: "link", header: "Link" },
|
|
24
31
|
{ key: "holding", header: "Holding" }, { key: "pctOfRevenue", header: "Revenue %" }, { key: "revenueOf", header: "Of" }, { key: "averageMove", header: "Avg move", format: percent },
|
|
25
32
|
{ key: "holdingDate", header: "Holding reports" }, { key: "period", header: "Filing period" },
|
|
26
|
-
], rows: snapshot.rows.map((row) => ({ ...row, timing: row.timing?.toUpperCase() ?? null, link: row.link === "customer" ? "customer of" : "supplier to", revenueOf: revenueOwner(row) })) }
|
|
27
|
-
|
|
33
|
+
], rows: snapshot.rows.map((row) => ({ ...row, timing: row.timing?.toUpperCase() ?? null, link: row.link === "customer" ? "customer of" : "supplier to", revenueOf: revenueOwner(row) })) },
|
|
34
|
+
...(second && !second.locked ? [{ title: `Two hops away, reporting ${snapshot.from} to ${snapshot.to}`, columns: [
|
|
35
|
+
{ key: "date", header: "Date" }, { key: "timing", header: "Time" }, { key: "company", header: "Reports" }, { key: "link", header: "Link" },
|
|
36
|
+
{ key: "via", header: "Via" }, { key: "holding", header: "Holding" }, { key: "hop1", header: "Holding to via" }, { key: "hop2", header: "Via to company" },
|
|
37
|
+
{ key: "exposure", header: "Estimated exposure" }, { key: "averageMove", header: "Avg move", format: percent }, { key: "holdingDate", header: "Holding reports" },
|
|
38
|
+
], rows: second.rows.map((row) => ({ ...row, timing: row.timing?.toUpperCase() ?? null, link: row.link === "customer" ? "customer's customer" : "supplier's supplier",
|
|
39
|
+
hop1: hopLabel(row.hops[0]), hop2: hopLabel(row.hops[1]) })) }] : []),
|
|
40
|
+
],
|
|
41
|
+
complete: snapshot.failures.length === 0 && snapshot.truncated.length === 0 && !second?.locked && !second?.failures.length && !second?.truncated.length,
|
|
28
42
|
unavailableSymbols: snapshot.failures.map((failure) => failure.symbol),
|
|
29
43
|
// Same wording as SPLC: the preview's cut hides relationships, it does not mean there are none.
|
|
30
44
|
errors: [...snapshot.failures.map((failure) => `${failure.symbol}: ${failure.error}`),
|
|
31
|
-
...(snapshot.truncated.length ? ["Additional relationships need Gloom Pro"] : [])
|
|
32
|
-
|
|
45
|
+
...(snapshot.truncated.length ? ["Additional relationships need Gloom Pro"] : []),
|
|
46
|
+
...(second?.locked ? ["Companies two hops away need Gloom Pro"] : []),
|
|
47
|
+
...(second?.failures ?? []).map((failure) => `${failure.symbol} graph: ${failure.error}`),
|
|
48
|
+
// As SPLC words it: the graph stopped at a limit, so companies two hops away may be missing.
|
|
49
|
+
...(second?.truncated ?? []).map(({ symbol, reasons }) => `${symbol}: ${reasons.length
|
|
50
|
+
? `Graph search incomplete: ${reasons.join(", ").replaceAll("_", " ")}` : "Graph results are truncated"}`)],
|
|
51
|
+
metadata: { from: snapshot.from, to: snapshot.to, truncated: snapshot.truncated, ...(second ? { twoHopTruncated: second.truncated.map(({ symbol }) => symbol) } : {}),
|
|
52
|
+
unit: "percent of the seller's revenue, as the seller's filing discloses it", methodology: "docs/research-data.md#earnings-ripple" },
|
|
33
53
|
};
|
|
34
54
|
},
|
|
35
55
|
};
|
|
@@ -18,8 +18,8 @@ export const earningsRippleModule: PluginModule = {
|
|
|
18
18
|
}],
|
|
19
19
|
paneTemplates: [{
|
|
20
20
|
id: "earnings-ripple-pane", paneId: "earnings-ripple", label: "Earnings Ripple",
|
|
21
|
-
description: "Customers of your holdings that report soon, with the share
|
|
22
|
-
keywords: ["ripl", "ripple", "earnings", "customers", "supply chain", "exposure", "read-through"],
|
|
21
|
+
description: "Customers and suppliers of your holdings that report soon, with the disclosed revenue share, and with Pro the companies two hops away.",
|
|
22
|
+
keywords: ["ripl", "ripple", "earnings", "customers", "supply chain", "exposure", "read-through", "suppliers", "two hop", "second order"],
|
|
23
23
|
shortcut: { prefix: "RIPL", argPlaceholder: "tickers", argKind: "ticker-list", argOptional: true, openWithoutArg: true },
|
|
24
24
|
headless: earningsRippleHeadless,
|
|
25
25
|
canCreate: () => true,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { EarningsCalendarReport, EarningsTiming } from "../../../api-client/earnings";
|
|
2
|
-
import type { SupplyChainPayload, SupplyRow } from "../../../api-client/supply-chain";
|
|
2
|
+
import type { SupplyChainPayload, SupplyEntity, SupplyRow } from "../../../api-client/supply-chain";
|
|
3
|
+
import type { GraphPath, GraphPayload } from "../../../api-client/supply-chain-graph";
|
|
3
4
|
import { isUsListingExchange } from "../../../utils/exchanges";
|
|
4
5
|
import { MIN_AVERAGE_REPORTS } from "../earnings/board-model";
|
|
6
|
+
import { exposureLabel } from "../supply-chain/graph-model";
|
|
5
7
|
|
|
6
8
|
/** Days ahead the calendar is read. */
|
|
7
9
|
export const RIPPLE_DAYS = 30;
|
|
@@ -71,18 +73,24 @@ export function rippleCompanyTickers(chains: ReadonlyMap<string, SupplyChainPayl
|
|
|
71
73
|
return [...companies];
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
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[] {
|
|
76
|
+
/** Each company's soonest report in the window. */
|
|
77
|
+
function nextReports(reports: readonly EarningsCalendarReport[]) {
|
|
80
78
|
const next = new Map<string, EarningsCalendarReport>();
|
|
81
79
|
for (const report of reports) {
|
|
82
80
|
const key = report.symbol.toUpperCase();
|
|
83
81
|
const current = next.get(key);
|
|
84
82
|
if (!current || report.date < current.date) next.set(key, report);
|
|
85
83
|
}
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Customers a holding names in its own filings (`says`), and suppliers whose
|
|
89
|
+
* filings name the holding as a customer (`names`), that report in the
|
|
90
|
+
* calendar: soonest first, then by the share.
|
|
91
|
+
*/
|
|
92
|
+
export function projectRipple(chains: ReadonlyMap<string, SupplyChainPayload>, reports: readonly EarningsCalendarReport[]): RippleRow[] {
|
|
93
|
+
const next = nextReports(reports);
|
|
86
94
|
const rows: RippleRow[] = [];
|
|
87
95
|
for (const [holding, chain] of chains) {
|
|
88
96
|
for (const { row, link } of rippleLinks(chain)) {
|
|
@@ -96,3 +104,85 @@ export function projectRipple(chains: ReadonlyMap<string, SupplyChainPayload>, r
|
|
|
96
104
|
}
|
|
97
105
|
return rows.sort((a, b) => a.date.localeCompare(b.date) || b.pctOfRevenue - a.pctOfRevenue || a.holding.localeCompare(b.holding));
|
|
98
106
|
}
|
|
107
|
+
|
|
108
|
+
/** One hop of a two-hop route, with the share the reporting company's filing discloses. */
|
|
109
|
+
export interface RippleHop {
|
|
110
|
+
/** Whose figure it is: the reporting company. */
|
|
111
|
+
owner: string;
|
|
112
|
+
pct: number | null;
|
|
113
|
+
basis: SupplyRow["pctBasis"];
|
|
114
|
+
scoped: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SecondHopRow extends Omit<RippleRow, "pctOfRevenue" | "pctScope" | "period"> {
|
|
118
|
+
/** The company between the holding and the one that reports. */
|
|
119
|
+
via: string;
|
|
120
|
+
viaName: string;
|
|
121
|
+
/** Holding to via, then via to the company. */
|
|
122
|
+
hops: [RippleHop, RippleHop];
|
|
123
|
+
/** Chained estimate along the route, when every hop's figure is compatible. */
|
|
124
|
+
exposure: string | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const entityLabel = (entity: SupplyEntity) => entity.ticker?.toUpperCase() ?? entity.name;
|
|
128
|
+
|
|
129
|
+
interface SecondHopLink { holding: string; company: SupplyEntity; via: SupplyEntity; link: "customer" | "supplier"; path: GraphPath; graph: GraphPayload }
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Companies two disclosed hops from each holding: a supplier's supplier
|
|
133
|
+
* (upstream) or a customer's customer (downstream). Anyone the holding already
|
|
134
|
+
* has a direct link with, in its disclosures or in the graph, is hop 1 and is
|
|
135
|
+
* left out, as are companies outside the US calendar. One row per holding and
|
|
136
|
+
* company: the higher-scoring route when both directions reach it.
|
|
137
|
+
*/
|
|
138
|
+
export function secondHopLinks(chains: ReadonlyMap<string, SupplyChainPayload>, graphs: ReadonlyMap<string, GraphPayload>): SecondHopLink[] {
|
|
139
|
+
const result: SecondHopLink[] = [];
|
|
140
|
+
for (const [holding, graph] of graphs) {
|
|
141
|
+
const chain = chains.get(holding);
|
|
142
|
+
const nodes = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
143
|
+
const reaches = [...graph.upstream, ...graph.downstream];
|
|
144
|
+
const direct = new Set([holding.toUpperCase(),
|
|
145
|
+
...[...chain?.says ?? [], ...chain?.names ?? []].flatMap((row) => row.counterparty.ticker ? [row.counterparty.ticker.toUpperCase()] : []),
|
|
146
|
+
...reaches.filter((reach) => reach.hops === 1).flatMap((reach) => nodes.get(reach.entityId)?.ticker?.toUpperCase() ?? [])]);
|
|
147
|
+
const best = new Map<string, SecondHopLink>();
|
|
148
|
+
for (const reach of reaches) {
|
|
149
|
+
const path = reach.shortestPath;
|
|
150
|
+
const company = nodes.get(reach.entityId);
|
|
151
|
+
const via = nodes.get(path.nodeIds[1] ?? "");
|
|
152
|
+
const ticker = company?.ticker?.toUpperCase();
|
|
153
|
+
if (reach.hops !== 2 || path.hops !== 2 || !company || !via || !ticker || company.aggregate || direct.has(ticker)) continue;
|
|
154
|
+
if (company.exchange && !isUsListingExchange(company.exchange)) continue;
|
|
155
|
+
const current = best.get(ticker);
|
|
156
|
+
if (!current || path.score > current.path.score) best.set(ticker, { holding, company, via, link: reach.direction === "upstream" ? "supplier" : "customer", path, graph });
|
|
157
|
+
}
|
|
158
|
+
result.push(...best.values());
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function hopShare(graph: GraphPayload, linkId: string): RippleHop {
|
|
164
|
+
const link = graph.links.find((entry) => entry.id === linkId);
|
|
165
|
+
const evidence = link?.evidence.find((item) => item.id === link.primaryEvidenceId);
|
|
166
|
+
return { owner: evidence ? entityLabel(evidence.reporter) : "--", pct: evidence?.pctOfRevenue ?? null, basis: evidence?.pctBasis ?? null, scoped: !!evidence?.pctScope };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** `30% of TSM`, `12% of AVGO purchases`, or `--` where the filing names the link without a figure. */
|
|
170
|
+
export function hopLabel(hop: RippleHop) {
|
|
171
|
+
if (hop.pct == null) return "--";
|
|
172
|
+
return `${hop.pct}%${hop.scoped ? "*" : ""} of ${hop.owner}${hop.basis && hop.basis !== "revenue" ? ` ${hop.basis}` : ""}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function projectSecondHop(chains: ReadonlyMap<string, SupplyChainPayload>, graphs: ReadonlyMap<string, GraphPayload>, reports: readonly EarningsCalendarReport[]): SecondHopRow[] {
|
|
176
|
+
const next = nextReports(reports);
|
|
177
|
+
const rows: SecondHopRow[] = [];
|
|
178
|
+
for (const { holding, company, via, link, path, graph } of secondHopLinks(chains, graphs)) {
|
|
179
|
+
const ticker = company.ticker!.toUpperCase();
|
|
180
|
+
const report = next.get(ticker);
|
|
181
|
+
if (!report) continue;
|
|
182
|
+
rows.push({ id: `${holding}:${link}:2:${ticker}`, company: ticker, companyName: report.name ?? company.name, link, date: report.date, timing: report.timing,
|
|
183
|
+
averageMove: report.averageReports >= MIN_AVERAGE_REPORTS ? report.averageMove : null, holding, holdingDate: next.get(holding.toUpperCase())?.date ?? null,
|
|
184
|
+
via: entityLabel(via), viaName: via.name, hops: [hopShare(graph, path.linkIds[0]!), hopShare(graph, path.linkIds[1]!)],
|
|
185
|
+
exposure: path.exposure ? exposureLabel(path, graph) : null });
|
|
186
|
+
}
|
|
187
|
+
return rows.sort((a, b) => a.date.localeCompare(b.date) || a.holding.localeCompare(b.holding) || a.company.localeCompare(b.company));
|
|
188
|
+
}
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import { useCallback, useMemo } from "react";
|
|
2
|
-
import { DataTableView, EmptyState, PaneStatusBody, usePaneFooter, usePaneNoticeFooter,
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { DataTableView, EmptyState, PaneStatusBody, usePaneFooter, usePaneNoticeFooter, usePaneTabs,
|
|
3
3
|
type DataTableCell, type DataTableColumn, type PaneHint } from "../../../components";
|
|
4
4
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
5
|
+
import { getTableWidth } from "../../../components/ui/table-layout";
|
|
5
6
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
6
7
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState, useTickers } from "../../../public/react";
|
|
7
8
|
import type { PaneProps } from "../../../types/plugin";
|
|
8
9
|
import { Box } from "../../../ui";
|
|
9
10
|
import { isUsListingExchange } from "../../../utils/exchanges";
|
|
11
|
+
import { displayWidth } from "../../../utils/format";
|
|
10
12
|
import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
|
|
13
|
+
import { UpgradeLabel } from "../shared/locked-rows";
|
|
11
14
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
12
|
-
import { cachedRippleSources, loadRipple } from "./client";
|
|
13
|
-
import { revenueOwner, RIPPLE_DAYS, type RippleRow } from "./model";
|
|
15
|
+
import { cachedRippleSources, loadRipple, rippleRouteSettings } from "./client";
|
|
16
|
+
import { hopLabel, revenueOwner, RIPPLE_DAYS, type RippleRow, type SecondHopRow } from "./model";
|
|
14
17
|
|
|
15
|
-
/** Calls per refresh stay bounded: one disclosure request per holding. */
|
|
18
|
+
/** Calls per refresh stay bounded: one disclosure request per holding, plus one graph request per holding on the second hop. */
|
|
16
19
|
const RIPPLE_HOLDINGS_LIMIT = 60;
|
|
17
20
|
|
|
21
|
+
const TABS = [{ value: "direct", label: "1 hop" }, { value: "two-hop", label: "2 hops" }];
|
|
22
|
+
|
|
18
23
|
const COLUMNS: DataTableColumn[] = [
|
|
19
24
|
{ id: "date", label: "Date", width: 11, align: "left" },
|
|
20
25
|
{ id: "timing", label: "Time", width: 5, align: "left" },
|
|
@@ -26,6 +31,67 @@ const COLUMNS: DataTableColumn[] = [
|
|
|
26
31
|
{ id: "holdingDate", label: "Holding reports", width: 16, align: "right" },
|
|
27
32
|
];
|
|
28
33
|
|
|
34
|
+
const SECOND_HOP_COLUMNS: DataTableColumn[] = [
|
|
35
|
+
{ id: "date", label: "Date", width: 10, align: "left" },
|
|
36
|
+
{ id: "timing", label: "Time", width: 5, align: "left" },
|
|
37
|
+
{ id: "company", label: "Reports", width: 14, flexGrow: 1, align: "left" },
|
|
38
|
+
{ id: "link", label: "Link", width: 19, align: "left" },
|
|
39
|
+
{ id: "via", label: "Via", width: 9, align: "left" },
|
|
40
|
+
{ id: "holding", label: "Holding", width: 9, align: "left" },
|
|
41
|
+
{ id: "shares", label: "Share per hop", width: 28, align: "left" },
|
|
42
|
+
{ id: "move", label: "Avg move", width: 9, align: "right" },
|
|
43
|
+
{ id: "holdingDate", label: "Holding reports", width: 16, align: "right" },
|
|
44
|
+
];
|
|
45
|
+
const HOP_SEPARATOR = " › ";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Holding to via, then via to the company that reports. The first hop is
|
|
49
|
+
* padded with no-break spaces, which the desktop does not collapse.
|
|
50
|
+
*/
|
|
51
|
+
function shareText(row: SecondHopRow, firstHopWidth: number) {
|
|
52
|
+
const first = hopLabel(row.hops[0]);
|
|
53
|
+
return `${first}${"\u00a0".repeat(Math.max(0, firstHopWidth - displayWidth(first)))}${HOP_SEPARATOR}${hopLabel(row.hops[1])}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The 2 hops table at this width. The date, the company and the route (via,
|
|
58
|
+
* holding, every hop's share) always show, so the route never scrolls out of
|
|
59
|
+
* view; a pane too narrow for all of it clips the shares. What room is left
|
|
60
|
+
* goes to the rest, most needed first, each only if it fits: the link, the
|
|
61
|
+
* report time, a long via name in full, the average move, shares lined up so
|
|
62
|
+
* every second hop starts at the same place, and the holding's own report.
|
|
63
|
+
* `firstHopWidth` is what the first hop is padded to, 0 when not lined up.
|
|
64
|
+
*/
|
|
65
|
+
function secondHopLayout(width: number, rows: readonly SecondHopRow[]) {
|
|
66
|
+
const widest = (texts: readonly string[]) => Math.max(0, ...texts.map(displayWidth));
|
|
67
|
+
const firstHop = widest(rows.map((row) => hopLabel(row.hops[0])));
|
|
68
|
+
const shares = Math.min(44, Math.max(28, widest(rows.map((row) => shareText(row, 0)))));
|
|
69
|
+
const aligned = Math.min(44, Math.max(shares, firstHop + HOP_SEPARATOR.length + widest(rows.map((row) => hopLabel(row.hops[1])))));
|
|
70
|
+
const widths: Record<string, number> = { via: 9, shares };
|
|
71
|
+
let firstHopWidth = 0;
|
|
72
|
+
const shown = new Set(["date", "company", "via", "holding", "shares"]);
|
|
73
|
+
const columns = () => SECOND_HOP_COLUMNS.filter((column) => shown.has(column.id))
|
|
74
|
+
.map((column) => widths[column.id] ? { ...column, width: widths[column.id]! } : column);
|
|
75
|
+
// The vertical scrollbar takes a cell, including when the rows fit.
|
|
76
|
+
const room = () => width - 1 - getTableWidth(columns());
|
|
77
|
+
const fitted = widths.shares = Math.max(28, shares + Math.min(0, room()));
|
|
78
|
+
const extras: [add: () => void, undo: () => void][] = [
|
|
79
|
+
...["link", "timing"].map((id): [() => void, () => void] => [() => shown.add(id), () => shown.delete(id)]),
|
|
80
|
+
[() => { widths.via = Math.min(18, Math.max(9, widest(rows.map((row) => row.via)))); }, () => { widths.via = 9; }],
|
|
81
|
+
[() => shown.add("move"), () => shown.delete("move")],
|
|
82
|
+
[() => { widths.shares = aligned; firstHopWidth = firstHop; }, () => { widths.shares = fitted; firstHopWidth = 0; }],
|
|
83
|
+
[() => shown.add("holdingDate"), () => shown.delete("holdingDate")],
|
|
84
|
+
];
|
|
85
|
+
for (const [add, undo] of extras) {
|
|
86
|
+
add();
|
|
87
|
+
if (room() < 0) undo();
|
|
88
|
+
}
|
|
89
|
+
return { columns: columns(), firstHopWidth };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const move = (row: RippleRow | SecondHopRow): DataTableCell =>
|
|
93
|
+
({ text: row.averageMove == null ? "--" : `±${(row.averageMove * 100).toFixed(1)}%`, value: row.averageMove });
|
|
94
|
+
|
|
29
95
|
function renderCell(row: RippleRow, column: DataTableColumn): DataTableCell {
|
|
30
96
|
switch (column.id) {
|
|
31
97
|
case "date": return { text: row.date };
|
|
@@ -34,16 +100,34 @@ function renderCell(row: RippleRow, column: DataTableColumn): DataTableCell {
|
|
|
34
100
|
case "link": return { text: row.link === "customer" ? "customer of" : "supplier to" };
|
|
35
101
|
case "holding": return { text: row.holding };
|
|
36
102
|
case "pct": return { text: `${row.pctOfRevenue}%${row.pctScope ? "*" : ""} of ${revenueOwner(row)}`, value: row.pctOfRevenue };
|
|
37
|
-
case "move": return
|
|
103
|
+
case "move": return move(row);
|
|
104
|
+
default: return { text: row.holdingDate ?? "--" };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function renderSecondHopCell(row: SecondHopRow, column: DataTableColumn, firstHopWidth: number): DataTableCell {
|
|
109
|
+
switch (column.id) {
|
|
110
|
+
case "date": return { text: row.date };
|
|
111
|
+
case "timing": return { text: row.timing?.toUpperCase() ?? "--" };
|
|
112
|
+
case "company": return { text: `${row.company} ${row.companyName}` };
|
|
113
|
+
case "link": return { text: row.link === "customer" ? "customer's customer" : "supplier's supplier" };
|
|
114
|
+
case "via": return { text: row.via };
|
|
115
|
+
case "holding": return { text: row.holding };
|
|
116
|
+
case "shares": return { text: shareText(row, firstHopWidth), value: shareText(row, 0) };
|
|
117
|
+
case "move": return move(row);
|
|
38
118
|
default: return { text: row.holdingDate ?? "--" };
|
|
39
119
|
}
|
|
40
120
|
}
|
|
41
121
|
|
|
42
122
|
export function EarningsRipplePane({ width, height, focused }: PaneProps) {
|
|
43
123
|
const [symbolsText] = usePaneSettingValue("symbols", "");
|
|
124
|
+
const [openingTab] = usePaneSettingValue("tab", "direct");
|
|
125
|
+
const [savedTab, setTab] = usePluginPaneState<string>("tab", openingTab);
|
|
126
|
+
const tab = savedTab === "two-hop" ? "two-hop" : "direct";
|
|
44
127
|
const tickers = useTickers();
|
|
45
128
|
const { createPaneFromTemplate } = usePluginAppActions();
|
|
46
129
|
const [selectedId, setSelectedId] = usePluginPaneState<string | null>("selectedRow", null);
|
|
130
|
+
const [selectedSecondId, setSelectedSecondId] = usePluginPaneState<string | null>("selectedSecondHop", null);
|
|
47
131
|
const session = useResearchCloudSession();
|
|
48
132
|
const access = usePlanAccess();
|
|
49
133
|
const accessKey = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
|
|
@@ -58,46 +142,87 @@ export function EarningsRipplePane({ width, height, focused }: PaneProps) {
|
|
|
58
142
|
}, [symbolsText, tickers]);
|
|
59
143
|
const holdings = scope.holdings;
|
|
60
144
|
const holdingsKey = holdings.join(",");
|
|
61
|
-
const
|
|
145
|
+
const secondHop = tab === "two-hop";
|
|
146
|
+
// A free account's graph stops at one hop, so it makes no graph requests and its second tab reuses the first load.
|
|
147
|
+
// Once a Pro account opens 2 hops the graph stays in the load, so going back and forth between tabs never reloads.
|
|
148
|
+
// A refresh on 1 hop reads the graphs from the cache instead of asking for them again.
|
|
149
|
+
const [graphOpened, setGraphOpened] = useState(false);
|
|
150
|
+
useEffect(() => { if (secondHop && access.hasProAccess) setGraphOpened(true); }, [secondHop, access.hasProAccess]);
|
|
151
|
+
const graphHop = access.hasProAccess && (secondHop || graphOpened);
|
|
152
|
+
const secondHopShown = useRef(secondHop);
|
|
153
|
+
secondHopShown.current = secondHop;
|
|
154
|
+
const loader = useCallback((force: boolean) => loadRipple(holdings,
|
|
155
|
+
cachedRippleSources(accessKey, force, !graphHop ? "off" : secondHopShown.current ? "load" : "cached"), undefined, { secondHop: graphHop }),
|
|
156
|
+
[holdingsKey, accessKey, graphHop]);
|
|
62
157
|
const ripple = useAsyncResource(holdings.length ? loader : null);
|
|
63
158
|
useAutoRefresh(ripple.updatedAt, ripple.load);
|
|
64
159
|
usePaneRefreshKey(() => { void ripple.reload(); }, { focused });
|
|
160
|
+
const { strip, rows: tabRows } = usePaneTabs(holdings.length ? { tabs: TABS, activeValue: tab, onSelect: setTab, focused, dense: true } : null);
|
|
65
161
|
|
|
66
162
|
const rows = ripple.data?.rows ?? [];
|
|
163
|
+
const second = secondHop ? ripple.data?.secondHop ?? null : null;
|
|
164
|
+
const secondRows = second?.rows ?? [];
|
|
67
165
|
const selected = rows.find((row) => row.id === selectedId) ?? rows[0] ?? null;
|
|
166
|
+
const selectedSecond = secondRows.find((row) => row.id === selectedSecondId) ?? secondRows[0] ?? null;
|
|
167
|
+
const current = secondHop ? selectedSecond : selected;
|
|
68
168
|
const failures = ripple.data?.failures ?? [];
|
|
69
169
|
const truncated = ripple.data?.truncated ?? [];
|
|
170
|
+
const locked = secondHop && (!access.hasProAccess || !!second?.locked);
|
|
70
171
|
const openUpgrade = useCloudUpgradeAction("ripl");
|
|
71
172
|
usePaneNoticeFooter({ registrationId: "earnings-ripple-notices", focused, notices: [
|
|
72
173
|
...(scope.total > holdings.length ? [`Checking ${holdings.length} of ${scope.total} holdings${scope.named ? "" : ", positions first. Name tickers in pane settings to pick others"}`] : []),
|
|
73
174
|
...(failures.length ? [`No disclosures for ${failures.map((failure) => failure.symbol).join(", ")}`] : []),
|
|
74
|
-
...(truncated.length ? [`Free preview: top three customers and suppliers for ${truncated.join(", ")}`] : []),
|
|
175
|
+
...(!secondHop && truncated.length ? [`Free preview: top three customers and suppliers for ${truncated.join(", ")}`] : []),
|
|
176
|
+
...(second?.failures.length ? [`No two-hop graph for ${second.failures.map((failure) => failure.symbol).join(", ")}`] : []),
|
|
177
|
+
// The graph returns at most 50 companies per direction, so a large holding's list can be cut short.
|
|
178
|
+
...(second?.truncated.length ? [`Graph search capped for ${second.truncated.map((entry) => entry.symbol).join(", ")}; some companies two hops away may be missing`] : []),
|
|
75
179
|
] });
|
|
76
180
|
const openSupply = useCallback((row: RippleRow) => createPaneFromTemplate("supply-chain-pane", { symbol: row.holding }), [createPaneFromTemplate]);
|
|
181
|
+
// SPLC's own table does not reach a company two hops away; its Path view shows the row's route.
|
|
182
|
+
const openRoute = useCallback((row: SecondHopRow) => createPaneFromTemplate("supply-chain-pane", { symbol: row.holding, values: rippleRouteSettings(row) }),
|
|
183
|
+
[createPaneFromTemplate]);
|
|
77
184
|
usePaneFooter("earnings-ripple", () => {
|
|
78
185
|
const hints: PaneHint[] = [
|
|
79
|
-
...(
|
|
80
|
-
|
|
81
|
-
{ id: "
|
|
82
|
-
] : []),
|
|
83
|
-
...(truncated.length ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade }] : []),
|
|
186
|
+
...(secondHop && selectedSecond ? [{ id: "supply", key: "s", label: "upply chain", title: `Supply chain path from ${selectedSecond.holding} to ${selectedSecond.company}`,
|
|
187
|
+
onPress: () => openRoute(selectedSecond) }]
|
|
188
|
+
: !secondHop && selected ? [{ id: "supply", key: "s", label: "upply chain", onPress: () => openSupply(selected) }] : []),
|
|
189
|
+
...(current ? [{ id: "earnings", key: "e", label: "arnings", onPress: () => createPaneFromTemplate("earnings-calendar-pane", { arg: current.company }) }] : []),
|
|
190
|
+
...((secondHop ? locked : truncated.length) ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade }] : []),
|
|
84
191
|
];
|
|
85
192
|
return { info: [
|
|
86
|
-
...(ripple.loading ? [{ id: "loading", parts: [{ text: "loading disclosures", tone: "muted" as const }] }] : []),
|
|
193
|
+
...(ripple.loading ? [{ id: "loading", parts: [{ text: secondHop && graphHop ? "loading supply chain graph" : "loading disclosures", tone: "muted" as const }] }] : []),
|
|
87
194
|
...(ripple.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
88
195
|
], hints };
|
|
89
|
-
}, [ripple.loading, ripple.data?.stale, selected, openSupply, createPaneFromTemplate, truncated.length, openUpgrade]);
|
|
196
|
+
}, [ripple.loading, ripple.data?.stale, current, selected, selectedSecond, secondHop, graphHop, locked, openSupply, openRoute, createPaneFromTemplate, truncated.length, openUpgrade]);
|
|
90
197
|
|
|
198
|
+
const secondLayout = useMemo(() => secondHopLayout(width, secondRows), [width, secondRows]);
|
|
199
|
+
const firstHopWidth = secondLayout.firstHopWidth;
|
|
200
|
+
const renderSecondCell = useCallback((row: SecondHopRow, column: DataTableColumn) => renderSecondHopCell(row, column, firstHopWidth), [firstHopWidth]);
|
|
201
|
+
const windowMeta = () => [["window", `${ripple.data?.from} to ${ripple.data?.to}`]];
|
|
202
|
+
const bodyHeight = Math.max(3, height - tabRows);
|
|
91
203
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
204
|
+
{strip}
|
|
92
205
|
{!holdings.length ? <EmptyState title="Add tickers to a portfolio or watchlist, or name them in pane settings." />
|
|
93
|
-
:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
206
|
+
: secondHop && locked ? <Box paddingX={1} paddingTop={1}><EmptyState title="Companies two hops away need Gloom Pro."
|
|
207
|
+
actions={<UpgradeLabel text="Upgrade for two-hop supply chains" onPress={openUpgrade} role="ripple-upgrade" />} /></Box>
|
|
208
|
+
: <PaneStatusBody subject={secondHop ? "two-hop supply chains" : "earnings ripple"} loading={ripple.loading && (!ripple.data || (secondHop && !second))}
|
|
209
|
+
error={!ripple.data ? ripple.error : null}
|
|
210
|
+
empty={secondHop ? !!second && !secondRows.length : !!ripple.data && !rows.length}
|
|
211
|
+
emptyTitle={secondHop ? `No company two disclosed hops from these holdings reports in the next ${RIPPLE_DAYS} days.`
|
|
212
|
+
: `No disclosed customer or supplier of these holdings reports in the next ${RIPPLE_DAYS} days.`}>
|
|
213
|
+
{secondHop
|
|
214
|
+
? <DataTableView<SecondHopRow> focused={focused} columns={secondLayout.columns} items={secondRows} rootWidth={width} rootHeight={bodyHeight}
|
|
215
|
+
getItemKey={(row) => row.id} emptyStateTitle="No reports." sortColumnId={null} sortDirection="asc"
|
|
216
|
+
selection={{ kind: "id", selectedId: selectedSecond?.id ?? null, getId: (row) => row.id, onChange: setSelectedSecondId }}
|
|
217
|
+
onActivate={openRoute}
|
|
218
|
+
getExportMetadata={() => [...windowMeta(), ["share per hop", "percent of the reporting company's revenue unless another basis is named, holding to via, then via to the company"]]}
|
|
219
|
+
renderCell={renderSecondCell} />
|
|
220
|
+
: <DataTableView<RippleRow> focused={focused} columns={COLUMNS} items={rows} rootWidth={width} rootHeight={bodyHeight}
|
|
221
|
+
getItemKey={(row) => row.id} emptyStateTitle="No reports." sortColumnId={null} sortDirection="asc"
|
|
222
|
+
selection={{ kind: "id", selectedId: selected?.id ?? null, getId: (row) => row.id, onChange: setSelectedId }}
|
|
223
|
+
onActivate={openSupply}
|
|
224
|
+
getExportMetadata={() => [...windowMeta(), ["revenue share", "percent of the seller's revenue, as its filing discloses"]]}
|
|
225
|
+
renderCell={renderCell} />}
|
|
101
226
|
</PaneStatusBody>}
|
|
102
227
|
</Box>;
|
|
103
228
|
}
|
|
@@ -361,6 +361,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
361
361
|
export const economicCalendarModule: PluginModule = {
|
|
362
362
|
panes: [{
|
|
363
363
|
id: "econ-calendar",
|
|
364
|
+
reportFreshness: { status: "not-a-feed", basis: "calendar" },
|
|
364
365
|
name: "Economic Calendar",
|
|
365
366
|
icon: "E",
|
|
366
367
|
component: EconCalendarPane,
|
|
@@ -123,6 +123,8 @@ export function projectStatsHeadlessBundle(
|
|
|
123
123
|
|
|
124
124
|
export const econStatisticsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
125
125
|
shape: "bundle",
|
|
126
|
+
// Each row checks its own release schedule and says when it is stale.
|
|
127
|
+
freshness: { source: "FRED", status: "not-a-feed", basis: "published statistics" },
|
|
126
128
|
argument: {
|
|
127
129
|
kind: "free-text",
|
|
128
130
|
placeholder: "statistic",
|