@signaliz/sdk 1.0.17 → 1.0.18
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 +7 -2
- package/dist/{chunk-ZKE4L57J.mjs → chunk-VFBQCWOM.mjs} +631 -42
- package/dist/cli.js +660 -44
- package/dist/cli.mjs +28 -1
- package/dist/index.d.mts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +631 -42
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +631 -42
- package/dist/mcp-config.mjs +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -890,7 +890,11 @@ var Campaigns = class {
|
|
|
890
890
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
891
891
|
brainContext: data.brain_context ?? {},
|
|
892
892
|
learningHoldout: data.learning_holdout ?? {},
|
|
893
|
-
providerRoute: mapProviderRoute(data)
|
|
893
|
+
providerRoute: mapProviderRoute(data),
|
|
894
|
+
effectiveSourceRouting: recordOrNull(data.effective_source_routing ?? data.source_routing),
|
|
895
|
+
sourcePlan: recordOrNull(data.source_plan ?? data.plan?.source_plan),
|
|
896
|
+
sourceRoutes: recordArray(data.source_routes ?? data.source_plan?.routes ?? data.plan?.source_plan?.routes),
|
|
897
|
+
sourceShards: recordArray(data.source_shards ?? data.source_plan?.shards ?? data.plan?.source_plan?.shards)
|
|
894
898
|
};
|
|
895
899
|
}
|
|
896
900
|
async getCampaignBuildStatus(campaignBuildId) {
|
|
@@ -1082,6 +1086,9 @@ function recordOrNull(value) {
|
|
|
1082
1086
|
function stringArray(value) {
|
|
1083
1087
|
return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
|
|
1084
1088
|
}
|
|
1089
|
+
function recordArray(value) {
|
|
1090
|
+
return Array.isArray(value) ? value.map(recordOrNull).filter((item) => Boolean(item)) : [];
|
|
1091
|
+
}
|
|
1085
1092
|
function diagnosticMessages(value) {
|
|
1086
1093
|
if (!Array.isArray(value)) return [];
|
|
1087
1094
|
return value.map((item) => {
|
|
@@ -1243,7 +1250,8 @@ function buildArgs(request) {
|
|
|
1243
1250
|
suppress_prior_campaign_ids: request.cacheReusePolicy.suppressPriorCampaignIds,
|
|
1244
1251
|
suppress_prior_list_ids: request.cacheReusePolicy.suppressPriorListIds,
|
|
1245
1252
|
uniqueness: request.cacheReusePolicy.uniqueness,
|
|
1246
|
-
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata
|
|
1253
|
+
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata,
|
|
1254
|
+
verified_cache_ttl_days: request.cacheReusePolicy.verifiedCacheTtlDays
|
|
1247
1255
|
};
|
|
1248
1256
|
}
|
|
1249
1257
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
@@ -1926,6 +1934,7 @@ var CampaignBuilderAgent = class {
|
|
|
1926
1934
|
},
|
|
1927
1935
|
brain_defaults: plan.buildRequest.brainDefaults,
|
|
1928
1936
|
brain_preflight: plan.buildRequest.brainPreflight,
|
|
1937
|
+
learning_holdout: plan.buildRequest.learningHoldout,
|
|
1929
1938
|
campaign_strategy_context: plan.buildRequest.campaignStrategyContext,
|
|
1930
1939
|
delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
1931
1940
|
delivery_risk: plan.buildRequest.deliveryRisk
|
|
@@ -1939,7 +1948,8 @@ var CampaignBuilderAgent = class {
|
|
|
1939
1948
|
estimated_credits: plan.estimatedCredits
|
|
1940
1949
|
},
|
|
1941
1950
|
brain_delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
1942
|
-
delivery_risk: plan.buildRequest.deliveryRisk
|
|
1951
|
+
delivery_risk: plan.buildRequest.deliveryRisk,
|
|
1952
|
+
learning_holdout: plan.buildRequest.learningHoldout
|
|
1943
1953
|
},
|
|
1944
1954
|
approval_required: plan.approvals.some((approval) => approval.blocking),
|
|
1945
1955
|
actor_type: "agent",
|
|
@@ -3345,6 +3355,21 @@ function routesFromCustomerTools(tools) {
|
|
|
3345
3355
|
}))
|
|
3346
3356
|
);
|
|
3347
3357
|
}
|
|
3358
|
+
function normalizeCampaignBuilderLearningHoldout(input, gtmCampaignId, campaignName) {
|
|
3359
|
+
const source = asRecord(input);
|
|
3360
|
+
if (source.enabled === false) return { enabled: false };
|
|
3361
|
+
const controlPercentage = numberOrUndefined2(source.control_percentage ?? source.controlPercentage);
|
|
3362
|
+
const minControlSize = numberOrUndefined2(source.min_control_size ?? source.minControlSize);
|
|
3363
|
+
const experimentKey = stringValue(source.experiment_key ?? source.experimentKey) ?? (gtmCampaignId ? `gtm_campaign:${gtmCampaignId}:learning_holdout` : `campaign_builder_agent:${hashString(campaignName)}:learning_holdout`);
|
|
3364
|
+
return {
|
|
3365
|
+
enabled: true,
|
|
3366
|
+
controlPercentage: controlPercentage ?? 0.2,
|
|
3367
|
+
minControlSize: minControlSize ?? 50,
|
|
3368
|
+
experimentKey,
|
|
3369
|
+
sourceCampaignId: stringValue(source.source_campaign_id ?? source.sourceCampaignId) ?? gtmCampaignId,
|
|
3370
|
+
primaryMetric: stringValue(source.primary_metric ?? source.primaryMetric) ?? "positive_reply_rate"
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3348
3373
|
function createBuildRequest(request, defaults, targetCount, campaignName, scopeBuildArgs, routes = [], memory, scopeBrainPreflight) {
|
|
3349
3374
|
const buildRequest = {
|
|
3350
3375
|
name: campaignName,
|
|
@@ -3377,6 +3402,11 @@ function createBuildRequest(request, defaults, targetCount, campaignName, scopeB
|
|
|
3377
3402
|
};
|
|
3378
3403
|
const scoped = asRecord(scopeBuildArgs);
|
|
3379
3404
|
buildRequest.gtmCampaignId = request.gtmCampaignId ?? stringValue(scoped.gtm_campaign_id ?? scoped.gtmCampaignId);
|
|
3405
|
+
buildRequest.learningHoldout = normalizeCampaignBuilderLearningHoldout(
|
|
3406
|
+
request.learningHoldout ?? scoped.learning_holdout ?? scoped.learningHoldout,
|
|
3407
|
+
buildRequest.gtmCampaignId,
|
|
3408
|
+
campaignName
|
|
3409
|
+
);
|
|
3380
3410
|
if (typeof scoped.name === "string") buildRequest.name = scoped.name;
|
|
3381
3411
|
if (typeof scoped.prompt === "string") buildRequest.prompt = scoped.prompt;
|
|
3382
3412
|
if (typeof scoped.target_count === "number") buildRequest.targetCount = scoped.target_count;
|
|
@@ -3451,6 +3481,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
|
|
|
3451
3481
|
target_icp: targetIcp,
|
|
3452
3482
|
provider_chain: providerChain,
|
|
3453
3483
|
lead_source: providerChain[0] ?? "signaliz",
|
|
3484
|
+
learning_holdout: buildRequest.learningHoldout,
|
|
3454
3485
|
tool_sequence: [
|
|
3455
3486
|
{ tool: "gtm_brain_learning_cycle_plan", purpose: "Plan the Brain learning loop for this campaign.", arguments: learningArgs },
|
|
3456
3487
|
{ tool: "gtm_brain_seed_defaults", purpose: "Seed campaign defaults from workspace memory and safe aggregate patterns.", arguments: defaultsArgs },
|
|
@@ -4118,6 +4149,16 @@ function buildCampaignArgs(request) {
|
|
|
4118
4149
|
if (request.dedupKeys) args.dedup_keys = request.dedupKeys;
|
|
4119
4150
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
4120
4151
|
if (request.brainDefaults) args.brain_defaults = request.brainDefaults;
|
|
4152
|
+
if (request.learningHoldout) {
|
|
4153
|
+
args.learning_holdout = compact({
|
|
4154
|
+
enabled: request.learningHoldout.enabled,
|
|
4155
|
+
control_percentage: request.learningHoldout.controlPercentage,
|
|
4156
|
+
min_control_size: request.learningHoldout.minControlSize,
|
|
4157
|
+
experiment_key: request.learningHoldout.experimentKey,
|
|
4158
|
+
source_campaign_id: request.learningHoldout.sourceCampaignId,
|
|
4159
|
+
primary_metric: request.learningHoldout.primaryMetric
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4121
4162
|
if (request.campaignStrategyContext) args.campaign_strategy_context = request.campaignStrategyContext;
|
|
4122
4163
|
if (request.deliveryRisk) args.delivery_risk = request.deliveryRisk;
|
|
4123
4164
|
if (request.icp) {
|
|
@@ -4214,6 +4255,7 @@ function buildExecutionPrepareArgs(request, dryRun, confirmSpend) {
|
|
|
4214
4255
|
delivery_risk: buildArgs2.delivery_risk,
|
|
4215
4256
|
dedup_keys: buildArgs2.dedup_keys,
|
|
4216
4257
|
policy: buildArgs2.policy,
|
|
4258
|
+
learning_holdout: buildArgs2.learning_holdout,
|
|
4217
4259
|
signals: buildArgs2.signals,
|
|
4218
4260
|
qualification: buildArgs2.qualification,
|
|
4219
4261
|
copy: buildArgs2.copy,
|
|
@@ -4248,10 +4290,145 @@ function mapBuildResult(data, dryRun) {
|
|
|
4248
4290
|
targetLimitReason: data.target_limit_reason,
|
|
4249
4291
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
4250
4292
|
brainContext: data.brain_context ?? {},
|
|
4293
|
+
learningHoldout: data.learning_holdout ?? {},
|
|
4251
4294
|
providerRoute: mapProviderRoute2(data)
|
|
4252
4295
|
};
|
|
4253
4296
|
}
|
|
4297
|
+
var CAMPAIGN_BUILDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled", "cancelled"]);
|
|
4298
|
+
var CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["failed", "canceled", "cancelled"]);
|
|
4299
|
+
function mapAgentCampaignPhaseHealth(value) {
|
|
4300
|
+
if (!Array.isArray(value)) return [];
|
|
4301
|
+
return value.map((phase) => {
|
|
4302
|
+
const record = asRecord(phase);
|
|
4303
|
+
return {
|
|
4304
|
+
phase: stringValue(record.phase) ?? "unknown",
|
|
4305
|
+
status: stringValue(record.status) ?? "unknown",
|
|
4306
|
+
recordsProcessed: numberOrNull2(record.records_processed ?? record.recordsProcessed),
|
|
4307
|
+
recordsSucceeded: numberOrNull2(record.records_succeeded ?? record.recordsSucceeded),
|
|
4308
|
+
recordsFailed: numberOrNull2(record.records_failed ?? record.recordsFailed),
|
|
4309
|
+
heartbeatAgeSeconds: numberOrNull2(record.heartbeat_age_seconds ?? record.heartbeatAgeSeconds ?? record.phase_age_seconds)
|
|
4310
|
+
};
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
function deriveCampaignBuildTerminalState(input) {
|
|
4314
|
+
const status = input.status.toLowerCase();
|
|
4315
|
+
const heartbeatAgeSeconds = input.phaseAgeSeconds;
|
|
4316
|
+
const stale = input.staleRunningPhase === true;
|
|
4317
|
+
const safeWatchAction = input.triggerRunId ? `signaliz ops run-status ${input.triggerRunId} --watch` : "signaliz campaign-agent status <campaign_build_id>";
|
|
4318
|
+
if (CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES.has(status)) {
|
|
4319
|
+
return {
|
|
4320
|
+
kind: "blocked",
|
|
4321
|
+
label: "Blocked terminal state",
|
|
4322
|
+
reviewable: true,
|
|
4323
|
+
terminal: true,
|
|
4324
|
+
stale: false,
|
|
4325
|
+
phase: input.phase,
|
|
4326
|
+
phaseStatus: input.phaseStatus,
|
|
4327
|
+
heartbeatAgeSeconds,
|
|
4328
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4329
|
+
terminalReason: input.terminalReason,
|
|
4330
|
+
staleReason: null,
|
|
4331
|
+
safeNextAction: input.nextAction || "Review errors and rerun only after the blocker is resolved."
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
if (status === "pending_approval") {
|
|
4335
|
+
return {
|
|
4336
|
+
kind: "pending_approval",
|
|
4337
|
+
label: "Pending delivery approval",
|
|
4338
|
+
reviewable: true,
|
|
4339
|
+
terminal: false,
|
|
4340
|
+
stale: false,
|
|
4341
|
+
phase: input.phase,
|
|
4342
|
+
phaseStatus: input.phaseStatus,
|
|
4343
|
+
heartbeatAgeSeconds,
|
|
4344
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4345
|
+
terminalReason: input.terminalReason,
|
|
4346
|
+
staleReason: null,
|
|
4347
|
+
safeNextAction: input.approvalAction || input.nextAction || "Review rows and approve delivery only after the approval packet passes."
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
if (CAMPAIGN_BUILDER_TERMINAL_STATUSES.has(status)) {
|
|
4351
|
+
return {
|
|
4352
|
+
kind: "terminal",
|
|
4353
|
+
label: "Terminal and reviewable",
|
|
4354
|
+
reviewable: true,
|
|
4355
|
+
terminal: true,
|
|
4356
|
+
stale: false,
|
|
4357
|
+
phase: input.phase,
|
|
4358
|
+
phaseStatus: input.phaseStatus,
|
|
4359
|
+
heartbeatAgeSeconds,
|
|
4360
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4361
|
+
terminalReason: input.terminalReason,
|
|
4362
|
+
staleReason: null,
|
|
4363
|
+
safeNextAction: input.nextAction || "Review rows, artifacts, scorecard, and approval locks before delivery."
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
if (status === "running" || status === "queued" || status === "processing") {
|
|
4367
|
+
if (stale) {
|
|
4368
|
+
return {
|
|
4369
|
+
kind: "running_stale",
|
|
4370
|
+
label: "Running but stale",
|
|
4371
|
+
reviewable: false,
|
|
4372
|
+
terminal: false,
|
|
4373
|
+
stale: true,
|
|
4374
|
+
phase: input.phase,
|
|
4375
|
+
phaseStatus: input.phaseStatus,
|
|
4376
|
+
heartbeatAgeSeconds,
|
|
4377
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4378
|
+
terminalReason: null,
|
|
4379
|
+
staleReason: input.staleReason || "The active phase heartbeat is stale.",
|
|
4380
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
return {
|
|
4384
|
+
kind: "running_healthy",
|
|
4385
|
+
label: "Running and healthy",
|
|
4386
|
+
reviewable: false,
|
|
4387
|
+
terminal: false,
|
|
4388
|
+
stale: false,
|
|
4389
|
+
phase: input.phase,
|
|
4390
|
+
phaseStatus: input.phaseStatus,
|
|
4391
|
+
heartbeatAgeSeconds,
|
|
4392
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4393
|
+
terminalReason: null,
|
|
4394
|
+
staleReason: null,
|
|
4395
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
4396
|
+
};
|
|
4397
|
+
}
|
|
4398
|
+
return {
|
|
4399
|
+
kind: "unknown",
|
|
4400
|
+
label: "Unknown build state",
|
|
4401
|
+
reviewable: false,
|
|
4402
|
+
terminal: false,
|
|
4403
|
+
stale,
|
|
4404
|
+
phase: input.phase,
|
|
4405
|
+
phaseStatus: input.phaseStatus,
|
|
4406
|
+
heartbeatAgeSeconds,
|
|
4407
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4408
|
+
terminalReason: input.terminalReason,
|
|
4409
|
+
staleReason: input.staleReason,
|
|
4410
|
+
safeNextAction: input.nextAction || "Read campaign build status again before making a launch or delivery decision."
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4254
4413
|
function mapAgentCampaignBuildStatus(data) {
|
|
4414
|
+
const customerRowCounts = mapAgentCustomerRowCounts(data.customer_row_counts);
|
|
4415
|
+
const phaseHealth = mapAgentCampaignPhaseHealth(data.phases);
|
|
4416
|
+
const terminalReason = stringValue(data.terminal_reason) ?? customerRowCounts?.terminalReason ?? null;
|
|
4417
|
+
const staleReason = stringValue(data.stale_reason) ?? stringValue(asRecord(data.diagnostics).stale_reason) ?? null;
|
|
4418
|
+
const nextPollAfterSeconds = numberOrNull2(data.next_poll_after_seconds ?? data.nextPollAfterSeconds);
|
|
4419
|
+
const terminalState = deriveCampaignBuildTerminalState({
|
|
4420
|
+
status: stringValue(data.status) ?? "unknown",
|
|
4421
|
+
phase: stringValue(data.current_phase) ?? null,
|
|
4422
|
+
phaseStatus: stringValue(data.current_phase_status) ?? null,
|
|
4423
|
+
staleRunningPhase: data.stale_running_phase === true,
|
|
4424
|
+
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
4425
|
+
nextPollAfterSeconds,
|
|
4426
|
+
terminalReason,
|
|
4427
|
+
staleReason,
|
|
4428
|
+
nextAction: formatAgentNextAction(data.next_action),
|
|
4429
|
+
approvalAction: formatAgentNextAction(data.approval_action),
|
|
4430
|
+
triggerRunId: stringValue(data.trigger_run_id) ?? null
|
|
4431
|
+
});
|
|
4255
4432
|
return {
|
|
4256
4433
|
campaignBuildId: stringValue(data.campaign_build_id) ?? "",
|
|
4257
4434
|
campaignId: stringValue(data.campaign_id) ?? null,
|
|
@@ -4269,12 +4446,18 @@ function mapAgentCampaignBuildStatus(data) {
|
|
|
4269
4446
|
errors: diagnosticMessages2(data.errors),
|
|
4270
4447
|
artifactCount: numberOrUndefined2(data.artifact_count) ?? 0,
|
|
4271
4448
|
artifactDownloads: Array.isArray(data.artifact_downloads) ? data.artifact_downloads.map(mapAgentCampaignArtifactDownload) : [],
|
|
4272
|
-
customerRowCounts
|
|
4449
|
+
customerRowCounts,
|
|
4273
4450
|
phaseAttemptTotals: nullableRecord(data.phase_attempt_totals) ?? void 0,
|
|
4274
4451
|
providerRoute: mapProviderRoute2(data),
|
|
4275
4452
|
triggerRunId: stringValue(data.trigger_run_id) ?? null,
|
|
4276
4453
|
staleRunningPhase: data.stale_running_phase === true,
|
|
4277
4454
|
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
4455
|
+
nextPollAfterSeconds,
|
|
4456
|
+
terminalReason,
|
|
4457
|
+
staleReason,
|
|
4458
|
+
safeNextAction: terminalState.safeNextAction,
|
|
4459
|
+
terminalState,
|
|
4460
|
+
phaseHealth,
|
|
4278
4461
|
diagnostics: nullableRecord(data.diagnostics) ?? {},
|
|
4279
4462
|
nextAction: formatAgentNextAction(data.next_action),
|
|
4280
4463
|
approvalAction: formatAgentNextAction(data.approval_action),
|
|
@@ -4362,67 +4545,285 @@ function mapAgentCampaignArtifact(data) {
|
|
|
4362
4545
|
createdAt: stringValue(data.created_at) ?? ""
|
|
4363
4546
|
};
|
|
4364
4547
|
}
|
|
4548
|
+
var NORTH_STAR_BANNED_COPY_PHRASES = [
|
|
4549
|
+
"i have been following",
|
|
4550
|
+
"i saw that",
|
|
4551
|
+
"would you be opposed",
|
|
4552
|
+
"unlock new opportunities",
|
|
4553
|
+
"significant success",
|
|
4554
|
+
"we understand",
|
|
4555
|
+
"hope this finds you well"
|
|
4556
|
+
];
|
|
4557
|
+
function campaignReviewGate(id, label, status, detail) {
|
|
4558
|
+
return {
|
|
4559
|
+
id,
|
|
4560
|
+
label,
|
|
4561
|
+
status,
|
|
4562
|
+
passed: status !== "blocked",
|
|
4563
|
+
detail
|
|
4564
|
+
};
|
|
4565
|
+
}
|
|
4566
|
+
function northStarMetric(id, label, status, detail, value, target) {
|
|
4567
|
+
return {
|
|
4568
|
+
id,
|
|
4569
|
+
label,
|
|
4570
|
+
status,
|
|
4571
|
+
detail,
|
|
4572
|
+
...value !== void 0 ? { value } : {},
|
|
4573
|
+
...target !== void 0 ? { target } : {}
|
|
4574
|
+
};
|
|
4575
|
+
}
|
|
4576
|
+
function createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState) {
|
|
4577
|
+
if (!terminalState.reviewable) {
|
|
4578
|
+
const pendingMetric = northStarMetric(
|
|
4579
|
+
"terminal_state",
|
|
4580
|
+
"Terminal-state contract",
|
|
4581
|
+
terminalState.kind === "running_stale" ? "blocked" : "pending",
|
|
4582
|
+
terminalState.kind === "running_stale" ? `Build is stale in ${terminalState.phase || "unknown phase"}; do not grade output yet.` : `Build is ${terminalState.label.toLowerCase()}; wait for terminal or approval state before grading.`,
|
|
4583
|
+
terminalState.kind,
|
|
4584
|
+
"terminal or pending_approval"
|
|
4585
|
+
);
|
|
4586
|
+
return {
|
|
4587
|
+
version: "campaign-builder-north-star.v1",
|
|
4588
|
+
status: pendingMetric.status,
|
|
4589
|
+
score: null,
|
|
4590
|
+
moatState: "scaffolding_ready",
|
|
4591
|
+
metrics: [pendingMetric],
|
|
4592
|
+
blockers: pendingMetric.status === "blocked" ? [pendingMetric.detail] : [],
|
|
4593
|
+
warnings: pendingMetric.status === "pending" ? [pendingMetric.detail] : [],
|
|
4594
|
+
nextAction: terminalState.safeNextAction
|
|
4595
|
+
};
|
|
4596
|
+
}
|
|
4597
|
+
const sampledRows = rows.rows.length;
|
|
4598
|
+
const rowData = rows.rows.map((row) => asRecord(row.data));
|
|
4599
|
+
const requestedTarget = status.customerRowCounts?.requestedTarget ?? status.recordsTotal ?? rows.count;
|
|
4600
|
+
const finalRows = status.customerRowCounts?.deliveredRows ?? status.customerRowCounts?.qaReadyRows ?? status.customerRowCounts?.copiedRows ?? status.customerRowCounts?.acceptedRows ?? rows.count;
|
|
4601
|
+
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
4602
|
+
const verifiedEmailRows = rowData.filter(campaignReviewDataHasVerifiedEmail).length;
|
|
4603
|
+
const uniqueEmails = new Set(rowData.map(campaignReviewDataEmail).filter(Boolean)).size;
|
|
4604
|
+
const uniqueDomains = new Set(rowData.map(campaignReviewDataDomain).filter(Boolean)).size;
|
|
4605
|
+
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
4606
|
+
const copyQaIssuesByRow = rowData.map(campaignReviewDataCopyQaIssues).filter((issues) => issues.length > 0);
|
|
4607
|
+
const copyQaIssueCounts = countCampaignReviewIssues(copyQaIssuesByRow);
|
|
4608
|
+
const signalBackedRows = rowData.filter(campaignReviewDataHasSignalBasis).length;
|
|
4609
|
+
const noSupportedSignalRows = rowData.filter(campaignReviewDataHasNoSupportedSignal).length;
|
|
4610
|
+
const unsupportedSignalClaims = rowData.filter(campaignReviewDataHasUnsupportedSignalClaim).length;
|
|
4611
|
+
const approvalLockedRows = rowData.filter(campaignReviewDataHasApprovalLock).length;
|
|
4612
|
+
const holdoutRows = rowData.filter(campaignReviewDataHasHoldoutAttribution).length;
|
|
4613
|
+
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
4614
|
+
const deliveredRows = status.customerRowCounts?.deliveredRows ?? 0;
|
|
4615
|
+
const feedbackReady = campaignReviewStatusHasFeedbackReadiness(status);
|
|
4616
|
+
const feedbackRequiredForApproval = terminalState.kind === "pending_approval";
|
|
4617
|
+
const moat = campaignReviewMoatState(status, holdoutRows);
|
|
4618
|
+
const metrics = [
|
|
4619
|
+
northStarMetric(
|
|
4620
|
+
"terminal_state",
|
|
4621
|
+
"Terminal-state contract",
|
|
4622
|
+
terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
4623
|
+
terminalState.label,
|
|
4624
|
+
terminalState.kind,
|
|
4625
|
+
"terminal or pending_approval"
|
|
4626
|
+
),
|
|
4627
|
+
northStarMetric(
|
|
4628
|
+
"target_fill",
|
|
4629
|
+
"Target fill",
|
|
4630
|
+
requestedTarget && finalRows < requestedTarget ? "review" : "pass",
|
|
4631
|
+
requestedTarget ? `${finalRows || 0}/${requestedTarget} final or reviewable row(s) are accounted for.` : "No requested target was reported; using sampled rows for review.",
|
|
4632
|
+
finalRows || sampledRows,
|
|
4633
|
+
requestedTarget || sampledRows
|
|
4634
|
+
),
|
|
4635
|
+
northStarMetric(
|
|
4636
|
+
"unique_emails",
|
|
4637
|
+
"Unique emails",
|
|
4638
|
+
sampledRows === 0 ? "blocked" : uniqueEmails === rowsWithEmail ? "pass" : "blocked",
|
|
4639
|
+
sampledRows === 0 ? "No sampled rows were available for email uniqueness review." : `${uniqueEmails}/${rowsWithEmail} sampled email(s) are unique.`,
|
|
4640
|
+
uniqueEmails,
|
|
4641
|
+
rowsWithEmail
|
|
4642
|
+
),
|
|
4643
|
+
northStarMetric(
|
|
4644
|
+
"unique_domains",
|
|
4645
|
+
"Unique domains",
|
|
4646
|
+
sampledRows === 0 ? "blocked" : uniqueDomains === sampledRows ? "pass" : "review",
|
|
4647
|
+
sampledRows === 0 ? "No sampled rows were available for domain uniqueness review." : `${uniqueDomains}/${sampledRows} sampled company domain(s) are unique.`,
|
|
4648
|
+
uniqueDomains,
|
|
4649
|
+
sampledRows
|
|
4650
|
+
),
|
|
4651
|
+
northStarMetric(
|
|
4652
|
+
"verified_email_rows",
|
|
4653
|
+
"Verified email rows",
|
|
4654
|
+
sampledRows === 0 ? "blocked" : verifiedEmailRows === sampledRows ? "pass" : rowsWithEmail === sampledRows ? "review" : "blocked",
|
|
4655
|
+
sampledRows === 0 ? "No sampled rows were available for email verification review." : `${verifiedEmailRows}/${sampledRows} sampled row(s) carry verified-email evidence.`,
|
|
4656
|
+
verifiedEmailRows,
|
|
4657
|
+
sampledRows
|
|
4658
|
+
),
|
|
4659
|
+
northStarMetric(
|
|
4660
|
+
"copy_completeness",
|
|
4661
|
+
"Copy completeness",
|
|
4662
|
+
sampledRows === 0 ? "blocked" : copyReadyRows === sampledRows ? "pass" : "blocked",
|
|
4663
|
+
sampledRows === 0 ? "No sampled rows were available for copy review." : `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.`,
|
|
4664
|
+
copyReadyRows,
|
|
4665
|
+
sampledRows
|
|
4666
|
+
),
|
|
4667
|
+
northStarMetric(
|
|
4668
|
+
"copy_quality",
|
|
4669
|
+
"Copy QA",
|
|
4670
|
+
copyQaIssuesByRow.length === 0 ? "pass" : "blocked",
|
|
4671
|
+
copyQaIssuesByRow.length === 0 ? "No sampled copy QA blockers were detected." : `${copyQaIssuesByRow.length}/${sampledRows} sampled row(s) have copy QA issue(s): ${formatCampaignReviewIssueCounts(copyQaIssueCounts)}.`,
|
|
4672
|
+
copyQaIssuesByRow.length,
|
|
4673
|
+
0
|
|
4674
|
+
),
|
|
4675
|
+
northStarMetric(
|
|
4676
|
+
"signal_grounding",
|
|
4677
|
+
"Signal grounding",
|
|
4678
|
+
unsupportedSignalClaims > 0 ? "blocked" : signalBackedRows > 0 ? "pass" : "review",
|
|
4679
|
+
unsupportedSignalClaims > 0 ? `${unsupportedSignalClaims} sampled row(s) make unsupported signal claims.` : signalBackedRows > 0 ? `${signalBackedRows}/${sampledRows} sampled row(s) include supported signal or personalization basis.` : noSupportedSignalRows > 0 ? `${noSupportedSignalRows}/${sampledRows} sampled row(s) are explicitly marked no_supported_signal; copy should stay conservative.` : "No sampled rows include supported signal evidence; copy should stay conservative.",
|
|
4680
|
+
signalBackedRows,
|
|
4681
|
+
sampledRows
|
|
4682
|
+
),
|
|
4683
|
+
northStarMetric(
|
|
4684
|
+
"approval_locks",
|
|
4685
|
+
"Approval locks",
|
|
4686
|
+
approvalLockedRows > 0 || deliveredRows === 0 ? "pass" : "review",
|
|
4687
|
+
approvalLockedRows > 0 ? `${approvalLockedRows}/${sampledRows} sampled row(s) show approval/export lock metadata.` : "Approval-lock metadata was not visible in sampled rows; preserve delivery approval gates.",
|
|
4688
|
+
approvalLockedRows,
|
|
4689
|
+
sampledRows
|
|
4690
|
+
),
|
|
4691
|
+
northStarMetric(
|
|
4692
|
+
"export_send_safety",
|
|
4693
|
+
"Export/send safety",
|
|
4694
|
+
deliveredRows > 0 && status.status !== "completed" ? "blocked" : "pass",
|
|
4695
|
+
deliveredRows > 0 ? `${deliveredRows} row(s) are delivered; status is ${status.status}.` : "No delivered rows are reported before approval.",
|
|
4696
|
+
deliveredRows,
|
|
4697
|
+
0
|
|
4698
|
+
),
|
|
4699
|
+
northStarMetric(
|
|
4700
|
+
"holdout_attribution",
|
|
4701
|
+
"Holdout attribution",
|
|
4702
|
+
holdoutRows > 0 ? "pass" : "review",
|
|
4703
|
+
holdoutRows > 0 ? `${holdoutRows}/${sampledRows} sampled row(s) preserve holdout attribution.` : "No holdout attribution was visible in the sampled rows.",
|
|
4704
|
+
holdoutRows,
|
|
4705
|
+
sampledRows
|
|
4706
|
+
),
|
|
4707
|
+
northStarMetric(
|
|
4708
|
+
"feedback_readiness",
|
|
4709
|
+
"Feedback readiness",
|
|
4710
|
+
feedbackReady ? "pass" : feedbackRequiredForApproval ? "blocked" : "review",
|
|
4711
|
+
feedbackReady ? "Feedback readiness is visible in status diagnostics." : feedbackRequiredForApproval ? "Feedback webhook or approved pull readiness is required before delivery approval." : "Feedback webhook or approved pull readiness was not visible; require it before real send approval.",
|
|
4712
|
+
feedbackReady,
|
|
4713
|
+
true
|
|
4714
|
+
),
|
|
4715
|
+
northStarMetric(
|
|
4716
|
+
"causal_moat",
|
|
4717
|
+
"Causal moat",
|
|
4718
|
+
moat.state === "proved" ? "pass" : "review",
|
|
4719
|
+
moat.detail,
|
|
4720
|
+
moat.state,
|
|
4721
|
+
"proved"
|
|
4722
|
+
),
|
|
4723
|
+
northStarMetric(
|
|
4724
|
+
"artifact_readback",
|
|
4725
|
+
"Artifact readback",
|
|
4726
|
+
artifactCount > 0 ? "pass" : "review",
|
|
4727
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available for review.` : "No review artifact is available yet.",
|
|
4728
|
+
artifactCount,
|
|
4729
|
+
1
|
|
4730
|
+
)
|
|
4731
|
+
];
|
|
4732
|
+
const gradedMetrics = metrics.filter((metric) => metric.status !== "pending");
|
|
4733
|
+
const score = gradedMetrics.length === 0 ? null : Math.round(gradedMetrics.filter((metric) => metric.status === "pass").length / gradedMetrics.length * 100);
|
|
4734
|
+
const blockers = metrics.filter((metric) => metric.status === "blocked").map((metric) => metric.detail);
|
|
4735
|
+
const warnings = metrics.filter((metric) => metric.status === "review").map((metric) => metric.detail);
|
|
4736
|
+
const scorecardStatus = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "review" : "pass";
|
|
4737
|
+
return {
|
|
4738
|
+
version: "campaign-builder-north-star.v1",
|
|
4739
|
+
status: scorecardStatus,
|
|
4740
|
+
score,
|
|
4741
|
+
moatState: moat.state,
|
|
4742
|
+
metrics,
|
|
4743
|
+
blockers,
|
|
4744
|
+
warnings,
|
|
4745
|
+
nextAction: scorecardStatus === "blocked" ? "Fix blocked North Star gates before approval or delivery." : terminalState.safeNextAction
|
|
4746
|
+
};
|
|
4747
|
+
}
|
|
4365
4748
|
function createCampaignBuilderBuildReview(status, rows, artifacts) {
|
|
4749
|
+
const terminalState = status.terminalState ?? deriveCampaignBuildTerminalState({
|
|
4750
|
+
status: status.status,
|
|
4751
|
+
phase: status.currentPhase,
|
|
4752
|
+
phaseStatus: status.currentPhaseStatus,
|
|
4753
|
+
staleRunningPhase: status.staleRunningPhase === true,
|
|
4754
|
+
phaseAgeSeconds: status.phaseAgeSeconds ?? null,
|
|
4755
|
+
nextPollAfterSeconds: status.nextPollAfterSeconds ?? null,
|
|
4756
|
+
terminalReason: status.terminalReason ?? status.customerRowCounts?.terminalReason ?? null,
|
|
4757
|
+
staleReason: status.staleReason ?? null,
|
|
4758
|
+
nextAction: status.nextAction,
|
|
4759
|
+
approvalAction: status.approvalAction,
|
|
4760
|
+
triggerRunId: status.triggerRunId ?? null
|
|
4761
|
+
});
|
|
4366
4762
|
const sampledRows = rows.rows.length;
|
|
4367
4763
|
const availableRows = rows.count || sampledRows;
|
|
4368
4764
|
const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
|
|
4369
4765
|
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
4370
4766
|
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
4371
4767
|
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
4768
|
+
const northStarScorecard = createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState);
|
|
4372
4769
|
const gates = [
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4770
|
+
campaignReviewGate(
|
|
4771
|
+
"build_reviewable",
|
|
4772
|
+
"Build reviewable",
|
|
4773
|
+
terminalState.kind === "running_healthy" ? "pending" : terminalState.kind === "running_stale" || terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
4774
|
+
terminalState.label
|
|
4775
|
+
),
|
|
4776
|
+
campaignReviewGate(
|
|
4777
|
+
"rows_available",
|
|
4778
|
+
"Rows available",
|
|
4779
|
+
sampledRows > 0 ? "pass" : terminalState.reviewable ? "blocked" : "pending",
|
|
4780
|
+
sampledRows > 0 ? `${sampledRows} sampled row(s) are available for review.` : terminalState.reviewable ? "No rows were returned for review." : "Rows are not graded until the build is reviewable."
|
|
4781
|
+
),
|
|
4782
|
+
campaignReviewGate(
|
|
4783
|
+
"qualified_sample",
|
|
4784
|
+
"Sample rows qualified",
|
|
4785
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : qualifiedRows === sampledRows ? "pass" : "blocked",
|
|
4786
|
+
sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
|
|
4787
|
+
),
|
|
4788
|
+
campaignReviewGate(
|
|
4789
|
+
"email_coverage",
|
|
4790
|
+
"Email coverage",
|
|
4791
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : rowsWithEmail === sampledRows ? "pass" : "blocked",
|
|
4792
|
+
sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
|
|
4793
|
+
),
|
|
4794
|
+
campaignReviewGate(
|
|
4795
|
+
"artifact_available",
|
|
4796
|
+
"Artifact available",
|
|
4797
|
+
artifactCount > 0 ? "pass" : terminalState.reviewable ? "review" : "pending",
|
|
4798
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
|
|
4799
|
+
),
|
|
4800
|
+
campaignReviewGate(
|
|
4801
|
+
"no_failed_rows",
|
|
4802
|
+
"No failed rows",
|
|
4803
|
+
status.recordsFailed === 0 ? "pass" : "blocked",
|
|
4804
|
+
status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
|
|
4805
|
+
)
|
|
4409
4806
|
];
|
|
4410
|
-
const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
|
|
4807
|
+
const blockedReasons = gates.filter((gate) => gate.status === "blocked" || !gate.passed).map((gate) => gate.detail);
|
|
4411
4808
|
const warnings = [
|
|
4412
4809
|
rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
|
|
4413
4810
|
sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
|
|
4414
4811
|
...status.warnings
|
|
4415
4812
|
].filter((item) => Boolean(item));
|
|
4416
|
-
const readyForDeliveryApproval = blockedReasons.length === 0;
|
|
4813
|
+
const readyForDeliveryApproval = terminalState.reviewable && blockedReasons.length === 0 && northStarScorecard.status !== "blocked";
|
|
4417
4814
|
return {
|
|
4418
4815
|
campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
|
|
4419
4816
|
status,
|
|
4420
4817
|
rows,
|
|
4421
4818
|
artifacts,
|
|
4422
4819
|
gates,
|
|
4820
|
+
northStarScorecard,
|
|
4423
4821
|
summary: {
|
|
4424
4822
|
status: status.status,
|
|
4425
4823
|
phase: status.currentPhase,
|
|
4824
|
+
terminalState: terminalState.kind,
|
|
4825
|
+
scorecardStatus: northStarScorecard.status,
|
|
4826
|
+
scorecardScore: northStarScorecard.score,
|
|
4426
4827
|
sampledRows,
|
|
4427
4828
|
availableRows,
|
|
4428
4829
|
qualifiedRows,
|
|
@@ -4476,6 +4877,184 @@ function campaignReviewRowHasCopy(row) {
|
|
|
4476
4877
|
stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(data.opener) || stringValue(data.body) || stringValue(data.cta) || stringValue(copy.subject) || stringValue(copy.body) || stringValue(rawCopy.subject) || stringValue(rawCopy.body)
|
|
4477
4878
|
);
|
|
4478
4879
|
}
|
|
4880
|
+
function campaignReviewDataEmail(data) {
|
|
4881
|
+
return stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail) || stringValue(asRecord(data.contact).email);
|
|
4882
|
+
}
|
|
4883
|
+
function campaignReviewDataDomain(data) {
|
|
4884
|
+
const domain = stringValue(data.company_domain) || stringValue(data.companyDomain) || stringValue(asRecord(data.company).domain);
|
|
4885
|
+
return domain?.toLowerCase();
|
|
4886
|
+
}
|
|
4887
|
+
function campaignReviewDataHasVerifiedEmail(data) {
|
|
4888
|
+
const status = String(data.email_status ?? data.emailStatus ?? asRecord(data.contact).email_status ?? "").toLowerCase();
|
|
4889
|
+
return data.email_verified === true || data.emailVerified === true || asRecord(data.contact).email_verified === true || ["valid", "validated", "verified"].includes(status);
|
|
4890
|
+
}
|
|
4891
|
+
function campaignReviewDataCopyParts(data) {
|
|
4892
|
+
const copy = asRecord(data.copy);
|
|
4893
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4894
|
+
return [
|
|
4895
|
+
stringValue(data.subject),
|
|
4896
|
+
stringValue(data.subject_line),
|
|
4897
|
+
stringValue(data.opener),
|
|
4898
|
+
stringValue(data.body),
|
|
4899
|
+
stringValue(data.cta),
|
|
4900
|
+
stringValue(copy.subject),
|
|
4901
|
+
stringValue(copy.opener),
|
|
4902
|
+
stringValue(copy.body),
|
|
4903
|
+
stringValue(copy.cta),
|
|
4904
|
+
stringValue(rawCopy.subject),
|
|
4905
|
+
stringValue(rawCopy.opener),
|
|
4906
|
+
stringValue(rawCopy.body),
|
|
4907
|
+
stringValue(rawCopy.cta)
|
|
4908
|
+
].filter((part) => Boolean(part));
|
|
4909
|
+
}
|
|
4910
|
+
function campaignReviewDataHasBadGreeting(data) {
|
|
4911
|
+
return campaignReviewDataCopyParts(data).some(
|
|
4912
|
+
(part) => /\bhi\s*(?:,|\{\{|\[)|\bhello\s*(?:,|\{\{|\[)/i.test(part) || /\b(first_name|firstname|name)\b/i.test(part)
|
|
4913
|
+
);
|
|
4914
|
+
}
|
|
4915
|
+
function campaignReviewDataHasBannedPhrase(data) {
|
|
4916
|
+
const text = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4917
|
+
return NORTH_STAR_BANNED_COPY_PHRASES.some((phrase) => text.includes(phrase));
|
|
4918
|
+
}
|
|
4919
|
+
function campaignReviewDataSubject(data) {
|
|
4920
|
+
const copy = asRecord(data.copy);
|
|
4921
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4922
|
+
return stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(copy.subject) || stringValue(rawCopy.subject);
|
|
4923
|
+
}
|
|
4924
|
+
function campaignReviewDataCta(data) {
|
|
4925
|
+
const copy = asRecord(data.copy);
|
|
4926
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4927
|
+
return stringValue(data.cta) || stringValue(copy.cta) || stringValue(rawCopy.cta);
|
|
4928
|
+
}
|
|
4929
|
+
function countCampaignReviewIssues(issueRows) {
|
|
4930
|
+
return issueRows.flat().reduce((acc, issue) => {
|
|
4931
|
+
acc[issue] = (acc[issue] || 0) + 1;
|
|
4932
|
+
return acc;
|
|
4933
|
+
}, {});
|
|
4934
|
+
}
|
|
4935
|
+
function formatCampaignReviewIssueCounts(counts) {
|
|
4936
|
+
const entries = Object.entries(counts);
|
|
4937
|
+
return entries.length > 0 ? entries.map(([issue, count]) => `${issue}=${count}`).join(", ") : "none";
|
|
4938
|
+
}
|
|
4939
|
+
function campaignReviewDataCopyQaIssues(data) {
|
|
4940
|
+
const issues = /* @__PURE__ */ new Set();
|
|
4941
|
+
const copyParts = campaignReviewDataCopyParts(data);
|
|
4942
|
+
if (copyParts.length === 0) return [];
|
|
4943
|
+
if (campaignReviewDataHasBadGreeting(data)) issues.add("bad_greeting");
|
|
4944
|
+
if (campaignReviewDataHasBannedPhrase(data)) issues.add("banned_or_vague_phrase");
|
|
4945
|
+
if (campaignReviewDataHasUnsupportedSignalClaim(data)) issues.add("unsupported_signal_claim");
|
|
4946
|
+
if (campaignReviewDataPersonalizationBasis(data).length === 0) issues.add("missing_personalization_basis");
|
|
4947
|
+
if (!campaignReviewDataCta(data)) issues.add("missing_cta");
|
|
4948
|
+
const subject = campaignReviewDataSubject(data);
|
|
4949
|
+
if (subject && subject.length > 70) issues.add("overlong_subject");
|
|
4950
|
+
return [...issues];
|
|
4951
|
+
}
|
|
4952
|
+
function campaignReviewDataPersonalizationBasis(data) {
|
|
4953
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4954
|
+
const copy = asRecord(data.copy);
|
|
4955
|
+
const metadata = asRecord(data.metadata);
|
|
4956
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4957
|
+
return [
|
|
4958
|
+
...arrayOfStrings(data.personalization_basis) ?? [],
|
|
4959
|
+
...arrayOfStrings(data.personalizationBasis) ?? [],
|
|
4960
|
+
...arrayOfStrings(copy.personalization_basis) ?? [],
|
|
4961
|
+
...arrayOfStrings(rawCopy.personalization_basis) ?? [],
|
|
4962
|
+
...arrayOfStrings(metadata.personalization_basis) ?? [],
|
|
4963
|
+
...arrayOfStrings(metadata.personalizationBasis) ?? [],
|
|
4964
|
+
...arrayOfStrings(signalEvidence.personalization_basis) ?? []
|
|
4965
|
+
];
|
|
4966
|
+
}
|
|
4967
|
+
function campaignReviewDataHasNoSupportedSignal(data) {
|
|
4968
|
+
const metadata = asRecord(data.metadata);
|
|
4969
|
+
const copy = asRecord(data.copy);
|
|
4970
|
+
const state = [
|
|
4971
|
+
data.copy_signal_state,
|
|
4972
|
+
data.signal_basis,
|
|
4973
|
+
data.signal_type,
|
|
4974
|
+
metadata.copy_signal_state,
|
|
4975
|
+
metadata.signal_basis,
|
|
4976
|
+
metadata.signal_type,
|
|
4977
|
+
copy.copy_signal_state
|
|
4978
|
+
].map((value) => stringValue(value)?.toLowerCase()).filter(Boolean);
|
|
4979
|
+
return data.no_supported_signal === true || metadata.no_supported_signal === true || state.includes("no_supported_signal");
|
|
4980
|
+
}
|
|
4981
|
+
function campaignReviewDataHasSignalBasis(data) {
|
|
4982
|
+
if (campaignReviewDataHasNoSupportedSignal(data)) return false;
|
|
4983
|
+
const metadata = asRecord(data.metadata);
|
|
4984
|
+
const signal = asRecord(data.signal);
|
|
4985
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4986
|
+
const basis = campaignReviewDataPersonalizationBasis(data);
|
|
4987
|
+
return Boolean(
|
|
4988
|
+
stringValue(data.signal_type) || stringValue(data.signalType) || stringValue(data.signal_summary) || stringValue(data.signalSummary) || stringValue(data.signal_source_url) || stringValue(data.signalSourceUrl) || stringValue(metadata.signal_type) || stringValue(metadata.signal_summary) || stringValue(metadata.signal_source_url) || stringValue(signal.type) || stringValue(signal.summary) || stringValue(signal.source_url) || stringValue(signalEvidence.signal_type) || stringValue(signalEvidence.summary) || stringValue(signalEvidence.source_url) || basis.some((item) => /\bsignal|hiring|funding|leadership|growth|launch|news|technology\b/i.test(item))
|
|
4989
|
+
);
|
|
4990
|
+
}
|
|
4991
|
+
function campaignReviewDataHasUnsupportedSignalClaim(data) {
|
|
4992
|
+
const copyText = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4993
|
+
const claimsSignal = /\b(hiring|funding|raised|launched|expanding|appointed|announced|recent news|new role|job posting)\b/.test(copyText);
|
|
4994
|
+
return claimsSignal && !campaignReviewDataHasSignalBasis(data);
|
|
4995
|
+
}
|
|
4996
|
+
function campaignReviewDataHasApprovalLock(data) {
|
|
4997
|
+
const metadata = asRecord(data.fit_metadata ?? data.metadata);
|
|
4998
|
+
const approval = String(data.approval_status ?? data.delivery_approval_status ?? metadata.approval_status ?? "").toLowerCase();
|
|
4999
|
+
const exportStatus = String(data.export_status ?? data.exportStatus ?? "").toLowerCase();
|
|
5000
|
+
return data.export_ready === false || data.exportReady === false || data.delivery_approved === false || data.deliveryApproved === false || ["pending", "pending_approval", "blocked", "approval_required", "held"].includes(approval) || ["pending", "blocked", "approval_required", "held"].includes(exportStatus);
|
|
5001
|
+
}
|
|
5002
|
+
function campaignReviewDataHasHoldoutAttribution(data) {
|
|
5003
|
+
const fitMetadata = asRecord(data.fit_metadata ?? data.fitMetadata);
|
|
5004
|
+
const metadata = asRecord(data.metadata);
|
|
5005
|
+
const holdout = asRecord(
|
|
5006
|
+
fitMetadata.learning_holdout ?? fitMetadata.learningHoldout ?? metadata.learning_holdout ?? metadata.learningHoldout ?? data.learning_holdout ?? data.learningHoldout
|
|
5007
|
+
);
|
|
5008
|
+
const experimentKey = stringValue(holdout.experiment_key ?? holdout.experimentKey ?? data.experiment_key ?? data.experimentKey);
|
|
5009
|
+
const cohort = stringValue(holdout.cohort_role ?? holdout.cohortRole ?? data.cohort_role ?? data.learning_cohort ?? data.learningCohort);
|
|
5010
|
+
const brainApplied = holdout.brain_applied ?? holdout.brainApplied ?? data.brain_applied ?? data.signaliz_brain_applied;
|
|
5011
|
+
return Boolean(experimentKey && cohort && brainApplied !== void 0);
|
|
5012
|
+
}
|
|
5013
|
+
function campaignReviewStatusHasFeedbackReadiness(status) {
|
|
5014
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
5015
|
+
const feedbackReadiness = asRecord(diagnostics.feedback_readiness ?? diagnostics.feedbackReadiness);
|
|
5016
|
+
const feedbackSubstrate = asRecord(diagnostics.feedback_substrate ?? diagnostics.feedbackSubstrate);
|
|
5017
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
5018
|
+
return feedbackReadiness.ready === true || feedbackReadiness.webhook_configured === true || feedbackSubstrate.stage === "ready" || Number(evidenceCounts.feedback_events ?? 0) > 0;
|
|
5019
|
+
}
|
|
5020
|
+
function campaignReviewMoatState(status, holdoutRows) {
|
|
5021
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
5022
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
5023
|
+
const embeddedHoldout = asRecord(diagnostics.embedded_holdout ?? diagnostics.embeddedHoldout);
|
|
5024
|
+
const holdoutLift = asRecord(diagnostics.holdout_lift ?? diagnostics.holdoutLift ?? diagnostics.holdout_measurement ?? diagnostics.holdoutMeasurement);
|
|
5025
|
+
const controlSample = Math.max(
|
|
5026
|
+
Number(evidenceCounts.holdout_control_sample_size ?? 0),
|
|
5027
|
+
Number(evidenceCounts.embedded_holdout_control_sample ?? 0),
|
|
5028
|
+
Number(embeddedHoldout.control_sample_size ?? 0)
|
|
5029
|
+
);
|
|
5030
|
+
const learnedSample = Math.max(
|
|
5031
|
+
Number(evidenceCounts.holdout_learned_sample_size ?? 0),
|
|
5032
|
+
Number(evidenceCounts.embedded_holdout_learned_sample ?? 0),
|
|
5033
|
+
Number(embeddedHoldout.learned_sample_size ?? 0)
|
|
5034
|
+
);
|
|
5035
|
+
const feedbackEvents = Math.max(
|
|
5036
|
+
Number(evidenceCounts.feedback_events ?? 0),
|
|
5037
|
+
Number(evidenceCounts.campaign_build_feedback_events ?? 0),
|
|
5038
|
+
Number(evidenceCounts.delivery_holdout_sent_outcomes ?? 0)
|
|
5039
|
+
);
|
|
5040
|
+
const proved = diagnostics.closed_loop_proof_ready === true || holdoutLift.honest_measurement === true && holdoutLift.positive_lift === true;
|
|
5041
|
+
if (proved) {
|
|
5042
|
+
return {
|
|
5043
|
+
state: "proved",
|
|
5044
|
+
detail: "Real attributed feedback shows positive lift for explicit control and learned cohorts."
|
|
5045
|
+
};
|
|
5046
|
+
}
|
|
5047
|
+
if (controlSample > 0 && learnedSample > 0 && feedbackEvents > 0) {
|
|
5048
|
+
return {
|
|
5049
|
+
state: "moved_needs_lift_volume",
|
|
5050
|
+
detail: `Control and learned cohorts have attributed feedback (${controlSample} control, ${learnedSample} learned, ${feedbackEvents} feedback event(s)); keep collecting lift volume before claiming proof.`
|
|
5051
|
+
};
|
|
5052
|
+
}
|
|
5053
|
+
return {
|
|
5054
|
+
state: "scaffolding_ready",
|
|
5055
|
+
detail: holdoutRows > 0 ? `${holdoutRows} sampled row(s) carry holdout attribution, but real control/learned feedback lift is not proved yet.` : "Holdout and feedback scaffolding may be ready, but no real control/learned feedback lift is proved yet."
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
4479
5058
|
function createCampaignBuilderReadinessLane(route, plan) {
|
|
4480
5059
|
const builtIn = campaignBuilderBuiltInForRoute(route);
|
|
4481
5060
|
const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
|
|
@@ -7691,6 +8270,16 @@ var GtmKernel = class {
|
|
|
7691
8270
|
min_privacy_k: input.minPrivacyK
|
|
7692
8271
|
});
|
|
7693
8272
|
}
|
|
8273
|
+
/** Queue a dry-run-first bridge from imported historical feedback to attribution-only Campaign Build surrogates. */
|
|
8274
|
+
async runHistoricalCampaignBuildBridge(input = {}) {
|
|
8275
|
+
return this.callMcp("gtm_historical_campaign_build_bridge_run", {
|
|
8276
|
+
dry_run: input.dryRun,
|
|
8277
|
+
write_approved: input.writeApproved,
|
|
8278
|
+
max_events: input.maxEvents,
|
|
8279
|
+
max_campaigns: input.maxCampaigns,
|
|
8280
|
+
min_feedback_events: input.minFeedbackEvents
|
|
8281
|
+
});
|
|
8282
|
+
}
|
|
7694
8283
|
/** Prepare a provider-agnostic feedback webhook URL for Smartlead, HeyReach, Airbyte, or custom sender events. */
|
|
7695
8284
|
async prepareFeedbackWebhook(input) {
|
|
7696
8285
|
return this.callMcp("gtm_feedback_webhook_prepare", {
|