gloomberb 0.16.4 → 0.16.5
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 +14 -91
- package/package.json +4 -1
- package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
- package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
- package/src/account/billing.ts +57 -0
- package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
- package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
- package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
- package/src/account/session.ts +16 -0
- package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
- package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
- package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
- package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
- package/src/api-client/data.ts +24 -2
- package/src/api-client/earnings.ts +6 -0
- package/src/api-client/futures-curve.ts +56 -0
- package/src/api-client/index.ts +4 -1
- package/src/api-client/market-discovery.ts +10 -1
- package/src/api-client/paths.ts +7 -0
- package/src/api-client/research-activity.ts +15 -10
- package/src/api-client/types.ts +17 -1
- package/src/api-client/yield-curves.ts +8 -0
- package/src/app/global-shortcuts.ts +15 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
- package/src/architecture/core-builtin-allowlist.json +4 -13
- package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
- package/src/capabilities/chart-series.ts +11 -1
- package/src/cli/commands/crypto-hints.ts +7 -0
- package/src/cli/commands/fred-series.ts +61 -0
- package/src/cli/commands/market.ts +407 -104
- package/src/cli/commands/news-hints.ts +62 -0
- package/src/cli/commands/news-search.ts +53 -0
- package/src/cli/commands/options-leaps.ts +5 -2
- package/src/cli/commands/overview.ts +93 -20
- package/src/cli/commands/remote.ts +2 -1
- package/src/cli/commands/search.ts +82 -9
- package/src/cli/commands/system.ts +55 -8
- package/src/cli/commands/ticker.ts +88 -11
- package/src/cli/context.ts +45 -14
- package/src/cli/desktop-pane-shot.ts +59 -7
- package/src/cli/earnings-calendar.ts +71 -0
- package/src/cli/freshness.ts +18 -7
- package/src/cli/glossary.ts +429 -0
- package/src/cli/history-rows.ts +48 -7
- package/src/cli/index-roots.ts +7 -0
- package/src/cli/index.ts +21 -7
- package/src/cli/known-gaps.ts +27 -0
- package/src/cli/listing-arg.ts +417 -56
- package/src/cli/not-a-ticker.ts +12 -5
- package/src/cli/pane-functions/access-gate.ts +5 -0
- package/src/cli/pane-functions/capabilities.ts +3 -2
- package/src/cli/pane-functions/catalog.ts +35 -5
- package/src/cli/pane-functions/data.ts +12 -8
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/dom.ts +20 -6
- package/src/cli/pane-functions/freshness.ts +363 -43
- package/src/cli/pane-functions/function-help.ts +12 -5
- package/src/cli/pane-functions/headless.ts +45 -17
- package/src/cli/pane-functions/index.ts +63 -10
- package/src/cli/pane-functions/market-session.ts +22 -8
- package/src/cli/pane-functions/options.ts +63 -22
- package/src/cli/pane-functions/report-notices.ts +25 -7
- package/src/cli/pane-functions/report.ts +15 -8
- package/src/cli/pane-functions/resolver.ts +49 -12
- package/src/cli/pane-functions/screenshot.ts +110 -23
- package/src/cli/report-tables.ts +3 -0
- package/src/cli/result.ts +4 -1
- package/src/cli/staged-tickers.ts +52 -0
- package/src/cli/symbol-args.ts +9 -0
- package/src/cli/symbol-list.ts +172 -0
- package/src/cli/ticker-resolution.ts +223 -30
- package/src/cli/types.ts +5 -0
- package/src/components/chart/composite/axis-overlays.tsx +6 -2
- package/src/components/chart/composite/composite-chart.tsx +40 -6
- package/src/components/chart/composite/legend.tsx +49 -2
- package/src/components/chart/composite/panel-surface.tsx +44 -17
- package/src/components/chart/composite/price-axis-labels.tsx +3 -1
- package/src/components/command-bar/panel/index.tsx +11 -3
- package/src/components/command-bar/panel/runtime.ts +3 -0
- package/src/components/command-bar/panel/types.ts +2 -0
- package/src/components/command-bar/routes/root/provider-search.ts +4 -3
- package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
- package/src/components/command-bar/workflow/tickers.ts +23 -4
- package/src/components/layout/detached-pane-shell.tsx +9 -0
- package/src/components/layout/floating-pane.tsx +3 -0
- package/src/components/layout/header.tsx +20 -5
- package/src/components/layout/pane/content.tsx +7 -13
- package/src/components/layout/pane/footer/index.tsx +6 -2
- package/src/components/layout/pane/footer/model.ts +35 -1
- package/src/components/layout/pane/header.tsx +24 -1
- package/src/components/layout/pane/index.tsx +12 -2
- package/src/components/layout/pane/sidebar.tsx +78 -10
- package/src/components/layout/shell/active-drag.ts +21 -5
- package/src/components/layout/shell/drag/index.ts +39 -9
- package/src/components/layout/shell/drag/live.ts +61 -51
- package/src/components/layout/shell/drag/runtime.ts +54 -12
- package/src/components/layout/shell/index.tsx +25 -7
- package/src/components/layout/shell/menu.ts +5 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
- package/src/components/layout/shell/pane/layers.tsx +192 -45
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
- package/src/components/markdown-text.tsx +62 -20
- package/src/components/metric-treemap/heat-surface.tsx +10 -1
- package/src/components/onboarding/account-step/form-panel.tsx +1 -1
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-keyboard.ts +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +2 -2
- package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
- package/src/components/onboarding/wizard-account.ts +2 -2
- package/src/components/pane-template-wizard.tsx +9 -1
- package/src/components/price-selector-dialog.tsx +11 -2
- package/src/components/sign-in-gate.tsx +3 -3
- package/src/components/ticker/list-table-view.tsx +24 -1
- package/src/components/ui/action-row.tsx +2 -1
- package/src/components/ui/button.tsx +7 -1
- package/src/components/ui/choice-dialog.tsx +6 -4
- package/src/components/ui/confirm-dialog.tsx +28 -18
- package/src/components/ui/data-table/index.tsx +17 -6
- package/src/components/ui/data-table/opentui/index.tsx +37 -6
- package/src/components/ui/data-table/types.ts +12 -0
- package/src/components/ui/dialog-actions.tsx +64 -0
- package/src/components/ui/display.tsx +17 -3
- package/src/components/ui/frame.tsx +35 -2
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/text-prompt-dialog.tsx +11 -9
- package/src/data/cached-query.ts +2 -0
- package/src/data/config/store/normalize.ts +7 -2
- package/src/data/ticker-repository.ts +5 -0
- package/src/data/ticker-store.ts +15 -0
- package/src/docs/content.ts +215 -0
- package/src/docs/load.ts +139 -0
- package/src/docs/requests.ts +39 -0
- package/src/docs/snapshot.chunk.ts +8 -0
- package/src/docs/snapshot.json +7416 -0
- package/src/docs/topics.ts +216 -0
- package/src/i18n/es.ts +61 -4
- package/src/i18n/ja.ts +61 -4
- package/src/i18n/ko.ts +61 -4
- package/src/i18n/zh-cn.ts +61 -4
- package/src/i18n/zh-tw.ts +61 -4
- package/src/layout/desks.ts +1 -1
- package/src/layout/pane-manager/dock-tree.ts +18 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/chart.ts +10 -4
- package/src/market-data/coordinator/index.ts +6 -3
- package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
- package/src/market-data/futures-contract.ts +151 -0
- package/src/market-data/hooks.tsx +10 -2
- package/src/market-data/market/format.ts +39 -21
- package/src/market-data/market/freshness.ts +81 -18
- package/src/market-data/market/status.ts +19 -23
- package/src/market-data/published-apac-sessions.ts +23 -1
- package/src/market-data/quotes/contributions.ts +3 -0
- package/src/market-data/quotes/metadata.ts +7 -24
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +5 -4
- package/src/market-data/result-types.ts +2 -0
- package/src/plugins/builtin/account-management/model.ts +1 -56
- package/src/plugins/builtin/account-management/pane.tsx +6 -4
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/cli.ts +313 -47
- package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
- package/src/plugins/builtin/alerts/events.ts +23 -2
- package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +41 -9
- package/src/plugins/builtin/alerts/pane.tsx +8 -0
- package/src/plugins/builtin/alerts/research-builder.ts +8 -3
- package/src/plugins/builtin/alerts/research-rules.ts +18 -2
- package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
- package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
- package/src/plugins/builtin/alerts/scheduled.ts +254 -0
- package/src/plugins/builtin/alerts/targets.ts +92 -0
- package/src/plugins/builtin/analytics/index.tsx +4 -2
- package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
- package/src/plugins/builtin/analytics/risk-math.ts +91 -27
- package/src/plugins/builtin/analytics/risk-model.ts +35 -5
- package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
- package/src/plugins/builtin/attention/index.tsx +2 -0
- package/src/plugins/builtin/attention/pane.tsx +3 -1
- package/src/plugins/builtin/attention/series.ts +26 -0
- package/src/plugins/builtin/awards/index.tsx +2 -0
- package/src/plugins/builtin/awards/pane.tsx +3 -1
- package/src/plugins/builtin/awards/series.ts +29 -0
- package/src/plugins/builtin/bond-calculator/model.ts +2 -1
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/client.ts +70 -13
- package/src/plugins/builtin/cds/headless.ts +116 -19
- package/src/plugins/builtin/cds/index.tsx +3 -1
- package/src/plugins/builtin/cds/model.ts +193 -31
- package/src/plugins/builtin/cds/pane.tsx +56 -14
- package/src/plugins/builtin/cds/series.ts +19 -0
- package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
- package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
- package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
- package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
- package/src/plugins/builtin/chart-composer/headless.ts +8 -3
- package/src/plugins/builtin/chart-composer/index.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
- package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
- package/src/plugins/builtin/chat/controller/index.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
- package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
- package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
- package/src/plugins/builtin/cloud/askg/model.ts +27 -8
- package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
- package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
- package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
- package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
- package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
- package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
- package/src/plugins/builtin/company-attention/headless.ts +19 -5
- package/src/plugins/builtin/company-attention/index.tsx +2 -0
- package/src/plugins/builtin/company-attention/model.ts +26 -7
- package/src/plugins/builtin/company-attention/pane.tsx +12 -9
- package/src/plugins/builtin/company-attention/series.ts +52 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
- package/src/plugins/builtin/composite-plugins.ts +19 -13
- package/src/plugins/builtin/cot/index.tsx +3 -1
- package/src/plugins/builtin/cot/model.ts +40 -11
- package/src/plugins/builtin/cot/series.ts +27 -0
- package/src/plugins/builtin/credit-boards/index.tsx +2 -0
- package/src/plugins/builtin/credit-boards/series.ts +66 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
- package/src/plugins/builtin/credit-conditions/series.ts +26 -0
- package/src/plugins/builtin/credit-documents/headless.ts +31 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
- package/src/plugins/builtin/crypto-board/headless.ts +2 -3
- package/src/plugins/builtin/crypto-board/index.tsx +2 -0
- package/src/plugins/builtin/crypto-board/model.ts +12 -0
- package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
- package/src/plugins/builtin/crypto-board/series.ts +48 -0
- package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
- package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
- package/src/plugins/builtin/debt-maturities/series.ts +45 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
- package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
- package/src/plugins/builtin/dividend-yield/series.ts +20 -0
- package/src/plugins/builtin/docs/blocks.tsx +181 -0
- package/src/plugins/builtin/docs/index.tsx +68 -0
- package/src/plugins/builtin/docs/pane.tsx +426 -0
- package/src/plugins/builtin/docs/targets.ts +72 -0
- package/src/plugins/builtin/docs/use-docs.ts +60 -0
- package/src/plugins/builtin/doe/index.tsx +2 -0
- package/src/plugins/builtin/doe/series.ts +62 -0
- package/src/plugins/builtin/earnings/client.ts +3 -2
- package/src/plugins/builtin/earnings/format.ts +1 -5
- package/src/plugins/builtin/earnings/headless.ts +58 -6
- package/src/plugins/builtin/earnings/history-model.ts +15 -1
- package/src/plugins/builtin/earnings/history-table.ts +13 -2
- package/src/plugins/builtin/earnings/history.tsx +7 -3
- package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
- package/src/plugins/builtin/econ/index.tsx +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
- package/src/plugins/builtin/exposure/holdings.ts +1 -1
- package/src/plugins/builtin/exposure/pane.tsx +3 -1
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +43 -3
- package/src/plugins/builtin/futures/model.ts +1 -1
- package/src/plugins/builtin/futures/returns.ts +1 -1
- package/src/plugins/builtin/futures/series.ts +13 -0
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +70 -9
- package/src/plugins/builtin/futures-curve/index.tsx +1 -1
- package/src/plugins/builtin/futures-curve/model.ts +66 -11
- package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
- package/src/plugins/builtin/gpu/board.tsx +13 -10
- package/src/plugins/builtin/gpu/changes.tsx +4 -4
- package/src/plugins/builtin/gpu/client.ts +19 -1
- package/src/plugins/builtin/gpu/equities.tsx +14 -10
- package/src/plugins/builtin/gpu/headless.ts +56 -25
- package/src/plugins/builtin/gpu/history.tsx +3 -3
- package/src/plugins/builtin/gpu/index.tsx +2 -0
- package/src/plugins/builtin/gpu/model.ts +101 -16
- package/src/plugins/builtin/gpu/pane.tsx +28 -12
- package/src/plugins/builtin/gpu/series.ts +38 -0
- package/src/plugins/builtin/help/function-card.tsx +25 -6
- package/src/plugins/builtin/help/index.tsx +17 -8
- package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
- package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
- package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
- package/src/plugins/builtin/holders/client.ts +22 -3
- package/src/plugins/builtin/holders/format.ts +18 -3
- package/src/plugins/builtin/holders/headless.ts +36 -133
- package/src/plugins/builtin/holders/pane.tsx +11 -4
- package/src/plugins/builtin/holders/report-header.ts +40 -5
- package/src/plugins/builtin/holders/report-rows.ts +240 -0
- package/src/plugins/builtin/insider/client.ts +35 -20
- package/src/plugins/builtin/insider/headless.ts +41 -11
- package/src/plugins/builtin/insider/index.tsx +26 -9
- package/src/plugins/builtin/insider/insider-data.ts +10 -0
- package/src/plugins/builtin/insider/issuer.ts +49 -0
- package/src/plugins/builtin/insider/model.ts +3 -0
- package/src/plugins/builtin/iv-history/client.ts +14 -4
- package/src/plugins/builtin/iv-history/headless.ts +7 -4
- package/src/plugins/builtin/iv-history/index.tsx +2 -0
- package/src/plugins/builtin/iv-history/model.ts +111 -7
- package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
- package/src/plugins/builtin/iv-history/series.ts +29 -0
- package/src/plugins/builtin/jobs/headless.ts +28 -3
- package/src/plugins/builtin/jobs/index.tsx +3 -1
- package/src/plugins/builtin/jobs/series.ts +19 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
- package/src/plugins/builtin/macro-day/client.ts +44 -3
- package/src/plugins/builtin/macro-day/headless.ts +13 -10
- package/src/plugins/builtin/macro-day/model.ts +56 -10
- package/src/plugins/builtin/macro-day/pane.tsx +30 -20
- package/src/plugins/builtin/market-heatmap/model.ts +19 -11
- package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
- package/src/plugins/builtin/market-movers/client.ts +7 -5
- package/src/plugins/builtin/market-movers/index.tsx +3 -2
- package/src/plugins/builtin/market-movers/model.ts +30 -0
- package/src/plugins/builtin/market-movers/screener.ts +31 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
- package/src/plugins/builtin/market-valuation/index.tsx +3 -3
- package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
- package/src/plugins/builtin/members/headless.ts +7 -2
- package/src/plugins/builtin/members/model.ts +31 -3
- package/src/plugins/builtin/members/pane.tsx +3 -6
- package/src/plugins/builtin/mna/headless.ts +15 -3
- package/src/plugins/builtin/money-markets/index.tsx +2 -0
- package/src/plugins/builtin/money-markets/series.ts +41 -0
- package/src/plugins/builtin/notes/cli.ts +12 -7
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +7 -3
- package/src/plugins/builtin/options-positioning/headless.ts +1 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +2 -1
- package/src/plugins/builtin/options-scenario/headless.ts +1 -0
- package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
- package/src/plugins/builtin/perps/client.ts +40 -6
- package/src/plugins/builtin/perps/equity-row.tsx +33 -11
- package/src/plugins/builtin/perps/freshness.ts +66 -0
- package/src/plugins/builtin/perps/headless.ts +45 -30
- package/src/plugins/builtin/perps/index.tsx +18 -1
- package/src/plugins/builtin/perps/model.ts +20 -5
- package/src/plugins/builtin/perps/pane.tsx +76 -20
- package/src/plugins/builtin/perps/series.ts +76 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
- package/src/plugins/builtin/plugin-module.ts +3 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
- package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
- package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
- package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
- package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
- package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
- package/src/plugins/builtin/power/client.ts +16 -0
- package/src/plugins/builtin/power/evidence.ts +6 -2
- package/src/plugins/builtin/power/headless.ts +71 -7
- package/src/plugins/builtin/power/index.tsx +4 -1
- package/src/plugins/builtin/power/model.ts +72 -1
- package/src/plugins/builtin/power/pane.tsx +32 -9
- package/src/plugins/builtin/rate-path/headless.ts +11 -3
- package/src/plugins/builtin/realized-vol/headless.ts +5 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
- package/src/plugins/builtin/research/event-model.ts +68 -24
- package/src/plugins/builtin/research/events-headless.ts +2 -1
- package/src/plugins/builtin/research/headless.ts +2 -1
- package/src/plugins/builtin/seasonality/client.ts +15 -7
- package/src/plugins/builtin/seasonality/headless.ts +36 -12
- package/src/plugins/builtin/seasonality/index.tsx +2 -2
- package/src/plugins/builtin/seasonality/model.ts +119 -0
- package/src/plugins/builtin/seasonality/pane.tsx +108 -37
- package/src/plugins/builtin/series-inventory.ts +91 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
- package/src/plugins/builtin/shared/command-tab.ts +30 -12
- package/src/plugins/builtin/shared/earnings-report.ts +55 -0
- package/src/plugins/builtin/shared/fred-points.ts +20 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
- package/src/plugins/builtin/shared/open-description.ts +29 -0
- package/src/plugins/builtin/shared/report-freshness.ts +9 -0
- package/src/plugins/builtin/shared/series-warning.ts +5 -0
- package/src/plugins/builtin/short-interest/headless.ts +1 -0
- package/src/plugins/builtin/short-interest/index.tsx +2 -0
- package/src/plugins/builtin/short-interest/series.ts +36 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/short-volume/index.tsx +2 -0
- package/src/plugins/builtin/short-volume/series.ts +23 -0
- package/src/plugins/builtin/social-mentions/index.tsx +2 -0
- package/src/plugins/builtin/social-mentions/series.ts +23 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
- package/src/plugins/builtin/thirteenf/api.ts +1 -0
- package/src/plugins/builtin/thirteenf/data.ts +6 -4
- package/src/plugins/builtin/thirteenf/headless.ts +217 -36
- package/src/plugins/builtin/thirteenf/model.ts +39 -1
- package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
- package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
- package/src/plugins/builtin/time-sales/headless.ts +1 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
- package/src/plugins/builtin/vol-surface/headless.ts +1 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -0
- package/src/plugins/builtin/volatility/series.ts +84 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
- package/src/plugins/builtin/world-venue-map/index.ts +2 -1
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
- package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
- package/src/plugins/builtin/yield-curve/chart.ts +2 -2
- package/src/plugins/builtin/yield-curve/curves.ts +21 -8
- package/src/plugins/builtin/yield-curve/headless.ts +71 -18
- package/src/plugins/builtin/yield-curve/index.tsx +3 -1
- package/src/plugins/builtin/yield-curve/series.ts +86 -0
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/registry/index.ts +89 -4
- package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
- package/src/portfolio/position-value.ts +40 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
- package/src/public/time-series.ts +17 -0
- package/src/react/auto-refresh.ts +13 -3
- package/src/remote/app-host.tsx +20 -4
- package/src/remote/confirm-change.tsx +85 -0
- package/src/remote/controller-utils.ts +8 -1
- package/src/remote/controller.ts +123 -6
- package/src/remote/pane-screenshot.ts +47 -0
- package/src/remote/schema.ts +62 -0
- package/src/remote/types.ts +30 -0
- package/src/remote/watchlist.ts +306 -0
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/dom/data-table/frozen-column.ts +3 -1
- package/src/renderers/dom/data-table/index.tsx +33 -6
- package/src/renderers/dom/data-table/row.tsx +27 -1
- package/src/renderers/dom/desktop/control-styles.ts +63 -1
- package/src/renderers/dom/desktop/controls.tsx +30 -23
- package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/dom-ui-host.tsx +3 -0
- package/src/renderers/dom/host/box.tsx +3 -0
- package/src/renderers/dom/host/chart-surface.tsx +6 -2
- package/src/renderers/dom/host/live-frame.ts +6 -2
- package/src/renderers/dom/host/overflow-fade.ts +14 -1
- package/src/renderers/dom/host/size-observer.ts +26 -0
- package/src/renderers/dom/host/surface-3d.tsx +7 -2
- package/src/renderers/dom/host/text.tsx +1 -1
- package/src/renderers/dom/styles.css +61 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/series/capability.ts +75 -0
- package/src/series/csv.ts +160 -0
- package/src/series/files.ts +107 -0
- package/src/series/registry.ts +198 -0
- package/src/series/resolve.ts +107 -0
- package/src/series/standalone.ts +48 -0
- package/src/series/types.ts +145 -0
- package/src/series/validate.ts +171 -0
- package/src/sources/gloomberb-cloud/index.ts +7 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
- package/src/sources/history-result.ts +1 -0
- package/src/sources/listing-symbols.ts +4 -1
- package/src/sources/provider-router/batches.ts +4 -1
- package/src/sources/provider-router/cache.ts +4 -2
- package/src/sources/provider-router/cached-routes.ts +1 -1
- package/src/sources/provider-router/financial-routes.ts +1 -0
- package/src/sources/provider-router/financials.ts +13 -0
- package/src/sources/provider-router/history.ts +43 -25
- package/src/sources/provider-router/search.ts +1 -0
- package/src/sync/core-contributors.ts +1 -0
- package/src/tickers/company-name.ts +75 -0
- package/src/tickers/instrument-kind.ts +25 -0
- package/src/tickers/open-target.ts +3 -2
- package/src/tickers/search/index.ts +45 -7
- package/src/tickers/search/ranking.ts +33 -17
- package/src/tickers/search/types.ts +7 -1
- package/src/tickers/us-listing.ts +34 -0
- package/src/time-series/hooks.ts +9 -0
- package/src/time-series/resolve.ts +95 -16
- package/src/time-series/types.ts +4 -2
- package/src/time-series/use-chart-resolution.ts +32 -0
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -0
- package/src/types/financials.ts +52 -1
- package/src/types/headless.ts +61 -0
- package/src/types/plugin.ts +19 -0
- package/src/types/price-history.ts +7 -0
- package/src/types/ticker.ts +14 -0
- package/src/ui/host.tsx +18 -0
- package/src/ui/live-resize.ts +81 -0
- package/src/ui/live-stretch.ts +121 -0
- package/src/ui/measured-cells.ts +58 -0
- package/src/ui/pane-fill.ts +11 -0
- package/src/utils/basis-points.ts +17 -0
- package/src/utils/cli-output.ts +7 -1
- package/src/utils/concurrency.ts +25 -0
- package/src/utils/crypto-pair.ts +20 -0
- package/src/utils/currency-pair.ts +19 -0
- package/src/utils/decimal-comma.ts +19 -0
- package/src/utils/dom-screenshot-css.ts +265 -0
- package/src/utils/dom-screenshot-rasters.ts +276 -0
- package/src/utils/dom-screenshot.ts +437 -52
- package/src/utils/exchange-rate-snapshot.ts +6 -2
- package/src/utils/exchanges.ts +107 -1
- package/src/utils/format.ts +16 -3
- package/src/utils/fundamentals.ts +66 -0
- package/src/utils/futures-generic.ts +6 -12
- package/src/utils/fx-market-hours.ts +15 -5
- package/src/utils/price-history.ts +99 -42
- package/src/utils/sec-eps-basis.ts +24 -11
- package/src/utils/sec.ts +61 -7
- package/src/utils/utc-time.ts +51 -4
- package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
- /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
extendedQuoteFields,
|
|
14
14
|
extendedSessionsOf,
|
|
15
15
|
findCollection,
|
|
16
|
+
listingLabel,
|
|
16
17
|
loadCollectionQuotes,
|
|
17
18
|
valuePortfolioAllocation,
|
|
18
19
|
valuePortfolioPositions,
|
|
@@ -27,9 +28,10 @@ import {
|
|
|
27
28
|
formatAllocationWeight,
|
|
28
29
|
formatTradeUnits,
|
|
29
30
|
} from "./allocation";
|
|
30
|
-
import {
|
|
31
|
+
import { currencyPriceMinDigits, formatMarketPrice, formatMarketQuantity } from "../../../market-data/market/format";
|
|
31
32
|
import { resolvePortfolioTotalsCurrency } from "./summary/totals";
|
|
32
33
|
import { quoteFreshnessFields } from "../shared/report-freshness";
|
|
34
|
+
import { importedFromText } from "./imported-from";
|
|
33
35
|
|
|
34
36
|
const DEFAULT_ROW_LIMIT = 50;
|
|
35
37
|
const MAX_ROW_LIMIT = 200;
|
|
@@ -54,7 +56,7 @@ const tradeUnits = (value: unknown, row: HeadlessPaneRow) => (
|
|
|
54
56
|
/** Money per unit at the currency's minor digits: $230.00, not 230. */
|
|
55
57
|
const unitMoney = (currencyKey: string) => (value: unknown, row: HeadlessPaneRow) => (
|
|
56
58
|
typeof value === "number"
|
|
57
|
-
? formatMarketPrice(value, { assetCategory: textOf(row.assetCategory), minimumFractionDigits:
|
|
59
|
+
? formatMarketPrice(value, { assetCategory: textOf(row.assetCategory), minimumFractionDigits: currencyPriceMinDigits(textOf(row[currencyKey])) })
|
|
58
60
|
: formatNumber(undefined)
|
|
59
61
|
);
|
|
60
62
|
|
|
@@ -113,6 +115,25 @@ const WATCHLIST_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
113
115
|
{ key: "changePercent", header: "Chg", align: "right", format: percent },
|
|
114
116
|
];
|
|
115
117
|
|
|
118
|
+
/**
|
|
119
|
+
* `columns` with the text report's Ticker column naming each listing as
|
|
120
|
+
* `portfolio show` does (SAN:EPA, SAN:NYSE), not by the key it is stored under,
|
|
121
|
+
* which leaves two listings of one symbol looking alike. `textFormat` is text
|
|
122
|
+
* only: JSON and CSV keep the stored key in `symbol`, and the rows stay keyed
|
|
123
|
+
* by it.
|
|
124
|
+
*/
|
|
125
|
+
function withListingLabels(columns: HeadlessPaneColumn[], tickers: readonly TickerRecord[]): HeadlessPaneColumn[] {
|
|
126
|
+
const labels = new Map(tickers.map((ticker) => [ticker.metadata.ticker, listingLabel(ticker)]));
|
|
127
|
+
return columns.map((column) => column.key !== "symbol" ? column : {
|
|
128
|
+
...column,
|
|
129
|
+
textFormat: (value, row) => {
|
|
130
|
+
// The cash line is not a listing, even when a ticker is called CASH; its row carries no exchange.
|
|
131
|
+
if (typeof value !== "string" || !("exchange" in row)) return textOf(value) ?? formatNumber(undefined);
|
|
132
|
+
return labels.get(value) ?? value;
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
function finite(value: unknown): number | null {
|
|
117
138
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
118
139
|
}
|
|
@@ -234,7 +255,9 @@ async function portfolioHoldings(
|
|
|
234
255
|
const broker = !!(target.portfolio.brokerId || target.portfolio.brokerInstanceId);
|
|
235
256
|
const showTargets = allocation.targetSum != null;
|
|
236
257
|
const targetNote = describeTargetSum(allocation.targetSum);
|
|
258
|
+
const imported = importedFromText(target.portfolio);
|
|
237
259
|
const notices = [
|
|
260
|
+
...(imported ? [imported] : []),
|
|
238
261
|
...(rows.length > limit
|
|
239
262
|
? [`${rows.length - limit} more position${rows.length - limit === 1 ? "" : "s"} not shown; totals include every position.`]
|
|
240
263
|
: []),
|
|
@@ -244,7 +267,10 @@ async function portfolioHoldings(
|
|
|
244
267
|
|
|
245
268
|
return {
|
|
246
269
|
freshness: { source: broker ? "Your broker account and Gloom Cloud" : "Local portfolio and Gloom Cloud" },
|
|
247
|
-
columns:
|
|
270
|
+
columns: withListingLabels(
|
|
271
|
+
withExtendedColumns(showTargets ? [...POSITION_COLUMNS, ...TARGET_COLUMNS] : POSITION_COLUMNS, extendedSessions),
|
|
272
|
+
tickers,
|
|
273
|
+
),
|
|
248
274
|
rows: shown,
|
|
249
275
|
complete: unavailable.length === 0,
|
|
250
276
|
...(unavailable.length
|
|
@@ -252,6 +278,7 @@ async function portfolioHoldings(
|
|
|
252
278
|
: {}),
|
|
253
279
|
metadata: {
|
|
254
280
|
collection: { kind: "portfolio", id: target.id, name: target.name, broker },
|
|
281
|
+
...(target.portfolio.importedFrom ? { importedFrom: target.portfolio.importedFrom } : {}),
|
|
255
282
|
currency,
|
|
256
283
|
positions: rows.length,
|
|
257
284
|
totals: {
|
|
@@ -299,7 +326,10 @@ async function watchlistHoldings(
|
|
|
299
326
|
const shown = rows.slice(0, limit);
|
|
300
327
|
return {
|
|
301
328
|
freshness: { source: "Local watchlist and Gloom Cloud" },
|
|
302
|
-
columns:
|
|
329
|
+
columns: withListingLabels(
|
|
330
|
+
withExtendedColumns(WATCHLIST_COLUMNS, extendedSessionsOf(shown.map((row) => row.extendedSession))),
|
|
331
|
+
tickers,
|
|
332
|
+
),
|
|
303
333
|
rows: shown,
|
|
304
334
|
metadata: {
|
|
305
335
|
collection: { kind: "watchlist", id: target.id, name: target.name },
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Portfolio } from "../../../types/ticker";
|
|
2
|
+
|
|
3
|
+
/** One line naming the filing an imported portfolio comes from, as `portfolio show`, `fn PF` and the import print it. */
|
|
4
|
+
export function importedFromText(portfolio: Pick<Portfolio, "importedFrom">): string | null {
|
|
5
|
+
const source = portfolio.importedFrom;
|
|
6
|
+
if (!source) return null;
|
|
7
|
+
return `Weights from ${source.name}'s 13F for ${source.period}, filed ${source.filed}; not your holdings.`;
|
|
8
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnalystResearchData, Fundamentals, Quote } from "../../../types/financials";
|
|
2
2
|
import { hasLikelyQuoteUnitMismatch } from "../../../utils/currency-units";
|
|
3
|
+
import { priceEarningsEps } from "../../../utils/fundamentals";
|
|
3
4
|
import { selectMarketCapitalization, type MarketCapitalization } from "../../../utils/market-capitalization";
|
|
4
5
|
import { priceEarningsOnEarnings, type PriceEarnings } from "../../../utils/price-earnings";
|
|
5
6
|
|
|
@@ -54,9 +55,12 @@ function repriceMultiple(
|
|
|
54
55
|
return price / perShare;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Trailing P/E over the current price; not meaningful when the trailing EPS is zero or a loss. An EPS in another
|
|
60
|
+
* currency than the price is repriced at the rate the served P/E converted it at.
|
|
61
|
+
*/
|
|
58
62
|
export function liveTrailingPE(quote: Quote | null | undefined, fundamentals: Fundamentals | undefined): PriceEarnings | undefined {
|
|
59
|
-
return priceEarningsOnEarnings(repriceMultiple(fundamentals?.trailingPE, fundamentals
|
|
63
|
+
return priceEarningsOnEarnings(repriceMultiple(fundamentals?.trailingPE, priceEarningsEps(fundamentals), quote, fundamentals), fundamentals?.eps);
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
/** Forward P/E over the current price; not meaningful when the forward EPS behind it is zero or a loss. */
|
|
@@ -88,7 +88,7 @@ import { useThrottledTickerOrder } from "../use-throttled-ticker-order";
|
|
|
88
88
|
import { useThrottledMemo } from "../use-throttled-memo";
|
|
89
89
|
import { useColumnClock } from "../use-column-clock";
|
|
90
90
|
import { liveMarketCapitalization } from "../live-valuation";
|
|
91
|
-
import { hasUnknownOptionMultiplier } from "
|
|
91
|
+
import { hasUnknownOptionMultiplier } from "../../../../portfolio/position-metrics";
|
|
92
92
|
import {
|
|
93
93
|
allocationTotal,
|
|
94
94
|
buildPortfolioAllocation,
|
|
@@ -126,6 +126,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
126
126
|
|
|
127
127
|
const [streamWindow, setStreamWindow] = useState({ start: 0, end: 24 });
|
|
128
128
|
const [quickAddFocused, setQuickAddFocused] = useState(false);
|
|
129
|
+
// Bumped by the empty table's "+ ticker" button to open the add row.
|
|
130
|
+
const [quickAddRequest, setQuickAddRequest] = useState(0);
|
|
129
131
|
|
|
130
132
|
const {
|
|
131
133
|
cursorSymbol,
|
|
@@ -612,6 +614,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
612
614
|
return null;
|
|
613
615
|
}, [activeCollectionId, config, currentPortfolio]);
|
|
614
616
|
const showQuickAdd = !!(activeCollectionId && activeCollectionEntry && quickAddCollectionKind);
|
|
617
|
+
const requestQuickAdd = useCallback(() => setQuickAddRequest((count) => count + 1), []);
|
|
615
618
|
const quickAddHeight = showQuickAdd ? 1 : 0;
|
|
616
619
|
const selectedFinancials = cursorSymbol ? financialsMap.get(cursorSymbol) : undefined;
|
|
617
620
|
const selectedCap = liveMarketCapitalization(selectedFinancials?.quote, selectedFinancials?.fundamentals);
|
|
@@ -636,6 +639,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
636
639
|
width={width}
|
|
637
640
|
onAdded={handleTickerAdded}
|
|
638
641
|
onFocusChange={setQuickAddFocused}
|
|
642
|
+
focusRequest={quickAddRequest}
|
|
639
643
|
/>
|
|
640
644
|
) : null;
|
|
641
645
|
|
|
@@ -674,6 +678,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
674
678
|
visibleRangeBuffer={3}
|
|
675
679
|
resetScrollKey={activeCollectionId}
|
|
676
680
|
onRowActivate={handleRowActivate}
|
|
681
|
+
onAddTicker={showQuickAdd ? requestQuickAdd : undefined}
|
|
677
682
|
rootHeight={contentHeight}
|
|
678
683
|
/>
|
|
679
684
|
) : (
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
} from "./resolution";
|
|
31
31
|
|
|
32
32
|
const QUICK_ADD_DEBOUNCE_MS = 300;
|
|
33
|
+
/** The pane key that opens the add row, as its footer hint shows it. */
|
|
34
|
+
export const QUICK_ADD_KEY = "a";
|
|
33
35
|
|
|
34
36
|
export type { QuickAddCollectionKind } from "./resolution";
|
|
35
37
|
|
|
@@ -101,6 +103,7 @@ export function QuickAddTickerInput({
|
|
|
101
103
|
width,
|
|
102
104
|
onAdded,
|
|
103
105
|
onFocusChange,
|
|
106
|
+
focusRequest = 0,
|
|
104
107
|
}: {
|
|
105
108
|
collectionId: string;
|
|
106
109
|
collectionKind: QuickAddCollectionKind;
|
|
@@ -109,6 +112,12 @@ export function QuickAddTickerInput({
|
|
|
109
112
|
width: number;
|
|
110
113
|
onAdded: (symbol: string) => void;
|
|
111
114
|
onFocusChange?: (focused: boolean) => void;
|
|
115
|
+
/**
|
|
116
|
+
* Bumped by a control outside the row, such as the empty table's
|
|
117
|
+
* "+ ticker" button, to open it. A click there focuses the pane in the same
|
|
118
|
+
* press, so the row opens once the pane is focused.
|
|
119
|
+
*/
|
|
120
|
+
focusRequest?: number;
|
|
112
121
|
}) {
|
|
113
122
|
const dispatch = useAppDispatch();
|
|
114
123
|
const { notify } = usePluginAppActions();
|
|
@@ -132,6 +141,13 @@ export function QuickAddTickerInput({
|
|
|
132
141
|
setInputFocused(false);
|
|
133
142
|
}, []);
|
|
134
143
|
|
|
144
|
+
const handledFocusRequestRef = useRef(focusRequest);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (focusRequest === handledFocusRequestRef.current || !focused) return;
|
|
147
|
+
handledFocusRequestRef.current = focusRequest;
|
|
148
|
+
focusInput();
|
|
149
|
+
}, [focusInput, focusRequest, focused]);
|
|
150
|
+
|
|
135
151
|
const resetInput = useCallback(() => {
|
|
136
152
|
setInputValue("");
|
|
137
153
|
setValidation(IDLE_VALIDATION);
|
|
@@ -350,7 +366,7 @@ export function QuickAddTickerInput({
|
|
|
350
366
|
|
|
351
367
|
// The row shows only its placeholder, so the footer says which key reaches it.
|
|
352
368
|
usePaneFooter("portfolio-list:quick-add", () => ({
|
|
353
|
-
hints: inputFocused ? [] : [{ id: "add", key:
|
|
369
|
+
hints: inputFocused ? [] : [{ id: "add", key: QUICK_ADD_KEY, label: "dd", onPress: focusInput }],
|
|
354
370
|
}), [focusInput, inputFocused]);
|
|
355
371
|
|
|
356
372
|
return (
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
resolvePortfolioPositionPnl,
|
|
15
15
|
portfolioPnlPercent,
|
|
16
16
|
type PortfolioPositionPnl,
|
|
17
|
-
} from "
|
|
17
|
+
} from "../../../../portfolio/position-metrics";
|
|
18
18
|
|
|
19
19
|
/** A position lot valued from a current quote, in the totals currency. */
|
|
20
20
|
interface PricedPortfolioLot {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import { QUICK_ADD_KEY } from "./quick-add";
|
|
2
3
|
import {
|
|
3
4
|
TickerListTableView,
|
|
4
5
|
type DataTableKeyEvent,
|
|
@@ -15,7 +16,7 @@ import type { TickerRecord } from "../../../types/ticker";
|
|
|
15
16
|
import { objectVersion } from "../../../utils/object-version";
|
|
16
17
|
import { columnContextVersion } from "./cell-version";
|
|
17
18
|
import { getColumnValue, type ColumnContext } from "./metrics";
|
|
18
|
-
import { portfolioPnlLabel } from "
|
|
19
|
+
import { portfolioPnlLabel } from "../../../portfolio/position-metrics";
|
|
19
20
|
import { PORTFOLIO_ROW_MENU_KEYS } from "./row-actions";
|
|
20
21
|
|
|
21
22
|
export type { QuoteFlashDirection };
|
|
@@ -98,6 +99,7 @@ export function PortfolioTickerTable({
|
|
|
98
99
|
visibleRangeBuffer,
|
|
99
100
|
resetScrollKey,
|
|
100
101
|
onRowActivate,
|
|
102
|
+
onAddTicker,
|
|
101
103
|
rootHeight,
|
|
102
104
|
}: {
|
|
103
105
|
columns: ColumnConfig[];
|
|
@@ -117,6 +119,8 @@ export function PortfolioTickerTable({
|
|
|
117
119
|
visibleRangeBuffer?: number;
|
|
118
120
|
resetScrollKey?: unknown;
|
|
119
121
|
onRowActivate?: (ticker: TickerRecord) => void;
|
|
122
|
+
/** Opens the add row; the empty table offers it as a button. */
|
|
123
|
+
onAddTicker?: () => void;
|
|
120
124
|
rootHeight?: number;
|
|
121
125
|
}) {
|
|
122
126
|
const cellCacheRef = useRef(createRowValueCache<string, ReturnType<typeof getColumnValue>>(5000));
|
|
@@ -175,6 +179,8 @@ export function PortfolioTickerTable({
|
|
|
175
179
|
resetScrollKey={resetScrollKey}
|
|
176
180
|
onRowActivate={onRowActivate}
|
|
177
181
|
rowMenuAccelerators={PORTFOLIO_ROW_MENU_KEYS}
|
|
182
|
+
onAddTicker={onAddTicker}
|
|
183
|
+
addTickerShortcut={onAddTicker ? QUICK_ADD_KEY : undefined}
|
|
178
184
|
rootHeight={rootHeight}
|
|
179
185
|
/>
|
|
180
186
|
);
|
|
@@ -2,6 +2,8 @@ import { apiClient } from "../../../api-client";
|
|
|
2
2
|
import type { PowerBoard, PowerDetail, PowerFilter, PowerHistory, PowerProject } from "../../../api-client/power";
|
|
3
3
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
4
|
import { loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
5
|
+
import { powerTickerScopes, type PowerTab, type PowerTickerScope } from "./model";
|
|
6
|
+
import { powerQuery } from "./query";
|
|
5
7
|
|
|
6
8
|
const cacheOptions = { source: "gloom-cloud", schemaVersion: 1, policy: { staleMs: 60 * 60_000, expireMs: 30 * 86_400_000 } };
|
|
7
9
|
export const powerBoardCache = createPluginCache<PowerBoard>({ ...cacheOptions, kind: "power-board" });
|
|
@@ -88,6 +90,20 @@ export async function fetchAllPowerHistory(filter: PowerFilter, client: Pick<Pow
|
|
|
88
90
|
}
|
|
89
91
|
return { ...first, points, hasMore: false, nextOffset: null };
|
|
90
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* How many records name a ticker on each other tab, for a tab that has none:
|
|
95
|
+
* one single-row page per scope, read for its total, which a free preview
|
|
96
|
+
* reports in full. A scope that fails to answer has no total rather than
|
|
97
|
+
* failing the tab it explains.
|
|
98
|
+
*/
|
|
99
|
+
export async function fetchPowerTickerScopes(symbol: string, tab: PowerTab, context: string,
|
|
100
|
+
client: Pick<PowerApi, "getCloudPowerBoard"> = apiClient, signal?: AbortSignal): Promise<PowerTickerScope[]> {
|
|
101
|
+
return Promise.all(powerTickerScopes(tab, context).map((scope) => {
|
|
102
|
+
const count = () => fetchPowerBoard({ ...powerQuery({ tab: scope.tab, context: scope.context }, symbol), limit: 1 }, client, signal).then((board) => board.total);
|
|
103
|
+
// One retry: a count is cheap, and the first of several at once is the one most often refused.
|
|
104
|
+
return count().catch(() => count()).then((total) => ({ ...scope, total }), () => ({ ...scope, total: null }));
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
91
107
|
const key = (query: unknown, scope: string) => `${scope}:${JSON.stringify(query)}`;
|
|
92
108
|
export const loadPowerBoard = (filter: PowerFilter, scope: string, force = false, signal?: AbortSignal) =>
|
|
93
109
|
loadCloudResource(powerBoardCache, key(filter, scope), () => fetchPowerBoard(filter, apiClient, signal), { force, validate: validatePowerBoard });
|
|
@@ -2,7 +2,7 @@ import type { PowerBoard, PowerHistory } from "../../../api-client/power";
|
|
|
2
2
|
import type { PaneScreenshotEvidenceHook } from "../../../cli/pane-functions/screenshot-evidence";
|
|
3
3
|
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
4
4
|
import { isRecord } from "../../../utils/guards";
|
|
5
|
-
import { fetchPowerBoard, fetchAllPowerHistory, validatePowerBoard, validatePowerHistory } from "./client";
|
|
5
|
+
import { fetchPowerBoard, fetchAllPowerHistory, fetchPowerTickerScopes, validatePowerBoard, validatePowerHistory } from "./client";
|
|
6
6
|
import { otherRows, powerTab, projectRows } from "./model";
|
|
7
7
|
import { powerQuery } from "./query";
|
|
8
8
|
interface PowerEvidence {
|
|
@@ -29,7 +29,11 @@ export const powerScreenshotEvidence: PaneScreenshotEvidenceHook<PowerEvidence>
|
|
|
29
29
|
const filter = powerQuery({ ...settings, ...resolved.options, tab }, symbol);
|
|
30
30
|
const board = await fetchPowerBoard({ ...filter, limit: 100 });
|
|
31
31
|
const history = tab === "history" ? await fetchAllPowerHistory(filter) : undefined;
|
|
32
|
-
|
|
32
|
+
// A ticker with nothing on this tab: the counts that say where its records are, captured with the rest.
|
|
33
|
+
const empty = !!symbol && tab !== "coverage" && !(tab === "history" ? history?.points.length : tab === "outcomes" ? board.rates.length
|
|
34
|
+
: tab === "utilities" ? board.exposure.length : board.projects.length);
|
|
35
|
+
const tickerScopes = empty && symbol ? await fetchPowerTickerScopes(symbol, tab, String(settings.context ?? resolved.options.context ?? "capacity")) : undefined;
|
|
36
|
+
return { settings: { powerSnapshot: { board, ...(history ? { history } : {}), ...(tickerScopes ? { tickerScopes } : {}) } }, financials: [] };
|
|
33
37
|
},
|
|
34
38
|
read(value) {
|
|
35
39
|
if (!isRecord(value) || value.kind !== "power" || value.version !== 1 || value.complete !== true || !isRecord(value.board)
|
|
@@ -1,11 +1,50 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { DataTableColumn } from "../../../components";
|
|
2
|
+
import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneRow } from "../../../types/plugin";
|
|
3
|
+
import { withoutQuietColumns } from "../shared/research-cells";
|
|
4
|
+
import { fetchPowerBoard, fetchAllPowerHistory, fetchPowerTickerScopes } from "./client";
|
|
3
5
|
import { powerQuery } from "./query";
|
|
4
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
COVERAGE_COLUMNS, EXPOSURE_COLUMNS, GENERATION_COLUMNS, HISTORY_COLUMNS, POWER_TABS, PROJECT_COLUMNS, RATE_COLUMNS, UTILITY_COLUMNS,
|
|
8
|
+
historyRows, powerCoverageNote, powerLoadedSources, powerNumber, powerPercent, powerRegion, powerScopeOptions, powerTab, powerTickerEmpty, projectRows, tickerLabel, titleCase, UNMAPPED, type PowerTab,
|
|
9
|
+
} from "./model";
|
|
10
|
+
|
|
11
|
+
/** Bars the pane draws beside the numbers they are made of; the report has the numbers. */
|
|
12
|
+
const GRAPHIC_COLUMNS = new Set(["outcome", "pipeline"]);
|
|
13
|
+
/** Columns that only repeat a default on every row, as the pane leaves them out. */
|
|
14
|
+
const QUIET_COLUMNS: Record<string, readonly string[]> = { scope: ["Project"], unknownCapacity: ["0"], unknown: ["0"], fuel: ["Load"], role: ["Direct"], kind: ["Queue"] };
|
|
15
|
+
const text = (value: unknown) => value == null || value === "" ? "-" : String(value);
|
|
16
|
+
const coded = (value: unknown) => typeof value === "string" && value ? titleCase(value) : "-";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The pane's columns for a tab, printed as the pane prints them: MW and MWh
|
|
20
|
+
* to a tenth with thousands separators, rates as percentages, register codes
|
|
21
|
+
* by name, and each record's listed company or "unmapped". Raw metrics and
|
|
22
|
+
* evidence stay in the exported rows, out of the table.
|
|
23
|
+
*/
|
|
24
|
+
function reportColumns(tab: PowerTab, context: unknown): HeadlessPaneColumn[] {
|
|
25
|
+
const columns: DataTableColumn[] = tab === "capacity" && context === "generation" ? GENERATION_COLUMNS : tab === "capacity" && context === "utility" ? UTILITY_COLUMNS
|
|
26
|
+
: ["queue", "loads", "capacity"].includes(tab) ? PROJECT_COLUMNS : tab === "history" ? HISTORY_COLUMNS : tab === "outcomes" ? RATE_COLUMNS : tab === "utilities" ? EXPOSURE_COLUMNS : COVERAGE_COLUMNS;
|
|
27
|
+
// The coverage list names its register by its own id; everything else by the record's source.
|
|
28
|
+
const region = (value: unknown, row: HeadlessPaneRow) => typeof value !== "string" ? "-"
|
|
29
|
+
: tab === "utilities" ? value : powerRegion(value, String((tab === "coverage" ? row.id : row.sourceId) ?? ""));
|
|
30
|
+
const format = (column: DataTableColumn): HeadlessPaneColumn["format"] => {
|
|
31
|
+
if (column.id === "region") return region;
|
|
32
|
+
if (["fuel", "status", "kind", "role"].includes(column.id)) return coded;
|
|
33
|
+
if (column.id === "ticker") return (value) => typeof value === "string" && value ? value : UNMAPPED;
|
|
34
|
+
if (column.id === "developer") return (value, row) => text(value ?? row.utility);
|
|
35
|
+
if (column.id === "observedAt") return (value) => typeof value === "string" ? value.slice(0, 16).replace("T", " ") : "-";
|
|
36
|
+
if (/Rate$/.test(column.id)) return (value) => typeof value === "number" ? powerPercent(value) : "-";
|
|
37
|
+
if (column.align === "right") return (value) => typeof value === "number" ? `${column.id === "changeMw" && value > 0 ? "+" : ""}${powerNumber(value)}` : "-";
|
|
38
|
+
return text;
|
|
39
|
+
};
|
|
40
|
+
// The pane groups outcomes under region headings; the report names the region on each row.
|
|
41
|
+
return [...(tab === "outcomes" ? [{ id: "region", label: "Region", width: 16, align: "left" as const }] : []), ...columns].filter((column) => !GRAPHIC_COLUMNS.has(column.id))
|
|
42
|
+
.map((column) => ({ key: column.id, header: column.label, align: column.align === "right" ? "right" as const : "left" as const, format: format(column) }));
|
|
43
|
+
}
|
|
5
44
|
|
|
6
45
|
export const powerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
7
46
|
shape: "bundle",
|
|
8
|
-
freshness: { source: "Grid operator queues and utility filings", status: "not-a-feed", basis: "published records" },
|
|
47
|
+
freshness: { source: "Grid operator queues and utility filings", status: "not-a-feed", basis: "published records", dataset: true },
|
|
9
48
|
argument: { kind: "free-text", optional: true, placeholder: "ticker", description: "Optional listed developer or utility ticker, with exchange suffix where needed." },
|
|
10
49
|
options: [
|
|
11
50
|
{ key: "tab", type: "enum", settingKey: "tab", defaultValue: "queue", values: POWER_TABS.map(({ value }) => ({ value })), description: "Queue, history, outcomes, large loads, utility exposure, capacity or coverage." },
|
|
@@ -36,10 +75,35 @@ export const powerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
36
75
|
projects.push(...page.projects); offset = page.nextOffset; hasMore = page.hasMore;
|
|
37
76
|
}
|
|
38
77
|
const history = tab === "history" ? await fetchAllPowerHistory(filter, ctx.apiClient) : null;
|
|
39
|
-
|
|
78
|
+
// Exported rows keep every field the server sent; the pane's derived cells (the ticker map, location, scope) ride beside them.
|
|
79
|
+
const cells = projectRows(projects);
|
|
80
|
+
const rows: HeadlessPaneRow[] = tab === "history" ? historyRows(history!.points).map((point) => ({ ...point, date: point.basis === "published" ? point.period : point.observedAt.slice(0, 16).replace("T", " ") }))
|
|
81
|
+
: tab === "outcomes" ? board.rates.map((rate) => ({ ...rate }))
|
|
82
|
+
: tab === "utilities" ? board.exposure.map((exposure) => ({ ...exposure, ticker: tickerLabel(exposure) }))
|
|
83
|
+
: tab === "coverage" ? board.coverage.map((coverage) => ({ ...coverage }))
|
|
84
|
+
: projects.map((project, index) => {
|
|
85
|
+
const { ticker, location, scope, generationMwh, summerPeakDemandMw, winterPeakDemandMw, salesMwh, revenueUsd } = cells[index]!.cells;
|
|
86
|
+
return { ...project, ticker, location, scope, generationMwh, summerPeakDemandMw, winterPeakDemandMw, salesMwh, revenueUsd };
|
|
87
|
+
});
|
|
40
88
|
const locked = history?.locked ?? (tab === "outcomes" ? board.locked.rates : tab === "utilities" ? board.locked.exposure : tab === "coverage" ? 0 : board.locked.projects);
|
|
41
|
-
|
|
89
|
+
const columns = reportColumns(tab, args.options.context);
|
|
90
|
+
const shown = withoutQuietColumns(columns.map((column) => ({ id: column.key, label: column.header, width: 0, align: "left" as const })), rows,
|
|
91
|
+
(row, id) => columns.find((column) => column.key === id)!.format!(row[id], row), QUIET_COLUMNS).map((column) => column.id);
|
|
92
|
+
// A ticker with nothing on this tab says where its records are instead; a narrower filter is the filters' doing.
|
|
93
|
+
const narrowed = ["country", "region", "fuel", "status", "search", "sourceId", "from", "to", "loadClass", "historical"].some((key) => !!args.options[key]);
|
|
94
|
+
const empty = !rows.length && symbol && !narrowed && tab !== "coverage" ? await fetchPowerTickerScopes(symbol, tab, String(args.options.context ?? "capacity"), ctx.apiClient)
|
|
95
|
+
.then((scopes) => {
|
|
96
|
+
const message = powerTickerEmpty(symbol, tab, String(args.options.context ?? "capacity"), scopes);
|
|
97
|
+
const where = message.found.map((scope) => `${powerNumber(scope.total)} ${scope.label} (${powerScopeOptions(scope)})`).join(", ");
|
|
98
|
+
const loaded = powerLoadedSources(tab, board.coverage);
|
|
99
|
+
return [message.title, where ? `${symbol} has ${where}.` : message.hint?.replace(/\.$/, " (--search <name>)."),
|
|
100
|
+
loaded ? `Sources loaded now: ${loaded}.` : null].filter(Boolean).join(" ");
|
|
101
|
+
}) : "No reported records match these filters.";
|
|
102
|
+
// Missing or unsupported sources are coverage, not failures: one plain line, details in --tab coverage.
|
|
103
|
+
const coverageNote = tab === "capacity" ? null : powerCoverageNote(board.coverage);
|
|
104
|
+
return { complete: locked === 0, sections: [{ title: POWER_TABS.find((t) => t.value === tab)!.label, columns: columns.filter((column) => shown.includes(column.key)), rows, empty }],
|
|
42
105
|
metadata: { generatedAt: board.generatedAt, access: board.access, locked, total: board.total, units: "MW unless specified", filters: filter, aggregates: board.aggregates, coverage: board.coverage },
|
|
43
|
-
|
|
106
|
+
...(coverageNote ? { notes: [coverageNote] } : {}),
|
|
107
|
+
...(locked ? { errors: ["Additional records and history need Gloom Pro."] } : {}) };
|
|
44
108
|
},
|
|
45
109
|
};
|
|
@@ -10,7 +10,10 @@ export const powerModule: PluginModule = {
|
|
|
10
10
|
description: "Pro: interconnection queues, historical MW and outcomes, datacenter loads, utility exposure and generation capacity, with primary evidence.",
|
|
11
11
|
keywords: ["power", "electricity", "interconnection", "grid", "datacenter", "utility", "renewables", "storage", "capacity"],
|
|
12
12
|
shortcut: { prefix: "POWER", argKind: "text", argPlaceholder: "ticker", argOptional: true }, headless: powerHeadless,
|
|
13
|
-
createInstance: (_context, options) =>
|
|
13
|
+
createInstance: (_context, options) => {
|
|
14
|
+
const symbol = options?.symbol ?? options?.arg?.trim().toUpperCase() ?? "";
|
|
15
|
+
return { placement: "floating", params: { symbol }, ...(symbol ? { title: `POWER ${symbol}` } : {}) };
|
|
16
|
+
} }],
|
|
14
17
|
setup(ctx) { for (const cache of [powerBoardCache, powerHistoryCache, powerDetailCache]) cache.attach(ctx.persistence); },
|
|
15
18
|
dispose() { for (const cache of [powerBoardCache, powerHistoryCache, powerDetailCache]) cache.reset(); },
|
|
16
19
|
};
|
|
@@ -8,10 +8,81 @@ export const powerTab = (value: unknown): PowerTab => POWER_TABS.find((tab) => t
|
|
|
8
8
|
const REGISTERS: Record<string, string> = { "neso-tec": "GB transmission", "neso-embedded": "GB embedded", "neso-interconnector": "GB interconnectors" };
|
|
9
9
|
const REGIONS: Record<string, string> = { LBNL: "US project queues", "LBNL-ANNUAL": "US annual queues", EIA860: "US generation capacity", EIA861: "US utilities", EIA923: "US generation output" };
|
|
10
10
|
export const powerRegion = (value: string, sourceId?: string) => REGISTERS[sourceId ?? ""] ?? REGIONS[value] ?? value;
|
|
11
|
+
/** A source with stored records, even when its latest refresh failed. */
|
|
12
|
+
const loadedSource = (c: PowerCoverage) => (c.status === "current" || c.status === "stale") && c.records > 0;
|
|
13
|
+
const names = (rows: readonly PowerCoverage[]) => [...new Set(rows.map((c) => c.region === "EirGrid" ? "Ireland" : c.region))].join(", ");
|
|
14
|
+
/**
|
|
15
|
+
* Which US grid operators' queues loaded, in plain words. Source errors and
|
|
16
|
+
* unsupported registers stay in the Coverage tab and on the server; a missing
|
|
17
|
+
* queue is a gap in coverage, never zero.
|
|
18
|
+
*/
|
|
19
|
+
export function powerCoverageNote(coverage: readonly PowerCoverage[]): string | null {
|
|
20
|
+
const queues = coverage.filter((c) => c.country === "US" && c.role === "direct" && c.kind === "queue");
|
|
21
|
+
const missing = queues.filter((c) => !loadedSource(c));
|
|
22
|
+
if (!missing.length) return null;
|
|
23
|
+
const loaded = queues.filter(loadedSource);
|
|
24
|
+
return `US queues: ${loaded.length ? `${names(loaded)} loaded; ` : ""}${names(missing)} not available`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The registers carrying records right now, named, for a ticker that has none
|
|
28
|
+
* in them: grid operator and TSO registers, or the EIA filings behind the
|
|
29
|
+
* Capacity tab. Null when nothing is loaded.
|
|
30
|
+
*/
|
|
31
|
+
export function powerLoadedSources(tab: PowerTab, coverage: readonly PowerCoverage[]): string | null {
|
|
32
|
+
const loaded = coverage.filter((c) => loadedSource(c) && (tab === "capacity" ? c.role === "context" : c.role === "direct"));
|
|
33
|
+
if (!loaded.length) return null;
|
|
34
|
+
return tab === "capacity" ? "US EIA generation and utility filings" : names([...loaded.filter((c) => c.country === "US"), ...loaded.filter((c) => c.country !== "US")]);
|
|
35
|
+
}
|
|
11
36
|
export const titleCase = (value: string) => value.replace(/(^|[ _-])\w/g, (match) => match.toUpperCase());
|
|
12
37
|
export const powerNumber = (n: number | null | undefined) => n == null ? "--" : n.toLocaleString("en-US", { maximumFractionDigits: 1 });
|
|
13
38
|
export const powerPercent = (n: number | null) => n == null ? "--" : `${(n * 100).toFixed(1)}%`;
|
|
14
|
-
|
|
39
|
+
/** The listed companies a record names, or null when its register names none: the ticker map. */
|
|
40
|
+
export const tickerLabel = (row: { entities: PowerProject["entities"] }) => [...new Set(row.entities.flatMap((e) => e.tickers.map((t) => t.ticker)))].join(", ") || null;
|
|
41
|
+
/** What the ticker column says for a record no listed company is mapped to. */
|
|
42
|
+
export const UNMAPPED = "unmapped";
|
|
43
|
+
|
|
44
|
+
/** Where else a ticker's records can sit: each tab, and the capacity tab's contexts, with what they list. */
|
|
45
|
+
const POWER_TICKER_SCOPES = [
|
|
46
|
+
{ tab: "queue", context: "capacity", label: "interconnection queue records", view: "Queue" },
|
|
47
|
+
{ tab: "loads", context: "capacity", label: "large-load requests", view: "Loads" },
|
|
48
|
+
{ tab: "capacity", context: "capacity", label: "generation capacity records", view: "Capacity" },
|
|
49
|
+
{ tab: "capacity", context: "generation", label: "monthly generation records", view: "Generation" },
|
|
50
|
+
{ tab: "capacity", context: "utility", label: "utility sales records", view: "Utility sales" },
|
|
51
|
+
] as const satisfies readonly { tab: PowerTab; context: string; label: string; view: string }[];
|
|
52
|
+
/** `total` is null for a scope that did not answer, which is no evidence either way. */
|
|
53
|
+
export type PowerTickerScope = (typeof POWER_TICKER_SCOPES)[number] & { total: number | null };
|
|
54
|
+
const sameScope = (scope: { tab: string; context: string }, tab: PowerTab, context: string) =>
|
|
55
|
+
scope.tab === tab && (tab !== "capacity" || scope.context === context)
|
|
56
|
+
// Utilities are the large loads summed by utility: a ticker with no loads has no exposure either.
|
|
57
|
+
|| tab === "utilities" && scope.tab === "loads";
|
|
58
|
+
/** The scopes to count a ticker's records in when the open tab has none of them. */
|
|
59
|
+
export const powerTickerScopes = (tab: PowerTab, context: string) => POWER_TICKER_SCOPES.filter((scope) => !sameScope(scope, tab, context));
|
|
60
|
+
|
|
61
|
+
/** What the open tab lists, for the sentence that says none of it maps to a ticker. */
|
|
62
|
+
function powerTabRecords(tab: PowerTab, context: string): string {
|
|
63
|
+
if (tab === "capacity") return POWER_TICKER_SCOPES.find((scope) => scope.tab === "capacity" && scope.context === context)?.label ?? "capacity records";
|
|
64
|
+
return { queue: "interconnection queue records", history: "queue history records", outcomes: "queue outcomes", loads: "large-load requests",
|
|
65
|
+
utilities: "large-load requests or utility exposure", coverage: "records" }[tab];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Why a ticker's tab is empty, and where its records are instead: the title
|
|
70
|
+
* says what does not map to it, the hint where it has records, or, once every
|
|
71
|
+
* other tab has answered with none, that a name search is what is left.
|
|
72
|
+
*/
|
|
73
|
+
export function powerTickerEmpty(symbol: string, tab: PowerTab, context: string, scopes: readonly PowerTickerScope[]) {
|
|
74
|
+
const found = scopes.filter((scope): scope is PowerTickerScope & { total: number } => (scope.total ?? 0) > 0);
|
|
75
|
+
const answered = scopes.length > 0 && scopes.every((scope) => scope.total != null);
|
|
76
|
+
return {
|
|
77
|
+
title: `No ${powerTabRecords(tab, context)} map to ${symbol}.`,
|
|
78
|
+
found,
|
|
79
|
+
hint: found.length ? `${symbol} has ${found.map((scope) => `${powerNumber(scope.total)} ${scope.label}`).join(", ")}.`
|
|
80
|
+
: answered ? `No power records map to ${symbol}; search by the company's or a subsidiary's name instead.` : null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** The options that open a scope from the command line. */
|
|
84
|
+
export const powerScopeOptions = (scope: { tab: string; context: string }) =>
|
|
85
|
+
scope.tab === "capacity" && scope.context !== "capacity" ? `--tab capacity --context ${scope.context}` : `--tab ${scope.tab}`;
|
|
15
86
|
const historyKey = (p: PowerHistoryPoint) => JSON.stringify([p.sourceId, p.country, p.region, p.kind, p.fuel, p.status, p.historical, p.basis]);
|
|
16
87
|
/** Annual benchmarks use their stated period. Current queues use when we observed them, never a proposed completion date. */
|
|
17
88
|
export function historyDate(p: PowerHistoryPoint): Date {
|