@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
|
@@ -837,7 +837,11 @@ var Campaigns = class {
|
|
|
837
837
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
838
838
|
brainContext: data.brain_context ?? {},
|
|
839
839
|
learningHoldout: data.learning_holdout ?? {},
|
|
840
|
-
providerRoute: mapProviderRoute(data)
|
|
840
|
+
providerRoute: mapProviderRoute(data),
|
|
841
|
+
effectiveSourceRouting: recordOrNull(data.effective_source_routing ?? data.source_routing),
|
|
842
|
+
sourcePlan: recordOrNull(data.source_plan ?? data.plan?.source_plan),
|
|
843
|
+
sourceRoutes: recordArray(data.source_routes ?? data.source_plan?.routes ?? data.plan?.source_plan?.routes),
|
|
844
|
+
sourceShards: recordArray(data.source_shards ?? data.source_plan?.shards ?? data.plan?.source_plan?.shards)
|
|
841
845
|
};
|
|
842
846
|
}
|
|
843
847
|
async getCampaignBuildStatus(campaignBuildId) {
|
|
@@ -1029,6 +1033,9 @@ function recordOrNull(value) {
|
|
|
1029
1033
|
function stringArray(value) {
|
|
1030
1034
|
return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
|
|
1031
1035
|
}
|
|
1036
|
+
function recordArray(value) {
|
|
1037
|
+
return Array.isArray(value) ? value.map(recordOrNull).filter((item) => Boolean(item)) : [];
|
|
1038
|
+
}
|
|
1032
1039
|
function diagnosticMessages(value) {
|
|
1033
1040
|
if (!Array.isArray(value)) return [];
|
|
1034
1041
|
return value.map((item) => {
|
|
@@ -1190,7 +1197,8 @@ function buildArgs(request) {
|
|
|
1190
1197
|
suppress_prior_campaign_ids: request.cacheReusePolicy.suppressPriorCampaignIds,
|
|
1191
1198
|
suppress_prior_list_ids: request.cacheReusePolicy.suppressPriorListIds,
|
|
1192
1199
|
uniqueness: request.cacheReusePolicy.uniqueness,
|
|
1193
|
-
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata
|
|
1200
|
+
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata,
|
|
1201
|
+
verified_cache_ttl_days: request.cacheReusePolicy.verifiedCacheTtlDays
|
|
1194
1202
|
};
|
|
1195
1203
|
}
|
|
1196
1204
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
@@ -1873,6 +1881,7 @@ var CampaignBuilderAgent = class {
|
|
|
1873
1881
|
},
|
|
1874
1882
|
brain_defaults: plan.buildRequest.brainDefaults,
|
|
1875
1883
|
brain_preflight: plan.buildRequest.brainPreflight,
|
|
1884
|
+
learning_holdout: plan.buildRequest.learningHoldout,
|
|
1876
1885
|
campaign_strategy_context: plan.buildRequest.campaignStrategyContext,
|
|
1877
1886
|
delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
1878
1887
|
delivery_risk: plan.buildRequest.deliveryRisk
|
|
@@ -1886,7 +1895,8 @@ var CampaignBuilderAgent = class {
|
|
|
1886
1895
|
estimated_credits: plan.estimatedCredits
|
|
1887
1896
|
},
|
|
1888
1897
|
brain_delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
1889
|
-
delivery_risk: plan.buildRequest.deliveryRisk
|
|
1898
|
+
delivery_risk: plan.buildRequest.deliveryRisk,
|
|
1899
|
+
learning_holdout: plan.buildRequest.learningHoldout
|
|
1890
1900
|
},
|
|
1891
1901
|
approval_required: plan.approvals.some((approval) => approval.blocking),
|
|
1892
1902
|
actor_type: "agent",
|
|
@@ -3292,6 +3302,21 @@ function routesFromCustomerTools(tools) {
|
|
|
3292
3302
|
}))
|
|
3293
3303
|
);
|
|
3294
3304
|
}
|
|
3305
|
+
function normalizeCampaignBuilderLearningHoldout(input, gtmCampaignId, campaignName) {
|
|
3306
|
+
const source = asRecord(input);
|
|
3307
|
+
if (source.enabled === false) return { enabled: false };
|
|
3308
|
+
const controlPercentage = numberOrUndefined2(source.control_percentage ?? source.controlPercentage);
|
|
3309
|
+
const minControlSize = numberOrUndefined2(source.min_control_size ?? source.minControlSize);
|
|
3310
|
+
const experimentKey = stringValue(source.experiment_key ?? source.experimentKey) ?? (gtmCampaignId ? `gtm_campaign:${gtmCampaignId}:learning_holdout` : `campaign_builder_agent:${hashString(campaignName)}:learning_holdout`);
|
|
3311
|
+
return {
|
|
3312
|
+
enabled: true,
|
|
3313
|
+
controlPercentage: controlPercentage ?? 0.2,
|
|
3314
|
+
minControlSize: minControlSize ?? 50,
|
|
3315
|
+
experimentKey,
|
|
3316
|
+
sourceCampaignId: stringValue(source.source_campaign_id ?? source.sourceCampaignId) ?? gtmCampaignId,
|
|
3317
|
+
primaryMetric: stringValue(source.primary_metric ?? source.primaryMetric) ?? "positive_reply_rate"
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3295
3320
|
function createBuildRequest(request, defaults, targetCount, campaignName, scopeBuildArgs, routes = [], memory, scopeBrainPreflight) {
|
|
3296
3321
|
const buildRequest = {
|
|
3297
3322
|
name: campaignName,
|
|
@@ -3324,6 +3349,11 @@ function createBuildRequest(request, defaults, targetCount, campaignName, scopeB
|
|
|
3324
3349
|
};
|
|
3325
3350
|
const scoped = asRecord(scopeBuildArgs);
|
|
3326
3351
|
buildRequest.gtmCampaignId = request.gtmCampaignId ?? stringValue(scoped.gtm_campaign_id ?? scoped.gtmCampaignId);
|
|
3352
|
+
buildRequest.learningHoldout = normalizeCampaignBuilderLearningHoldout(
|
|
3353
|
+
request.learningHoldout ?? scoped.learning_holdout ?? scoped.learningHoldout,
|
|
3354
|
+
buildRequest.gtmCampaignId,
|
|
3355
|
+
campaignName
|
|
3356
|
+
);
|
|
3327
3357
|
if (typeof scoped.name === "string") buildRequest.name = scoped.name;
|
|
3328
3358
|
if (typeof scoped.prompt === "string") buildRequest.prompt = scoped.prompt;
|
|
3329
3359
|
if (typeof scoped.target_count === "number") buildRequest.targetCount = scoped.target_count;
|
|
@@ -3398,6 +3428,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
|
|
|
3398
3428
|
target_icp: targetIcp,
|
|
3399
3429
|
provider_chain: providerChain,
|
|
3400
3430
|
lead_source: providerChain[0] ?? "signaliz",
|
|
3431
|
+
learning_holdout: buildRequest.learningHoldout,
|
|
3401
3432
|
tool_sequence: [
|
|
3402
3433
|
{ tool: "gtm_brain_learning_cycle_plan", purpose: "Plan the Brain learning loop for this campaign.", arguments: learningArgs },
|
|
3403
3434
|
{ tool: "gtm_brain_seed_defaults", purpose: "Seed campaign defaults from workspace memory and safe aggregate patterns.", arguments: defaultsArgs },
|
|
@@ -4065,6 +4096,16 @@ function buildCampaignArgs(request) {
|
|
|
4065
4096
|
if (request.dedupKeys) args.dedup_keys = request.dedupKeys;
|
|
4066
4097
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
4067
4098
|
if (request.brainDefaults) args.brain_defaults = request.brainDefaults;
|
|
4099
|
+
if (request.learningHoldout) {
|
|
4100
|
+
args.learning_holdout = compact({
|
|
4101
|
+
enabled: request.learningHoldout.enabled,
|
|
4102
|
+
control_percentage: request.learningHoldout.controlPercentage,
|
|
4103
|
+
min_control_size: request.learningHoldout.minControlSize,
|
|
4104
|
+
experiment_key: request.learningHoldout.experimentKey,
|
|
4105
|
+
source_campaign_id: request.learningHoldout.sourceCampaignId,
|
|
4106
|
+
primary_metric: request.learningHoldout.primaryMetric
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4068
4109
|
if (request.campaignStrategyContext) args.campaign_strategy_context = request.campaignStrategyContext;
|
|
4069
4110
|
if (request.deliveryRisk) args.delivery_risk = request.deliveryRisk;
|
|
4070
4111
|
if (request.icp) {
|
|
@@ -4161,6 +4202,7 @@ function buildExecutionPrepareArgs(request, dryRun, confirmSpend) {
|
|
|
4161
4202
|
delivery_risk: buildArgs2.delivery_risk,
|
|
4162
4203
|
dedup_keys: buildArgs2.dedup_keys,
|
|
4163
4204
|
policy: buildArgs2.policy,
|
|
4205
|
+
learning_holdout: buildArgs2.learning_holdout,
|
|
4164
4206
|
signals: buildArgs2.signals,
|
|
4165
4207
|
qualification: buildArgs2.qualification,
|
|
4166
4208
|
copy: buildArgs2.copy,
|
|
@@ -4195,10 +4237,145 @@ function mapBuildResult(data, dryRun) {
|
|
|
4195
4237
|
targetLimitReason: data.target_limit_reason,
|
|
4196
4238
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
4197
4239
|
brainContext: data.brain_context ?? {},
|
|
4240
|
+
learningHoldout: data.learning_holdout ?? {},
|
|
4198
4241
|
providerRoute: mapProviderRoute2(data)
|
|
4199
4242
|
};
|
|
4200
4243
|
}
|
|
4244
|
+
var CAMPAIGN_BUILDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled", "cancelled"]);
|
|
4245
|
+
var CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["failed", "canceled", "cancelled"]);
|
|
4246
|
+
function mapAgentCampaignPhaseHealth(value) {
|
|
4247
|
+
if (!Array.isArray(value)) return [];
|
|
4248
|
+
return value.map((phase) => {
|
|
4249
|
+
const record = asRecord(phase);
|
|
4250
|
+
return {
|
|
4251
|
+
phase: stringValue(record.phase) ?? "unknown",
|
|
4252
|
+
status: stringValue(record.status) ?? "unknown",
|
|
4253
|
+
recordsProcessed: numberOrNull2(record.records_processed ?? record.recordsProcessed),
|
|
4254
|
+
recordsSucceeded: numberOrNull2(record.records_succeeded ?? record.recordsSucceeded),
|
|
4255
|
+
recordsFailed: numberOrNull2(record.records_failed ?? record.recordsFailed),
|
|
4256
|
+
heartbeatAgeSeconds: numberOrNull2(record.heartbeat_age_seconds ?? record.heartbeatAgeSeconds ?? record.phase_age_seconds)
|
|
4257
|
+
};
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
function deriveCampaignBuildTerminalState(input) {
|
|
4261
|
+
const status = input.status.toLowerCase();
|
|
4262
|
+
const heartbeatAgeSeconds = input.phaseAgeSeconds;
|
|
4263
|
+
const stale = input.staleRunningPhase === true;
|
|
4264
|
+
const safeWatchAction = input.triggerRunId ? `signaliz ops run-status ${input.triggerRunId} --watch` : "signaliz campaign-agent status <campaign_build_id>";
|
|
4265
|
+
if (CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES.has(status)) {
|
|
4266
|
+
return {
|
|
4267
|
+
kind: "blocked",
|
|
4268
|
+
label: "Blocked terminal state",
|
|
4269
|
+
reviewable: true,
|
|
4270
|
+
terminal: true,
|
|
4271
|
+
stale: false,
|
|
4272
|
+
phase: input.phase,
|
|
4273
|
+
phaseStatus: input.phaseStatus,
|
|
4274
|
+
heartbeatAgeSeconds,
|
|
4275
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4276
|
+
terminalReason: input.terminalReason,
|
|
4277
|
+
staleReason: null,
|
|
4278
|
+
safeNextAction: input.nextAction || "Review errors and rerun only after the blocker is resolved."
|
|
4279
|
+
};
|
|
4280
|
+
}
|
|
4281
|
+
if (status === "pending_approval") {
|
|
4282
|
+
return {
|
|
4283
|
+
kind: "pending_approval",
|
|
4284
|
+
label: "Pending delivery approval",
|
|
4285
|
+
reviewable: true,
|
|
4286
|
+
terminal: false,
|
|
4287
|
+
stale: false,
|
|
4288
|
+
phase: input.phase,
|
|
4289
|
+
phaseStatus: input.phaseStatus,
|
|
4290
|
+
heartbeatAgeSeconds,
|
|
4291
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4292
|
+
terminalReason: input.terminalReason,
|
|
4293
|
+
staleReason: null,
|
|
4294
|
+
safeNextAction: input.approvalAction || input.nextAction || "Review rows and approve delivery only after the approval packet passes."
|
|
4295
|
+
};
|
|
4296
|
+
}
|
|
4297
|
+
if (CAMPAIGN_BUILDER_TERMINAL_STATUSES.has(status)) {
|
|
4298
|
+
return {
|
|
4299
|
+
kind: "terminal",
|
|
4300
|
+
label: "Terminal and reviewable",
|
|
4301
|
+
reviewable: true,
|
|
4302
|
+
terminal: true,
|
|
4303
|
+
stale: false,
|
|
4304
|
+
phase: input.phase,
|
|
4305
|
+
phaseStatus: input.phaseStatus,
|
|
4306
|
+
heartbeatAgeSeconds,
|
|
4307
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4308
|
+
terminalReason: input.terminalReason,
|
|
4309
|
+
staleReason: null,
|
|
4310
|
+
safeNextAction: input.nextAction || "Review rows, artifacts, scorecard, and approval locks before delivery."
|
|
4311
|
+
};
|
|
4312
|
+
}
|
|
4313
|
+
if (status === "running" || status === "queued" || status === "processing") {
|
|
4314
|
+
if (stale) {
|
|
4315
|
+
return {
|
|
4316
|
+
kind: "running_stale",
|
|
4317
|
+
label: "Running but stale",
|
|
4318
|
+
reviewable: false,
|
|
4319
|
+
terminal: false,
|
|
4320
|
+
stale: true,
|
|
4321
|
+
phase: input.phase,
|
|
4322
|
+
phaseStatus: input.phaseStatus,
|
|
4323
|
+
heartbeatAgeSeconds,
|
|
4324
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4325
|
+
terminalReason: null,
|
|
4326
|
+
staleReason: input.staleReason || "The active phase heartbeat is stale.",
|
|
4327
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
return {
|
|
4331
|
+
kind: "running_healthy",
|
|
4332
|
+
label: "Running and healthy",
|
|
4333
|
+
reviewable: false,
|
|
4334
|
+
terminal: false,
|
|
4335
|
+
stale: false,
|
|
4336
|
+
phase: input.phase,
|
|
4337
|
+
phaseStatus: input.phaseStatus,
|
|
4338
|
+
heartbeatAgeSeconds,
|
|
4339
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4340
|
+
terminalReason: null,
|
|
4341
|
+
staleReason: null,
|
|
4342
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
return {
|
|
4346
|
+
kind: "unknown",
|
|
4347
|
+
label: "Unknown build state",
|
|
4348
|
+
reviewable: false,
|
|
4349
|
+
terminal: false,
|
|
4350
|
+
stale,
|
|
4351
|
+
phase: input.phase,
|
|
4352
|
+
phaseStatus: input.phaseStatus,
|
|
4353
|
+
heartbeatAgeSeconds,
|
|
4354
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4355
|
+
terminalReason: input.terminalReason,
|
|
4356
|
+
staleReason: input.staleReason,
|
|
4357
|
+
safeNextAction: input.nextAction || "Read campaign build status again before making a launch or delivery decision."
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4201
4360
|
function mapAgentCampaignBuildStatus(data) {
|
|
4361
|
+
const customerRowCounts = mapAgentCustomerRowCounts(data.customer_row_counts);
|
|
4362
|
+
const phaseHealth = mapAgentCampaignPhaseHealth(data.phases);
|
|
4363
|
+
const terminalReason = stringValue(data.terminal_reason) ?? customerRowCounts?.terminalReason ?? null;
|
|
4364
|
+
const staleReason = stringValue(data.stale_reason) ?? stringValue(asRecord(data.diagnostics).stale_reason) ?? null;
|
|
4365
|
+
const nextPollAfterSeconds = numberOrNull2(data.next_poll_after_seconds ?? data.nextPollAfterSeconds);
|
|
4366
|
+
const terminalState = deriveCampaignBuildTerminalState({
|
|
4367
|
+
status: stringValue(data.status) ?? "unknown",
|
|
4368
|
+
phase: stringValue(data.current_phase) ?? null,
|
|
4369
|
+
phaseStatus: stringValue(data.current_phase_status) ?? null,
|
|
4370
|
+
staleRunningPhase: data.stale_running_phase === true,
|
|
4371
|
+
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
4372
|
+
nextPollAfterSeconds,
|
|
4373
|
+
terminalReason,
|
|
4374
|
+
staleReason,
|
|
4375
|
+
nextAction: formatAgentNextAction(data.next_action),
|
|
4376
|
+
approvalAction: formatAgentNextAction(data.approval_action),
|
|
4377
|
+
triggerRunId: stringValue(data.trigger_run_id) ?? null
|
|
4378
|
+
});
|
|
4202
4379
|
return {
|
|
4203
4380
|
campaignBuildId: stringValue(data.campaign_build_id) ?? "",
|
|
4204
4381
|
campaignId: stringValue(data.campaign_id) ?? null,
|
|
@@ -4216,12 +4393,18 @@ function mapAgentCampaignBuildStatus(data) {
|
|
|
4216
4393
|
errors: diagnosticMessages2(data.errors),
|
|
4217
4394
|
artifactCount: numberOrUndefined2(data.artifact_count) ?? 0,
|
|
4218
4395
|
artifactDownloads: Array.isArray(data.artifact_downloads) ? data.artifact_downloads.map(mapAgentCampaignArtifactDownload) : [],
|
|
4219
|
-
customerRowCounts
|
|
4396
|
+
customerRowCounts,
|
|
4220
4397
|
phaseAttemptTotals: nullableRecord(data.phase_attempt_totals) ?? void 0,
|
|
4221
4398
|
providerRoute: mapProviderRoute2(data),
|
|
4222
4399
|
triggerRunId: stringValue(data.trigger_run_id) ?? null,
|
|
4223
4400
|
staleRunningPhase: data.stale_running_phase === true,
|
|
4224
4401
|
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
4402
|
+
nextPollAfterSeconds,
|
|
4403
|
+
terminalReason,
|
|
4404
|
+
staleReason,
|
|
4405
|
+
safeNextAction: terminalState.safeNextAction,
|
|
4406
|
+
terminalState,
|
|
4407
|
+
phaseHealth,
|
|
4225
4408
|
diagnostics: nullableRecord(data.diagnostics) ?? {},
|
|
4226
4409
|
nextAction: formatAgentNextAction(data.next_action),
|
|
4227
4410
|
approvalAction: formatAgentNextAction(data.approval_action),
|
|
@@ -4309,67 +4492,285 @@ function mapAgentCampaignArtifact(data) {
|
|
|
4309
4492
|
createdAt: stringValue(data.created_at) ?? ""
|
|
4310
4493
|
};
|
|
4311
4494
|
}
|
|
4495
|
+
var NORTH_STAR_BANNED_COPY_PHRASES = [
|
|
4496
|
+
"i have been following",
|
|
4497
|
+
"i saw that",
|
|
4498
|
+
"would you be opposed",
|
|
4499
|
+
"unlock new opportunities",
|
|
4500
|
+
"significant success",
|
|
4501
|
+
"we understand",
|
|
4502
|
+
"hope this finds you well"
|
|
4503
|
+
];
|
|
4504
|
+
function campaignReviewGate(id, label, status, detail) {
|
|
4505
|
+
return {
|
|
4506
|
+
id,
|
|
4507
|
+
label,
|
|
4508
|
+
status,
|
|
4509
|
+
passed: status !== "blocked",
|
|
4510
|
+
detail
|
|
4511
|
+
};
|
|
4512
|
+
}
|
|
4513
|
+
function northStarMetric(id, label, status, detail, value, target) {
|
|
4514
|
+
return {
|
|
4515
|
+
id,
|
|
4516
|
+
label,
|
|
4517
|
+
status,
|
|
4518
|
+
detail,
|
|
4519
|
+
...value !== void 0 ? { value } : {},
|
|
4520
|
+
...target !== void 0 ? { target } : {}
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
function createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState) {
|
|
4524
|
+
if (!terminalState.reviewable) {
|
|
4525
|
+
const pendingMetric = northStarMetric(
|
|
4526
|
+
"terminal_state",
|
|
4527
|
+
"Terminal-state contract",
|
|
4528
|
+
terminalState.kind === "running_stale" ? "blocked" : "pending",
|
|
4529
|
+
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.`,
|
|
4530
|
+
terminalState.kind,
|
|
4531
|
+
"terminal or pending_approval"
|
|
4532
|
+
);
|
|
4533
|
+
return {
|
|
4534
|
+
version: "campaign-builder-north-star.v1",
|
|
4535
|
+
status: pendingMetric.status,
|
|
4536
|
+
score: null,
|
|
4537
|
+
moatState: "scaffolding_ready",
|
|
4538
|
+
metrics: [pendingMetric],
|
|
4539
|
+
blockers: pendingMetric.status === "blocked" ? [pendingMetric.detail] : [],
|
|
4540
|
+
warnings: pendingMetric.status === "pending" ? [pendingMetric.detail] : [],
|
|
4541
|
+
nextAction: terminalState.safeNextAction
|
|
4542
|
+
};
|
|
4543
|
+
}
|
|
4544
|
+
const sampledRows = rows.rows.length;
|
|
4545
|
+
const rowData = rows.rows.map((row) => asRecord(row.data));
|
|
4546
|
+
const requestedTarget = status.customerRowCounts?.requestedTarget ?? status.recordsTotal ?? rows.count;
|
|
4547
|
+
const finalRows = status.customerRowCounts?.deliveredRows ?? status.customerRowCounts?.qaReadyRows ?? status.customerRowCounts?.copiedRows ?? status.customerRowCounts?.acceptedRows ?? rows.count;
|
|
4548
|
+
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
4549
|
+
const verifiedEmailRows = rowData.filter(campaignReviewDataHasVerifiedEmail).length;
|
|
4550
|
+
const uniqueEmails = new Set(rowData.map(campaignReviewDataEmail).filter(Boolean)).size;
|
|
4551
|
+
const uniqueDomains = new Set(rowData.map(campaignReviewDataDomain).filter(Boolean)).size;
|
|
4552
|
+
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
4553
|
+
const copyQaIssuesByRow = rowData.map(campaignReviewDataCopyQaIssues).filter((issues) => issues.length > 0);
|
|
4554
|
+
const copyQaIssueCounts = countCampaignReviewIssues(copyQaIssuesByRow);
|
|
4555
|
+
const signalBackedRows = rowData.filter(campaignReviewDataHasSignalBasis).length;
|
|
4556
|
+
const noSupportedSignalRows = rowData.filter(campaignReviewDataHasNoSupportedSignal).length;
|
|
4557
|
+
const unsupportedSignalClaims = rowData.filter(campaignReviewDataHasUnsupportedSignalClaim).length;
|
|
4558
|
+
const approvalLockedRows = rowData.filter(campaignReviewDataHasApprovalLock).length;
|
|
4559
|
+
const holdoutRows = rowData.filter(campaignReviewDataHasHoldoutAttribution).length;
|
|
4560
|
+
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
4561
|
+
const deliveredRows = status.customerRowCounts?.deliveredRows ?? 0;
|
|
4562
|
+
const feedbackReady = campaignReviewStatusHasFeedbackReadiness(status);
|
|
4563
|
+
const feedbackRequiredForApproval = terminalState.kind === "pending_approval";
|
|
4564
|
+
const moat = campaignReviewMoatState(status, holdoutRows);
|
|
4565
|
+
const metrics = [
|
|
4566
|
+
northStarMetric(
|
|
4567
|
+
"terminal_state",
|
|
4568
|
+
"Terminal-state contract",
|
|
4569
|
+
terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
4570
|
+
terminalState.label,
|
|
4571
|
+
terminalState.kind,
|
|
4572
|
+
"terminal or pending_approval"
|
|
4573
|
+
),
|
|
4574
|
+
northStarMetric(
|
|
4575
|
+
"target_fill",
|
|
4576
|
+
"Target fill",
|
|
4577
|
+
requestedTarget && finalRows < requestedTarget ? "review" : "pass",
|
|
4578
|
+
requestedTarget ? `${finalRows || 0}/${requestedTarget} final or reviewable row(s) are accounted for.` : "No requested target was reported; using sampled rows for review.",
|
|
4579
|
+
finalRows || sampledRows,
|
|
4580
|
+
requestedTarget || sampledRows
|
|
4581
|
+
),
|
|
4582
|
+
northStarMetric(
|
|
4583
|
+
"unique_emails",
|
|
4584
|
+
"Unique emails",
|
|
4585
|
+
sampledRows === 0 ? "blocked" : uniqueEmails === rowsWithEmail ? "pass" : "blocked",
|
|
4586
|
+
sampledRows === 0 ? "No sampled rows were available for email uniqueness review." : `${uniqueEmails}/${rowsWithEmail} sampled email(s) are unique.`,
|
|
4587
|
+
uniqueEmails,
|
|
4588
|
+
rowsWithEmail
|
|
4589
|
+
),
|
|
4590
|
+
northStarMetric(
|
|
4591
|
+
"unique_domains",
|
|
4592
|
+
"Unique domains",
|
|
4593
|
+
sampledRows === 0 ? "blocked" : uniqueDomains === sampledRows ? "pass" : "review",
|
|
4594
|
+
sampledRows === 0 ? "No sampled rows were available for domain uniqueness review." : `${uniqueDomains}/${sampledRows} sampled company domain(s) are unique.`,
|
|
4595
|
+
uniqueDomains,
|
|
4596
|
+
sampledRows
|
|
4597
|
+
),
|
|
4598
|
+
northStarMetric(
|
|
4599
|
+
"verified_email_rows",
|
|
4600
|
+
"Verified email rows",
|
|
4601
|
+
sampledRows === 0 ? "blocked" : verifiedEmailRows === sampledRows ? "pass" : rowsWithEmail === sampledRows ? "review" : "blocked",
|
|
4602
|
+
sampledRows === 0 ? "No sampled rows were available for email verification review." : `${verifiedEmailRows}/${sampledRows} sampled row(s) carry verified-email evidence.`,
|
|
4603
|
+
verifiedEmailRows,
|
|
4604
|
+
sampledRows
|
|
4605
|
+
),
|
|
4606
|
+
northStarMetric(
|
|
4607
|
+
"copy_completeness",
|
|
4608
|
+
"Copy completeness",
|
|
4609
|
+
sampledRows === 0 ? "blocked" : copyReadyRows === sampledRows ? "pass" : "blocked",
|
|
4610
|
+
sampledRows === 0 ? "No sampled rows were available for copy review." : `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.`,
|
|
4611
|
+
copyReadyRows,
|
|
4612
|
+
sampledRows
|
|
4613
|
+
),
|
|
4614
|
+
northStarMetric(
|
|
4615
|
+
"copy_quality",
|
|
4616
|
+
"Copy QA",
|
|
4617
|
+
copyQaIssuesByRow.length === 0 ? "pass" : "blocked",
|
|
4618
|
+
copyQaIssuesByRow.length === 0 ? "No sampled copy QA blockers were detected." : `${copyQaIssuesByRow.length}/${sampledRows} sampled row(s) have copy QA issue(s): ${formatCampaignReviewIssueCounts(copyQaIssueCounts)}.`,
|
|
4619
|
+
copyQaIssuesByRow.length,
|
|
4620
|
+
0
|
|
4621
|
+
),
|
|
4622
|
+
northStarMetric(
|
|
4623
|
+
"signal_grounding",
|
|
4624
|
+
"Signal grounding",
|
|
4625
|
+
unsupportedSignalClaims > 0 ? "blocked" : signalBackedRows > 0 ? "pass" : "review",
|
|
4626
|
+
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.",
|
|
4627
|
+
signalBackedRows,
|
|
4628
|
+
sampledRows
|
|
4629
|
+
),
|
|
4630
|
+
northStarMetric(
|
|
4631
|
+
"approval_locks",
|
|
4632
|
+
"Approval locks",
|
|
4633
|
+
approvalLockedRows > 0 || deliveredRows === 0 ? "pass" : "review",
|
|
4634
|
+
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.",
|
|
4635
|
+
approvalLockedRows,
|
|
4636
|
+
sampledRows
|
|
4637
|
+
),
|
|
4638
|
+
northStarMetric(
|
|
4639
|
+
"export_send_safety",
|
|
4640
|
+
"Export/send safety",
|
|
4641
|
+
deliveredRows > 0 && status.status !== "completed" ? "blocked" : "pass",
|
|
4642
|
+
deliveredRows > 0 ? `${deliveredRows} row(s) are delivered; status is ${status.status}.` : "No delivered rows are reported before approval.",
|
|
4643
|
+
deliveredRows,
|
|
4644
|
+
0
|
|
4645
|
+
),
|
|
4646
|
+
northStarMetric(
|
|
4647
|
+
"holdout_attribution",
|
|
4648
|
+
"Holdout attribution",
|
|
4649
|
+
holdoutRows > 0 ? "pass" : "review",
|
|
4650
|
+
holdoutRows > 0 ? `${holdoutRows}/${sampledRows} sampled row(s) preserve holdout attribution.` : "No holdout attribution was visible in the sampled rows.",
|
|
4651
|
+
holdoutRows,
|
|
4652
|
+
sampledRows
|
|
4653
|
+
),
|
|
4654
|
+
northStarMetric(
|
|
4655
|
+
"feedback_readiness",
|
|
4656
|
+
"Feedback readiness",
|
|
4657
|
+
feedbackReady ? "pass" : feedbackRequiredForApproval ? "blocked" : "review",
|
|
4658
|
+
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.",
|
|
4659
|
+
feedbackReady,
|
|
4660
|
+
true
|
|
4661
|
+
),
|
|
4662
|
+
northStarMetric(
|
|
4663
|
+
"causal_moat",
|
|
4664
|
+
"Causal moat",
|
|
4665
|
+
moat.state === "proved" ? "pass" : "review",
|
|
4666
|
+
moat.detail,
|
|
4667
|
+
moat.state,
|
|
4668
|
+
"proved"
|
|
4669
|
+
),
|
|
4670
|
+
northStarMetric(
|
|
4671
|
+
"artifact_readback",
|
|
4672
|
+
"Artifact readback",
|
|
4673
|
+
artifactCount > 0 ? "pass" : "review",
|
|
4674
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available for review.` : "No review artifact is available yet.",
|
|
4675
|
+
artifactCount,
|
|
4676
|
+
1
|
|
4677
|
+
)
|
|
4678
|
+
];
|
|
4679
|
+
const gradedMetrics = metrics.filter((metric) => metric.status !== "pending");
|
|
4680
|
+
const score = gradedMetrics.length === 0 ? null : Math.round(gradedMetrics.filter((metric) => metric.status === "pass").length / gradedMetrics.length * 100);
|
|
4681
|
+
const blockers = metrics.filter((metric) => metric.status === "blocked").map((metric) => metric.detail);
|
|
4682
|
+
const warnings = metrics.filter((metric) => metric.status === "review").map((metric) => metric.detail);
|
|
4683
|
+
const scorecardStatus = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "review" : "pass";
|
|
4684
|
+
return {
|
|
4685
|
+
version: "campaign-builder-north-star.v1",
|
|
4686
|
+
status: scorecardStatus,
|
|
4687
|
+
score,
|
|
4688
|
+
moatState: moat.state,
|
|
4689
|
+
metrics,
|
|
4690
|
+
blockers,
|
|
4691
|
+
warnings,
|
|
4692
|
+
nextAction: scorecardStatus === "blocked" ? "Fix blocked North Star gates before approval or delivery." : terminalState.safeNextAction
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4312
4695
|
function createCampaignBuilderBuildReview(status, rows, artifacts) {
|
|
4696
|
+
const terminalState = status.terminalState ?? deriveCampaignBuildTerminalState({
|
|
4697
|
+
status: status.status,
|
|
4698
|
+
phase: status.currentPhase,
|
|
4699
|
+
phaseStatus: status.currentPhaseStatus,
|
|
4700
|
+
staleRunningPhase: status.staleRunningPhase === true,
|
|
4701
|
+
phaseAgeSeconds: status.phaseAgeSeconds ?? null,
|
|
4702
|
+
nextPollAfterSeconds: status.nextPollAfterSeconds ?? null,
|
|
4703
|
+
terminalReason: status.terminalReason ?? status.customerRowCounts?.terminalReason ?? null,
|
|
4704
|
+
staleReason: status.staleReason ?? null,
|
|
4705
|
+
nextAction: status.nextAction,
|
|
4706
|
+
approvalAction: status.approvalAction,
|
|
4707
|
+
triggerRunId: status.triggerRunId ?? null
|
|
4708
|
+
});
|
|
4313
4709
|
const sampledRows = rows.rows.length;
|
|
4314
4710
|
const availableRows = rows.count || sampledRows;
|
|
4315
4711
|
const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
|
|
4316
4712
|
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
4317
4713
|
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
4318
4714
|
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
4715
|
+
const northStarScorecard = createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState);
|
|
4319
4716
|
const gates = [
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4717
|
+
campaignReviewGate(
|
|
4718
|
+
"build_reviewable",
|
|
4719
|
+
"Build reviewable",
|
|
4720
|
+
terminalState.kind === "running_healthy" ? "pending" : terminalState.kind === "running_stale" || terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
4721
|
+
terminalState.label
|
|
4722
|
+
),
|
|
4723
|
+
campaignReviewGate(
|
|
4724
|
+
"rows_available",
|
|
4725
|
+
"Rows available",
|
|
4726
|
+
sampledRows > 0 ? "pass" : terminalState.reviewable ? "blocked" : "pending",
|
|
4727
|
+
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."
|
|
4728
|
+
),
|
|
4729
|
+
campaignReviewGate(
|
|
4730
|
+
"qualified_sample",
|
|
4731
|
+
"Sample rows qualified",
|
|
4732
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : qualifiedRows === sampledRows ? "pass" : "blocked",
|
|
4733
|
+
sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
|
|
4734
|
+
),
|
|
4735
|
+
campaignReviewGate(
|
|
4736
|
+
"email_coverage",
|
|
4737
|
+
"Email coverage",
|
|
4738
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : rowsWithEmail === sampledRows ? "pass" : "blocked",
|
|
4739
|
+
sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
|
|
4740
|
+
),
|
|
4741
|
+
campaignReviewGate(
|
|
4742
|
+
"artifact_available",
|
|
4743
|
+
"Artifact available",
|
|
4744
|
+
artifactCount > 0 ? "pass" : terminalState.reviewable ? "review" : "pending",
|
|
4745
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
|
|
4746
|
+
),
|
|
4747
|
+
campaignReviewGate(
|
|
4748
|
+
"no_failed_rows",
|
|
4749
|
+
"No failed rows",
|
|
4750
|
+
status.recordsFailed === 0 ? "pass" : "blocked",
|
|
4751
|
+
status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
|
|
4752
|
+
)
|
|
4356
4753
|
];
|
|
4357
|
-
const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
|
|
4754
|
+
const blockedReasons = gates.filter((gate) => gate.status === "blocked" || !gate.passed).map((gate) => gate.detail);
|
|
4358
4755
|
const warnings = [
|
|
4359
4756
|
rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
|
|
4360
4757
|
sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
|
|
4361
4758
|
...status.warnings
|
|
4362
4759
|
].filter((item) => Boolean(item));
|
|
4363
|
-
const readyForDeliveryApproval = blockedReasons.length === 0;
|
|
4760
|
+
const readyForDeliveryApproval = terminalState.reviewable && blockedReasons.length === 0 && northStarScorecard.status !== "blocked";
|
|
4364
4761
|
return {
|
|
4365
4762
|
campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
|
|
4366
4763
|
status,
|
|
4367
4764
|
rows,
|
|
4368
4765
|
artifacts,
|
|
4369
4766
|
gates,
|
|
4767
|
+
northStarScorecard,
|
|
4370
4768
|
summary: {
|
|
4371
4769
|
status: status.status,
|
|
4372
4770
|
phase: status.currentPhase,
|
|
4771
|
+
terminalState: terminalState.kind,
|
|
4772
|
+
scorecardStatus: northStarScorecard.status,
|
|
4773
|
+
scorecardScore: northStarScorecard.score,
|
|
4373
4774
|
sampledRows,
|
|
4374
4775
|
availableRows,
|
|
4375
4776
|
qualifiedRows,
|
|
@@ -4423,6 +4824,184 @@ function campaignReviewRowHasCopy(row) {
|
|
|
4423
4824
|
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)
|
|
4424
4825
|
);
|
|
4425
4826
|
}
|
|
4827
|
+
function campaignReviewDataEmail(data) {
|
|
4828
|
+
return stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail) || stringValue(asRecord(data.contact).email);
|
|
4829
|
+
}
|
|
4830
|
+
function campaignReviewDataDomain(data) {
|
|
4831
|
+
const domain = stringValue(data.company_domain) || stringValue(data.companyDomain) || stringValue(asRecord(data.company).domain);
|
|
4832
|
+
return domain?.toLowerCase();
|
|
4833
|
+
}
|
|
4834
|
+
function campaignReviewDataHasVerifiedEmail(data) {
|
|
4835
|
+
const status = String(data.email_status ?? data.emailStatus ?? asRecord(data.contact).email_status ?? "").toLowerCase();
|
|
4836
|
+
return data.email_verified === true || data.emailVerified === true || asRecord(data.contact).email_verified === true || ["valid", "validated", "verified"].includes(status);
|
|
4837
|
+
}
|
|
4838
|
+
function campaignReviewDataCopyParts(data) {
|
|
4839
|
+
const copy = asRecord(data.copy);
|
|
4840
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4841
|
+
return [
|
|
4842
|
+
stringValue(data.subject),
|
|
4843
|
+
stringValue(data.subject_line),
|
|
4844
|
+
stringValue(data.opener),
|
|
4845
|
+
stringValue(data.body),
|
|
4846
|
+
stringValue(data.cta),
|
|
4847
|
+
stringValue(copy.subject),
|
|
4848
|
+
stringValue(copy.opener),
|
|
4849
|
+
stringValue(copy.body),
|
|
4850
|
+
stringValue(copy.cta),
|
|
4851
|
+
stringValue(rawCopy.subject),
|
|
4852
|
+
stringValue(rawCopy.opener),
|
|
4853
|
+
stringValue(rawCopy.body),
|
|
4854
|
+
stringValue(rawCopy.cta)
|
|
4855
|
+
].filter((part) => Boolean(part));
|
|
4856
|
+
}
|
|
4857
|
+
function campaignReviewDataHasBadGreeting(data) {
|
|
4858
|
+
return campaignReviewDataCopyParts(data).some(
|
|
4859
|
+
(part) => /\bhi\s*(?:,|\{\{|\[)|\bhello\s*(?:,|\{\{|\[)/i.test(part) || /\b(first_name|firstname|name)\b/i.test(part)
|
|
4860
|
+
);
|
|
4861
|
+
}
|
|
4862
|
+
function campaignReviewDataHasBannedPhrase(data) {
|
|
4863
|
+
const text = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4864
|
+
return NORTH_STAR_BANNED_COPY_PHRASES.some((phrase) => text.includes(phrase));
|
|
4865
|
+
}
|
|
4866
|
+
function campaignReviewDataSubject(data) {
|
|
4867
|
+
const copy = asRecord(data.copy);
|
|
4868
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4869
|
+
return stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(copy.subject) || stringValue(rawCopy.subject);
|
|
4870
|
+
}
|
|
4871
|
+
function campaignReviewDataCta(data) {
|
|
4872
|
+
const copy = asRecord(data.copy);
|
|
4873
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4874
|
+
return stringValue(data.cta) || stringValue(copy.cta) || stringValue(rawCopy.cta);
|
|
4875
|
+
}
|
|
4876
|
+
function countCampaignReviewIssues(issueRows) {
|
|
4877
|
+
return issueRows.flat().reduce((acc, issue) => {
|
|
4878
|
+
acc[issue] = (acc[issue] || 0) + 1;
|
|
4879
|
+
return acc;
|
|
4880
|
+
}, {});
|
|
4881
|
+
}
|
|
4882
|
+
function formatCampaignReviewIssueCounts(counts) {
|
|
4883
|
+
const entries = Object.entries(counts);
|
|
4884
|
+
return entries.length > 0 ? entries.map(([issue, count]) => `${issue}=${count}`).join(", ") : "none";
|
|
4885
|
+
}
|
|
4886
|
+
function campaignReviewDataCopyQaIssues(data) {
|
|
4887
|
+
const issues = /* @__PURE__ */ new Set();
|
|
4888
|
+
const copyParts = campaignReviewDataCopyParts(data);
|
|
4889
|
+
if (copyParts.length === 0) return [];
|
|
4890
|
+
if (campaignReviewDataHasBadGreeting(data)) issues.add("bad_greeting");
|
|
4891
|
+
if (campaignReviewDataHasBannedPhrase(data)) issues.add("banned_or_vague_phrase");
|
|
4892
|
+
if (campaignReviewDataHasUnsupportedSignalClaim(data)) issues.add("unsupported_signal_claim");
|
|
4893
|
+
if (campaignReviewDataPersonalizationBasis(data).length === 0) issues.add("missing_personalization_basis");
|
|
4894
|
+
if (!campaignReviewDataCta(data)) issues.add("missing_cta");
|
|
4895
|
+
const subject = campaignReviewDataSubject(data);
|
|
4896
|
+
if (subject && subject.length > 70) issues.add("overlong_subject");
|
|
4897
|
+
return [...issues];
|
|
4898
|
+
}
|
|
4899
|
+
function campaignReviewDataPersonalizationBasis(data) {
|
|
4900
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4901
|
+
const copy = asRecord(data.copy);
|
|
4902
|
+
const metadata = asRecord(data.metadata);
|
|
4903
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4904
|
+
return [
|
|
4905
|
+
...arrayOfStrings(data.personalization_basis) ?? [],
|
|
4906
|
+
...arrayOfStrings(data.personalizationBasis) ?? [],
|
|
4907
|
+
...arrayOfStrings(copy.personalization_basis) ?? [],
|
|
4908
|
+
...arrayOfStrings(rawCopy.personalization_basis) ?? [],
|
|
4909
|
+
...arrayOfStrings(metadata.personalization_basis) ?? [],
|
|
4910
|
+
...arrayOfStrings(metadata.personalizationBasis) ?? [],
|
|
4911
|
+
...arrayOfStrings(signalEvidence.personalization_basis) ?? []
|
|
4912
|
+
];
|
|
4913
|
+
}
|
|
4914
|
+
function campaignReviewDataHasNoSupportedSignal(data) {
|
|
4915
|
+
const metadata = asRecord(data.metadata);
|
|
4916
|
+
const copy = asRecord(data.copy);
|
|
4917
|
+
const state = [
|
|
4918
|
+
data.copy_signal_state,
|
|
4919
|
+
data.signal_basis,
|
|
4920
|
+
data.signal_type,
|
|
4921
|
+
metadata.copy_signal_state,
|
|
4922
|
+
metadata.signal_basis,
|
|
4923
|
+
metadata.signal_type,
|
|
4924
|
+
copy.copy_signal_state
|
|
4925
|
+
].map((value) => stringValue(value)?.toLowerCase()).filter(Boolean);
|
|
4926
|
+
return data.no_supported_signal === true || metadata.no_supported_signal === true || state.includes("no_supported_signal");
|
|
4927
|
+
}
|
|
4928
|
+
function campaignReviewDataHasSignalBasis(data) {
|
|
4929
|
+
if (campaignReviewDataHasNoSupportedSignal(data)) return false;
|
|
4930
|
+
const metadata = asRecord(data.metadata);
|
|
4931
|
+
const signal = asRecord(data.signal);
|
|
4932
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4933
|
+
const basis = campaignReviewDataPersonalizationBasis(data);
|
|
4934
|
+
return Boolean(
|
|
4935
|
+
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))
|
|
4936
|
+
);
|
|
4937
|
+
}
|
|
4938
|
+
function campaignReviewDataHasUnsupportedSignalClaim(data) {
|
|
4939
|
+
const copyText = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4940
|
+
const claimsSignal = /\b(hiring|funding|raised|launched|expanding|appointed|announced|recent news|new role|job posting)\b/.test(copyText);
|
|
4941
|
+
return claimsSignal && !campaignReviewDataHasSignalBasis(data);
|
|
4942
|
+
}
|
|
4943
|
+
function campaignReviewDataHasApprovalLock(data) {
|
|
4944
|
+
const metadata = asRecord(data.fit_metadata ?? data.metadata);
|
|
4945
|
+
const approval = String(data.approval_status ?? data.delivery_approval_status ?? metadata.approval_status ?? "").toLowerCase();
|
|
4946
|
+
const exportStatus = String(data.export_status ?? data.exportStatus ?? "").toLowerCase();
|
|
4947
|
+
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);
|
|
4948
|
+
}
|
|
4949
|
+
function campaignReviewDataHasHoldoutAttribution(data) {
|
|
4950
|
+
const fitMetadata = asRecord(data.fit_metadata ?? data.fitMetadata);
|
|
4951
|
+
const metadata = asRecord(data.metadata);
|
|
4952
|
+
const holdout = asRecord(
|
|
4953
|
+
fitMetadata.learning_holdout ?? fitMetadata.learningHoldout ?? metadata.learning_holdout ?? metadata.learningHoldout ?? data.learning_holdout ?? data.learningHoldout
|
|
4954
|
+
);
|
|
4955
|
+
const experimentKey = stringValue(holdout.experiment_key ?? holdout.experimentKey ?? data.experiment_key ?? data.experimentKey);
|
|
4956
|
+
const cohort = stringValue(holdout.cohort_role ?? holdout.cohortRole ?? data.cohort_role ?? data.learning_cohort ?? data.learningCohort);
|
|
4957
|
+
const brainApplied = holdout.brain_applied ?? holdout.brainApplied ?? data.brain_applied ?? data.signaliz_brain_applied;
|
|
4958
|
+
return Boolean(experimentKey && cohort && brainApplied !== void 0);
|
|
4959
|
+
}
|
|
4960
|
+
function campaignReviewStatusHasFeedbackReadiness(status) {
|
|
4961
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
4962
|
+
const feedbackReadiness = asRecord(diagnostics.feedback_readiness ?? diagnostics.feedbackReadiness);
|
|
4963
|
+
const feedbackSubstrate = asRecord(diagnostics.feedback_substrate ?? diagnostics.feedbackSubstrate);
|
|
4964
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
4965
|
+
return feedbackReadiness.ready === true || feedbackReadiness.webhook_configured === true || feedbackSubstrate.stage === "ready" || Number(evidenceCounts.feedback_events ?? 0) > 0;
|
|
4966
|
+
}
|
|
4967
|
+
function campaignReviewMoatState(status, holdoutRows) {
|
|
4968
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
4969
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
4970
|
+
const embeddedHoldout = asRecord(diagnostics.embedded_holdout ?? diagnostics.embeddedHoldout);
|
|
4971
|
+
const holdoutLift = asRecord(diagnostics.holdout_lift ?? diagnostics.holdoutLift ?? diagnostics.holdout_measurement ?? diagnostics.holdoutMeasurement);
|
|
4972
|
+
const controlSample = Math.max(
|
|
4973
|
+
Number(evidenceCounts.holdout_control_sample_size ?? 0),
|
|
4974
|
+
Number(evidenceCounts.embedded_holdout_control_sample ?? 0),
|
|
4975
|
+
Number(embeddedHoldout.control_sample_size ?? 0)
|
|
4976
|
+
);
|
|
4977
|
+
const learnedSample = Math.max(
|
|
4978
|
+
Number(evidenceCounts.holdout_learned_sample_size ?? 0),
|
|
4979
|
+
Number(evidenceCounts.embedded_holdout_learned_sample ?? 0),
|
|
4980
|
+
Number(embeddedHoldout.learned_sample_size ?? 0)
|
|
4981
|
+
);
|
|
4982
|
+
const feedbackEvents = Math.max(
|
|
4983
|
+
Number(evidenceCounts.feedback_events ?? 0),
|
|
4984
|
+
Number(evidenceCounts.campaign_build_feedback_events ?? 0),
|
|
4985
|
+
Number(evidenceCounts.delivery_holdout_sent_outcomes ?? 0)
|
|
4986
|
+
);
|
|
4987
|
+
const proved = diagnostics.closed_loop_proof_ready === true || holdoutLift.honest_measurement === true && holdoutLift.positive_lift === true;
|
|
4988
|
+
if (proved) {
|
|
4989
|
+
return {
|
|
4990
|
+
state: "proved",
|
|
4991
|
+
detail: "Real attributed feedback shows positive lift for explicit control and learned cohorts."
|
|
4992
|
+
};
|
|
4993
|
+
}
|
|
4994
|
+
if (controlSample > 0 && learnedSample > 0 && feedbackEvents > 0) {
|
|
4995
|
+
return {
|
|
4996
|
+
state: "moved_needs_lift_volume",
|
|
4997
|
+
detail: `Control and learned cohorts have attributed feedback (${controlSample} control, ${learnedSample} learned, ${feedbackEvents} feedback event(s)); keep collecting lift volume before claiming proof.`
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
return {
|
|
5001
|
+
state: "scaffolding_ready",
|
|
5002
|
+
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."
|
|
5003
|
+
};
|
|
5004
|
+
}
|
|
4426
5005
|
function createCampaignBuilderReadinessLane(route, plan) {
|
|
4427
5006
|
const builtIn = campaignBuilderBuiltInForRoute(route);
|
|
4428
5007
|
const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
|
|
@@ -7638,6 +8217,16 @@ var GtmKernel = class {
|
|
|
7638
8217
|
min_privacy_k: input.minPrivacyK
|
|
7639
8218
|
});
|
|
7640
8219
|
}
|
|
8220
|
+
/** Queue a dry-run-first bridge from imported historical feedback to attribution-only Campaign Build surrogates. */
|
|
8221
|
+
async runHistoricalCampaignBuildBridge(input = {}) {
|
|
8222
|
+
return this.callMcp("gtm_historical_campaign_build_bridge_run", {
|
|
8223
|
+
dry_run: input.dryRun,
|
|
8224
|
+
write_approved: input.writeApproved,
|
|
8225
|
+
max_events: input.maxEvents,
|
|
8226
|
+
max_campaigns: input.maxCampaigns,
|
|
8227
|
+
min_feedback_events: input.minFeedbackEvents
|
|
8228
|
+
});
|
|
8229
|
+
}
|
|
7641
8230
|
/** Prepare a provider-agnostic feedback webhook URL for Smartlead, HeyReach, Airbyte, or custom sender events. */
|
|
7642
8231
|
async prepareFeedbackWebhook(input) {
|
|
7643
8232
|
return this.callMcp("gtm_feedback_webhook_prepare", {
|