@cesteral/dbm-mcp 1.0.0
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/LICENSE.md +201 -0
- package/README.md +197 -0
- package/dist/config/index.d.ts +112 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +55 -0
- package/dist/config/index.js.map +1 -0
- package/dist/generated/compatibility-rules.d.ts +26 -0
- package/dist/generated/compatibility-rules.d.ts.map +1 -0
- package/dist/generated/compatibility-rules.js +142 -0
- package/dist/generated/compatibility-rules.js.map +1 -0
- package/dist/generated/filters.d.ts +19 -0
- package/dist/generated/filters.d.ts.map +1 -0
- package/dist/generated/filters.js +2541 -0
- package/dist/generated/filters.js.map +1 -0
- package/dist/generated/index.d.ts +5 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +5 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/generated/metrics.d.ts +28 -0
- package/dist/generated/metrics.d.ts.map +1 -0
- package/dist/generated/metrics.js +961 -0
- package/dist/generated/metrics.js.map +1 -0
- package/dist/generated/report-types.d.ts +14 -0
- package/dist/generated/report-types.d.ts.map +1 -0
- package/dist/generated/report-types.js +94 -0
- package/dist/generated/report-types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.d.ts +4 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.js +299 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.d.ts +4 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.js +170 -0
- package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/custom-query-workflow.prompt.d.ts +4 -0
- package/dist/mcp-server/prompts/definitions/custom-query-workflow.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/custom-query-workflow.prompt.js +164 -0
- package/dist/mcp-server/prompts/definitions/custom-query-workflow.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/pacing-performance-analysis.prompt.d.ts +4 -0
- package/dist/mcp-server/prompts/definitions/pacing-performance-analysis.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/pacing-performance-analysis.prompt.js +204 -0
- package/dist/mcp-server/prompts/definitions/pacing-performance-analysis.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.d.ts +4 -0
- package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.js +66 -0
- package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/troubleshoot-report.prompt.d.ts +4 -0
- package/dist/mcp-server/prompts/definitions/troubleshoot-report.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/troubleshoot-report.prompt.js +179 -0
- package/dist/mcp-server/prompts/definitions/troubleshoot-report.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/types.d.ts +15 -0
- package/dist/mcp-server/prompts/definitions/types.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/types.js +2 -0
- package/dist/mcp-server/prompts/definitions/types.js.map +1 -0
- package/dist/mcp-server/prompts/index.d.ts +6 -0
- package/dist/mcp-server/prompts/index.d.ts.map +1 -0
- package/dist/mcp-server/prompts/index.js +57 -0
- package/dist/mcp-server/prompts/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/compatibility-rules.resource.d.ts +3 -0
- package/dist/mcp-server/resources/definitions/compatibility-rules.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/compatibility-rules.resource.js +130 -0
- package/dist/mcp-server/resources/definitions/compatibility-rules.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/filter-types.resource.d.ts +4 -0
- package/dist/mcp-server/resources/definitions/filter-types.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/filter-types.resource.js +198 -0
- package/dist/mcp-server/resources/definitions/filter-types.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +8 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/index.js +49 -0
- package/dist/mcp-server/resources/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/metric-types.resource.d.ts +4 -0
- package/dist/mcp-server/resources/definitions/metric-types.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/metric-types.resource.js +221 -0
- package/dist/mcp-server/resources/definitions/metric-types.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/query-examples.resource.d.ts +169 -0
- package/dist/mcp-server/resources/definitions/query-examples.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/query-examples.resource.js +261 -0
- package/dist/mcp-server/resources/definitions/query-examples.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/report-types.resource.d.ts +3 -0
- package/dist/mcp-server/resources/definitions/report-types.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/report-types.resource.js +198 -0
- package/dist/mcp-server/resources/definitions/report-types.resource.js.map +1 -0
- package/dist/mcp-server/resources/index.d.ts +3 -0
- package/dist/mcp-server/resources/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/index.js +2 -0
- package/dist/mcp-server/resources/index.js.map +1 -0
- package/dist/mcp-server/resources/types.d.ts +16 -0
- package/dist/mcp-server/resources/types.d.ts.map +1 -0
- package/dist/mcp-server/resources/types.js +2 -0
- package/dist/mcp-server/resources/types.js.map +1 -0
- package/dist/mcp-server/server.d.ts +5 -0
- package/dist/mcp-server/server.d.ts.map +1 -0
- package/dist/mcp-server/server.js +115 -0
- package/dist/mcp-server/server.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-campaign-delivery.tool.d.ts +89 -0
- package/dist/mcp-server/tools/definitions/get-campaign-delivery.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-campaign-delivery.tool.js +128 -0
- package/dist/mcp-server/tools/definitions/get-campaign-delivery.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-historical-metrics.tool.d.ts +155 -0
- package/dist/mcp-server/tools/definitions/get-historical-metrics.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-historical-metrics.tool.js +160 -0
- package/dist/mcp-server/tools/definitions/get-historical-metrics.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-pacing-status.tool.d.ts +138 -0
- package/dist/mcp-server/tools/definitions/get-pacing-status.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-pacing-status.tool.js +171 -0
- package/dist/mcp-server/tools/definitions/get-pacing-status.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-performance-metrics.tool.d.ts +122 -0
- package/dist/mcp-server/tools/definitions/get-performance-metrics.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-performance-metrics.tool.js +143 -0
- package/dist/mcp-server/tools/definitions/get-performance-metrics.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/index.d.ts +8 -0
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/index.js +23 -0
- package/dist/mcp-server/tools/definitions/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/run-custom-query-async.tool.d.ts +4 -0
- package/dist/mcp-server/tools/definitions/run-custom-query-async.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/run-custom-query-async.tool.js +46 -0
- package/dist/mcp-server/tools/definitions/run-custom-query-async.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/run-custom-query.tool.d.ts +186 -0
- package/dist/mcp-server/tools/definitions/run-custom-query.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/run-custom-query.tool.js +202 -0
- package/dist/mcp-server/tools/definitions/run-custom-query.tool.js.map +1 -0
- package/dist/mcp-server/tools/index.d.ts +2 -0
- package/dist/mcp-server/tools/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/index.js +2 -0
- package/dist/mcp-server/tools/index.js.map +1 -0
- package/dist/mcp-server/tools/utils/query-validation.d.ts +44 -0
- package/dist/mcp-server/tools/utils/query-validation.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/query-validation.js +107 -0
- package/dist/mcp-server/tools/utils/query-validation.js.map +1 -0
- package/dist/mcp-server/tools/utils/resolve-session.d.ts +4 -0
- package/dist/mcp-server/tools/utils/resolve-session.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/resolve-session.js +6 -0
- package/dist/mcp-server/tools/utils/resolve-session.js.map +1 -0
- package/dist/mcp-server/transports/streamable-http-transport.d.ts +55 -0
- package/dist/mcp-server/transports/streamable-http-transport.d.ts.map +1 -0
- package/dist/mcp-server/transports/streamable-http-transport.js +55 -0
- package/dist/mcp-server/transports/streamable-http-transport.js.map +1 -0
- package/dist/services/bid-manager/BidManagerService.d.ts +68 -0
- package/dist/services/bid-manager/BidManagerService.d.ts.map +1 -0
- package/dist/services/bid-manager/BidManagerService.js +464 -0
- package/dist/services/bid-manager/BidManagerService.js.map +1 -0
- package/dist/services/bid-manager/auth-bridge.d.ts +6 -0
- package/dist/services/bid-manager/auth-bridge.d.ts.map +1 -0
- package/dist/services/bid-manager/auth-bridge.js +13 -0
- package/dist/services/bid-manager/auth-bridge.js.map +1 -0
- package/dist/services/bid-manager/client.d.ts +4 -0
- package/dist/services/bid-manager/client.d.ts.map +1 -0
- package/dist/services/bid-manager/client.js +2 -0
- package/dist/services/bid-manager/client.js.map +1 -0
- package/dist/services/bid-manager/index.d.ts +6 -0
- package/dist/services/bid-manager/index.d.ts.map +1 -0
- package/dist/services/bid-manager/index.js +4 -0
- package/dist/services/bid-manager/index.js.map +1 -0
- package/dist/services/bid-manager/report-parser.d.ts +26 -0
- package/dist/services/bid-manager/report-parser.d.ts.map +1 -0
- package/dist/services/bid-manager/report-parser.js +141 -0
- package/dist/services/bid-manager/report-parser.js.map +1 -0
- package/dist/services/bid-manager/types.d.ts +668 -0
- package/dist/services/bid-manager/types.d.ts.map +1 -0
- package/dist/services/bid-manager/types.js +256 -0
- package/dist/services/bid-manager/types.js.map +1 -0
- package/dist/services/session-services.d.ts +12 -0
- package/dist/services/session-services.d.ts.map +1 -0
- package/dist/services/session-services.js +18 -0
- package/dist/services/session-services.js.map +1 -0
- package/dist/types-global/bid-manager.d.ts +76 -0
- package/dist/types-global/bid-manager.d.ts.map +1 -0
- package/dist/types-global/bid-manager.js +2 -0
- package/dist/types-global/bid-manager.js.map +1 -0
- package/dist/types-global/index.d.ts +2 -0
- package/dist/types-global/index.d.ts.map +1 -0
- package/dist/types-global/index.js +2 -0
- package/dist/types-global/index.js.map +1 -0
- package/dist/types-global/mcp.d.ts +2 -0
- package/dist/types-global/mcp.d.ts.map +1 -0
- package/dist/types-global/mcp.js +2 -0
- package/dist/types-global/mcp.js.map +1 -0
- package/dist/utils/date.d.ts +2 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +7 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/errors/bid-manager-errors.d.ts +57 -0
- package/dist/utils/errors/bid-manager-errors.d.ts.map +1 -0
- package/dist/utils/errors/bid-manager-errors.js +119 -0
- package/dist/utils/errors/bid-manager-errors.js.map +1 -0
- package/dist/utils/errors/index.d.ts +3 -0
- package/dist/utils/errors/index.d.ts.map +1 -0
- package/dist/utils/errors/index.js +3 -0
- package/dist/utils/errors/index.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/math.d.ts +9 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.js +38 -0
- package/dist/utils/math.js.map +1 -0
- package/dist/utils/metrics.d.ts +21 -0
- package/dist/utils/metrics.d.ts.map +1 -0
- package/dist/utils/metrics.js +92 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/platform.d.ts +3 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +5 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/security/index.d.ts +2 -0
- package/dist/utils/security/index.d.ts.map +1 -0
- package/dist/utils/security/index.js +2 -0
- package/dist/utils/security/index.js.map +1 -0
- package/dist/utils/security/rate-limiter.d.ts +3 -0
- package/dist/utils/security/rate-limiter.d.ts.map +1 -0
- package/dist/utils/security/rate-limiter.js +5 -0
- package/dist/utils/security/rate-limiter.js.map +1 -0
- package/dist/utils/telemetry/index.d.ts +2 -0
- package/dist/utils/telemetry/index.d.ts.map +1 -0
- package/dist/utils/telemetry/index.js +2 -0
- package/dist/utils/telemetry/index.js.map +1 -0
- package/dist/utils/telemetry/tracing.d.ts +3 -0
- package/dist/utils/telemetry/tracing.d.ts.map +1 -0
- package/dist/utils/telemetry/tracing.js +4 -0
- package/dist/utils/telemetry/tracing.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
export declare const createMcpHttpServer: (config: {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
port: number;
|
|
5
|
+
otelServiceName: string;
|
|
6
|
+
mcpAuthMode: "google-headers" | "jwt" | "none";
|
|
7
|
+
rateLimitPerMinute: number;
|
|
8
|
+
reportCacheTtlMs: number;
|
|
9
|
+
reportPollMaxRetries: number;
|
|
10
|
+
reportPollInitialDelayMs: number;
|
|
11
|
+
reportPollMaxDelayMs: number;
|
|
12
|
+
reportQueryRetries: number;
|
|
13
|
+
reportRetryCooldownMs: number;
|
|
14
|
+
host: string;
|
|
15
|
+
nodeEnv: "development" | "production" | "test";
|
|
16
|
+
mcpStatefulSessionTimeoutMs: number;
|
|
17
|
+
logLevel: "debug" | "info" | "notice" | "warning" | "error";
|
|
18
|
+
mcpLogLevel: "debug" | "info" | "notice" | "warning" | "error";
|
|
19
|
+
otelEnabled: boolean;
|
|
20
|
+
gcpProjectId?: string | undefined;
|
|
21
|
+
serviceAccountJson?: string | undefined;
|
|
22
|
+
serviceAccountFile?: string | undefined;
|
|
23
|
+
mcpAuthSecretKey?: string | undefined;
|
|
24
|
+
mcpAllowedOrigins?: string | undefined;
|
|
25
|
+
otelExporterOtlpTracesEndpoint?: string | undefined;
|
|
26
|
+
otelExporterOtlpMetricsEndpoint?: string | undefined;
|
|
27
|
+
gcsBucketName?: string | undefined;
|
|
28
|
+
}, logger: Logger) => ReturnType<typeof import("@cesteral/shared").createMcpHttpTransport>, startHttpServer: (config: {
|
|
29
|
+
serviceName: string;
|
|
30
|
+
port: number;
|
|
31
|
+
otelServiceName: string;
|
|
32
|
+
mcpAuthMode: "google-headers" | "jwt" | "none";
|
|
33
|
+
rateLimitPerMinute: number;
|
|
34
|
+
reportCacheTtlMs: number;
|
|
35
|
+
reportPollMaxRetries: number;
|
|
36
|
+
reportPollInitialDelayMs: number;
|
|
37
|
+
reportPollMaxDelayMs: number;
|
|
38
|
+
reportQueryRetries: number;
|
|
39
|
+
reportRetryCooldownMs: number;
|
|
40
|
+
host: string;
|
|
41
|
+
nodeEnv: "development" | "production" | "test";
|
|
42
|
+
mcpStatefulSessionTimeoutMs: number;
|
|
43
|
+
logLevel: "debug" | "info" | "notice" | "warning" | "error";
|
|
44
|
+
mcpLogLevel: "debug" | "info" | "notice" | "warning" | "error";
|
|
45
|
+
otelEnabled: boolean;
|
|
46
|
+
gcpProjectId?: string | undefined;
|
|
47
|
+
serviceAccountJson?: string | undefined;
|
|
48
|
+
serviceAccountFile?: string | undefined;
|
|
49
|
+
mcpAuthSecretKey?: string | undefined;
|
|
50
|
+
mcpAllowedOrigins?: string | undefined;
|
|
51
|
+
otelExporterOtlpTracesEndpoint?: string | undefined;
|
|
52
|
+
otelExporterOtlpMetricsEndpoint?: string | undefined;
|
|
53
|
+
gcsBucketName?: string | undefined;
|
|
54
|
+
}, logger: Logger) => Promise<import("@cesteral/shared").McpHttpServer>;
|
|
55
|
+
//# sourceMappingURL=streamable-http-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamable-http-transport.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/transports/streamable-http-transport.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAmEnC,eAAO,MAAQ,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;4FAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;uEACS,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createMcpServer } from "../server.js";
|
|
2
|
+
import { createAuthStrategy, buildServerCardExtras, createTransportEntrypoints, } from "@cesteral/shared";
|
|
3
|
+
import { createSessionServices, sessionServiceStore } from "../../services/session-services.js";
|
|
4
|
+
import { rateLimiter } from "../../utils/platform.js";
|
|
5
|
+
function buildPlatformConfig(config, logger) {
|
|
6
|
+
return {
|
|
7
|
+
authStrategy: createAuthStrategy(config.mcpAuthMode, {
|
|
8
|
+
scopes: ["https://www.googleapis.com/auth/doubleclickbidmanager"],
|
|
9
|
+
jwtSecret: config.mcpAuthSecretKey,
|
|
10
|
+
logger,
|
|
11
|
+
}),
|
|
12
|
+
corsAllowHeaders: [
|
|
13
|
+
"Content-Type",
|
|
14
|
+
"Authorization",
|
|
15
|
+
"Mcp-Session-Id",
|
|
16
|
+
"MCP-Protocol-Version",
|
|
17
|
+
"X-Google-Auth-Type",
|
|
18
|
+
"X-Google-Credentials",
|
|
19
|
+
"X-Google-Client-Id",
|
|
20
|
+
"X-Google-Client-Secret",
|
|
21
|
+
"X-Google-Refresh-Token",
|
|
22
|
+
],
|
|
23
|
+
authErrorHint: config.mcpAuthMode === "google-headers"
|
|
24
|
+
? "Provide Google credentials via X-Google-Auth-Type and associated headers."
|
|
25
|
+
: "Provide a valid Bearer token in the Authorization header.",
|
|
26
|
+
sessionServiceStore,
|
|
27
|
+
rateLimiter,
|
|
28
|
+
async createSessionForAuth(authResult, sessionId, appConfig, log) {
|
|
29
|
+
const adapter = authResult.googleAuthAdapter;
|
|
30
|
+
if (adapter) {
|
|
31
|
+
await adapter.validate();
|
|
32
|
+
const services = createSessionServices(adapter, appConfig, log, rateLimiter);
|
|
33
|
+
sessionServiceStore.set(sessionId, services, authResult.credentialFingerprint);
|
|
34
|
+
return { services };
|
|
35
|
+
}
|
|
36
|
+
if (appConfig.mcpAuthMode !== "none") {
|
|
37
|
+
return {
|
|
38
|
+
services: null,
|
|
39
|
+
error: {
|
|
40
|
+
message: "Google API credentials required for this server. Use MCP_AUTH_MODE=google-headers.",
|
|
41
|
+
status: 400,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { services: null };
|
|
46
|
+
},
|
|
47
|
+
async createMcpServer(log, sessionId, gcsBucket) {
|
|
48
|
+
return createMcpServer(log, sessionId, gcsBucket);
|
|
49
|
+
},
|
|
50
|
+
packageJsonPath: new URL("../../../package.json", import.meta.url).pathname,
|
|
51
|
+
serverCard: buildServerCardExtras("dbm-mcp"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export const { createMcpHttpServer, startHttpServer } = createTransportEntrypoints(buildPlatformConfig);
|
|
55
|
+
//# sourceMappingURL=streamable-http-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamable-http-transport.js","sourceRoot":"","sources":["../../../src/mcp-server/transports/streamable-http-transport.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACL,kBAAkB,EAIlB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,SAAS,mBAAmB,CAAC,MAAiB,EAAE,MAAc;IAC5D,OAAO;QACL,YAAY,EAAE,kBAAkB,CAAC,MAAM,CAAC,WAAuB,EAAE;YAC/D,MAAM,EAAE,CAAC,uDAAuD,CAAC;YACjE,SAAS,EAAE,MAAM,CAAC,gBAAgB;YAClC,MAAM;SACP,CAAC;QACF,gBAAgB,EAAE;YAChB,cAAc;YACd,eAAe;YACf,gBAAgB;YAChB,sBAAsB;YACtB,oBAAoB;YACpB,sBAAsB;YACtB,oBAAoB;YACpB,wBAAwB;YACxB,wBAAwB;SACzB;QACD,aAAa,EACX,MAAM,CAAC,WAAW,KAAK,gBAAgB;YACrC,CAAC,CAAC,2EAA2E;YAC7E,CAAC,CAAC,2DAA2D;QACjE,mBAAmB;QACnB,WAAW;QACX,KAAK,CAAC,oBAAoB,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG;YAC9D,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAkD,CAAC;YAC9E,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACzB,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,EAAE,SAAsB,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;gBAC1F,mBAAmB,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,qBAAqB,CAAC,CAAC;gBAC/E,OAAO,EAAE,QAAQ,EAAE,CAAC;YACtB,CAAC;YACD,IAAI,SAAS,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;gBACrC,OAAO;oBACL,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE;wBACL,OAAO,EACL,oFAAoF;wBACtF,MAAM,EAAE,GAAG;qBACZ;iBACF,CAAC;YACJ,CAAC;YAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QACD,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS;YAC7C,OAAO,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QACpD,CAAC;QACD,eAAe,EAAE,IAAI,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ;QAC3E,UAAU,EAAE,qBAAqB,CAAC,SAAS,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,EAAE,mBAAmB,EAAE,eAAe,EAAE,GACnD,0BAA0B,CAAY,mBAAmB,CAAC,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { AppConfig } from "../../config/index.js";
|
|
3
|
+
import type { BidManagerClient } from "./client.js";
|
|
4
|
+
import { type RateLimiter } from "@cesteral/shared";
|
|
5
|
+
import { calculatePerformanceMetrics } from "./report-parser.js";
|
|
6
|
+
import type { DeliveryMetrics, HistoricalDataPoint, PacingStatus, GetDeliveryMetricsInput, GetHistoricalMetricsInput, GetPacingStatusInput, QuerySpec, ReportMetadata, ExponentialBackoffConfig } from "./types.js";
|
|
7
|
+
export declare class BidManagerService {
|
|
8
|
+
private config;
|
|
9
|
+
private logger;
|
|
10
|
+
private client;
|
|
11
|
+
private rateLimiter?;
|
|
12
|
+
constructor(config: AppConfig, logger: Logger, client: BidManagerClient, rateLimiter?: RateLimiter | undefined);
|
|
13
|
+
createQuery(spec: QuerySpec): Promise<{
|
|
14
|
+
queryId: string;
|
|
15
|
+
}>;
|
|
16
|
+
runQuery(queryId: string): Promise<{
|
|
17
|
+
reportId: string;
|
|
18
|
+
}>;
|
|
19
|
+
getReportStatus(queryId: string, reportId: string): Promise<ReportMetadata>;
|
|
20
|
+
pollForCompletion(queryId: string, reportId: string, options?: Partial<ExponentialBackoffConfig>): Promise<ReportMetadata>;
|
|
21
|
+
continueQuery(queryId: string, reportId?: string): Promise<{
|
|
22
|
+
reportId: string;
|
|
23
|
+
isNewRun: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
executeQueryWithRetry(spec: QuerySpec, options?: {
|
|
26
|
+
maxRetries?: number;
|
|
27
|
+
retryCooldownMs?: number;
|
|
28
|
+
backoffConfig?: Partial<ExponentialBackoffConfig>;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
gcsPath: string;
|
|
31
|
+
queryId: string;
|
|
32
|
+
reportId: string;
|
|
33
|
+
}>;
|
|
34
|
+
private executeQueryWithRetryInner;
|
|
35
|
+
fetchReportData(gcsPath: string): Promise<string>;
|
|
36
|
+
private fetchReportDataInner;
|
|
37
|
+
getDeliveryMetrics(params: GetDeliveryMetricsInput): Promise<DeliveryMetrics>;
|
|
38
|
+
private getDeliveryMetricsInner;
|
|
39
|
+
getHistoricalMetrics(params: GetHistoricalMetricsInput): Promise<HistoricalDataPoint[]>;
|
|
40
|
+
private getHistoricalMetricsInner;
|
|
41
|
+
getPacingStatus(params: GetPacingStatusInput): Promise<PacingStatus>;
|
|
42
|
+
private getPacingStatusInner;
|
|
43
|
+
getPerformanceMetrics(params: GetDeliveryMetricsInput): Promise<ReturnType<typeof calculatePerformanceMetrics>>;
|
|
44
|
+
executeCustomQuery(params_: {
|
|
45
|
+
reportType: string;
|
|
46
|
+
groupBys: string[];
|
|
47
|
+
metrics: string[];
|
|
48
|
+
filters?: Array<{
|
|
49
|
+
type: string;
|
|
50
|
+
value: string;
|
|
51
|
+
}>;
|
|
52
|
+
dateRange: {
|
|
53
|
+
preset?: string;
|
|
54
|
+
startDate?: string;
|
|
55
|
+
endDate?: string;
|
|
56
|
+
};
|
|
57
|
+
outputFormat?: "structured" | "csv";
|
|
58
|
+
}): Promise<{
|
|
59
|
+
queryId: string;
|
|
60
|
+
reportId: string;
|
|
61
|
+
status: string;
|
|
62
|
+
rowCount: number;
|
|
63
|
+
columns: string[];
|
|
64
|
+
data: Record<string, unknown>[] | string;
|
|
65
|
+
}>;
|
|
66
|
+
private executeCustomQueryInner;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=BidManagerService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BidManagerService.d.ts","sourceRoot":"","sources":["../../../src/services/bid-manager/BidManagerService.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAGL,2BAA2B,EAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,uBAAuB,EACvB,yBAAyB,EACzB,oBAAoB,EACpB,SAAS,EACT,cAAc,EAEd,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAqBpB,qBAAa,iBAAiB;IAO1B,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,WAAW,CAAC;gBAHZ,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,CAAC,EAAE,WAAW,YAAA;IAM7B,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IA+C1D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IA8BxD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuB3E,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAC1C,OAAO,CAAC,cAAc,CAAC;IAsCpB,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0C7C,qBAAqB,CACzB,IAAI,EAAE,SAAS,EACf,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;KACnD,GACA,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;YAMpD,0BAA0B;IAuGlC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAMzC,oBAAoB;IAiC5B,kBAAkB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC;YAMrE,uBAAuB;IAqD/B,oBAAoB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;YAM/E,yBAAyB;IAsEjC,eAAe,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC;YAM5D,oBAAoB;IAuE5B,qBAAqB,CACzB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAC;IAapD,kBAAkB,CAAC,OAAO,EAAE;QAChC,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACjD,SAAS,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACrE,YAAY,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;KACrC,GAAG,OAAO,CAAC;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC;KAC1C,CAAC;YAMY,uBAAuB;CAwGtC"}
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { csvToJson } from "./report-parser.js";
|
|
2
|
+
import { delay, pollUntilComplete, ReportTimeoutError, ReportFailedError, mapReportingError, } from "@cesteral/shared";
|
|
3
|
+
import { QueryCreationError, QueryExecutionError, ReportGenerationError, ReportFetchError, BidManagerError, RetryExhaustedError, } from "../../utils/errors/bid-manager-errors.js";
|
|
4
|
+
import { parseCSVToDeliveryMetrics, parseCSVToHistoricalData, calculatePerformanceMetrics, } from "./report-parser.js";
|
|
5
|
+
import { safeDivide, round } from "../../utils/math.js";
|
|
6
|
+
import { daysBetween } from "../../utils/date.js";
|
|
7
|
+
import { withBidManagerApiSpan } from "../../utils/platform.js";
|
|
8
|
+
function parseDateString(dateStr) {
|
|
9
|
+
const [year, month, day] = dateStr.split("-").map(Number);
|
|
10
|
+
return { year, month, day };
|
|
11
|
+
}
|
|
12
|
+
function getTodayString() {
|
|
13
|
+
const today = new Date();
|
|
14
|
+
return today.toISOString().split("T")[0];
|
|
15
|
+
}
|
|
16
|
+
export class BidManagerService {
|
|
17
|
+
config;
|
|
18
|
+
logger;
|
|
19
|
+
client;
|
|
20
|
+
rateLimiter;
|
|
21
|
+
constructor(config, logger, client, rateLimiter) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.client = client;
|
|
25
|
+
this.rateLimiter = rateLimiter;
|
|
26
|
+
}
|
|
27
|
+
async createQuery(spec) {
|
|
28
|
+
this.logger.info({ title: spec.metadata.title }, "Creating Bid Manager query");
|
|
29
|
+
try {
|
|
30
|
+
await this.rateLimiter?.consume("bidmanager:global");
|
|
31
|
+
const response = await this.client.queries.create({
|
|
32
|
+
requestBody: {
|
|
33
|
+
metadata: {
|
|
34
|
+
title: spec.metadata.title,
|
|
35
|
+
dataRange: {
|
|
36
|
+
range: spec.metadata.dataRange.range,
|
|
37
|
+
customStartDate: spec.metadata.dataRange.customStartDate,
|
|
38
|
+
customEndDate: spec.metadata.dataRange.customEndDate,
|
|
39
|
+
},
|
|
40
|
+
format: spec.metadata.format || "CSV",
|
|
41
|
+
},
|
|
42
|
+
params: {
|
|
43
|
+
type: spec.params.type,
|
|
44
|
+
groupBys: spec.params.groupBys,
|
|
45
|
+
metrics: spec.params.metrics,
|
|
46
|
+
filters: spec.params.filters?.map((f) => ({
|
|
47
|
+
type: f.type,
|
|
48
|
+
value: f.value,
|
|
49
|
+
})),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const queryId = response.data.queryId;
|
|
54
|
+
if (!queryId) {
|
|
55
|
+
throw new QueryCreationError("No queryId returned from API");
|
|
56
|
+
}
|
|
57
|
+
this.logger.info({ queryId }, "Query created successfully");
|
|
58
|
+
return { queryId };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof QueryCreationError) {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
this.logger.error({ error }, "Failed to create query");
|
|
65
|
+
throw new QueryCreationError(error instanceof Error ? error.message : String(error), error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async runQuery(queryId) {
|
|
69
|
+
this.logger.info({ queryId }, "Running Bid Manager query");
|
|
70
|
+
try {
|
|
71
|
+
await this.rateLimiter?.consume("bidmanager:global");
|
|
72
|
+
const response = await this.client.queries.run({ queryId });
|
|
73
|
+
const reportId = response.data.key?.reportId;
|
|
74
|
+
if (!reportId) {
|
|
75
|
+
throw new QueryExecutionError(queryId, "No reportId returned from API");
|
|
76
|
+
}
|
|
77
|
+
this.logger.info({ queryId, reportId }, "Query execution started");
|
|
78
|
+
return { reportId };
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof QueryExecutionError) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
this.logger.error({ error, queryId }, "Failed to run query");
|
|
85
|
+
throw new QueryExecutionError(queryId, error instanceof Error ? error.message : String(error), error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async getReportStatus(queryId, reportId) {
|
|
89
|
+
try {
|
|
90
|
+
await this.rateLimiter?.consume("bidmanager:global");
|
|
91
|
+
const response = await this.client.queries.reports.get({ queryId, reportId });
|
|
92
|
+
return {
|
|
93
|
+
status: {
|
|
94
|
+
state: response.data.metadata?.status?.state || "QUEUED",
|
|
95
|
+
format: response.data.metadata?.status?.format ?? undefined,
|
|
96
|
+
},
|
|
97
|
+
googleCloudStoragePath: response.data.metadata?.googleCloudStoragePath ?? undefined,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.logger.error({ error, queryId, reportId }, "Failed to get report status");
|
|
102
|
+
throw mapReportingError(error, "dbm");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async pollForCompletion(queryId, reportId, options) {
|
|
106
|
+
const config = {
|
|
107
|
+
initialDelayMs: options?.initialDelayMs ?? this.config.reportPollInitialDelayMs ?? 2000,
|
|
108
|
+
maxDelayMs: options?.maxDelayMs ?? this.config.reportPollMaxDelayMs ?? 60000,
|
|
109
|
+
maxRetries: options?.maxRetries ?? this.config.reportPollMaxRetries ?? 10,
|
|
110
|
+
backoffMultiplier: options?.backoffMultiplier ?? 2,
|
|
111
|
+
};
|
|
112
|
+
this.logger.info({ queryId, reportId, config }, "Starting exponential backoff polling");
|
|
113
|
+
await delay(config.initialDelayMs);
|
|
114
|
+
try {
|
|
115
|
+
return await pollUntilComplete({
|
|
116
|
+
fetchStatus: () => this.getReportStatus(queryId, reportId),
|
|
117
|
+
isComplete: (r) => r.status.state === "DONE",
|
|
118
|
+
isFailed: (r) => r.status.state === "FAILED",
|
|
119
|
+
initialDelayMs: config.initialDelayMs,
|
|
120
|
+
maxDelayMs: config.maxDelayMs,
|
|
121
|
+
maxAttempts: config.maxRetries,
|
|
122
|
+
backoffFactor: config.backoffMultiplier,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof ReportFailedError) {
|
|
127
|
+
const failed = error.status;
|
|
128
|
+
throw new ReportGenerationError(queryId, reportId, failed.status.format);
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async continueQuery(queryId, reportId) {
|
|
134
|
+
this.logger.info({ queryId, existingReportId: reportId }, "Attempting to continue query");
|
|
135
|
+
if (reportId) {
|
|
136
|
+
try {
|
|
137
|
+
const report = await this.getReportStatus(queryId, reportId);
|
|
138
|
+
const state = report.status.state;
|
|
139
|
+
if (state === "DONE" || state === "RUNNING" || state === "QUEUED") {
|
|
140
|
+
this.logger.info({ queryId, reportId, status: state }, "Existing report still valid");
|
|
141
|
+
return { reportId, isNewRun: false };
|
|
142
|
+
}
|
|
143
|
+
this.logger.info({ queryId, reportId, status: state }, "Existing report not usable, will re-run query");
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.logger.warn({ error: error instanceof Error ? error.message : String(error), queryId, reportId }, "Could not get existing report status, will re-run query");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const result = await this.runQuery(queryId);
|
|
150
|
+
return { reportId: result.reportId, isNewRun: true };
|
|
151
|
+
}
|
|
152
|
+
async executeQueryWithRetry(spec, options) {
|
|
153
|
+
return withBidManagerApiSpan("executeQueryWithRetry", undefined, async () => {
|
|
154
|
+
return this.executeQueryWithRetryInner(spec, options);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async executeQueryWithRetryInner(spec, options) {
|
|
158
|
+
const maxRetries = options?.maxRetries ?? this.config.reportQueryRetries ?? 3;
|
|
159
|
+
const retryCooldownMs = options?.retryCooldownMs ?? this.config.reportRetryCooldownMs ?? 60000;
|
|
160
|
+
let queryId;
|
|
161
|
+
let reportId;
|
|
162
|
+
let lastError;
|
|
163
|
+
let lastStatus;
|
|
164
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
165
|
+
try {
|
|
166
|
+
this.logger.info({
|
|
167
|
+
attempt: attempt + 1,
|
|
168
|
+
maxRetries,
|
|
169
|
+
existingQueryId: queryId,
|
|
170
|
+
existingReportId: reportId,
|
|
171
|
+
}, "Query execution attempt");
|
|
172
|
+
if (!queryId) {
|
|
173
|
+
const createResult = await this.createQuery(spec);
|
|
174
|
+
queryId = createResult.queryId;
|
|
175
|
+
}
|
|
176
|
+
const continueResult = await this.continueQuery(queryId, reportId);
|
|
177
|
+
reportId = continueResult.reportId;
|
|
178
|
+
if (continueResult.isNewRun) {
|
|
179
|
+
this.logger.debug({ queryId, reportId }, "Started new query run");
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.logger.debug({ queryId, reportId }, "Continuing existing query run");
|
|
183
|
+
}
|
|
184
|
+
const report = await this.pollForCompletion(queryId, reportId, options?.backoffConfig);
|
|
185
|
+
lastStatus = report.status.state;
|
|
186
|
+
if (!report.googleCloudStoragePath) {
|
|
187
|
+
throw new ReportFetchError("No GCS path in completed report", undefined);
|
|
188
|
+
}
|
|
189
|
+
this.logger.info({ queryId, reportId, attempts: attempt + 1 }, "Query executed successfully");
|
|
190
|
+
return { gcsPath: report.googleCloudStoragePath, queryId, reportId };
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
194
|
+
if (error instanceof ReportTimeoutError) {
|
|
195
|
+
lastStatus = "TIMEOUT";
|
|
196
|
+
}
|
|
197
|
+
else if (error instanceof ReportGenerationError) {
|
|
198
|
+
lastStatus = "FAILED";
|
|
199
|
+
}
|
|
200
|
+
this.logger.warn({
|
|
201
|
+
error: lastError.message,
|
|
202
|
+
queryId,
|
|
203
|
+
reportId,
|
|
204
|
+
attempt: attempt + 1,
|
|
205
|
+
maxRetries,
|
|
206
|
+
lastStatus,
|
|
207
|
+
}, "Query execution attempt failed");
|
|
208
|
+
if (attempt < maxRetries - 1) {
|
|
209
|
+
this.logger.info({ cooldownMs: retryCooldownMs, nextAttempt: attempt + 2 }, "Waiting before retry with continuation");
|
|
210
|
+
await delay(retryCooldownMs);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
throw new RetryExhaustedError(maxRetries, {
|
|
215
|
+
queryId,
|
|
216
|
+
reportId,
|
|
217
|
+
lastError,
|
|
218
|
+
lastStatus,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async fetchReportData(gcsPath) {
|
|
222
|
+
return withBidManagerApiSpan("fetchReportData", undefined, async () => {
|
|
223
|
+
return this.fetchReportDataInner(gcsPath);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
async fetchReportDataInner(gcsPath) {
|
|
227
|
+
this.logger.info({ gcsPath: gcsPath.substring(0, 100) + "..." }, "Fetching report data");
|
|
228
|
+
try {
|
|
229
|
+
const response = await fetch(gcsPath);
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
throw new ReportFetchError(`HTTP ${response.status}: ${response.statusText}`, gcsPath);
|
|
232
|
+
}
|
|
233
|
+
const data = await response.text();
|
|
234
|
+
this.logger.info({ bytes: data.length }, "Report data fetched successfully");
|
|
235
|
+
return data;
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (error instanceof ReportFetchError) {
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
this.logger.error({ error }, "Failed to fetch report data");
|
|
242
|
+
throw new ReportFetchError(error instanceof Error ? error.message : String(error), gcsPath, error);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async getDeliveryMetrics(params) {
|
|
246
|
+
return withBidManagerApiSpan("getDeliveryMetrics", undefined, async () => {
|
|
247
|
+
return this.getDeliveryMetricsInner(params);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
async getDeliveryMetricsInner(params) {
|
|
251
|
+
this.logger.info({ advertiserId: params.advertiserId, campaignId: params.campaignId }, "Getting delivery metrics");
|
|
252
|
+
const querySpec = {
|
|
253
|
+
metadata: {
|
|
254
|
+
title: `Delivery metrics for campaign ${params.campaignId}`,
|
|
255
|
+
dataRange: {
|
|
256
|
+
range: "CUSTOM_DATES",
|
|
257
|
+
customStartDate: parseDateString(params.startDate),
|
|
258
|
+
customEndDate: parseDateString(params.endDate),
|
|
259
|
+
},
|
|
260
|
+
format: "CSV",
|
|
261
|
+
},
|
|
262
|
+
params: {
|
|
263
|
+
type: "STANDARD",
|
|
264
|
+
groupBys: ["FILTER_DATE", "FILTER_MEDIA_PLAN", "FILTER_ADVERTISER_CURRENCY"],
|
|
265
|
+
metrics: [
|
|
266
|
+
"METRIC_IMPRESSIONS",
|
|
267
|
+
"METRIC_CLICKS",
|
|
268
|
+
"METRIC_TOTAL_MEDIA_COST_ADVERTISER",
|
|
269
|
+
"METRIC_TOTAL_CONVERSIONS",
|
|
270
|
+
"METRIC_REVENUE_ADVERTISER",
|
|
271
|
+
],
|
|
272
|
+
filters: [
|
|
273
|
+
{ type: "FILTER_ADVERTISER", value: params.advertiserId },
|
|
274
|
+
{ type: "FILTER_MEDIA_PLAN", value: params.campaignId },
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
const result = await this.executeQueryWithRetry(querySpec);
|
|
279
|
+
const csvData = await this.fetchReportData(result.gcsPath);
|
|
280
|
+
const metrics = parseCSVToDeliveryMetrics(csvData);
|
|
281
|
+
this.logger.info({ advertiserId: params.advertiserId, campaignId: params.campaignId, metrics }, "Delivery metrics retrieved successfully");
|
|
282
|
+
return metrics;
|
|
283
|
+
}
|
|
284
|
+
async getHistoricalMetrics(params) {
|
|
285
|
+
return withBidManagerApiSpan("getHistoricalMetrics", undefined, async () => {
|
|
286
|
+
return this.getHistoricalMetricsInner(params);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
async getHistoricalMetricsInner(params) {
|
|
290
|
+
this.logger.info({
|
|
291
|
+
advertiserId: params.advertiserId,
|
|
292
|
+
campaignId: params.campaignId,
|
|
293
|
+
granularity: params.granularity,
|
|
294
|
+
}, "Getting historical metrics");
|
|
295
|
+
const timeGroupBy = params.granularity === "weekly"
|
|
296
|
+
? "FILTER_WEEK"
|
|
297
|
+
: params.granularity === "monthly"
|
|
298
|
+
? "FILTER_MONTH"
|
|
299
|
+
: "FILTER_DATE";
|
|
300
|
+
const querySpec = {
|
|
301
|
+
metadata: {
|
|
302
|
+
title: `Historical metrics for campaign ${params.campaignId}`,
|
|
303
|
+
dataRange: {
|
|
304
|
+
range: "CUSTOM_DATES",
|
|
305
|
+
customStartDate: parseDateString(params.startDate),
|
|
306
|
+
customEndDate: parseDateString(params.endDate),
|
|
307
|
+
},
|
|
308
|
+
format: "CSV",
|
|
309
|
+
},
|
|
310
|
+
params: {
|
|
311
|
+
type: "STANDARD",
|
|
312
|
+
groupBys: [timeGroupBy, "FILTER_MEDIA_PLAN", "FILTER_ADVERTISER_CURRENCY"],
|
|
313
|
+
metrics: [
|
|
314
|
+
"METRIC_IMPRESSIONS",
|
|
315
|
+
"METRIC_CLICKS",
|
|
316
|
+
"METRIC_TOTAL_MEDIA_COST_ADVERTISER",
|
|
317
|
+
"METRIC_TOTAL_CONVERSIONS",
|
|
318
|
+
"METRIC_REVENUE_ADVERTISER",
|
|
319
|
+
],
|
|
320
|
+
filters: [
|
|
321
|
+
{ type: "FILTER_ADVERTISER", value: params.advertiserId },
|
|
322
|
+
{ type: "FILTER_MEDIA_PLAN", value: params.campaignId },
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
const result = await this.executeQueryWithRetry(querySpec);
|
|
327
|
+
const csvData = await this.fetchReportData(result.gcsPath);
|
|
328
|
+
const historicalData = parseCSVToHistoricalData(csvData);
|
|
329
|
+
this.logger.info({
|
|
330
|
+
advertiserId: params.advertiserId,
|
|
331
|
+
campaignId: params.campaignId,
|
|
332
|
+
dataPoints: historicalData.length,
|
|
333
|
+
}, "Historical metrics retrieved successfully");
|
|
334
|
+
return historicalData;
|
|
335
|
+
}
|
|
336
|
+
async getPacingStatus(params) {
|
|
337
|
+
return withBidManagerApiSpan("getPacingStatus", undefined, async () => {
|
|
338
|
+
return this.getPacingStatusInner(params);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async getPacingStatusInner(params) {
|
|
342
|
+
this.logger.info({ advertiserId: params.advertiserId, campaignId: params.campaignId }, "Getting pacing status");
|
|
343
|
+
const today = getTodayString();
|
|
344
|
+
const flightStartDate = params.flightStartDate;
|
|
345
|
+
const flightEndDate = params.flightEndDate;
|
|
346
|
+
const effectiveEndDate = today < flightEndDate ? today : flightEndDate;
|
|
347
|
+
const metrics = await this.getDeliveryMetrics({
|
|
348
|
+
advertiserId: params.advertiserId,
|
|
349
|
+
campaignId: params.campaignId,
|
|
350
|
+
startDate: flightStartDate,
|
|
351
|
+
endDate: effectiveEndDate,
|
|
352
|
+
});
|
|
353
|
+
const totalDays = daysBetween(flightStartDate, flightEndDate) + 1;
|
|
354
|
+
const daysPassed = daysBetween(flightStartDate, effectiveEndDate) + 1;
|
|
355
|
+
const daysRemaining = Math.max(0, totalDays - daysPassed);
|
|
356
|
+
const expectedDeliveryPercent = round(safeDivide(daysPassed, totalDays, 0) * 100, 2);
|
|
357
|
+
const actualDeliveryPercent = round(safeDivide(metrics.spend, params.budgetTotal, 0) * 100, 2);
|
|
358
|
+
const pacingRatio = round(safeDivide(actualDeliveryPercent, expectedDeliveryPercent, 0), 4);
|
|
359
|
+
let status;
|
|
360
|
+
if (pacingRatio >= 0.95 && pacingRatio <= 1.05) {
|
|
361
|
+
status = "ON_PACE";
|
|
362
|
+
}
|
|
363
|
+
else if (pacingRatio > 1.05) {
|
|
364
|
+
status = "AHEAD";
|
|
365
|
+
}
|
|
366
|
+
else if (pacingRatio >= 0.8) {
|
|
367
|
+
status = "BEHIND";
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
status = "SEVERELY_BEHIND";
|
|
371
|
+
}
|
|
372
|
+
const dailySpendRate = safeDivide(metrics.spend, daysPassed, 0);
|
|
373
|
+
const projectedEndSpend = round(metrics.spend + dailySpendRate * daysRemaining, 2);
|
|
374
|
+
const pacingStatus = {
|
|
375
|
+
advertiserId: params.advertiserId,
|
|
376
|
+
campaignId: params.campaignId,
|
|
377
|
+
budgetTotal: params.budgetTotal,
|
|
378
|
+
spendToDate: metrics.spend,
|
|
379
|
+
expectedDeliveryPercent,
|
|
380
|
+
actualDeliveryPercent,
|
|
381
|
+
pacingRatio,
|
|
382
|
+
status,
|
|
383
|
+
daysRemaining,
|
|
384
|
+
projectedEndSpend,
|
|
385
|
+
};
|
|
386
|
+
this.logger.info({ advertiserId: params.advertiserId, campaignId: params.campaignId, pacingStatus }, "Pacing status calculated");
|
|
387
|
+
return pacingStatus;
|
|
388
|
+
}
|
|
389
|
+
async getPerformanceMetrics(params) {
|
|
390
|
+
return withBidManagerApiSpan("getPerformanceMetrics", undefined, async () => {
|
|
391
|
+
const delivery = await this.getDeliveryMetrics(params);
|
|
392
|
+
return calculatePerformanceMetrics(delivery);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async executeCustomQuery(params_) {
|
|
396
|
+
return withBidManagerApiSpan("executeCustomQuery", undefined, async () => {
|
|
397
|
+
return this.executeCustomQueryInner(params_);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
async executeCustomQueryInner(params) {
|
|
401
|
+
this.logger.info({
|
|
402
|
+
reportType: params.reportType,
|
|
403
|
+
groupBysCount: params.groupBys.length,
|
|
404
|
+
metricsCount: params.metrics.length,
|
|
405
|
+
filtersCount: params.filters?.length || 0,
|
|
406
|
+
}, "Executing custom query");
|
|
407
|
+
let dataRangeConfig;
|
|
408
|
+
if (params.dateRange.preset) {
|
|
409
|
+
dataRangeConfig = {
|
|
410
|
+
range: params.dateRange.preset,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
else if (params.dateRange.startDate && params.dateRange.endDate) {
|
|
414
|
+
dataRangeConfig = {
|
|
415
|
+
range: "CUSTOM_DATES",
|
|
416
|
+
customStartDate: parseDateString(params.dateRange.startDate),
|
|
417
|
+
customEndDate: parseDateString(params.dateRange.endDate),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
throw new BidManagerError("Invalid dateRange: provide either preset or startDate/endDate");
|
|
422
|
+
}
|
|
423
|
+
const querySpec = {
|
|
424
|
+
metadata: {
|
|
425
|
+
title: `Custom query - ${new Date().toISOString()}`,
|
|
426
|
+
dataRange: dataRangeConfig,
|
|
427
|
+
format: "CSV",
|
|
428
|
+
},
|
|
429
|
+
params: {
|
|
430
|
+
type: params.reportType,
|
|
431
|
+
groupBys: params.groupBys,
|
|
432
|
+
metrics: params.metrics,
|
|
433
|
+
filters: params.filters,
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
const queryResult = await this.executeQueryWithRetry(querySpec);
|
|
437
|
+
const csvData = await this.fetchReportData(queryResult.gcsPath);
|
|
438
|
+
if (params.outputFormat === "csv") {
|
|
439
|
+
const csvRecords = csvToJson(csvData);
|
|
440
|
+
const columns = csvRecords.length > 0 ? Object.keys(csvRecords[0]) : [];
|
|
441
|
+
const rowCount = csvRecords.length;
|
|
442
|
+
this.logger.info({ rowCount, columnsCount: columns.length }, "Custom query completed (CSV format)");
|
|
443
|
+
return {
|
|
444
|
+
queryId: queryResult.queryId,
|
|
445
|
+
reportId: queryResult.reportId,
|
|
446
|
+
status: "DONE",
|
|
447
|
+
rowCount,
|
|
448
|
+
columns,
|
|
449
|
+
data: csvData,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const records = csvToJson(csvData);
|
|
453
|
+
this.logger.info({ rowCount: records.length, columnsCount: Object.keys(records[0] || {}).length }, "Custom query completed (structured format)");
|
|
454
|
+
return {
|
|
455
|
+
queryId: queryResult.queryId,
|
|
456
|
+
reportId: queryResult.reportId,
|
|
457
|
+
status: "DONE",
|
|
458
|
+
rowCount: records.length,
|
|
459
|
+
columns: records.length > 0 ? Object.keys(records[0]) : [],
|
|
460
|
+
data: records,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
//# sourceMappingURL=BidManagerService.js.map
|