gloomberb 0.12.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -2
- package/src/api-client/auth.ts +2 -1
- package/src/api-client/index.test.ts +34 -0
- package/src/api-client/index.ts +10 -2
- package/src/api-client/request.ts +4 -1
- package/src/api-client/research-activity.ts +61 -0
- package/src/api-client/types.ts +2 -1
- package/src/cli/commands/plugins.ts +4 -3
- package/src/cli/desktop-pane-shot.ts +79 -10
- package/src/cli/pane-functions/capabilities.ts +76 -23
- package/src/cli/pane-functions/cloud-session.test.ts +54 -0
- package/src/cli/pane-functions/cloud-session.ts +38 -0
- package/src/cli/pane-functions/dom.test.ts +41 -0
- package/src/cli/pane-functions/dom.ts +199 -0
- package/src/cli/pane-functions/index.test.ts +13 -2
- package/src/cli/pane-functions/index.ts +9 -5
- package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
- package/src/cli/pane-functions/intraday-shot.ts +334 -0
- package/src/cli/pane-functions/report.test.ts +29 -1
- package/src/cli/pane-functions/report.ts +68 -7
- package/src/cli/pane-functions/screenshot.ts +195 -42
- package/src/cli/restore-plugins.ts +10 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -1
- package/src/components/chart/composite/composite-chart.tsx +87 -21
- package/src/components/chart/composite/rasterizer.ts +33 -0
- package/src/components/chart/composite/renderers.test.ts +51 -4
- package/src/components/chart/composite/scene.test.ts +128 -2
- package/src/components/chart/composite/scene.ts +92 -16
- package/src/components/chart/composite/text-renderer.ts +13 -0
- package/src/components/chart/composite/time-scale.ts +30 -10
- package/src/components/chart/composite/types.ts +26 -0
- package/src/components/command-bar/assist/runtime.ts +38 -30
- package/src/components/command-bar/surface/assist.test.tsx +63 -9
- package/src/components/command-bar/surface/test-harness.tsx +23 -4
- package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
- package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
- package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
- package/src/components/onboarding/onboarding-wizard.tsx +49 -14
- package/src/data/config/store/index.test.ts +23 -0
- package/src/data/config/store/node.ts +7 -1
- package/src/data/config/store/normalize.ts +4 -0
- package/src/news/types.ts +1 -0
- package/src/plugins/builtin/cds/client.test.ts +20 -0
- package/src/plugins/builtin/cds/client.ts +13 -3
- package/src/plugins/builtin/cds/headless.test.ts +30 -0
- package/src/plugins/builtin/cds/headless.ts +113 -0
- package/src/plugins/builtin/cds/index.tsx +4 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chat/controller/channels.ts +2 -1
- package/src/plugins/builtin/chat/controller/index.ts +13 -5
- package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
- package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
- package/src/plugins/builtin/chat/controller/state.ts +1 -0
- package/src/plugins/builtin/chat/controller.test.ts +260 -4
- package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
- package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
- package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
- package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
- package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
- package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
- package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
- package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
- package/src/plugins/builtin/cloud/plugin.tsx +2 -0
- package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
- package/src/plugins/builtin/congress-trades/client.ts +11 -0
- package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
- package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
- package/src/plugins/builtin/congress-trades/headless.ts +145 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
- package/src/plugins/builtin/credit-conditions/client.ts +1 -1
- package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
- package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
- package/src/plugins/builtin/dividend-yield/model.ts +4 -22
- package/src/plugins/builtin/dividend-yield/view.ts +17 -0
- package/src/plugins/builtin/earnings/headless.test.ts +82 -0
- package/src/plugins/builtin/earnings/headless.ts +162 -0
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/data.ts +29 -8
- package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
- package/src/plugins/builtin/earnings-calls/model.ts +238 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
- package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
- package/src/plugins/builtin/fear-greed/headless.ts +84 -0
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/holders/client.ts +29 -0
- package/src/plugins/builtin/holders/headless.test.ts +58 -0
- package/src/plugins/builtin/holders/headless.ts +162 -0
- package/src/plugins/builtin/holders/index.tsx +14 -8
- package/src/plugins/builtin/holders/pane.tsx +7 -1
- package/src/plugins/builtin/insider/client.ts +50 -0
- package/src/plugins/builtin/insider/headless.test.ts +122 -0
- package/src/plugins/builtin/insider/headless.ts +117 -0
- package/src/plugins/builtin/insider/index.tsx +21 -50
- package/src/plugins/builtin/insider/insider-data.ts +1 -1
- package/src/plugins/builtin/insider/model.ts +72 -0
- package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
- package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
- package/src/plugins/builtin/market-movers/client.ts +76 -0
- package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
- package/src/plugins/builtin/market-movers/headless.ts +73 -0
- package/src/plugins/builtin/market-movers/index.tsx +11 -51
- package/src/plugins/builtin/news/headless.test.ts +138 -0
- package/src/plugins/builtin/news/headless.ts +190 -0
- package/src/plugins/builtin/news/index.tsx +13 -9
- package/src/plugins/builtin/news/wire/index.tsx +2 -1
- package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
- package/src/plugins/builtin/research/analyst-headless.ts +172 -0
- package/src/plugins/builtin/research/analyst-model.ts +219 -0
- package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
- package/src/plugins/builtin/research/client.ts +12 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
- package/src/plugins/builtin/research/event-model.ts +245 -0
- package/src/plugins/builtin/research/events-headless.test.ts +32 -0
- package/src/plugins/builtin/research/events-headless.ts +87 -0
- package/src/plugins/builtin/research/headless.test.ts +93 -0
- package/src/plugins/builtin/research/headless.ts +161 -0
- package/src/plugins/builtin/research/index.tsx +32 -18
- package/src/plugins/builtin/research-search/pane.tsx +4 -0
- package/src/plugins/builtin/scanner/headless.test.ts +59 -0
- package/src/plugins/builtin/scanner/headless.ts +125 -0
- package/src/plugins/builtin/scanner/index.ts +4 -0
- package/src/plugins/builtin/sec/client.ts +16 -0
- package/src/plugins/builtin/sec/headless.test.ts +72 -0
- package/src/plugins/builtin/sec/headless.ts +69 -0
- package/src/plugins/builtin/sec/index.tsx +20 -75
- package/src/plugins/builtin/sec/model.ts +89 -0
- package/src/plugins/builtin/sectors/client.ts +54 -0
- package/src/plugins/builtin/sectors/headless.test.ts +28 -0
- package/src/plugins/builtin/sectors/headless.ts +80 -0
- package/src/plugins/builtin/sectors/index.tsx +6 -51
- package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
- package/src/plugins/builtin/short-interest/client.ts +5 -2
- package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
- package/src/plugins/builtin/short-interest/headless.ts +111 -0
- package/src/plugins/builtin/short-interest/index.tsx +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +13 -11
- package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
- package/src/plugins/builtin/thirteenf/headless.ts +286 -0
- package/src/plugins/builtin/thirteenf/index.tsx +4 -0
- package/src/plugins/builtin/thirteenf/model.ts +13 -0
- package/src/plugins/builtin/thirteenf/table.ts +3 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
- package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
- package/src/plugins/builtin/volatility/client.ts +16 -3
- package/src/plugins/builtin/volatility/headless.test.ts +35 -0
- package/src/plugins/builtin/volatility/headless.ts +75 -0
- package/src/plugins/builtin/volatility/index.tsx +4 -0
- package/src/plugins/builtin/world-indices/client.ts +43 -0
- package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
- package/src/plugins/builtin/world-indices/headless.ts +109 -0
- package/src/plugins/builtin/world-indices/index.tsx +4 -0
- package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
- package/src/plugins/builtin/yield-curve/headless.ts +67 -0
- package/src/plugins/builtin/yield-curve/index.tsx +6 -8
- package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
- package/src/plugins/bundle.test.ts +58 -1
- package/src/plugins/bundle.ts +4 -2
- package/src/plugins/catalog-backend.test.ts +26 -0
- package/src/plugins/catalog-backend.ts +9 -2
- package/src/plugins/catalog-browser.test.ts +0 -13
- package/src/plugins/catalog-browser.ts +4 -0
- package/src/plugins/catalog-manifest.test.ts +15 -0
- package/src/plugins/catalog-manifest.ts +12 -5
- package/src/plugins/catalog-ui.ts +0 -6
- package/src/plugins/host-contract.ts +9 -0
- package/src/plugins/host-modules.ts +12 -0
- package/src/plugins/loader.ts +63 -15
- package/src/plugins/prediction-markets/headless.test.ts +119 -0
- package/src/plugins/prediction-markets/headless.ts +255 -0
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/seed.ts +3 -0
- package/src/public/broker.ts +6 -0
- package/src/remote/schema.test.ts +65 -0
- package/src/remote/schema.ts +322 -39
- package/src/remote/types.ts +49 -2
- package/src/renderers/browser/app-services.ts +3 -0
- package/src/renderers/browser/config-host.ts +24 -8
- package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
- package/src/renderers/browser/deeplink-bridge.ts +15 -3
- package/src/renderers/browser/main.tsx +3 -1
- package/src/renderers/browser/research-entry.test.ts +8 -0
- package/src/renderers/browser/research-entry.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
- package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
- package/src/renderers/electrobun/view/build-assets.ts +0 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
- package/src/sources/gloomberb-cloud/news.ts +1 -0
- package/src/sync/core-contributors.test.ts +111 -0
- package/src/sync/core-contributors.ts +155 -1
- package/src/time-series/resolve.test.ts +118 -1
- package/src/time-series/resolve.ts +14 -16
- package/src/types/config.ts +2 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
- package/src/plugins/broker-sync/native-loader.ts +0 -10
- package/src/plugins/broker-sync/normalize.ts +0 -308
- package/src/plugins/broker-sync/public.ts +0 -122
- package/src/plugins/broker-sync/robinhood-native.ts +0 -367
- package/src/plugins/broker-sync/robinhood.ts +0 -114
- package/src/plugins/broker-sync/simplefin-native.ts +0 -201
- package/src/plugins/broker-sync/simplefin.ts +0 -70
- package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
|
@@ -4,7 +4,12 @@ import {
|
|
|
4
4
|
type CloudEarningsTranscriptPayload,
|
|
5
5
|
} from "../../../api-client";
|
|
6
6
|
import { ApiRequestError } from "../../../api-client/errors";
|
|
7
|
-
import type { PluginPersistence } from "../../../types/plugin";
|
|
7
|
+
import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
|
|
8
|
+
|
|
9
|
+
type EarningsCallsApiClient = Pick<
|
|
10
|
+
HeadlessPaneApiClient,
|
|
11
|
+
"getCloudEarningsCalls" | "getCloudEarningsTranscript"
|
|
12
|
+
>;
|
|
8
13
|
|
|
9
14
|
const LIST_KIND = "calls";
|
|
10
15
|
const TRANSCRIPT_KIND = "transcript";
|
|
@@ -83,7 +88,8 @@ function listKey(ticker: string | null): string {
|
|
|
83
88
|
return ticker ? ticker.toUpperCase() : "__all__";
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
export async function
|
|
91
|
+
export async function loadEarningsCallsWithClient(
|
|
92
|
+
client: EarningsCallsApiClient,
|
|
87
93
|
ticker: string | null,
|
|
88
94
|
options?: { force?: boolean; limit?: number },
|
|
89
95
|
): Promise<EarningsCallsResult> {
|
|
@@ -101,7 +107,7 @@ export async function loadEarningsCalls(
|
|
|
101
107
|
const active = activeListFetches.get(key);
|
|
102
108
|
if (active && !force) return active;
|
|
103
109
|
|
|
104
|
-
const request =
|
|
110
|
+
const request = client
|
|
105
111
|
.getCloudEarningsCalls({
|
|
106
112
|
ticker: ticker ?? undefined,
|
|
107
113
|
limit: options?.limit ?? 50,
|
|
@@ -155,6 +161,13 @@ export async function loadEarningsCalls(
|
|
|
155
161
|
return request;
|
|
156
162
|
}
|
|
157
163
|
|
|
164
|
+
export function loadEarningsCalls(
|
|
165
|
+
ticker: string | null,
|
|
166
|
+
options?: { force?: boolean; limit?: number },
|
|
167
|
+
): Promise<EarningsCallsResult> {
|
|
168
|
+
return loadEarningsCallsWithClient(apiClient, ticker, options);
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
/**
|
|
159
172
|
* The server answers 202 with a pending marker when a call is known but not
|
|
160
173
|
* transcribed yet, having just queued it. That is not a transcript and must
|
|
@@ -163,12 +176,13 @@ export async function loadEarningsCalls(
|
|
|
163
176
|
export function isPendingTranscript(
|
|
164
177
|
value: CloudEarningsTranscriptPayload | { status?: string } | null,
|
|
165
178
|
): boolean {
|
|
166
|
-
if (!value) return false
|
|
167
|
-
const record = value as { status?: string; turns?: unknown }
|
|
168
|
-
return record.status === "pending" || !Array.isArray(record.turns)
|
|
179
|
+
if (!value) return false;
|
|
180
|
+
const record = value as { status?: string; turns?: unknown };
|
|
181
|
+
return record.status === "pending" || !Array.isArray(record.turns);
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
export async function
|
|
184
|
+
export async function loadTranscriptWithClient(
|
|
185
|
+
client: EarningsCallsApiClient,
|
|
172
186
|
callId: string,
|
|
173
187
|
options?: { force?: boolean },
|
|
174
188
|
): Promise<CloudEarningsTranscriptPayload> {
|
|
@@ -184,7 +198,7 @@ export async function loadTranscript(
|
|
|
184
198
|
const active = activeTranscriptFetches.get(callId);
|
|
185
199
|
if (active && !force) return active;
|
|
186
200
|
|
|
187
|
-
const request =
|
|
201
|
+
const request = client
|
|
188
202
|
.getCloudEarningsTranscript(callId)
|
|
189
203
|
.then((transcript) => {
|
|
190
204
|
if (isPendingTranscript(transcript)) return transcript;
|
|
@@ -221,3 +235,10 @@ export async function loadTranscript(
|
|
|
221
235
|
activeTranscriptFetches.set(callId, request);
|
|
222
236
|
return request;
|
|
223
237
|
}
|
|
238
|
+
|
|
239
|
+
export function loadTranscript(
|
|
240
|
+
callId: string,
|
|
241
|
+
options?: { force?: boolean },
|
|
242
|
+
): Promise<CloudEarningsTranscriptPayload> {
|
|
243
|
+
return loadTranscriptWithClient(apiClient, callId, options);
|
|
244
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type {
|
|
3
|
+
CloudEarningsCallPayload,
|
|
4
|
+
CloudEarningsTranscriptPayload,
|
|
5
|
+
} from "../../../api-client";
|
|
6
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
7
|
+
import {
|
|
8
|
+
createEarningsCallsHeadless,
|
|
9
|
+
createEarningsTranscriptHeadless,
|
|
10
|
+
type EarningsCallsHeadlessDependencies,
|
|
11
|
+
} from "./headless";
|
|
12
|
+
|
|
13
|
+
const calls: CloudEarningsCallPayload[] = [
|
|
14
|
+
{
|
|
15
|
+
id: "amd-fq2-2026",
|
|
16
|
+
ticker: "AMD",
|
|
17
|
+
companyName: "Advanced Micro Devices",
|
|
18
|
+
fiscalYear: 2026,
|
|
19
|
+
fiscalQuarter: 2,
|
|
20
|
+
callAt: "2026-07-29T21:00:00.000Z",
|
|
21
|
+
status: "published",
|
|
22
|
+
durationSeconds: 3_600,
|
|
23
|
+
wordCount: 12_000,
|
|
24
|
+
hasTranscript: true,
|
|
25
|
+
sentiment: 0.2,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "amd-fq1-2026",
|
|
29
|
+
ticker: "AMD",
|
|
30
|
+
companyName: "Advanced Micro Devices",
|
|
31
|
+
fiscalYear: 2026,
|
|
32
|
+
fiscalQuarter: 1,
|
|
33
|
+
callAt: "2026-04-29T21:00:00.000Z",
|
|
34
|
+
status: "discovered",
|
|
35
|
+
durationSeconds: null,
|
|
36
|
+
wordCount: null,
|
|
37
|
+
hasTranscript: false,
|
|
38
|
+
sentiment: null,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const transcript: CloudEarningsTranscriptPayload = {
|
|
43
|
+
id: "amd-fq2-2026",
|
|
44
|
+
ticker: "AMD",
|
|
45
|
+
companyName: "Advanced Micro Devices",
|
|
46
|
+
fiscalYear: 2026,
|
|
47
|
+
fiscalQuarter: 2,
|
|
48
|
+
callAt: "2026-07-29T21:00:00.000Z",
|
|
49
|
+
timing: "AMC",
|
|
50
|
+
webcastUrl: "https://example.com/call",
|
|
51
|
+
durationSeconds: 3_600,
|
|
52
|
+
status: "published",
|
|
53
|
+
fullText: "",
|
|
54
|
+
turns: [
|
|
55
|
+
{
|
|
56
|
+
speaker: "Jean Hu",
|
|
57
|
+
role: "Chief Financial Officer",
|
|
58
|
+
company: "AMD",
|
|
59
|
+
text: `Gross margin expanded. ${"Margin discipline remained strong. ".repeat(80)}`,
|
|
60
|
+
isQa: false,
|
|
61
|
+
startSeconds: 120,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
speaker: "Analyst One",
|
|
65
|
+
role: "Analyst",
|
|
66
|
+
company: "Research Firm",
|
|
67
|
+
text: "What should investors expect from the CFO on data center margins?",
|
|
68
|
+
isQa: true,
|
|
69
|
+
startSeconds: 2_400,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
participants: [
|
|
73
|
+
{ name: "Jean Hu", role: "Chief Financial Officer", company: "AMD" },
|
|
74
|
+
],
|
|
75
|
+
summary: "Revenue grew while gross margin expanded.",
|
|
76
|
+
guidance: "Management expects sequential growth.",
|
|
77
|
+
riskFactors: "Supply remains constrained.",
|
|
78
|
+
analystFocus: "Analysts focused on data center margins.",
|
|
79
|
+
notable: "Management raised its annual outlook.",
|
|
80
|
+
sentiment: 0.2,
|
|
81
|
+
sentimentRationale: "Constructive outlook.",
|
|
82
|
+
qaStartTurn: 1,
|
|
83
|
+
wordCount: 12_000,
|
|
84
|
+
asrModel: "test",
|
|
85
|
+
updatedAt: "2026-07-30T00:00:00.000Z",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function callArgs(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
|
|
89
|
+
return {
|
|
90
|
+
rawArgument: "",
|
|
91
|
+
argument: null,
|
|
92
|
+
symbols: [],
|
|
93
|
+
options,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function transcriptArgs(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
|
|
98
|
+
return {
|
|
99
|
+
rawArgument: "AMD",
|
|
100
|
+
argument: "AMD",
|
|
101
|
+
symbols: ["AMD"],
|
|
102
|
+
options,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function dependencies(): EarningsCallsHeadlessDependencies {
|
|
107
|
+
return {
|
|
108
|
+
loadCalls: async () => ({
|
|
109
|
+
calls,
|
|
110
|
+
fetchedAt: 123,
|
|
111
|
+
stale: false,
|
|
112
|
+
}),
|
|
113
|
+
loadTranscript: async () => transcript,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const context = {} as HeadlessPaneContext;
|
|
118
|
+
|
|
119
|
+
describe("earnings calls headless", () => {
|
|
120
|
+
test("maps call shelf rows and applies availability", async () => {
|
|
121
|
+
const definition = createEarningsCallsHeadless(dependencies());
|
|
122
|
+
|
|
123
|
+
const result = await definition.load(
|
|
124
|
+
callArgs({ availability: "pending", limit: 1 }),
|
|
125
|
+
context,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(result.rows).toEqual([
|
|
129
|
+
expect.objectContaining({
|
|
130
|
+
id: "amd-fq1-2026",
|
|
131
|
+
ticker: "AMD",
|
|
132
|
+
status: "queued",
|
|
133
|
+
hasTranscript: false,
|
|
134
|
+
}),
|
|
135
|
+
]);
|
|
136
|
+
expect(result.metadata).toMatchObject({ availability: "pending", returned: 1 });
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("earnings transcript headless", () => {
|
|
141
|
+
test("selects a quarter and filters speakers by role acronym", async () => {
|
|
142
|
+
const definition = createEarningsTranscriptHeadless(dependencies());
|
|
143
|
+
|
|
144
|
+
const result = await definition.load(transcriptArgs({
|
|
145
|
+
section: "transcript",
|
|
146
|
+
quarter: "FQ2-2026",
|
|
147
|
+
speaker: "CFO",
|
|
148
|
+
search: "margin",
|
|
149
|
+
offset: 0,
|
|
150
|
+
limit: 20,
|
|
151
|
+
}), context);
|
|
152
|
+
|
|
153
|
+
expect(result.rows.length).toBeGreaterThan(0);
|
|
154
|
+
expect(result.rows.every((row) => row.speaker === "Jean Hu")).toBe(true);
|
|
155
|
+
expect(result.metadata).toMatchObject({
|
|
156
|
+
fiscalYear: 2026,
|
|
157
|
+
fiscalQuarter: 2,
|
|
158
|
+
section: "transcript",
|
|
159
|
+
speaker: "CFO",
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("pages bounded transcript segments and reports truncation", async () => {
|
|
164
|
+
const definition = createEarningsTranscriptHeadless(dependencies());
|
|
165
|
+
const first = await definition.load(transcriptArgs({
|
|
166
|
+
section: "transcript",
|
|
167
|
+
quarter: "latest",
|
|
168
|
+
speaker: "CFO",
|
|
169
|
+
search: "",
|
|
170
|
+
offset: 0,
|
|
171
|
+
limit: 1,
|
|
172
|
+
}), context);
|
|
173
|
+
const second = await definition.load(transcriptArgs({
|
|
174
|
+
section: "transcript",
|
|
175
|
+
quarter: "latest",
|
|
176
|
+
speaker: "CFO",
|
|
177
|
+
search: "",
|
|
178
|
+
offset: 1,
|
|
179
|
+
limit: 1,
|
|
180
|
+
}), context);
|
|
181
|
+
|
|
182
|
+
expect(first.rows).toHaveLength(1);
|
|
183
|
+
expect(first.metadata).toMatchObject({
|
|
184
|
+
returnedSegments: 1,
|
|
185
|
+
truncated: true,
|
|
186
|
+
hasMore: true,
|
|
187
|
+
nextOffset: 1,
|
|
188
|
+
});
|
|
189
|
+
expect(second.rows).toHaveLength(1);
|
|
190
|
+
expect(second.rows[0]?.index).toBe(1);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CloudEarningsCallPayload,
|
|
3
|
+
CloudEarningsTranscriptPayload,
|
|
4
|
+
} from "../../../api-client";
|
|
5
|
+
import type {
|
|
6
|
+
HeadlessPaneContext,
|
|
7
|
+
HeadlessPaneDefinition,
|
|
8
|
+
HeadlessPaneLoadArgs,
|
|
9
|
+
HeadlessRowsResult,
|
|
10
|
+
} from "../../../types/plugin";
|
|
11
|
+
import {
|
|
12
|
+
callStatusLabel,
|
|
13
|
+
isPendingTranscript,
|
|
14
|
+
loadEarningsCallsWithClient,
|
|
15
|
+
loadTranscriptWithClient,
|
|
16
|
+
type EarningsCallsResult,
|
|
17
|
+
} from "./data";
|
|
18
|
+
import {
|
|
19
|
+
buildTranscriptSegments,
|
|
20
|
+
findCallForQuarter,
|
|
21
|
+
sortCallsNewest,
|
|
22
|
+
type TranscriptSection,
|
|
23
|
+
} from "./model";
|
|
24
|
+
|
|
25
|
+
const CALL_COLUMNS = [
|
|
26
|
+
{ key: "ticker", header: "Ticker" },
|
|
27
|
+
{ key: "company", header: "Company" },
|
|
28
|
+
{ key: "callAt", header: "Call date" },
|
|
29
|
+
{ key: "fiscalYear", header: "FY", align: "right" as const },
|
|
30
|
+
{ key: "fiscalQuarter", header: "FQ", align: "right" as const },
|
|
31
|
+
{ key: "status", header: "Status" },
|
|
32
|
+
{ key: "durationSeconds", header: "Seconds", align: "right" as const },
|
|
33
|
+
{ key: "wordCount", header: "Words", align: "right" as const },
|
|
34
|
+
{ key: "sentiment", header: "Tone", align: "right" as const },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const TRANSCRIPT_COLUMNS = [
|
|
38
|
+
{ key: "index", header: "#", align: "right" as const },
|
|
39
|
+
{ key: "section", header: "Section" },
|
|
40
|
+
{ key: "startSeconds", header: "Start", align: "right" as const },
|
|
41
|
+
{ key: "speaker", header: "Speaker" },
|
|
42
|
+
{ key: "role", header: "Role" },
|
|
43
|
+
{ key: "isQa", header: "Q&A" },
|
|
44
|
+
{ key: "text", header: "Text" },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export interface EarningsCallsHeadlessDependencies {
|
|
48
|
+
loadCalls(
|
|
49
|
+
ticker: string | null,
|
|
50
|
+
limit: number,
|
|
51
|
+
context: HeadlessPaneContext,
|
|
52
|
+
): Promise<EarningsCallsResult>;
|
|
53
|
+
loadTranscript(
|
|
54
|
+
callId: string,
|
|
55
|
+
context: HeadlessPaneContext,
|
|
56
|
+
): Promise<CloudEarningsTranscriptPayload>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const defaultDependencies: EarningsCallsHeadlessDependencies = {
|
|
60
|
+
loadCalls: (ticker, limit, context) => loadEarningsCallsWithClient(
|
|
61
|
+
context.apiClient,
|
|
62
|
+
ticker,
|
|
63
|
+
{ limit },
|
|
64
|
+
),
|
|
65
|
+
loadTranscript: (callId, context) => loadTranscriptWithClient(context.apiClient, callId),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function callRow(call: CloudEarningsCallPayload) {
|
|
69
|
+
return {
|
|
70
|
+
id: call.id,
|
|
71
|
+
ticker: call.ticker,
|
|
72
|
+
company: call.companyName,
|
|
73
|
+
callAt: call.callAt,
|
|
74
|
+
fiscalYear: call.fiscalYear,
|
|
75
|
+
fiscalQuarter: call.fiscalQuarter,
|
|
76
|
+
status: call.hasTranscript ? "transcribed" : callStatusLabel(call) || call.status,
|
|
77
|
+
durationSeconds: call.durationSeconds,
|
|
78
|
+
wordCount: call.wordCount,
|
|
79
|
+
hasTranscript: call.hasTranscript,
|
|
80
|
+
sentiment: call.sentiment,
|
|
81
|
+
webcastUrl: call.webcastUrl ?? null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function availabilityMatches(call: CloudEarningsCallPayload, availability: string): boolean {
|
|
86
|
+
if (availability === "all") return true;
|
|
87
|
+
return availability === "transcribed" ? call.hasTranscript : !call.hasTranscript;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function projectEarningsCallsHeadless(
|
|
91
|
+
result: EarningsCallsResult,
|
|
92
|
+
args: HeadlessPaneLoadArgs,
|
|
93
|
+
): HeadlessRowsResult {
|
|
94
|
+
const availability = String(args.options.availability ?? "all");
|
|
95
|
+
const limit = Number(args.options.limit ?? 50);
|
|
96
|
+
const matching = sortCallsNewest(result.calls)
|
|
97
|
+
.filter((call) => availabilityMatches(call, availability));
|
|
98
|
+
const rows = matching.slice(0, limit).map(callRow);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
columns: CALL_COLUMNS,
|
|
102
|
+
rows,
|
|
103
|
+
errors: result.refreshError ? [result.refreshError] : undefined,
|
|
104
|
+
metadata: {
|
|
105
|
+
fetchedAt: result.fetchedAt,
|
|
106
|
+
stale: result.stale,
|
|
107
|
+
pending: result.pending ?? false,
|
|
108
|
+
unknownTicker: result.unknownTicker ?? false,
|
|
109
|
+
availability,
|
|
110
|
+
total: matching.length,
|
|
111
|
+
returned: rows.length,
|
|
112
|
+
truncated: rows.length < matching.length,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function selectedSection(args: HeadlessPaneLoadArgs): TranscriptSection {
|
|
118
|
+
return String(args.options.section ?? "overview") as TranscriptSection;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function transcriptResult(
|
|
122
|
+
transcript: CloudEarningsTranscriptPayload,
|
|
123
|
+
call: CloudEarningsCallPayload,
|
|
124
|
+
args: HeadlessPaneLoadArgs,
|
|
125
|
+
): HeadlessRowsResult {
|
|
126
|
+
const section = selectedSection(args);
|
|
127
|
+
const speaker = String(args.options.speaker ?? "").trim();
|
|
128
|
+
const search = String(args.options.search ?? "").trim();
|
|
129
|
+
const offset = Number(args.options.offset ?? 0);
|
|
130
|
+
const limit = Number(args.options.limit ?? 20);
|
|
131
|
+
const segments = buildTranscriptSegments(transcript, section, { speaker, search });
|
|
132
|
+
const rows = segments.slice(offset, offset + limit).map((segment) => ({ ...segment }));
|
|
133
|
+
const hasMore = offset + rows.length < segments.length;
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
columns: TRANSCRIPT_COLUMNS,
|
|
137
|
+
rows,
|
|
138
|
+
metadata: {
|
|
139
|
+
callId: call.id,
|
|
140
|
+
ticker: transcript.ticker,
|
|
141
|
+
company: transcript.companyName,
|
|
142
|
+
fiscalYear: transcript.fiscalYear,
|
|
143
|
+
fiscalQuarter: transcript.fiscalQuarter,
|
|
144
|
+
callAt: transcript.callAt,
|
|
145
|
+
durationSeconds: transcript.durationSeconds,
|
|
146
|
+
wordCount: transcript.wordCount,
|
|
147
|
+
sentiment: transcript.sentiment,
|
|
148
|
+
updatedAt: transcript.updatedAt,
|
|
149
|
+
section,
|
|
150
|
+
speaker: speaker || null,
|
|
151
|
+
search: search || null,
|
|
152
|
+
offset,
|
|
153
|
+
limit,
|
|
154
|
+
totalSegments: segments.length,
|
|
155
|
+
returnedSegments: rows.length,
|
|
156
|
+
truncated: offset > 0 || hasMore,
|
|
157
|
+
hasMore,
|
|
158
|
+
nextOffset: hasMore ? offset + rows.length : null,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function transcriptOptions() {
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
key: "section",
|
|
167
|
+
description: "Transcript section to return.",
|
|
168
|
+
type: "enum" as const,
|
|
169
|
+
values: [
|
|
170
|
+
{ value: "overview" },
|
|
171
|
+
{ value: "summary" },
|
|
172
|
+
{ value: "transcript" },
|
|
173
|
+
{ value: "qa", aliases: ["q&a"] },
|
|
174
|
+
{ value: "guidance" },
|
|
175
|
+
{ value: "risks" },
|
|
176
|
+
{ value: "notable" },
|
|
177
|
+
{ value: "analyst-focus", aliases: ["analyst"] },
|
|
178
|
+
{ value: "participants", aliases: ["speakers"] },
|
|
179
|
+
],
|
|
180
|
+
defaultValue: "overview",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: "quarter",
|
|
184
|
+
description: "Fiscal quarter such as latest, FQ2-2026, or FY2026.",
|
|
185
|
+
type: "string" as const,
|
|
186
|
+
defaultValue: "latest",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
key: "speaker",
|
|
190
|
+
description: "Speaker name, role, or role acronym such as CFO.",
|
|
191
|
+
type: "string" as const,
|
|
192
|
+
defaultValue: "",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
key: "search",
|
|
196
|
+
description: "Text to find in transcript turns.",
|
|
197
|
+
type: "string" as const,
|
|
198
|
+
defaultValue: "",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
key: "offset",
|
|
202
|
+
description: "Transcript segment offset for paging.",
|
|
203
|
+
type: "integer" as const,
|
|
204
|
+
defaultValue: 0,
|
|
205
|
+
minimum: 0,
|
|
206
|
+
maximum: 100_000,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
key: "limit",
|
|
210
|
+
description: "Maximum transcript segments to return.",
|
|
211
|
+
type: "integer" as const,
|
|
212
|
+
defaultValue: 20,
|
|
213
|
+
minimum: 1,
|
|
214
|
+
maximum: 200,
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function createEarningsCallsHeadless(
|
|
220
|
+
dependencies: EarningsCallsHeadlessDependencies = defaultDependencies,
|
|
221
|
+
): HeadlessPaneDefinition<"rows"> {
|
|
222
|
+
return {
|
|
223
|
+
shape: "rows",
|
|
224
|
+
argument: {
|
|
225
|
+
kind: "ticker",
|
|
226
|
+
placeholder: "ticker",
|
|
227
|
+
description: "Optional company symbol. Omit it to browse all calls.",
|
|
228
|
+
optional: true,
|
|
229
|
+
},
|
|
230
|
+
options: [
|
|
231
|
+
{
|
|
232
|
+
key: "availability",
|
|
233
|
+
description: "Transcript availability to include.",
|
|
234
|
+
type: "enum",
|
|
235
|
+
values: [
|
|
236
|
+
{ value: "all" },
|
|
237
|
+
{ value: "transcribed" },
|
|
238
|
+
{ value: "pending" },
|
|
239
|
+
],
|
|
240
|
+
defaultValue: "all",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
key: "limit",
|
|
244
|
+
description: "Maximum calls to return.",
|
|
245
|
+
type: "integer",
|
|
246
|
+
defaultValue: 50,
|
|
247
|
+
minimum: 1,
|
|
248
|
+
maximum: 200,
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
columns: CALL_COLUMNS,
|
|
252
|
+
describe: (args) => args.argument
|
|
253
|
+
? `Earnings Calls | ${String(args.argument)}`
|
|
254
|
+
: "Earnings Calls",
|
|
255
|
+
async load(args, context) {
|
|
256
|
+
const ticker = typeof args.argument === "string" ? args.argument : null;
|
|
257
|
+
return projectEarningsCallsHeadless(
|
|
258
|
+
await dependencies.loadCalls(ticker, 200, context),
|
|
259
|
+
args,
|
|
260
|
+
);
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function createEarningsTranscriptHeadless(
|
|
266
|
+
dependencies: EarningsCallsHeadlessDependencies = defaultDependencies,
|
|
267
|
+
): HeadlessPaneDefinition<"rows"> {
|
|
268
|
+
return {
|
|
269
|
+
shape: "rows",
|
|
270
|
+
argument: {
|
|
271
|
+
kind: "ticker",
|
|
272
|
+
placeholder: "ticker",
|
|
273
|
+
description: "Company symbol.",
|
|
274
|
+
},
|
|
275
|
+
options: transcriptOptions(),
|
|
276
|
+
columns: TRANSCRIPT_COLUMNS,
|
|
277
|
+
describe: (args) => (
|
|
278
|
+
`Earnings Call Transcript | ${String(args.argument)} | ${String(args.options.quarter)}`
|
|
279
|
+
),
|
|
280
|
+
async load(args, context) {
|
|
281
|
+
const ticker = String(args.argument);
|
|
282
|
+
const calls = await dependencies.loadCalls(ticker, 200, context);
|
|
283
|
+
const quarter = String(args.options.quarter ?? "latest");
|
|
284
|
+
const selected = findCallForQuarter(calls.calls, quarter);
|
|
285
|
+
if (!selected) {
|
|
286
|
+
if (calls.unknownTicker) throw new Error(`${ticker} is not a known listed company.`);
|
|
287
|
+
if (calls.pending) throw new Error(`Earnings call discovery for ${ticker} is still pending.`);
|
|
288
|
+
throw new Error(`No ${quarter} earnings call found for ${ticker}.`);
|
|
289
|
+
}
|
|
290
|
+
const transcript = await dependencies.loadTranscript(selected.id, context);
|
|
291
|
+
if (isPendingTranscript(transcript)) {
|
|
292
|
+
throw new Error(`Transcript for ${ticker} ${quarter} is still being produced.`);
|
|
293
|
+
}
|
|
294
|
+
return transcriptResult(transcript, selected, args);
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const earningsCallsHeadless = createEarningsCallsHeadless();
|
|
300
|
+
export const earningsTranscriptHeadless = createEarningsTranscriptHeadless();
|
|
@@ -2,6 +2,12 @@ import type { PluginModule } from "../plugin-module";
|
|
|
2
2
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
3
3
|
import { attachEarningsCallsPersistence, resetEarningsCallsPersistence } from "./data";
|
|
4
4
|
import { EarningsCallsPane, EARNINGS_CALLS_PANE_ID } from "./pane";
|
|
5
|
+
import {
|
|
6
|
+
earningsCallsHeadless,
|
|
7
|
+
earningsTranscriptHeadless,
|
|
8
|
+
} from "./headless";
|
|
9
|
+
|
|
10
|
+
export { earningsCallsHeadless, earningsTranscriptHeadless } from "./headless";
|
|
5
11
|
|
|
6
12
|
const description =
|
|
7
13
|
"Earnings call transcripts with speaker attribution, analyst Q&A, and extracted guidance.";
|
|
@@ -54,16 +60,20 @@ export const earningsCallsModule: PluginModule = {
|
|
|
54
60
|
"qa",
|
|
55
61
|
],
|
|
56
62
|
shortcut: { prefix: "CALLS" },
|
|
63
|
+
headless: earningsCallsHeadless,
|
|
57
64
|
createInstance: () => ({ placement: "floating" }),
|
|
58
65
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
{
|
|
67
|
+
...createTickerSurfacePaneTemplate({
|
|
68
|
+
id: "earnings-call-transcripts-pane",
|
|
69
|
+
paneId: EARNINGS_CALLS_PANE_ID,
|
|
70
|
+
label: "Earnings Call Transcripts",
|
|
71
|
+
description,
|
|
72
|
+
keywords: ["earnings", "call", "transcript", "ect", "qa", "guidance"],
|
|
73
|
+
shortcut: "ECT",
|
|
74
|
+
publicShare: false,
|
|
75
|
+
}),
|
|
76
|
+
headless: earningsTranscriptHeadless,
|
|
77
|
+
},
|
|
68
78
|
],
|
|
69
79
|
};
|