@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/mcp-config.js
CHANGED
|
@@ -868,7 +868,11 @@ var Campaigns = class {
|
|
|
868
868
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
869
869
|
brainContext: data.brain_context ?? {},
|
|
870
870
|
learningHoldout: data.learning_holdout ?? {},
|
|
871
|
-
providerRoute: mapProviderRoute(data)
|
|
871
|
+
providerRoute: mapProviderRoute(data),
|
|
872
|
+
effectiveSourceRouting: recordOrNull(data.effective_source_routing ?? data.source_routing),
|
|
873
|
+
sourcePlan: recordOrNull(data.source_plan ?? data.plan?.source_plan),
|
|
874
|
+
sourceRoutes: recordArray(data.source_routes ?? data.source_plan?.routes ?? data.plan?.source_plan?.routes),
|
|
875
|
+
sourceShards: recordArray(data.source_shards ?? data.source_plan?.shards ?? data.plan?.source_plan?.shards)
|
|
872
876
|
};
|
|
873
877
|
}
|
|
874
878
|
async getCampaignBuildStatus(campaignBuildId) {
|
|
@@ -1060,6 +1064,9 @@ function recordOrNull(value) {
|
|
|
1060
1064
|
function stringArray(value) {
|
|
1061
1065
|
return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
|
|
1062
1066
|
}
|
|
1067
|
+
function recordArray(value) {
|
|
1068
|
+
return Array.isArray(value) ? value.map(recordOrNull).filter((item) => Boolean(item)) : [];
|
|
1069
|
+
}
|
|
1063
1070
|
function diagnosticMessages(value) {
|
|
1064
1071
|
if (!Array.isArray(value)) return [];
|
|
1065
1072
|
return value.map((item) => {
|
|
@@ -1221,7 +1228,8 @@ function buildArgs(request) {
|
|
|
1221
1228
|
suppress_prior_campaign_ids: request.cacheReusePolicy.suppressPriorCampaignIds,
|
|
1222
1229
|
suppress_prior_list_ids: request.cacheReusePolicy.suppressPriorListIds,
|
|
1223
1230
|
uniqueness: request.cacheReusePolicy.uniqueness,
|
|
1224
|
-
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata
|
|
1231
|
+
require_usage_metadata: request.cacheReusePolicy.requireUsageMetadata,
|
|
1232
|
+
verified_cache_ttl_days: request.cacheReusePolicy.verifiedCacheTtlDays
|
|
1225
1233
|
};
|
|
1226
1234
|
}
|
|
1227
1235
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
@@ -1904,6 +1912,7 @@ var CampaignBuilderAgent = class {
|
|
|
1904
1912
|
},
|
|
1905
1913
|
brain_defaults: plan.buildRequest.brainDefaults,
|
|
1906
1914
|
brain_preflight: plan.buildRequest.brainPreflight,
|
|
1915
|
+
learning_holdout: plan.buildRequest.learningHoldout,
|
|
1907
1916
|
campaign_strategy_context: plan.buildRequest.campaignStrategyContext,
|
|
1908
1917
|
delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
1909
1918
|
delivery_risk: plan.buildRequest.deliveryRisk
|
|
@@ -1917,7 +1926,8 @@ var CampaignBuilderAgent = class {
|
|
|
1917
1926
|
estimated_credits: plan.estimatedCredits
|
|
1918
1927
|
},
|
|
1919
1928
|
brain_delivery_risk_preflight: plan.buildRequest.deliveryRisk,
|
|
1920
|
-
delivery_risk: plan.buildRequest.deliveryRisk
|
|
1929
|
+
delivery_risk: plan.buildRequest.deliveryRisk,
|
|
1930
|
+
learning_holdout: plan.buildRequest.learningHoldout
|
|
1921
1931
|
},
|
|
1922
1932
|
approval_required: plan.approvals.some((approval) => approval.blocking),
|
|
1923
1933
|
actor_type: "agent",
|
|
@@ -3316,6 +3326,21 @@ function routesFromCustomerTools(tools) {
|
|
|
3316
3326
|
}))
|
|
3317
3327
|
);
|
|
3318
3328
|
}
|
|
3329
|
+
function normalizeCampaignBuilderLearningHoldout(input, gtmCampaignId, campaignName) {
|
|
3330
|
+
const source = asRecord(input);
|
|
3331
|
+
if (source.enabled === false) return { enabled: false };
|
|
3332
|
+
const controlPercentage = numberOrUndefined2(source.control_percentage ?? source.controlPercentage);
|
|
3333
|
+
const minControlSize = numberOrUndefined2(source.min_control_size ?? source.minControlSize);
|
|
3334
|
+
const experimentKey = stringValue(source.experiment_key ?? source.experimentKey) ?? (gtmCampaignId ? `gtm_campaign:${gtmCampaignId}:learning_holdout` : `campaign_builder_agent:${hashString(campaignName)}:learning_holdout`);
|
|
3335
|
+
return {
|
|
3336
|
+
enabled: true,
|
|
3337
|
+
controlPercentage: controlPercentage ?? 0.2,
|
|
3338
|
+
minControlSize: minControlSize ?? 50,
|
|
3339
|
+
experimentKey,
|
|
3340
|
+
sourceCampaignId: stringValue(source.source_campaign_id ?? source.sourceCampaignId) ?? gtmCampaignId,
|
|
3341
|
+
primaryMetric: stringValue(source.primary_metric ?? source.primaryMetric) ?? "positive_reply_rate"
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3319
3344
|
function createBuildRequest(request, defaults, targetCount, campaignName, scopeBuildArgs, routes = [], memory, scopeBrainPreflight) {
|
|
3320
3345
|
const buildRequest = {
|
|
3321
3346
|
name: campaignName,
|
|
@@ -3348,6 +3373,11 @@ function createBuildRequest(request, defaults, targetCount, campaignName, scopeB
|
|
|
3348
3373
|
};
|
|
3349
3374
|
const scoped = asRecord(scopeBuildArgs);
|
|
3350
3375
|
buildRequest.gtmCampaignId = request.gtmCampaignId ?? stringValue(scoped.gtm_campaign_id ?? scoped.gtmCampaignId);
|
|
3376
|
+
buildRequest.learningHoldout = normalizeCampaignBuilderLearningHoldout(
|
|
3377
|
+
request.learningHoldout ?? scoped.learning_holdout ?? scoped.learningHoldout,
|
|
3378
|
+
buildRequest.gtmCampaignId,
|
|
3379
|
+
campaignName
|
|
3380
|
+
);
|
|
3351
3381
|
if (typeof scoped.name === "string") buildRequest.name = scoped.name;
|
|
3352
3382
|
if (typeof scoped.prompt === "string") buildRequest.prompt = scoped.prompt;
|
|
3353
3383
|
if (typeof scoped.target_count === "number") buildRequest.targetCount = scoped.target_count;
|
|
@@ -3422,6 +3452,7 @@ function createBrainPreflight(request, buildRequest, routes, memory) {
|
|
|
3422
3452
|
target_icp: targetIcp,
|
|
3423
3453
|
provider_chain: providerChain,
|
|
3424
3454
|
lead_source: providerChain[0] ?? "signaliz",
|
|
3455
|
+
learning_holdout: buildRequest.learningHoldout,
|
|
3425
3456
|
tool_sequence: [
|
|
3426
3457
|
{ tool: "gtm_brain_learning_cycle_plan", purpose: "Plan the Brain learning loop for this campaign.", arguments: learningArgs },
|
|
3427
3458
|
{ tool: "gtm_brain_seed_defaults", purpose: "Seed campaign defaults from workspace memory and safe aggregate patterns.", arguments: defaultsArgs },
|
|
@@ -4089,6 +4120,16 @@ function buildCampaignArgs(request) {
|
|
|
4089
4120
|
if (request.dedupKeys) args.dedup_keys = request.dedupKeys;
|
|
4090
4121
|
if (request.brainPreflight) args.brain_preflight = request.brainPreflight;
|
|
4091
4122
|
if (request.brainDefaults) args.brain_defaults = request.brainDefaults;
|
|
4123
|
+
if (request.learningHoldout) {
|
|
4124
|
+
args.learning_holdout = compact({
|
|
4125
|
+
enabled: request.learningHoldout.enabled,
|
|
4126
|
+
control_percentage: request.learningHoldout.controlPercentage,
|
|
4127
|
+
min_control_size: request.learningHoldout.minControlSize,
|
|
4128
|
+
experiment_key: request.learningHoldout.experimentKey,
|
|
4129
|
+
source_campaign_id: request.learningHoldout.sourceCampaignId,
|
|
4130
|
+
primary_metric: request.learningHoldout.primaryMetric
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4092
4133
|
if (request.campaignStrategyContext) args.campaign_strategy_context = request.campaignStrategyContext;
|
|
4093
4134
|
if (request.deliveryRisk) args.delivery_risk = request.deliveryRisk;
|
|
4094
4135
|
if (request.icp) {
|
|
@@ -4185,6 +4226,7 @@ function buildExecutionPrepareArgs(request, dryRun, confirmSpend) {
|
|
|
4185
4226
|
delivery_risk: buildArgs2.delivery_risk,
|
|
4186
4227
|
dedup_keys: buildArgs2.dedup_keys,
|
|
4187
4228
|
policy: buildArgs2.policy,
|
|
4229
|
+
learning_holdout: buildArgs2.learning_holdout,
|
|
4188
4230
|
signals: buildArgs2.signals,
|
|
4189
4231
|
qualification: buildArgs2.qualification,
|
|
4190
4232
|
copy: buildArgs2.copy,
|
|
@@ -4219,10 +4261,145 @@ function mapBuildResult(data, dryRun) {
|
|
|
4219
4261
|
targetLimitReason: data.target_limit_reason,
|
|
4220
4262
|
maxSupportedTargetCount: data.max_supported_target_count,
|
|
4221
4263
|
brainContext: data.brain_context ?? {},
|
|
4264
|
+
learningHoldout: data.learning_holdout ?? {},
|
|
4222
4265
|
providerRoute: mapProviderRoute2(data)
|
|
4223
4266
|
};
|
|
4224
4267
|
}
|
|
4268
|
+
var CAMPAIGN_BUILDER_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "canceled", "cancelled"]);
|
|
4269
|
+
var CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["failed", "canceled", "cancelled"]);
|
|
4270
|
+
function mapAgentCampaignPhaseHealth(value) {
|
|
4271
|
+
if (!Array.isArray(value)) return [];
|
|
4272
|
+
return value.map((phase) => {
|
|
4273
|
+
const record = asRecord(phase);
|
|
4274
|
+
return {
|
|
4275
|
+
phase: stringValue(record.phase) ?? "unknown",
|
|
4276
|
+
status: stringValue(record.status) ?? "unknown",
|
|
4277
|
+
recordsProcessed: numberOrNull2(record.records_processed ?? record.recordsProcessed),
|
|
4278
|
+
recordsSucceeded: numberOrNull2(record.records_succeeded ?? record.recordsSucceeded),
|
|
4279
|
+
recordsFailed: numberOrNull2(record.records_failed ?? record.recordsFailed),
|
|
4280
|
+
heartbeatAgeSeconds: numberOrNull2(record.heartbeat_age_seconds ?? record.heartbeatAgeSeconds ?? record.phase_age_seconds)
|
|
4281
|
+
};
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
function deriveCampaignBuildTerminalState(input) {
|
|
4285
|
+
const status = input.status.toLowerCase();
|
|
4286
|
+
const heartbeatAgeSeconds = input.phaseAgeSeconds;
|
|
4287
|
+
const stale = input.staleRunningPhase === true;
|
|
4288
|
+
const safeWatchAction = input.triggerRunId ? `signaliz ops run-status ${input.triggerRunId} --watch` : "signaliz campaign-agent status <campaign_build_id>";
|
|
4289
|
+
if (CAMPAIGN_BUILDER_BLOCKED_TERMINAL_STATUSES.has(status)) {
|
|
4290
|
+
return {
|
|
4291
|
+
kind: "blocked",
|
|
4292
|
+
label: "Blocked terminal state",
|
|
4293
|
+
reviewable: true,
|
|
4294
|
+
terminal: true,
|
|
4295
|
+
stale: false,
|
|
4296
|
+
phase: input.phase,
|
|
4297
|
+
phaseStatus: input.phaseStatus,
|
|
4298
|
+
heartbeatAgeSeconds,
|
|
4299
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4300
|
+
terminalReason: input.terminalReason,
|
|
4301
|
+
staleReason: null,
|
|
4302
|
+
safeNextAction: input.nextAction || "Review errors and rerun only after the blocker is resolved."
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
if (status === "pending_approval") {
|
|
4306
|
+
return {
|
|
4307
|
+
kind: "pending_approval",
|
|
4308
|
+
label: "Pending delivery approval",
|
|
4309
|
+
reviewable: true,
|
|
4310
|
+
terminal: false,
|
|
4311
|
+
stale: false,
|
|
4312
|
+
phase: input.phase,
|
|
4313
|
+
phaseStatus: input.phaseStatus,
|
|
4314
|
+
heartbeatAgeSeconds,
|
|
4315
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4316
|
+
terminalReason: input.terminalReason,
|
|
4317
|
+
staleReason: null,
|
|
4318
|
+
safeNextAction: input.approvalAction || input.nextAction || "Review rows and approve delivery only after the approval packet passes."
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
if (CAMPAIGN_BUILDER_TERMINAL_STATUSES.has(status)) {
|
|
4322
|
+
return {
|
|
4323
|
+
kind: "terminal",
|
|
4324
|
+
label: "Terminal and reviewable",
|
|
4325
|
+
reviewable: true,
|
|
4326
|
+
terminal: true,
|
|
4327
|
+
stale: false,
|
|
4328
|
+
phase: input.phase,
|
|
4329
|
+
phaseStatus: input.phaseStatus,
|
|
4330
|
+
heartbeatAgeSeconds,
|
|
4331
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4332
|
+
terminalReason: input.terminalReason,
|
|
4333
|
+
staleReason: null,
|
|
4334
|
+
safeNextAction: input.nextAction || "Review rows, artifacts, scorecard, and approval locks before delivery."
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
if (status === "running" || status === "queued" || status === "processing") {
|
|
4338
|
+
if (stale) {
|
|
4339
|
+
return {
|
|
4340
|
+
kind: "running_stale",
|
|
4341
|
+
label: "Running but stale",
|
|
4342
|
+
reviewable: false,
|
|
4343
|
+
terminal: false,
|
|
4344
|
+
stale: true,
|
|
4345
|
+
phase: input.phase,
|
|
4346
|
+
phaseStatus: input.phaseStatus,
|
|
4347
|
+
heartbeatAgeSeconds,
|
|
4348
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4349
|
+
terminalReason: null,
|
|
4350
|
+
staleReason: input.staleReason || "The active phase heartbeat is stale.",
|
|
4351
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
4352
|
+
};
|
|
4353
|
+
}
|
|
4354
|
+
return {
|
|
4355
|
+
kind: "running_healthy",
|
|
4356
|
+
label: "Running and healthy",
|
|
4357
|
+
reviewable: false,
|
|
4358
|
+
terminal: false,
|
|
4359
|
+
stale: false,
|
|
4360
|
+
phase: input.phase,
|
|
4361
|
+
phaseStatus: input.phaseStatus,
|
|
4362
|
+
heartbeatAgeSeconds,
|
|
4363
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4364
|
+
terminalReason: null,
|
|
4365
|
+
staleReason: null,
|
|
4366
|
+
safeNextAction: input.nextAction || safeWatchAction
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
return {
|
|
4370
|
+
kind: "unknown",
|
|
4371
|
+
label: "Unknown build state",
|
|
4372
|
+
reviewable: false,
|
|
4373
|
+
terminal: false,
|
|
4374
|
+
stale,
|
|
4375
|
+
phase: input.phase,
|
|
4376
|
+
phaseStatus: input.phaseStatus,
|
|
4377
|
+
heartbeatAgeSeconds,
|
|
4378
|
+
nextPollAfterSeconds: input.nextPollAfterSeconds,
|
|
4379
|
+
terminalReason: input.terminalReason,
|
|
4380
|
+
staleReason: input.staleReason,
|
|
4381
|
+
safeNextAction: input.nextAction || "Read campaign build status again before making a launch or delivery decision."
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4225
4384
|
function mapAgentCampaignBuildStatus(data) {
|
|
4385
|
+
const customerRowCounts = mapAgentCustomerRowCounts(data.customer_row_counts);
|
|
4386
|
+
const phaseHealth = mapAgentCampaignPhaseHealth(data.phases);
|
|
4387
|
+
const terminalReason = stringValue(data.terminal_reason) ?? customerRowCounts?.terminalReason ?? null;
|
|
4388
|
+
const staleReason = stringValue(data.stale_reason) ?? stringValue(asRecord(data.diagnostics).stale_reason) ?? null;
|
|
4389
|
+
const nextPollAfterSeconds = numberOrNull2(data.next_poll_after_seconds ?? data.nextPollAfterSeconds);
|
|
4390
|
+
const terminalState = deriveCampaignBuildTerminalState({
|
|
4391
|
+
status: stringValue(data.status) ?? "unknown",
|
|
4392
|
+
phase: stringValue(data.current_phase) ?? null,
|
|
4393
|
+
phaseStatus: stringValue(data.current_phase_status) ?? null,
|
|
4394
|
+
staleRunningPhase: data.stale_running_phase === true,
|
|
4395
|
+
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
4396
|
+
nextPollAfterSeconds,
|
|
4397
|
+
terminalReason,
|
|
4398
|
+
staleReason,
|
|
4399
|
+
nextAction: formatAgentNextAction(data.next_action),
|
|
4400
|
+
approvalAction: formatAgentNextAction(data.approval_action),
|
|
4401
|
+
triggerRunId: stringValue(data.trigger_run_id) ?? null
|
|
4402
|
+
});
|
|
4226
4403
|
return {
|
|
4227
4404
|
campaignBuildId: stringValue(data.campaign_build_id) ?? "",
|
|
4228
4405
|
campaignId: stringValue(data.campaign_id) ?? null,
|
|
@@ -4240,12 +4417,18 @@ function mapAgentCampaignBuildStatus(data) {
|
|
|
4240
4417
|
errors: diagnosticMessages2(data.errors),
|
|
4241
4418
|
artifactCount: numberOrUndefined2(data.artifact_count) ?? 0,
|
|
4242
4419
|
artifactDownloads: Array.isArray(data.artifact_downloads) ? data.artifact_downloads.map(mapAgentCampaignArtifactDownload) : [],
|
|
4243
|
-
customerRowCounts
|
|
4420
|
+
customerRowCounts,
|
|
4244
4421
|
phaseAttemptTotals: nullableRecord(data.phase_attempt_totals) ?? void 0,
|
|
4245
4422
|
providerRoute: mapProviderRoute2(data),
|
|
4246
4423
|
triggerRunId: stringValue(data.trigger_run_id) ?? null,
|
|
4247
4424
|
staleRunningPhase: data.stale_running_phase === true,
|
|
4248
4425
|
phaseAgeSeconds: numberOrNull2(data.phase_age_seconds),
|
|
4426
|
+
nextPollAfterSeconds,
|
|
4427
|
+
terminalReason,
|
|
4428
|
+
staleReason,
|
|
4429
|
+
safeNextAction: terminalState.safeNextAction,
|
|
4430
|
+
terminalState,
|
|
4431
|
+
phaseHealth,
|
|
4249
4432
|
diagnostics: nullableRecord(data.diagnostics) ?? {},
|
|
4250
4433
|
nextAction: formatAgentNextAction(data.next_action),
|
|
4251
4434
|
approvalAction: formatAgentNextAction(data.approval_action),
|
|
@@ -4333,67 +4516,285 @@ function mapAgentCampaignArtifact(data) {
|
|
|
4333
4516
|
createdAt: stringValue(data.created_at) ?? ""
|
|
4334
4517
|
};
|
|
4335
4518
|
}
|
|
4519
|
+
var NORTH_STAR_BANNED_COPY_PHRASES = [
|
|
4520
|
+
"i have been following",
|
|
4521
|
+
"i saw that",
|
|
4522
|
+
"would you be opposed",
|
|
4523
|
+
"unlock new opportunities",
|
|
4524
|
+
"significant success",
|
|
4525
|
+
"we understand",
|
|
4526
|
+
"hope this finds you well"
|
|
4527
|
+
];
|
|
4528
|
+
function campaignReviewGate(id, label, status, detail) {
|
|
4529
|
+
return {
|
|
4530
|
+
id,
|
|
4531
|
+
label,
|
|
4532
|
+
status,
|
|
4533
|
+
passed: status !== "blocked",
|
|
4534
|
+
detail
|
|
4535
|
+
};
|
|
4536
|
+
}
|
|
4537
|
+
function northStarMetric(id, label, status, detail, value, target) {
|
|
4538
|
+
return {
|
|
4539
|
+
id,
|
|
4540
|
+
label,
|
|
4541
|
+
status,
|
|
4542
|
+
detail,
|
|
4543
|
+
...value !== void 0 ? { value } : {},
|
|
4544
|
+
...target !== void 0 ? { target } : {}
|
|
4545
|
+
};
|
|
4546
|
+
}
|
|
4547
|
+
function createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState) {
|
|
4548
|
+
if (!terminalState.reviewable) {
|
|
4549
|
+
const pendingMetric = northStarMetric(
|
|
4550
|
+
"terminal_state",
|
|
4551
|
+
"Terminal-state contract",
|
|
4552
|
+
terminalState.kind === "running_stale" ? "blocked" : "pending",
|
|
4553
|
+
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.`,
|
|
4554
|
+
terminalState.kind,
|
|
4555
|
+
"terminal or pending_approval"
|
|
4556
|
+
);
|
|
4557
|
+
return {
|
|
4558
|
+
version: "campaign-builder-north-star.v1",
|
|
4559
|
+
status: pendingMetric.status,
|
|
4560
|
+
score: null,
|
|
4561
|
+
moatState: "scaffolding_ready",
|
|
4562
|
+
metrics: [pendingMetric],
|
|
4563
|
+
blockers: pendingMetric.status === "blocked" ? [pendingMetric.detail] : [],
|
|
4564
|
+
warnings: pendingMetric.status === "pending" ? [pendingMetric.detail] : [],
|
|
4565
|
+
nextAction: terminalState.safeNextAction
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
const sampledRows = rows.rows.length;
|
|
4569
|
+
const rowData = rows.rows.map((row) => asRecord(row.data));
|
|
4570
|
+
const requestedTarget = status.customerRowCounts?.requestedTarget ?? status.recordsTotal ?? rows.count;
|
|
4571
|
+
const finalRows = status.customerRowCounts?.deliveredRows ?? status.customerRowCounts?.qaReadyRows ?? status.customerRowCounts?.copiedRows ?? status.customerRowCounts?.acceptedRows ?? rows.count;
|
|
4572
|
+
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
4573
|
+
const verifiedEmailRows = rowData.filter(campaignReviewDataHasVerifiedEmail).length;
|
|
4574
|
+
const uniqueEmails = new Set(rowData.map(campaignReviewDataEmail).filter(Boolean)).size;
|
|
4575
|
+
const uniqueDomains = new Set(rowData.map(campaignReviewDataDomain).filter(Boolean)).size;
|
|
4576
|
+
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
4577
|
+
const copyQaIssuesByRow = rowData.map(campaignReviewDataCopyQaIssues).filter((issues) => issues.length > 0);
|
|
4578
|
+
const copyQaIssueCounts = countCampaignReviewIssues(copyQaIssuesByRow);
|
|
4579
|
+
const signalBackedRows = rowData.filter(campaignReviewDataHasSignalBasis).length;
|
|
4580
|
+
const noSupportedSignalRows = rowData.filter(campaignReviewDataHasNoSupportedSignal).length;
|
|
4581
|
+
const unsupportedSignalClaims = rowData.filter(campaignReviewDataHasUnsupportedSignalClaim).length;
|
|
4582
|
+
const approvalLockedRows = rowData.filter(campaignReviewDataHasApprovalLock).length;
|
|
4583
|
+
const holdoutRows = rowData.filter(campaignReviewDataHasHoldoutAttribution).length;
|
|
4584
|
+
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
4585
|
+
const deliveredRows = status.customerRowCounts?.deliveredRows ?? 0;
|
|
4586
|
+
const feedbackReady = campaignReviewStatusHasFeedbackReadiness(status);
|
|
4587
|
+
const feedbackRequiredForApproval = terminalState.kind === "pending_approval";
|
|
4588
|
+
const moat = campaignReviewMoatState(status, holdoutRows);
|
|
4589
|
+
const metrics = [
|
|
4590
|
+
northStarMetric(
|
|
4591
|
+
"terminal_state",
|
|
4592
|
+
"Terminal-state contract",
|
|
4593
|
+
terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
4594
|
+
terminalState.label,
|
|
4595
|
+
terminalState.kind,
|
|
4596
|
+
"terminal or pending_approval"
|
|
4597
|
+
),
|
|
4598
|
+
northStarMetric(
|
|
4599
|
+
"target_fill",
|
|
4600
|
+
"Target fill",
|
|
4601
|
+
requestedTarget && finalRows < requestedTarget ? "review" : "pass",
|
|
4602
|
+
requestedTarget ? `${finalRows || 0}/${requestedTarget} final or reviewable row(s) are accounted for.` : "No requested target was reported; using sampled rows for review.",
|
|
4603
|
+
finalRows || sampledRows,
|
|
4604
|
+
requestedTarget || sampledRows
|
|
4605
|
+
),
|
|
4606
|
+
northStarMetric(
|
|
4607
|
+
"unique_emails",
|
|
4608
|
+
"Unique emails",
|
|
4609
|
+
sampledRows === 0 ? "blocked" : uniqueEmails === rowsWithEmail ? "pass" : "blocked",
|
|
4610
|
+
sampledRows === 0 ? "No sampled rows were available for email uniqueness review." : `${uniqueEmails}/${rowsWithEmail} sampled email(s) are unique.`,
|
|
4611
|
+
uniqueEmails,
|
|
4612
|
+
rowsWithEmail
|
|
4613
|
+
),
|
|
4614
|
+
northStarMetric(
|
|
4615
|
+
"unique_domains",
|
|
4616
|
+
"Unique domains",
|
|
4617
|
+
sampledRows === 0 ? "blocked" : uniqueDomains === sampledRows ? "pass" : "review",
|
|
4618
|
+
sampledRows === 0 ? "No sampled rows were available for domain uniqueness review." : `${uniqueDomains}/${sampledRows} sampled company domain(s) are unique.`,
|
|
4619
|
+
uniqueDomains,
|
|
4620
|
+
sampledRows
|
|
4621
|
+
),
|
|
4622
|
+
northStarMetric(
|
|
4623
|
+
"verified_email_rows",
|
|
4624
|
+
"Verified email rows",
|
|
4625
|
+
sampledRows === 0 ? "blocked" : verifiedEmailRows === sampledRows ? "pass" : rowsWithEmail === sampledRows ? "review" : "blocked",
|
|
4626
|
+
sampledRows === 0 ? "No sampled rows were available for email verification review." : `${verifiedEmailRows}/${sampledRows} sampled row(s) carry verified-email evidence.`,
|
|
4627
|
+
verifiedEmailRows,
|
|
4628
|
+
sampledRows
|
|
4629
|
+
),
|
|
4630
|
+
northStarMetric(
|
|
4631
|
+
"copy_completeness",
|
|
4632
|
+
"Copy completeness",
|
|
4633
|
+
sampledRows === 0 ? "blocked" : copyReadyRows === sampledRows ? "pass" : "blocked",
|
|
4634
|
+
sampledRows === 0 ? "No sampled rows were available for copy review." : `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.`,
|
|
4635
|
+
copyReadyRows,
|
|
4636
|
+
sampledRows
|
|
4637
|
+
),
|
|
4638
|
+
northStarMetric(
|
|
4639
|
+
"copy_quality",
|
|
4640
|
+
"Copy QA",
|
|
4641
|
+
copyQaIssuesByRow.length === 0 ? "pass" : "blocked",
|
|
4642
|
+
copyQaIssuesByRow.length === 0 ? "No sampled copy QA blockers were detected." : `${copyQaIssuesByRow.length}/${sampledRows} sampled row(s) have copy QA issue(s): ${formatCampaignReviewIssueCounts(copyQaIssueCounts)}.`,
|
|
4643
|
+
copyQaIssuesByRow.length,
|
|
4644
|
+
0
|
|
4645
|
+
),
|
|
4646
|
+
northStarMetric(
|
|
4647
|
+
"signal_grounding",
|
|
4648
|
+
"Signal grounding",
|
|
4649
|
+
unsupportedSignalClaims > 0 ? "blocked" : signalBackedRows > 0 ? "pass" : "review",
|
|
4650
|
+
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.",
|
|
4651
|
+
signalBackedRows,
|
|
4652
|
+
sampledRows
|
|
4653
|
+
),
|
|
4654
|
+
northStarMetric(
|
|
4655
|
+
"approval_locks",
|
|
4656
|
+
"Approval locks",
|
|
4657
|
+
approvalLockedRows > 0 || deliveredRows === 0 ? "pass" : "review",
|
|
4658
|
+
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.",
|
|
4659
|
+
approvalLockedRows,
|
|
4660
|
+
sampledRows
|
|
4661
|
+
),
|
|
4662
|
+
northStarMetric(
|
|
4663
|
+
"export_send_safety",
|
|
4664
|
+
"Export/send safety",
|
|
4665
|
+
deliveredRows > 0 && status.status !== "completed" ? "blocked" : "pass",
|
|
4666
|
+
deliveredRows > 0 ? `${deliveredRows} row(s) are delivered; status is ${status.status}.` : "No delivered rows are reported before approval.",
|
|
4667
|
+
deliveredRows,
|
|
4668
|
+
0
|
|
4669
|
+
),
|
|
4670
|
+
northStarMetric(
|
|
4671
|
+
"holdout_attribution",
|
|
4672
|
+
"Holdout attribution",
|
|
4673
|
+
holdoutRows > 0 ? "pass" : "review",
|
|
4674
|
+
holdoutRows > 0 ? `${holdoutRows}/${sampledRows} sampled row(s) preserve holdout attribution.` : "No holdout attribution was visible in the sampled rows.",
|
|
4675
|
+
holdoutRows,
|
|
4676
|
+
sampledRows
|
|
4677
|
+
),
|
|
4678
|
+
northStarMetric(
|
|
4679
|
+
"feedback_readiness",
|
|
4680
|
+
"Feedback readiness",
|
|
4681
|
+
feedbackReady ? "pass" : feedbackRequiredForApproval ? "blocked" : "review",
|
|
4682
|
+
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.",
|
|
4683
|
+
feedbackReady,
|
|
4684
|
+
true
|
|
4685
|
+
),
|
|
4686
|
+
northStarMetric(
|
|
4687
|
+
"causal_moat",
|
|
4688
|
+
"Causal moat",
|
|
4689
|
+
moat.state === "proved" ? "pass" : "review",
|
|
4690
|
+
moat.detail,
|
|
4691
|
+
moat.state,
|
|
4692
|
+
"proved"
|
|
4693
|
+
),
|
|
4694
|
+
northStarMetric(
|
|
4695
|
+
"artifact_readback",
|
|
4696
|
+
"Artifact readback",
|
|
4697
|
+
artifactCount > 0 ? "pass" : "review",
|
|
4698
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available for review.` : "No review artifact is available yet.",
|
|
4699
|
+
artifactCount,
|
|
4700
|
+
1
|
|
4701
|
+
)
|
|
4702
|
+
];
|
|
4703
|
+
const gradedMetrics = metrics.filter((metric) => metric.status !== "pending");
|
|
4704
|
+
const score = gradedMetrics.length === 0 ? null : Math.round(gradedMetrics.filter((metric) => metric.status === "pass").length / gradedMetrics.length * 100);
|
|
4705
|
+
const blockers = metrics.filter((metric) => metric.status === "blocked").map((metric) => metric.detail);
|
|
4706
|
+
const warnings = metrics.filter((metric) => metric.status === "review").map((metric) => metric.detail);
|
|
4707
|
+
const scorecardStatus = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "review" : "pass";
|
|
4708
|
+
return {
|
|
4709
|
+
version: "campaign-builder-north-star.v1",
|
|
4710
|
+
status: scorecardStatus,
|
|
4711
|
+
score,
|
|
4712
|
+
moatState: moat.state,
|
|
4713
|
+
metrics,
|
|
4714
|
+
blockers,
|
|
4715
|
+
warnings,
|
|
4716
|
+
nextAction: scorecardStatus === "blocked" ? "Fix blocked North Star gates before approval or delivery." : terminalState.safeNextAction
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4336
4719
|
function createCampaignBuilderBuildReview(status, rows, artifacts) {
|
|
4720
|
+
const terminalState = status.terminalState ?? deriveCampaignBuildTerminalState({
|
|
4721
|
+
status: status.status,
|
|
4722
|
+
phase: status.currentPhase,
|
|
4723
|
+
phaseStatus: status.currentPhaseStatus,
|
|
4724
|
+
staleRunningPhase: status.staleRunningPhase === true,
|
|
4725
|
+
phaseAgeSeconds: status.phaseAgeSeconds ?? null,
|
|
4726
|
+
nextPollAfterSeconds: status.nextPollAfterSeconds ?? null,
|
|
4727
|
+
terminalReason: status.terminalReason ?? status.customerRowCounts?.terminalReason ?? null,
|
|
4728
|
+
staleReason: status.staleReason ?? null,
|
|
4729
|
+
nextAction: status.nextAction,
|
|
4730
|
+
approvalAction: status.approvalAction,
|
|
4731
|
+
triggerRunId: status.triggerRunId ?? null
|
|
4732
|
+
});
|
|
4337
4733
|
const sampledRows = rows.rows.length;
|
|
4338
4734
|
const availableRows = rows.count || sampledRows;
|
|
4339
4735
|
const qualifiedRows = rows.rows.filter((row) => row.qualified !== false).length;
|
|
4340
4736
|
const rowsWithEmail = rows.rows.filter(campaignReviewRowHasEmail).length;
|
|
4341
4737
|
const copyReadyRows = rows.rows.filter(campaignReviewRowHasCopy).length;
|
|
4342
4738
|
const artifactCount = artifacts.length || status.artifactCount || 0;
|
|
4739
|
+
const northStarScorecard = createCampaignBuilderNorthStarScorecard(status, rows, artifacts, terminalState);
|
|
4343
4740
|
const gates = [
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4741
|
+
campaignReviewGate(
|
|
4742
|
+
"build_reviewable",
|
|
4743
|
+
"Build reviewable",
|
|
4744
|
+
terminalState.kind === "running_healthy" ? "pending" : terminalState.kind === "running_stale" || terminalState.kind === "blocked" ? "blocked" : "pass",
|
|
4745
|
+
terminalState.label
|
|
4746
|
+
),
|
|
4747
|
+
campaignReviewGate(
|
|
4748
|
+
"rows_available",
|
|
4749
|
+
"Rows available",
|
|
4750
|
+
sampledRows > 0 ? "pass" : terminalState.reviewable ? "blocked" : "pending",
|
|
4751
|
+
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."
|
|
4752
|
+
),
|
|
4753
|
+
campaignReviewGate(
|
|
4754
|
+
"qualified_sample",
|
|
4755
|
+
"Sample rows qualified",
|
|
4756
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : qualifiedRows === sampledRows ? "pass" : "blocked",
|
|
4757
|
+
sampledRows > 0 ? `${qualifiedRows}/${sampledRows} sampled row(s) are marked qualified.` : "Qualification could not be checked without sampled rows."
|
|
4758
|
+
),
|
|
4759
|
+
campaignReviewGate(
|
|
4760
|
+
"email_coverage",
|
|
4761
|
+
"Email coverage",
|
|
4762
|
+
sampledRows === 0 ? terminalState.reviewable ? "blocked" : "pending" : rowsWithEmail === sampledRows ? "pass" : "blocked",
|
|
4763
|
+
sampledRows > 0 ? `${rowsWithEmail}/${sampledRows} sampled row(s) include an email.` : "Email coverage could not be checked without sampled rows."
|
|
4764
|
+
),
|
|
4765
|
+
campaignReviewGate(
|
|
4766
|
+
"artifact_available",
|
|
4767
|
+
"Artifact available",
|
|
4768
|
+
artifactCount > 0 ? "pass" : terminalState.reviewable ? "review" : "pending",
|
|
4769
|
+
artifactCount > 0 ? `${artifactCount} artifact(s) are available.` : "No delivery artifact is available yet."
|
|
4770
|
+
),
|
|
4771
|
+
campaignReviewGate(
|
|
4772
|
+
"no_failed_rows",
|
|
4773
|
+
"No failed rows",
|
|
4774
|
+
status.recordsFailed === 0 ? "pass" : "blocked",
|
|
4775
|
+
status.recordsFailed === 0 ? "The build reports zero failed rows." : `${status.recordsFailed} row(s) failed during the build.`
|
|
4776
|
+
)
|
|
4380
4777
|
];
|
|
4381
|
-
const blockedReasons = gates.filter((gate) => !gate.passed).map((gate) => gate.detail);
|
|
4778
|
+
const blockedReasons = gates.filter((gate) => gate.status === "blocked" || !gate.passed).map((gate) => gate.detail);
|
|
4382
4779
|
const warnings = [
|
|
4383
4780
|
rows.hasMore ? "This review is based on a row sample; page through remaining rows before final approval." : void 0,
|
|
4384
4781
|
sampledRows > 0 && copyReadyRows < sampledRows ? `${copyReadyRows}/${sampledRows} sampled row(s) include generated copy.` : void 0,
|
|
4385
4782
|
...status.warnings
|
|
4386
4783
|
].filter((item) => Boolean(item));
|
|
4387
|
-
const readyForDeliveryApproval = blockedReasons.length === 0;
|
|
4784
|
+
const readyForDeliveryApproval = terminalState.reviewable && blockedReasons.length === 0 && northStarScorecard.status !== "blocked";
|
|
4388
4785
|
return {
|
|
4389
4786
|
campaignBuildId: status.campaignBuildId || rows.campaignBuildId,
|
|
4390
4787
|
status,
|
|
4391
4788
|
rows,
|
|
4392
4789
|
artifacts,
|
|
4393
4790
|
gates,
|
|
4791
|
+
northStarScorecard,
|
|
4394
4792
|
summary: {
|
|
4395
4793
|
status: status.status,
|
|
4396
4794
|
phase: status.currentPhase,
|
|
4795
|
+
terminalState: terminalState.kind,
|
|
4796
|
+
scorecardStatus: northStarScorecard.status,
|
|
4797
|
+
scorecardScore: northStarScorecard.score,
|
|
4397
4798
|
sampledRows,
|
|
4398
4799
|
availableRows,
|
|
4399
4800
|
qualifiedRows,
|
|
@@ -4447,6 +4848,184 @@ function campaignReviewRowHasCopy(row) {
|
|
|
4447
4848
|
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)
|
|
4448
4849
|
);
|
|
4449
4850
|
}
|
|
4851
|
+
function campaignReviewDataEmail(data) {
|
|
4852
|
+
return stringValue(data.email) || stringValue(data.work_email) || stringValue(data.workEmail) || stringValue(asRecord(data.contact).email);
|
|
4853
|
+
}
|
|
4854
|
+
function campaignReviewDataDomain(data) {
|
|
4855
|
+
const domain = stringValue(data.company_domain) || stringValue(data.companyDomain) || stringValue(asRecord(data.company).domain);
|
|
4856
|
+
return domain?.toLowerCase();
|
|
4857
|
+
}
|
|
4858
|
+
function campaignReviewDataHasVerifiedEmail(data) {
|
|
4859
|
+
const status = String(data.email_status ?? data.emailStatus ?? asRecord(data.contact).email_status ?? "").toLowerCase();
|
|
4860
|
+
return data.email_verified === true || data.emailVerified === true || asRecord(data.contact).email_verified === true || ["valid", "validated", "verified"].includes(status);
|
|
4861
|
+
}
|
|
4862
|
+
function campaignReviewDataCopyParts(data) {
|
|
4863
|
+
const copy = asRecord(data.copy);
|
|
4864
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4865
|
+
return [
|
|
4866
|
+
stringValue(data.subject),
|
|
4867
|
+
stringValue(data.subject_line),
|
|
4868
|
+
stringValue(data.opener),
|
|
4869
|
+
stringValue(data.body),
|
|
4870
|
+
stringValue(data.cta),
|
|
4871
|
+
stringValue(copy.subject),
|
|
4872
|
+
stringValue(copy.opener),
|
|
4873
|
+
stringValue(copy.body),
|
|
4874
|
+
stringValue(copy.cta),
|
|
4875
|
+
stringValue(rawCopy.subject),
|
|
4876
|
+
stringValue(rawCopy.opener),
|
|
4877
|
+
stringValue(rawCopy.body),
|
|
4878
|
+
stringValue(rawCopy.cta)
|
|
4879
|
+
].filter((part) => Boolean(part));
|
|
4880
|
+
}
|
|
4881
|
+
function campaignReviewDataHasBadGreeting(data) {
|
|
4882
|
+
return campaignReviewDataCopyParts(data).some(
|
|
4883
|
+
(part) => /\bhi\s*(?:,|\{\{|\[)|\bhello\s*(?:,|\{\{|\[)/i.test(part) || /\b(first_name|firstname|name)\b/i.test(part)
|
|
4884
|
+
);
|
|
4885
|
+
}
|
|
4886
|
+
function campaignReviewDataHasBannedPhrase(data) {
|
|
4887
|
+
const text = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4888
|
+
return NORTH_STAR_BANNED_COPY_PHRASES.some((phrase) => text.includes(phrase));
|
|
4889
|
+
}
|
|
4890
|
+
function campaignReviewDataSubject(data) {
|
|
4891
|
+
const copy = asRecord(data.copy);
|
|
4892
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4893
|
+
return stringValue(data.subject) || stringValue(data.subject_line) || stringValue(data.subjectLine) || stringValue(copy.subject) || stringValue(rawCopy.subject);
|
|
4894
|
+
}
|
|
4895
|
+
function campaignReviewDataCta(data) {
|
|
4896
|
+
const copy = asRecord(data.copy);
|
|
4897
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4898
|
+
return stringValue(data.cta) || stringValue(copy.cta) || stringValue(rawCopy.cta);
|
|
4899
|
+
}
|
|
4900
|
+
function countCampaignReviewIssues(issueRows) {
|
|
4901
|
+
return issueRows.flat().reduce((acc, issue) => {
|
|
4902
|
+
acc[issue] = (acc[issue] || 0) + 1;
|
|
4903
|
+
return acc;
|
|
4904
|
+
}, {});
|
|
4905
|
+
}
|
|
4906
|
+
function formatCampaignReviewIssueCounts(counts) {
|
|
4907
|
+
const entries = Object.entries(counts);
|
|
4908
|
+
return entries.length > 0 ? entries.map(([issue, count]) => `${issue}=${count}`).join(", ") : "none";
|
|
4909
|
+
}
|
|
4910
|
+
function campaignReviewDataCopyQaIssues(data) {
|
|
4911
|
+
const issues = /* @__PURE__ */ new Set();
|
|
4912
|
+
const copyParts = campaignReviewDataCopyParts(data);
|
|
4913
|
+
if (copyParts.length === 0) return [];
|
|
4914
|
+
if (campaignReviewDataHasBadGreeting(data)) issues.add("bad_greeting");
|
|
4915
|
+
if (campaignReviewDataHasBannedPhrase(data)) issues.add("banned_or_vague_phrase");
|
|
4916
|
+
if (campaignReviewDataHasUnsupportedSignalClaim(data)) issues.add("unsupported_signal_claim");
|
|
4917
|
+
if (campaignReviewDataPersonalizationBasis(data).length === 0) issues.add("missing_personalization_basis");
|
|
4918
|
+
if (!campaignReviewDataCta(data)) issues.add("missing_cta");
|
|
4919
|
+
const subject = campaignReviewDataSubject(data);
|
|
4920
|
+
if (subject && subject.length > 70) issues.add("overlong_subject");
|
|
4921
|
+
return [...issues];
|
|
4922
|
+
}
|
|
4923
|
+
function campaignReviewDataPersonalizationBasis(data) {
|
|
4924
|
+
const rawCopy = asRecord(data.raw_copy ?? data.rawCopy);
|
|
4925
|
+
const copy = asRecord(data.copy);
|
|
4926
|
+
const metadata = asRecord(data.metadata);
|
|
4927
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4928
|
+
return [
|
|
4929
|
+
...arrayOfStrings(data.personalization_basis) ?? [],
|
|
4930
|
+
...arrayOfStrings(data.personalizationBasis) ?? [],
|
|
4931
|
+
...arrayOfStrings(copy.personalization_basis) ?? [],
|
|
4932
|
+
...arrayOfStrings(rawCopy.personalization_basis) ?? [],
|
|
4933
|
+
...arrayOfStrings(metadata.personalization_basis) ?? [],
|
|
4934
|
+
...arrayOfStrings(metadata.personalizationBasis) ?? [],
|
|
4935
|
+
...arrayOfStrings(signalEvidence.personalization_basis) ?? []
|
|
4936
|
+
];
|
|
4937
|
+
}
|
|
4938
|
+
function campaignReviewDataHasNoSupportedSignal(data) {
|
|
4939
|
+
const metadata = asRecord(data.metadata);
|
|
4940
|
+
const copy = asRecord(data.copy);
|
|
4941
|
+
const state = [
|
|
4942
|
+
data.copy_signal_state,
|
|
4943
|
+
data.signal_basis,
|
|
4944
|
+
data.signal_type,
|
|
4945
|
+
metadata.copy_signal_state,
|
|
4946
|
+
metadata.signal_basis,
|
|
4947
|
+
metadata.signal_type,
|
|
4948
|
+
copy.copy_signal_state
|
|
4949
|
+
].map((value) => stringValue(value)?.toLowerCase()).filter(Boolean);
|
|
4950
|
+
return data.no_supported_signal === true || metadata.no_supported_signal === true || state.includes("no_supported_signal");
|
|
4951
|
+
}
|
|
4952
|
+
function campaignReviewDataHasSignalBasis(data) {
|
|
4953
|
+
if (campaignReviewDataHasNoSupportedSignal(data)) return false;
|
|
4954
|
+
const metadata = asRecord(data.metadata);
|
|
4955
|
+
const signal = asRecord(data.signal);
|
|
4956
|
+
const signalEvidence = asRecord(data.signal_evidence ?? metadata.signal_evidence ?? metadata.signalEvidence);
|
|
4957
|
+
const basis = campaignReviewDataPersonalizationBasis(data);
|
|
4958
|
+
return Boolean(
|
|
4959
|
+
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))
|
|
4960
|
+
);
|
|
4961
|
+
}
|
|
4962
|
+
function campaignReviewDataHasUnsupportedSignalClaim(data) {
|
|
4963
|
+
const copyText = campaignReviewDataCopyParts(data).join(" ").toLowerCase();
|
|
4964
|
+
const claimsSignal = /\b(hiring|funding|raised|launched|expanding|appointed|announced|recent news|new role|job posting)\b/.test(copyText);
|
|
4965
|
+
return claimsSignal && !campaignReviewDataHasSignalBasis(data);
|
|
4966
|
+
}
|
|
4967
|
+
function campaignReviewDataHasApprovalLock(data) {
|
|
4968
|
+
const metadata = asRecord(data.fit_metadata ?? data.metadata);
|
|
4969
|
+
const approval = String(data.approval_status ?? data.delivery_approval_status ?? metadata.approval_status ?? "").toLowerCase();
|
|
4970
|
+
const exportStatus = String(data.export_status ?? data.exportStatus ?? "").toLowerCase();
|
|
4971
|
+
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);
|
|
4972
|
+
}
|
|
4973
|
+
function campaignReviewDataHasHoldoutAttribution(data) {
|
|
4974
|
+
const fitMetadata = asRecord(data.fit_metadata ?? data.fitMetadata);
|
|
4975
|
+
const metadata = asRecord(data.metadata);
|
|
4976
|
+
const holdout = asRecord(
|
|
4977
|
+
fitMetadata.learning_holdout ?? fitMetadata.learningHoldout ?? metadata.learning_holdout ?? metadata.learningHoldout ?? data.learning_holdout ?? data.learningHoldout
|
|
4978
|
+
);
|
|
4979
|
+
const experimentKey = stringValue(holdout.experiment_key ?? holdout.experimentKey ?? data.experiment_key ?? data.experimentKey);
|
|
4980
|
+
const cohort = stringValue(holdout.cohort_role ?? holdout.cohortRole ?? data.cohort_role ?? data.learning_cohort ?? data.learningCohort);
|
|
4981
|
+
const brainApplied = holdout.brain_applied ?? holdout.brainApplied ?? data.brain_applied ?? data.signaliz_brain_applied;
|
|
4982
|
+
return Boolean(experimentKey && cohort && brainApplied !== void 0);
|
|
4983
|
+
}
|
|
4984
|
+
function campaignReviewStatusHasFeedbackReadiness(status) {
|
|
4985
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
4986
|
+
const feedbackReadiness = asRecord(diagnostics.feedback_readiness ?? diagnostics.feedbackReadiness);
|
|
4987
|
+
const feedbackSubstrate = asRecord(diagnostics.feedback_substrate ?? diagnostics.feedbackSubstrate);
|
|
4988
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
4989
|
+
return feedbackReadiness.ready === true || feedbackReadiness.webhook_configured === true || feedbackSubstrate.stage === "ready" || Number(evidenceCounts.feedback_events ?? 0) > 0;
|
|
4990
|
+
}
|
|
4991
|
+
function campaignReviewMoatState(status, holdoutRows) {
|
|
4992
|
+
const diagnostics = asRecord(status.diagnostics);
|
|
4993
|
+
const evidenceCounts = asRecord(diagnostics.evidence_counts ?? diagnostics.evidenceCounts);
|
|
4994
|
+
const embeddedHoldout = asRecord(diagnostics.embedded_holdout ?? diagnostics.embeddedHoldout);
|
|
4995
|
+
const holdoutLift = asRecord(diagnostics.holdout_lift ?? diagnostics.holdoutLift ?? diagnostics.holdout_measurement ?? diagnostics.holdoutMeasurement);
|
|
4996
|
+
const controlSample = Math.max(
|
|
4997
|
+
Number(evidenceCounts.holdout_control_sample_size ?? 0),
|
|
4998
|
+
Number(evidenceCounts.embedded_holdout_control_sample ?? 0),
|
|
4999
|
+
Number(embeddedHoldout.control_sample_size ?? 0)
|
|
5000
|
+
);
|
|
5001
|
+
const learnedSample = Math.max(
|
|
5002
|
+
Number(evidenceCounts.holdout_learned_sample_size ?? 0),
|
|
5003
|
+
Number(evidenceCounts.embedded_holdout_learned_sample ?? 0),
|
|
5004
|
+
Number(embeddedHoldout.learned_sample_size ?? 0)
|
|
5005
|
+
);
|
|
5006
|
+
const feedbackEvents = Math.max(
|
|
5007
|
+
Number(evidenceCounts.feedback_events ?? 0),
|
|
5008
|
+
Number(evidenceCounts.campaign_build_feedback_events ?? 0),
|
|
5009
|
+
Number(evidenceCounts.delivery_holdout_sent_outcomes ?? 0)
|
|
5010
|
+
);
|
|
5011
|
+
const proved = diagnostics.closed_loop_proof_ready === true || holdoutLift.honest_measurement === true && holdoutLift.positive_lift === true;
|
|
5012
|
+
if (proved) {
|
|
5013
|
+
return {
|
|
5014
|
+
state: "proved",
|
|
5015
|
+
detail: "Real attributed feedback shows positive lift for explicit control and learned cohorts."
|
|
5016
|
+
};
|
|
5017
|
+
}
|
|
5018
|
+
if (controlSample > 0 && learnedSample > 0 && feedbackEvents > 0) {
|
|
5019
|
+
return {
|
|
5020
|
+
state: "moved_needs_lift_volume",
|
|
5021
|
+
detail: `Control and learned cohorts have attributed feedback (${controlSample} control, ${learnedSample} learned, ${feedbackEvents} feedback event(s)); keep collecting lift volume before claiming proof.`
|
|
5022
|
+
};
|
|
5023
|
+
}
|
|
5024
|
+
return {
|
|
5025
|
+
state: "scaffolding_ready",
|
|
5026
|
+
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."
|
|
5027
|
+
};
|
|
5028
|
+
}
|
|
4450
5029
|
function createCampaignBuilderReadinessLane(route, plan) {
|
|
4451
5030
|
const builtIn = campaignBuilderBuiltInForRoute(route);
|
|
4452
5031
|
const customRoute = route.provider !== "signaliz" && route.provider !== "csv" && builtIn === void 0;
|
|
@@ -7662,6 +8241,16 @@ var GtmKernel = class {
|
|
|
7662
8241
|
min_privacy_k: input.minPrivacyK
|
|
7663
8242
|
});
|
|
7664
8243
|
}
|
|
8244
|
+
/** Queue a dry-run-first bridge from imported historical feedback to attribution-only Campaign Build surrogates. */
|
|
8245
|
+
async runHistoricalCampaignBuildBridge(input = {}) {
|
|
8246
|
+
return this.callMcp("gtm_historical_campaign_build_bridge_run", {
|
|
8247
|
+
dry_run: input.dryRun,
|
|
8248
|
+
write_approved: input.writeApproved,
|
|
8249
|
+
max_events: input.maxEvents,
|
|
8250
|
+
max_campaigns: input.maxCampaigns,
|
|
8251
|
+
min_feedback_events: input.minFeedbackEvents
|
|
8252
|
+
});
|
|
8253
|
+
}
|
|
7665
8254
|
/** Prepare a provider-agnostic feedback webhook URL for Smartlead, HeyReach, Airbyte, or custom sender events. */
|
|
7666
8255
|
async prepareFeedbackWebhook(input) {
|
|
7667
8256
|
return this.callMcp("gtm_feedback_webhook_prepare", {
|