gloomberb 0.13.1 → 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/package.json +1 -1
- package/src/api-client/index.test.ts +34 -0
- package/src/api-client/request.ts +4 -1
- 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/index.ts +5 -1
- package/src/cli/pane-functions/screenshot.ts +2 -19
- package/src/components/command-bar/assist/runtime.ts +38 -30
- package/src/components/command-bar/surface/assist.test.tsx +43 -0
- package/src/news/types.ts +1 -0
- 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 +3 -2
- package/src/plugins/builtin/cloud/askg/protocol.ts +16 -4
- 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/plugin.tsx +2 -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/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/transcript-view.tsx +8 -9
- 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/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/headless.test.ts +93 -0
- package/src/plugins/builtin/research/headless.ts +161 -0
- package/src/plugins/builtin/research/index.tsx +28 -18
- 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/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/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/sources/gloomberb-cloud/news.ts +1 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { EarningsEvent } from "../../../types/data-provider";
|
|
2
|
+
import type {
|
|
3
|
+
HeadlessPaneContext,
|
|
4
|
+
HeadlessPaneDefinition,
|
|
5
|
+
HeadlessPaneLoadArgs,
|
|
6
|
+
HeadlessRowsResult,
|
|
7
|
+
} from "../../../types/plugin";
|
|
8
|
+
import {
|
|
9
|
+
loadEarningsCalendar,
|
|
10
|
+
type EarningsCalendarResult,
|
|
11
|
+
} from "./data/cache";
|
|
12
|
+
|
|
13
|
+
const EARNINGS_COLUMNS = [
|
|
14
|
+
{ key: "date", header: "Date" },
|
|
15
|
+
{ key: "timing", header: "When" },
|
|
16
|
+
{ key: "dateStatus", header: "Status" },
|
|
17
|
+
{ key: "symbol", header: "Ticker" },
|
|
18
|
+
{ key: "name", header: "Name" },
|
|
19
|
+
{ key: "epsEstimate", header: "EPS est", align: "right" as const },
|
|
20
|
+
{ key: "epsLow", header: "EPS low", align: "right" as const },
|
|
21
|
+
{ key: "epsHigh", header: "EPS high", align: "right" as const },
|
|
22
|
+
{ key: "epsGrowth", header: "EPS growth", align: "right" as const },
|
|
23
|
+
{ key: "epsTrend30dAgo", header: "EPS 30D ago", align: "right" as const },
|
|
24
|
+
{ key: "epsRevisionUp30d", header: "Rev up", align: "right" as const },
|
|
25
|
+
{ key: "epsRevisionDown30d", header: "Rev down", align: "right" as const },
|
|
26
|
+
{ key: "revenueEstimate", header: "Sales est", align: "right" as const },
|
|
27
|
+
{ key: "revenueLow", header: "Sales low", align: "right" as const },
|
|
28
|
+
{ key: "revenueHigh", header: "Sales high", align: "right" as const },
|
|
29
|
+
{ key: "revenueGrowth", header: "Sales growth", align: "right" as const },
|
|
30
|
+
{ key: "epsAnalysts", header: "EPS analysts", align: "right" as const },
|
|
31
|
+
{ key: "revenueAnalysts", header: "Sales analysts", align: "right" as const },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export interface EarningsCalendarHeadlessDependencies {
|
|
35
|
+
loadCalendar(
|
|
36
|
+
symbols: string[],
|
|
37
|
+
context: HeadlessPaneContext,
|
|
38
|
+
): Promise<EarningsCalendarResult>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const defaultDependencies: EarningsCalendarHeadlessDependencies = {
|
|
42
|
+
loadCalendar: (symbols, context) => loadEarningsCalendar(context.marketData, symbols),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function timingMatches(event: EarningsEvent, timing: string): boolean {
|
|
46
|
+
if (timing === "all") return true;
|
|
47
|
+
if (timing === "unknown") return !event.timing;
|
|
48
|
+
return event.timing?.toUpperCase() === timing;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function eventRow(event: EarningsEvent) {
|
|
52
|
+
return {
|
|
53
|
+
date: event.earningsDate.toISOString(),
|
|
54
|
+
earningsCallDate: event.earningsCallDate?.toISOString() ?? null,
|
|
55
|
+
timing: event.timing || null,
|
|
56
|
+
dateStatus: event.isDateEstimate == null
|
|
57
|
+
? null
|
|
58
|
+
: event.isDateEstimate
|
|
59
|
+
? "estimated"
|
|
60
|
+
: "confirmed",
|
|
61
|
+
symbol: event.symbol,
|
|
62
|
+
name: event.name,
|
|
63
|
+
epsEstimate: event.epsEstimate ?? null,
|
|
64
|
+
epsLow: event.epsLow ?? null,
|
|
65
|
+
epsHigh: event.epsHigh ?? null,
|
|
66
|
+
epsYearAgo: event.epsYearAgo ?? null,
|
|
67
|
+
epsGrowth: event.epsGrowth ?? null,
|
|
68
|
+
epsAnalysts: event.epsAnalysts ?? null,
|
|
69
|
+
epsTrend7dAgo: event.epsTrend7dAgo ?? null,
|
|
70
|
+
epsTrend30dAgo: event.epsTrend30dAgo ?? null,
|
|
71
|
+
epsRevisionUp7d: event.epsRevisionUp7d ?? null,
|
|
72
|
+
epsRevisionUp30d: event.epsRevisionUp30d ?? null,
|
|
73
|
+
epsRevisionDown7d: event.epsRevisionDown7d ?? null,
|
|
74
|
+
epsRevisionDown30d: event.epsRevisionDown30d ?? null,
|
|
75
|
+
epsActual: event.epsActual ?? null,
|
|
76
|
+
revenueEstimate: event.revenueEstimate ?? null,
|
|
77
|
+
revenueLow: event.revenueLow ?? null,
|
|
78
|
+
revenueHigh: event.revenueHigh ?? null,
|
|
79
|
+
revenueYearAgo: event.revenueYearAgo ?? null,
|
|
80
|
+
revenueGrowth: event.revenueGrowth ?? null,
|
|
81
|
+
revenueAnalysts: event.revenueAnalysts ?? null,
|
|
82
|
+
revenueActual: event.revenueActual ?? null,
|
|
83
|
+
surprise: event.surprise ?? null,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function projectEarningsCalendarHeadless(
|
|
88
|
+
result: EarningsCalendarResult,
|
|
89
|
+
args: HeadlessPaneLoadArgs,
|
|
90
|
+
): HeadlessRowsResult {
|
|
91
|
+
const timing = String(args.options.timing ?? "all").toUpperCase();
|
|
92
|
+
const normalizedTiming = timing === "ALL" || timing === "UNKNOWN"
|
|
93
|
+
? timing.toLowerCase()
|
|
94
|
+
: timing;
|
|
95
|
+
const limit = Number(args.options.limit ?? 50);
|
|
96
|
+
const matching = result.events
|
|
97
|
+
.filter((event) => timingMatches(event, normalizedTiming))
|
|
98
|
+
.sort((left, right) => left.earningsDate.getTime() - right.earningsDate.getTime());
|
|
99
|
+
const rows = matching.slice(0, limit).map(eventRow);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
columns: EARNINGS_COLUMNS,
|
|
103
|
+
rows,
|
|
104
|
+
errors: result.refreshError ? [result.refreshError] : undefined,
|
|
105
|
+
metadata: {
|
|
106
|
+
fetchedAt: result.fetchedAt,
|
|
107
|
+
stale: result.stale,
|
|
108
|
+
timing: normalizedTiming,
|
|
109
|
+
total: matching.length,
|
|
110
|
+
returned: rows.length,
|
|
111
|
+
truncated: rows.length < matching.length,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createEarningsCalendarHeadless(
|
|
117
|
+
dependencies: EarningsCalendarHeadlessDependencies = defaultDependencies,
|
|
118
|
+
): HeadlessPaneDefinition<"rows"> {
|
|
119
|
+
return {
|
|
120
|
+
shape: "rows",
|
|
121
|
+
argument: {
|
|
122
|
+
kind: "symbol-list",
|
|
123
|
+
placeholder: "tickers",
|
|
124
|
+
description: "Comma-separated company symbols.",
|
|
125
|
+
minimum: 1,
|
|
126
|
+
maximum: 100,
|
|
127
|
+
},
|
|
128
|
+
options: [
|
|
129
|
+
{
|
|
130
|
+
key: "timing",
|
|
131
|
+
description: "Release timing to include.",
|
|
132
|
+
type: "enum",
|
|
133
|
+
values: [
|
|
134
|
+
{ value: "all" },
|
|
135
|
+
{ value: "BMO" },
|
|
136
|
+
{ value: "AMC" },
|
|
137
|
+
{ value: "TNS" },
|
|
138
|
+
{ value: "unknown" },
|
|
139
|
+
],
|
|
140
|
+
defaultValue: "all",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
key: "limit",
|
|
144
|
+
description: "Maximum earnings rows to return.",
|
|
145
|
+
type: "integer",
|
|
146
|
+
defaultValue: 50,
|
|
147
|
+
minimum: 1,
|
|
148
|
+
maximum: 200,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
columns: EARNINGS_COLUMNS,
|
|
152
|
+
describe: (args) => `Earnings Calendar | ${args.symbols.join(", ")}`,
|
|
153
|
+
async load(args, context) {
|
|
154
|
+
return projectEarningsCalendarHeadless(
|
|
155
|
+
await dependencies.loadCalendar(args.symbols, context),
|
|
156
|
+
args,
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const earningsCalendarHeadless = createEarningsCalendarHeadless();
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type EarningsDisplayRow,
|
|
24
24
|
type EarningsEventDisplayRow,
|
|
25
25
|
} from "./model";
|
|
26
|
+
import { earningsCalendarHeadless } from "./headless";
|
|
26
27
|
import {
|
|
27
28
|
buildEarningsColumns,
|
|
28
29
|
renderEarningsCell,
|
|
@@ -266,6 +267,7 @@ export const earningsModule: PluginModule = {
|
|
|
266
267
|
description: "Upcoming earnings dates and estimates for your tickers.",
|
|
267
268
|
keywords: ["earn", "earnings", "calendar", "eps", "revenue", "quarterly"],
|
|
268
269
|
shortcut: { prefix: "ERN" },
|
|
270
|
+
headless: earningsCalendarHeadless,
|
|
269
271
|
createInstance: (context) => ({
|
|
270
272
|
settings: context.activeCollectionId
|
|
271
273
|
? { collectionId: context.activeCollectionId }
|
|
@@ -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
|
+
});
|