gloomberb 0.19.0 → 0.19.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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +23 -1
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/data.ts +2 -1
- package/src/api-client/errors.ts +38 -0
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +12 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/architecture/core-builtin-allowlist.json +2 -2
- package/src/capabilities/chart-series.ts +2 -1
- package/src/cli/commands/market.ts +44 -16
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/overview.ts +84 -19
- package/src/cli/commands/search.ts +13 -13
- package/src/cli/commands/ticker.ts +32 -2
- package/src/cli/context.ts +1 -0
- package/src/cli/errors.ts +17 -2
- package/src/cli/glossary.ts +8 -1
- package/src/cli/history-rows.ts +35 -7
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/known-gaps.ts +11 -0
- package/src/cli/listing-arg.ts +74 -11
- package/src/cli/pane-functions/capabilities.ts +1 -0
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/freshness.ts +7 -1
- package/src/cli/pane-functions/function-help.ts +7 -0
- package/src/cli/pane-functions/index.ts +6 -3
- package/src/cli/quote-answer.ts +30 -7
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/routes/root/runtime.ts +29 -8
- package/src/components/command-bar/selection-runtime.ts +64 -32
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +2 -2
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +23 -6
- package/src/components/layout/pane/footer/model.ts +116 -16
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +37 -3
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-wizard.tsx +6 -3
- package/src/components/ticker/list-table-view.tsx +4 -20
- package/src/components/ui/display.tsx +29 -3
- package/src/components/ui/icon.tsx +12 -3
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/plugin-cache.ts +20 -3
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/resource-store.ts +130 -58
- package/src/data/saved-copies.ts +8 -0
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +313 -0
- package/src/data/sqlite/checkpointer.ts +198 -0
- package/src/data/sqlite/database.ts +47 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +32 -5
- package/src/i18n/es.ts +2 -4
- package/src/i18n/ja.ts +2 -4
- package/src/i18n/ko.ts +2 -4
- package/src/i18n/zh-cn.ts +2 -4
- package/src/i18n/zh-tw.ts +2 -4
- package/src/market-data/depositary-pair.ts +25 -1
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -1
- package/src/news/types.ts +6 -0
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli.ts +34 -11
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +15 -6
- package/src/plugins/builtin/alerts/headless.ts +10 -6
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +5 -0
- package/src/plugins/builtin/chart-composer/index.tsx +4 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +5 -1
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -22
- package/src/plugins/builtin/clinical-trials/pane.tsx +46 -27
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +87 -9
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/company-attention/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/pane.tsx +3 -0
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +4 -2
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/credit-documents/headless.ts +3 -3
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/series.ts +4 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +37 -17
- package/src/plugins/builtin/earnings-calls/headless.ts +13 -3
- package/src/plugins/builtin/earnings-calls/pane.tsx +34 -14
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +18 -4
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +10 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +3 -0
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/gpu/client.ts +4 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +61 -59
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +4 -2
- package/src/plugins/builtin/holders/headless.ts +4 -2
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/macro-day/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- package/src/plugins/builtin/market-movers/client.ts +3 -0
- package/src/plugins/builtin/market-movers/headless.ts +3 -1
- package/src/plugins/builtin/market-movers/index.tsx +10 -5
- package/src/plugins/builtin/market-movers/screener.ts +27 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +3 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -6
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -6
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +9 -7
- package/src/plugins/builtin/research/event-model.ts +30 -4
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +8 -25
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +52 -15
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- package/src/plugins/builtin/risk-factors/data.ts +12 -6
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- package/src/plugins/builtin/scanner/live-headless.ts +21 -2
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +20 -7
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/report-freshness.ts +8 -0
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/thirteenf/api.ts +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +39 -11
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +21 -5
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -2
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +59 -8
- package/src/plugins/builtin/ticker-detail/overview/model.ts +10 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +11 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +24 -13
- package/src/plugins/builtin/treasury-auctions/pane.tsx +18 -6
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +11 -4
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/yield-curve/client.ts +59 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/installer.ts +50 -39
- package/src/portfolio/column-values.ts +11 -7
- package/src/renderers/dom/desktop/icons.tsx +10 -2
- package/src/renderers/electrobun/bun/desktop/backend.ts +9 -10
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +9 -1
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +45 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/launcher/preload.ts +13 -0
- package/src/renderers/electrobun/launcher/windows-emulation.ts +50 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/news-source.ts +2 -0
- package/src/renderers/electrobun/view/ui-host.tsx +16 -1
- package/src/series/resolve.ts +2 -1
- package/src/series/types.ts +5 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +8 -4
- package/src/sources/gloomberb-cloud/index.ts +42 -8
- package/src/telemetry/stall-reports-node.ts +13 -2
- package/src/telemetry/stall-reports.ts +189 -13
- package/src/test-support/outage.ts +8 -0
- package/src/tickers/company-name-listing.ts +110 -40
- package/src/tickers/company-name.ts +71 -2
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/search/home-listing.ts +93 -8
- package/src/tickers/search/index.ts +7 -3
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/market.ts +5 -3
- package/src/time-series/price-comparison.ts +20 -2
- package/src/time-series/resolve.ts +20 -1
- package/src/time-series/types.ts +7 -0
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +45 -3
- package/src/types/headless.ts +6 -0
- package/src/ui/host.tsx +6 -1
- package/src/utils/crypto-pair.ts +121 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +53 -10
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +47 -2
- package/src/utils/perf-marks.ts +23 -11
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/version.ts +2 -2
|
@@ -2,10 +2,14 @@ import type { CotClass, CotContractPayload, CotFamily, CotHistoryPoint, CotPosit
|
|
|
2
2
|
import { staticSeries } from "../../../components/chart/static/series";
|
|
3
3
|
import type { ResolvedSeries } from "../../../time-series/types";
|
|
4
4
|
import type { PricePoint } from "../../../types/financials";
|
|
5
|
-
import { formatPriceObservation } from "../../../market-data/market/format";
|
|
5
|
+
import { formatMarketPriceWithCurrency, formatPriceObservation } from "../../../market-data/market/format";
|
|
6
|
+
import { futuresPricePer } from "../../../market-data/futures-contract";
|
|
7
|
+
import { unitCurrencyToken } from "../../../time-series/price-units";
|
|
6
8
|
import { cleanFloat32Price } from "../../../cli/history-rows";
|
|
7
9
|
import { cryptoPairCoin, isFuturesSymbol } from "../shared/crypto-pair";
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
bestNameMatch, FUTURES_CONTRACTS, futuresBoardContract, futuresBoardContractForTicker, futuresContractByCode, futuresContractByName, type FuturesContract,
|
|
12
|
+
} from "../../../market-data/futures-board";
|
|
9
13
|
|
|
10
14
|
export const COT_CLASSES: Record<CotFamily, Array<{ value: CotClass; label: string }>> = {
|
|
11
15
|
legacy: [{ value: "noncommercial", label: "Noncommercial" }, { value: "commercial", label: "Commercial" }, { value: "nonreportable", label: "Nonreportable" }],
|
|
@@ -162,7 +166,35 @@ export function cotContractCode(value: unknown): string | null {
|
|
|
162
166
|
if (alias) return alias;
|
|
163
167
|
const market = typedMarket(value);
|
|
164
168
|
if (market && "root" in market) return ROOTS[market.root]!.code;
|
|
165
|
-
|
|
169
|
+
if (market) return boardMarketCode(market.board);
|
|
170
|
+
const proxy = proxyMarket(value);
|
|
171
|
+
const proxied = proxy ? futuresContractByCode(proxy.root) : null;
|
|
172
|
+
return (proxied ? boardMarketCode(proxied) : null) ?? marketCode(input);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Markets the CFTC does not report, opened as the one traders price and hedge
|
|
176
|
+
* them by: jet fuel (kerosene) as NY Harbor ULSD, heating oil (HO), which
|
|
177
|
+
* airlines' fuel hedges and analysts follow it with. Opened this way, the
|
|
178
|
+
* pane's title and the report say which market stands in.
|
|
179
|
+
*/
|
|
180
|
+
const PROXY_MARKETS: ReadonlyArray<{ names: readonly string[]; root: string; subject: string }> = [
|
|
181
|
+
{ names: ["jet fuel", "jet fuels", "kerosene", "kerosine", "jet kerosene", "aviation fuel", "avtur"], root: "HO", subject: "jet fuel" },
|
|
182
|
+
];
|
|
183
|
+
function proxyMarket(value: string) {
|
|
184
|
+
const typed = value.trim().toLowerCase().replace(/[\s_-]+/g, " ");
|
|
185
|
+
return PROXY_MARKETS.find((proxy) => proxy.names.includes(typed)) ?? null;
|
|
186
|
+
}
|
|
187
|
+
/** What a typed name opens in place of a market the CFTC does not report ("jet fuel" opens HO), or null. */
|
|
188
|
+
export function cotProxyFor(value: unknown): { subject: string; root: string } | null {
|
|
189
|
+
if (typeof value !== "string" || typedMarket(value) || rootAlias(value.trim().toUpperCase())) return null;
|
|
190
|
+
const proxy = proxyMarket(value);
|
|
191
|
+
return proxy ? { subject: proxy.subject, root: proxy.root } : null;
|
|
192
|
+
}
|
|
193
|
+
/** The line a report opened through a proxy says: "Jet fuel has no CFTC market: heating oil (NY Harbor ULSD, HO) is the usual proxy." */
|
|
194
|
+
export function cotProxyNote(proxy: { subject: string; root: string }): string {
|
|
195
|
+
const subject = `${proxy.subject[0]!.toUpperCase()}${proxy.subject.slice(1)}`;
|
|
196
|
+
return `${subject} has no CFTC market: heating oil (NY Harbor ULSD, ${proxy.root}) is the usual proxy. Its price: gloom quote ${proxy.root}=F; `
|
|
197
|
+
+ "US jet fuel stocks (EIA, weekly): gloom fn G CAP:doe:jet";
|
|
166
198
|
}
|
|
167
199
|
/** The markets the unknown-market hint names, by the code and the word a newcomer knows them by. */
|
|
168
200
|
const SUGGESTED_MARKETS: ReadonlyArray<readonly [string, string]> = [
|
|
@@ -234,10 +266,27 @@ export function cotInteger(value: number | null, signed = false): string {
|
|
|
234
266
|
return value == null ? "--" : `${signed && value > 0 ? "+" : ""}${value.toLocaleString("en-US", { maximumFractionDigits: 0 })}`;
|
|
235
267
|
}
|
|
236
268
|
|
|
269
|
+
/**
|
|
270
|
+
* The front price's unit as a quote of the future writes it after the price:
|
|
271
|
+
* `USD/gal` for heating oil ($4.6218/gal), `USD` for gold, from the
|
|
272
|
+
* contract's unit ("USD / gallon"). Null where the unit names no currency
|
|
273
|
+
* (index points, Treasury points) or is unknown: the price reads bare.
|
|
274
|
+
*/
|
|
275
|
+
export function cotFrontPriceUnit(code: string, contractUnit: string | null | undefined): string | null {
|
|
276
|
+
const currency = contractUnit ? unitCurrencyToken(contractUnit.split("/")[0] ?? "") : null;
|
|
277
|
+
if (!currency) return null;
|
|
278
|
+
const root = cotRoot(code) ?? undefined;
|
|
279
|
+
return `${currency}${futuresPricePer({ root, spec: { unit: contractUnit } } as Parameters<typeof futuresPricePer>[0], currency)}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
237
282
|
/** Legend values stay exact: a compact 7.8K hides the futures price and -100K the net position. */
|
|
238
|
-
export function cotLegendValue(value: number, series: Pick<ResolvedSeries, "id">): string {
|
|
283
|
+
export function cotLegendValue(value: number, series: Pick<ResolvedSeries, "id"> & Partial<Pick<ResolvedSeries, "unit">>): string {
|
|
284
|
+
if (series.id !== "price") return cotInteger(value, true);
|
|
239
285
|
// Drop the float32 tail of provider bars without rounding away a 1/128 Treasury tick.
|
|
240
|
-
|
|
286
|
+
const price = cleanFloat32Price(value);
|
|
287
|
+
// In its currency and per its quantity, as a quote of the future reads: $4.6218/gal.
|
|
288
|
+
const [currency, per] = [series.unit?.split("/")[0]?.trim(), series.unit?.includes("/") ? `/${series.unit.split("/")[1]}` : ""];
|
|
289
|
+
return currency ? `${formatMarketPriceWithCurrency(price, currency, { assetCategory: "FUT" })}${per}` : formatPriceObservation(price);
|
|
241
290
|
}
|
|
242
291
|
|
|
243
292
|
/**
|
|
@@ -263,7 +312,9 @@ export function cotNetPoints(history: readonly CotHistoryPoint[], traderClass: C
|
|
|
263
312
|
}
|
|
264
313
|
return points;
|
|
265
314
|
}
|
|
266
|
-
|
|
315
|
+
/** `priceUnit` is the front price's (cotFrontPriceUnit), which its legend value is written in. */
|
|
316
|
+
export function cotChartSeries(data: CotContractPayload, traderClass: CotClass, price: readonly PricePoint[], colors: { positive: string; warning: string },
|
|
317
|
+
priceUnit: string | null = null): ResolvedSeries[] {
|
|
267
318
|
const series: ResolvedSeries[] = [{
|
|
268
319
|
...staticSeries(cotNetPoints(data.history, traderClass), { id: "net", label: `${COT_CLASSES[data.reportFamily].find((row) => row.value === traderClass)?.label ?? traderClass} net`, color: colors.warning, calendarSpaced: true }),
|
|
269
320
|
panelId: "net", unit: "contracts", unitGroup: "positions",
|
|
@@ -271,7 +322,7 @@ export function cotChartSeries(data: CotContractPayload, traderClass: CotClass,
|
|
|
271
322
|
// Net positioning is the subject, so it leads the legend and the strip; the front price follows.
|
|
272
323
|
if (price.length) series.push({
|
|
273
324
|
...staticSeries(price.map((row) => ({ date: row.date, observedAt: row.date, value: row.close })), { id: "price", label: "Front price", color: colors.positive, calendarSpaced: true }),
|
|
274
|
-
panelId: "price", unit: "", unitGroup: "price",
|
|
325
|
+
panelId: "price", unit: priceUnit ?? "", unitGroup: "price",
|
|
275
326
|
});
|
|
276
327
|
return series;
|
|
277
328
|
}
|
|
@@ -12,7 +12,7 @@ import { Box } from "../../../ui";
|
|
|
12
12
|
import { useAutoRefresh } from "../../../react/auto-refresh";
|
|
13
13
|
import { compareSortValues, nextHeaderSort, type SortPreference } from "../../../utils/sort-values";
|
|
14
14
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
15
|
-
import { cachedCotBoard, loadCotBoard, loadCotDetail } from "./client";
|
|
15
|
+
import { cachedCotBoard, loadCotBoard, loadCotDetail, loadCotPriceUnit } from "./client";
|
|
16
16
|
import { COT_CLASSES, COT_FAMILIES, COT_MAJOR_CODES, COT_POSITIONS, COT_SCOPES, cotChartSeries, cotClass, cotContractUnitText, cotFamily, cotInteger, cotLegendValue, cotMarketName, cotPositions, cotPositionsText, cotScope, type CotScope } from "./model";
|
|
17
17
|
|
|
18
18
|
const BOARD_COLUMNS: DataTableColumn[] = [
|
|
@@ -159,10 +159,14 @@ function CotDetail({ width, height, focused, code, family, positions, traderClas
|
|
|
159
159
|
const session = useResearchCloudSession();
|
|
160
160
|
const loader = useCallback(() => loadCotDetail(code, family, undefined, positions), [code, family, positions, session.requestKey]);
|
|
161
161
|
const resource = useAsyncResource(loader, { clearOnError: isAccessDenied });
|
|
162
|
+
// The front price's unit joins the legend once the contract terms answer, or
|
|
163
|
+
// the catalog's after a short wait: the chart draws without waiting for it.
|
|
164
|
+
const unitLoader = useCallback(() => loadCotPriceUnit(code), [code]);
|
|
165
|
+
const priceUnit = useAsyncResource(unitLoader).data ?? null;
|
|
162
166
|
const data = resource.data;
|
|
163
167
|
const payload = data?.payload;
|
|
164
168
|
const current = payload?.positions.find((row) => row.id === traderClass);
|
|
165
|
-
const series = useMemo(() => data ? cotChartSeries(data.payload, traderClass, data.price, colors) : [], [data, traderClass, colors]);
|
|
169
|
+
const series = useMemo(() => data ? cotChartSeries(data.payload, traderClass, data.price, colors, priceUnit) : [], [data, traderClass, colors, priceUnit]);
|
|
166
170
|
// The selected class row already carries the net and both ranks, and the
|
|
167
171
|
// legend names the class it charts, so the detail opens on the chart. A rank
|
|
168
172
|
// over a short history is a limitation, so it sits behind the warning.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { sourceDidNotAnswer } from "../../../api-client/errors";
|
|
2
3
|
import type { CpiBoardPayload, CpiRow } from "../../../api-client/cpi";
|
|
3
|
-
import { ApiRequestError } from "../../../api-client/errors";
|
|
4
4
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
5
|
-
import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
|
|
5
|
+
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
6
6
|
|
|
7
7
|
/** What the pane says while the server has no months to serve: before the backend ships, or before its first read. */
|
|
8
8
|
export const CPI_NOT_AVAILABLE = "US consumer prices are not available yet.";
|
|
@@ -54,11 +54,12 @@ export async function fetchCpiBoard(client: Pick<typeof apiClient, "getCloudCpiB
|
|
|
54
54
|
try {
|
|
55
55
|
payload = await client.getCloudCpiBoard();
|
|
56
56
|
} catch (error) {
|
|
57
|
-
|
|
58
|
-
throw error;
|
|
57
|
+
throw unavailableOnServer(error, CPI_NOT_AVAILABLE, [404, 503]);
|
|
59
58
|
}
|
|
60
59
|
const board = validateCpiBoard(payload);
|
|
61
|
-
|
|
60
|
+
// The server's source did not answer this time: a saved copy answers, as it does a 5xx.
|
|
61
|
+
if (board.status === "unavailable") throw sourceDidNotAnswer(CPI_NOT_AVAILABLE);
|
|
62
|
+
if (!board.release.period || !board.rows.length) throw new Error(CPI_NOT_AVAILABLE);
|
|
62
63
|
return board;
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CreditBoardPoint } from "../../../api-client/credit-boards";
|
|
2
2
|
import type { SeriesDefinition, SeriesLoadResult } from "../../../series/types";
|
|
3
|
-
import {
|
|
3
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
4
4
|
import { loadCdxBoard, loadSovrBoard } from "./client";
|
|
5
5
|
|
|
6
6
|
/** The five on-the-run 5Y index contracts the CDX tab lists; IG and iTraxx quote spread, HY and EM price. */
|
|
@@ -27,14 +27,13 @@ function boardPoints(
|
|
|
27
27
|
rows: ReadonlyArray<{ id: string; points: readonly CreditBoardPoint[] }>,
|
|
28
28
|
id: string,
|
|
29
29
|
label: string,
|
|
30
|
-
resource: { stale?: boolean; refreshError?: string | null },
|
|
30
|
+
resource: { stale?: boolean; refreshError?: string | null; savedAt?: number },
|
|
31
31
|
): SeriesLoadResult {
|
|
32
32
|
const row = rows.find((entry) => entry.id === id);
|
|
33
33
|
if (!row) throw new Error(`${label} is not on the board right now.`);
|
|
34
|
-
const warning = cachedSeriesWarning(resource);
|
|
35
34
|
return {
|
|
36
35
|
points: row.points.map((point) => ({ date: point.date, value: point.level })),
|
|
37
|
-
...(
|
|
36
|
+
...cachedSeriesNote(resource),
|
|
38
37
|
};
|
|
39
38
|
}
|
|
40
39
|
|
|
@@ -66,20 +66,28 @@ async function loadSeries(
|
|
|
66
66
|
loader: CreditSeriesLoader,
|
|
67
67
|
) {
|
|
68
68
|
const request = requestFor(definition.seriesId);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
let result: Awaited<ReturnType<typeof loadCachedFredSeries>>;
|
|
70
|
+
try {
|
|
71
|
+
result = await loadCachedFredSeries(
|
|
72
|
+
request,
|
|
73
|
+
async () => {
|
|
74
|
+
const data = trimHistory(await loader(definition.seriesId, {
|
|
75
|
+
limit: HISTORY_LIMIT,
|
|
76
|
+
sortOrder: "desc",
|
|
77
|
+
}));
|
|
78
|
+
// Reject incompatible source responses before they replace usable cache.
|
|
79
|
+
normalizeCreditSeries(definition, data);
|
|
80
|
+
return data;
|
|
81
|
+
},
|
|
82
|
+
{ force },
|
|
83
|
+
);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// A refusal or a reply that fails validation is no saved copy's to answer: it is this row's
|
|
86
|
+
// error, beside the dated prior value already read, which stays as stale rather than vanish.
|
|
87
|
+
const kept = getCachedFredSeries(request, { allowExpired: true });
|
|
88
|
+
if (!kept) throw error;
|
|
89
|
+
return { row: normalizeCreditSeries(definition, kept.data, true), refreshError: error instanceof Error ? error.message : String(error) };
|
|
90
|
+
}
|
|
83
91
|
return {
|
|
84
92
|
row: normalizeCreditSeries(definition, result.data, result.stale),
|
|
85
93
|
refreshError: result.refreshError,
|
|
@@ -31,10 +31,10 @@ export const creditHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
31
31
|
const data = await fetchCreditDocuments(args.symbols[0]!, ctx.apiClient);
|
|
32
32
|
const instrument = args.options.instrument ? await fetchCreditInstrument(data.symbol, String(args.options.instrument), ctx.apiClient) : null;
|
|
33
33
|
const reason = instrument ? null : creditEmptyReason(data, args.symbols[0]!);
|
|
34
|
-
// Nothing on file says why once, instead of four empty sections.
|
|
34
|
+
// Nothing on file says why once, as the report's one reason, instead of four empty sections and a generic "not covered".
|
|
35
35
|
if (reason) {
|
|
36
|
-
return { complete: false, errors: data.warnings, sections: [],
|
|
37
|
-
metadata: { symbol: data.symbol, issuer: data.issuer, asOf: data.asOf, access: data.access, lockedRows: data.lockedRows, status: data.status, reason
|
|
36
|
+
return { complete: false, errors: data.warnings, sections: [], empty: reason,
|
|
37
|
+
metadata: { symbol: data.symbol, issuer: data.issuer, asOf: data.asOf, access: data.access, lockedRows: data.lockedRows, status: data.status, reason } };
|
|
38
38
|
}
|
|
39
39
|
return { complete: data.status === "available" && data.lockedRows === 0, errors: [...data.warnings, ...(data.lockedRows ? ["Full credit documents and history require Gloom Pro"] : [])],
|
|
40
40
|
sections: instrument ? instrumentSections(data, instrument) : [capitalSection(data), covenantSection(data), maturitySection(data), controlSection(data)],
|
|
@@ -145,10 +145,10 @@ export async function fetchCryptoMarkets(
|
|
|
145
145
|
try {
|
|
146
146
|
return validateCryptoMarkets(await client.getCloudCryptoMarkets());
|
|
147
147
|
} catch (error) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
// Each keeps its status: a 503 passes with time and a saved copy answers it; a 404 does not.
|
|
149
|
+
throw error instanceof ApiRequestError && error.status === 503
|
|
150
|
+
? unavailableOnServer(error, "Crypto prices are temporarily unavailable", [503])
|
|
151
|
+
: unavailableOnServer(error, "Crypto markets are not available yet.");
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -248,7 +248,7 @@ function validateStablecoinSupplyHistory(data: StablecoinSupplyHistoryPayload):
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
function supplyError(error: unknown, what: string): unknown {
|
|
251
|
-
if (error instanceof ApiRequestError && error.status === 503) return
|
|
251
|
+
if (error instanceof ApiRequestError && error.status === 503) return unavailableOnServer(error, `${what} is temporarily unavailable`, [503]);
|
|
252
252
|
return unavailableOnServer(error, `${what} is not available yet.`);
|
|
253
253
|
}
|
|
254
254
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SeriesDefinition, SeriesPoint } from "../../../series/types";
|
|
2
|
-
import {
|
|
2
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
3
3
|
import { loadDefiTvl, loadStablecoinSupply, loadStablecoinSupplyHistory } from "./client";
|
|
4
4
|
|
|
5
5
|
const DAY_MS = 86_400_000;
|
|
@@ -29,7 +29,7 @@ const STABLECOIN_SUPPLY_SERIES: readonly SeriesDefinition[] = [
|
|
|
29
29
|
const resource = await loadStablecoinSupply();
|
|
30
30
|
const history = resource.payload.history;
|
|
31
31
|
if (!history) throw new Error("Stablecoin supply is not available yet.");
|
|
32
|
-
return { points: dailyPoints(history.start, history.total),
|
|
32
|
+
return { points: dailyPoints(history.start, history.total), ...cachedSeriesNote(resource) };
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
{
|
|
@@ -45,7 +45,7 @@ const STABLECOIN_SUPPLY_SERIES: readonly SeriesDefinition[] = [
|
|
|
45
45
|
const resource = await loadStablecoinSupplyHistory(stablecoinSymbol(ticker!));
|
|
46
46
|
const history = resource.payload.history;
|
|
47
47
|
if (!history) throw new Error(`${resource.payload.symbol} supply history is not available yet.`);
|
|
48
|
-
return { points: dailyPoints(history.start, history.values),
|
|
48
|
+
return { points: dailyPoints(history.start, history.values), ...cachedSeriesNote(resource) };
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
51
|
];
|
|
@@ -63,7 +63,7 @@ const DEFI_TVL_SERIES: SeriesDefinition = {
|
|
|
63
63
|
const resource = await loadDefiTvl();
|
|
64
64
|
const history = resource.payload.history;
|
|
65
65
|
if (!history) throw new Error("DeFi TVL is not available yet.");
|
|
66
|
-
return { points: dailyPoints(history.start, history.total),
|
|
66
|
+
return { points: dailyPoints(history.start, history.total), ...cachedSeriesNote(resource) };
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
69
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DebtHistoryPoint } from "../../../api-client/debt-maturities";
|
|
2
2
|
import type { SeriesDefinition } from "../../../series/types";
|
|
3
|
-
import {
|
|
3
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
4
4
|
import { loadDebtMaturities } from "./client";
|
|
5
5
|
|
|
6
6
|
type HistoryField = "totalPrincipal" | "next12MonthsShare" | "next3YearsShare";
|
|
@@ -36,10 +36,10 @@ export const DEBT_MATURITY_SERIES: readonly SeriesDefinition[] = FIELDS.map(({ i
|
|
|
36
36
|
// An amount stays in the unit it is listed in: a filing in another currency is a gap, not a converted figure.
|
|
37
37
|
const listed = (point: DebtHistoryPoint) => unit !== "USD" || point.currency === "USD";
|
|
38
38
|
const other = resource.payload.history.find((point) => !listed(point))?.currency;
|
|
39
|
-
const warning = other ? `Filings reported in ${other} are left out.` : cachedSeriesWarning(resource);
|
|
40
39
|
return {
|
|
41
40
|
points: resource.payload.history.map((point) => ({ date: point.asOf, value: listed(point) ? point[field] : null })),
|
|
42
|
-
...(
|
|
41
|
+
...cachedSeriesNote(resource),
|
|
42
|
+
...(other ? { warning: `Filings reported in ${other} are left out.` } : {}),
|
|
43
43
|
};
|
|
44
44
|
},
|
|
45
45
|
}));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { sourceDidNotAnswer } from "../../../api-client/errors";
|
|
2
3
|
import type { DoeBoardPayload, DoeSeriesRow } from "../../../api-client/doe";
|
|
3
|
-
import { ApiRequestError } from "../../../api-client/errors";
|
|
4
4
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
5
|
-
import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
|
|
5
|
+
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
6
6
|
|
|
7
7
|
/** What the pane says while the server has no EIA weeks to serve: before the backend ships, or before its first read. */
|
|
8
8
|
export const DOE_NOT_AVAILABLE = "Oil and gas inventories are not available yet.";
|
|
@@ -65,11 +65,12 @@ export async function fetchDoeBoard(client: Pick<typeof apiClient, "getCloudDoeB
|
|
|
65
65
|
try {
|
|
66
66
|
payload = await client.getCloudDoeBoard();
|
|
67
67
|
} catch (error) {
|
|
68
|
-
|
|
69
|
-
throw error;
|
|
68
|
+
throw unavailableOnServer(error, DOE_NOT_AVAILABLE, [404, 503]);
|
|
70
69
|
}
|
|
71
70
|
const board = validateDoeBoard(payload);
|
|
72
|
-
|
|
71
|
+
// The server's source did not answer this time: a saved copy answers, as it does a 5xx.
|
|
72
|
+
if (board.status === "unavailable") throw sourceDidNotAnswer(DOE_NOT_AVAILABLE);
|
|
73
|
+
if (!board.series.some((row) => row.value != null)) throw new Error(DOE_NOT_AVAILABLE);
|
|
73
74
|
return board;
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DoeSeriesRow, DoeTab } from "../../../api-client/doe";
|
|
2
2
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
3
|
-
import {
|
|
3
|
+
import { loadDoeBoard } from "./client";
|
|
4
4
|
import {
|
|
5
5
|
DOE_TABS,
|
|
6
6
|
DOE_UNIT_LABEL,
|
|
@@ -61,7 +61,8 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
|
|
|
61
61
|
"Five-year ranges read earlier years on the same calendar day.", "A range holds fewer than five years where a series is younger."] },
|
|
62
62
|
describe: "EIA weekly petroleum and natural gas storage",
|
|
63
63
|
async load(args) {
|
|
64
|
-
|
|
64
|
+
// Read afresh; a failed read answers from the board an earlier one saved, which the report says it is.
|
|
65
|
+
const data = (await loadDoeBoard(true)).payload;
|
|
65
66
|
// Each tab is dated by its own EIA report (petroleum Wednesday, gas Thursday);
|
|
66
67
|
// once the next one is a day overdue the tables have missed it. The basis
|
|
67
68
|
// names what Vs 1Y and Vs 5Y avg are measured against.
|
|
@@ -13,8 +13,10 @@ import {
|
|
|
13
13
|
usePaneTabs,
|
|
14
14
|
type ChartTableChart,
|
|
15
15
|
type DataTableColumn,
|
|
16
|
+
type PaneFooterSegment,
|
|
16
17
|
} from "../../../components";
|
|
17
18
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
19
|
+
import { savedCopyFooterSegment } from "../../../components/layout/pane/footer";
|
|
18
20
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState } from "../../../public/react";
|
|
19
21
|
import { usePaneInstance } from "../../../state/app/context";
|
|
20
22
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
@@ -136,13 +138,21 @@ export function DoePane({ width, height, focused }: PaneProps) {
|
|
|
136
138
|
const { strip, rows: tabRows } = usePaneTabs({ tabs: [...DOE_TABS], activeValue: tab, onSelect: setTab, focused, dense: true });
|
|
137
139
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
138
140
|
usePaneRefreshKey(() => void resource.reload(), { focused });
|
|
141
|
+
// A refresh that failed shows the board an earlier read saved, and says when that copy is from.
|
|
142
|
+
const savedAt = resource.data?.savedAt ?? null;
|
|
139
143
|
usePaneNoticeFooter({ registrationId: "doe:notices", focused,
|
|
140
|
-
notices: [...(data?.gaps ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
|
|
144
|
+
notices: [...(data?.gaps ?? []), ...(resource.data?.refreshError && savedAt == null ? [resource.data.refreshError] : [])] });
|
|
141
145
|
// What Vs 1Y and Vs 5Y are measured against on this tab: the EIA's published figures for gas storage.
|
|
142
146
|
const basis = useMemo(() => data ? doeComparisonBasis(doeRows(data, tab)) : null, [data, tab]);
|
|
143
|
-
const info = useMemo(() =>
|
|
147
|
+
const info = useMemo(() => {
|
|
148
|
+
const segments: PaneFooterSegment[] = [
|
|
149
|
+
...(basis ? [{ id: "basis", parts: [{ text: basis, tone: "muted" as const }] }] : []),
|
|
150
|
+
...(savedAt != null ? [savedCopyFooterSegment("saved-copy", savedAt)] : []),
|
|
151
|
+
];
|
|
152
|
+
return segments.length ? segments : undefined;
|
|
153
|
+
}, [basis, savedAt]);
|
|
144
154
|
usePaneStatusFooter({ registrationId: "doe", loading: resource.loading, error: notAvailable ? null : resource.error,
|
|
145
|
-
stale: !!data && resource.data?.stale, info });
|
|
155
|
+
stale: !!data && !!resource.data?.stale && savedAt == null, info });
|
|
146
156
|
return (
|
|
147
157
|
<Box width={width} height={height} flexDirection="column">
|
|
148
158
|
{strip}
|
|
@@ -2,7 +2,7 @@ import { apiClient } from "../../../api-client";
|
|
|
2
2
|
import type { DoePricesPayload } from "../../../api-client/doe";
|
|
3
3
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
4
|
import type { SeriesDefinition } from "../../../series/types";
|
|
5
|
-
import {
|
|
5
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
6
6
|
import { loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -73,10 +73,9 @@ export const DOE_PRICE_SERIES: readonly SeriesDefinition[] = PRICE_SERIES.map((s
|
|
|
73
73
|
const resource = await loadCloudResource(doePriceCache, "prices", () => fetchDoePrices(), { validate: validatePrices });
|
|
74
74
|
const row = resource.payload.series.find((entry) => entry.id === series.id);
|
|
75
75
|
if (!row?.points.length) throw new Error(`${series.name} is not available yet.`);
|
|
76
|
-
const warning = cachedSeriesWarning(resource);
|
|
77
76
|
return {
|
|
78
77
|
points: row.points.map(([date, value]) => ({ date, value })),
|
|
79
|
-
...(
|
|
78
|
+
...cachedSeriesNote(resource),
|
|
80
79
|
// Cited as the EIA series it is, not as the route that served it.
|
|
81
80
|
attribution: { source: `EIA series ${series.key}`, publisher: "EIA" },
|
|
82
81
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DoeSeriesRow, DoeUnit } from "../../../api-client/doe";
|
|
2
2
|
import type { SeriesDefinition, SeriesPoint } from "../../../series/types";
|
|
3
|
-
import {
|
|
3
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
4
4
|
import { loadDoeBoard } from "./client";
|
|
5
5
|
import { DOE_SERIES_OPTIONS } from "./model";
|
|
6
6
|
import { DOE_PRICE_SERIES } from "./prices";
|
|
@@ -50,14 +50,14 @@ const DOE_SERIES: readonly SeriesDefinition[] = DOE_SERIES_OPTIONS.map((option):
|
|
|
50
50
|
unit: UNITS[unit].unit,
|
|
51
51
|
frequency: "weekly",
|
|
52
52
|
group: "Energy",
|
|
53
|
-
|
|
53
|
+
// Jet fuel is kerosene, the word a reader may search it by.
|
|
54
|
+
tags: gas ? ["natural gas", "storage", "inventories"] : ["oil", "petroleum", "inventories", "US", ...option.value === "jet" ? ["kerosene"] : []],
|
|
54
55
|
publisher: "EIA",
|
|
55
56
|
load: async () => {
|
|
56
57
|
const resource = await loadDoeBoard();
|
|
57
58
|
const row = resource.payload.series.find((entry) => entry.id === option.value);
|
|
58
59
|
if (!row) throw new Error(`${option.label} is not in this week's report.`);
|
|
59
|
-
|
|
60
|
-
return { points: doeRowPoints(row), ...(warning ? { warning } : {}) };
|
|
60
|
+
return { points: doeRowPoints(row), ...cachedSeriesNote(resource) };
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
});
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
type SectionedRow,
|
|
20
20
|
} from "../../../components";
|
|
21
21
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
22
|
+
import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
23
|
+
import { isPermanentClientError } from "../../../api-client/errors";
|
|
22
24
|
import { TABLE_COLUMN_GAP, tableColumnWidth } from "../../../components/ui/table-layout";
|
|
23
25
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
24
26
|
import { useAgeClock, useAutoRefresh } from "../../../react/auto-refresh";
|
|
@@ -162,6 +164,8 @@ export function UpcomingCallsView({ focused, width, height, view, renderCompany
|
|
|
162
164
|
}, [range]);
|
|
163
165
|
const resource = useAsyncResource(entitled ? loader : null, {
|
|
164
166
|
keepPreviousData: true,
|
|
167
|
+
// A refusal asking again will not change takes the calendar off screen, for its wall or its error.
|
|
168
|
+
clearOnError: isPermanentClientError,
|
|
165
169
|
initialData: () => cachedUpcomingCalls(range),
|
|
166
170
|
});
|
|
167
171
|
const data = resource.data;
|
|
@@ -204,12 +208,16 @@ export function UpcomingCallsView({ focused, width, height, view, renderCompany
|
|
|
204
208
|
const proRequired = !signInRequired && !verificationRequired && (!access.hasProAccess || (!data && refused === 402));
|
|
205
209
|
|
|
206
210
|
const hints = useMemo<PaneHint[]>(() => [{ id: "search", key: "/", label: "search", onPress: focusSearch }], [focusSearch]);
|
|
211
|
+
// A refresh that failed shows the calendar an earlier read saved, and says when that copy is from.
|
|
212
|
+
const savedAt = data?.savedAt ?? null;
|
|
213
|
+
const savedInfo = useMemo(() => savedCopyFooterInfo([savedAt]), [savedAt]);
|
|
207
214
|
usePaneStatusFooter({
|
|
208
215
|
registrationId: "earnings-calls:calendar",
|
|
209
216
|
enabled: !detailOpen && entitled,
|
|
210
217
|
loading: resource.loading && !!data,
|
|
211
|
-
error: data ? data.refreshError ?? resource.error : null,
|
|
212
|
-
stale: !!data && (data.stale || !!resource.error),
|
|
218
|
+
error: data ? (savedAt == null ? data.refreshError : null) ?? resource.error : null,
|
|
219
|
+
stale: !!data && savedAt == null && (data.stale || !!resource.error),
|
|
220
|
+
info: savedInfo,
|
|
213
221
|
hints,
|
|
214
222
|
});
|
|
215
223
|
// A calendar cut at the server's limit says so, rather than end the week early.
|