Create complex automation sequences with conditional logic, multi-step processes, and intelligent decision trees for advanced campaign management.
// Advanced workflow: Intelligent campaign lifecycle automation
class CampaignLifecycleWorkflow {
constructor() {
this.workflowState = this.loadWorkflowState();
this.decisionTree = this.buildDecisionTree();
}
execute() {
Logger.log("=== Smart Campaign Lifecycle Workflow ===");
const campaigns = this.getCampaignsForEvaluation();
campaigns.forEach(campaign => {
const context = this.buildCampaignContext(campaign);
const decision = this.evaluateDecisionTree(context);
Logger.log(`${campaign.getName()}: Stage=${context.stage}, Decision=${decision.action}`);
this.executeAction(campaign, decision, context);
this.updateWorkflowState(campaign, decision);
});
this.saveWorkflowState();
this.generateWorkflowReport();
}
buildCampaignContext(campaign) {
const stats30 = campaign.getStatsFor('LAST_30_DAYS');
const stats7 = campaign.getStatsFor('LAST_7_DAYS');
const createdDate = new Date(campaign.getCreatedDate());
const campaignAge = (Date.now() - createdDate.getTime()) / (1000 * 60 * 60 * 24);
return {
campaign: campaign,
age: campaignAge,
stage: this.determineCampaignStage(campaignAge, stats30),
performance: {
roas30: stats30.getConversionValue() / Math.max(stats30.getCost(), 1),
roas7: stats7.getConversionValue() / Math.max(stats7.getCost(), 1),
clicks30: stats30.getClicks(),
conversions30: stats30.getConversions(),
ctr: stats30.getCtr(),
avgCpc: stats30.getAverageCpc()
},
budget: {
current: campaign.getBudget(),
utilization: stats7.getCost() / (campaign.getBudget() * 7),
recommended: this.calculateOptimalBudget(stats30)
},
trends: this.analyzeTrends(campaign),
competitivePosition: this.getCompetitiveData(campaign)
};
}
buildDecisionTree() {
return {
// Learning Phase (0-14 days)
learning: {
conditions: [
{
if: ctx => ctx.age <= 14,
then: this.learningPhaseDecisions()
}
]
},
// Growth Phase (15-45 days)
growth: {
conditions: [
{
if: ctx => ctx.age > 14 && ctx.age <= 45,
then: this.growthPhaseDecisions()
}
]
},
// Maturity Phase (45+ days)
maturity: {
conditions: [
{
if: ctx => ctx.age > 45,
then: this.maturityPhaseDecisions()
}
]
}
};
}
learningPhaseDecisions() {
return [
{
if: ctx => ctx.performance.clicks30 < 100,
action: 'increase_budget',
params: { multiplier: 1.5, reason: 'Insufficient data volume' }
},
{
if: ctx => ctx.performance.ctr < 0.02,
action: 'optimize_ads',
params: { focus: 'creative_testing', reason: 'Low CTR indicates poor ad relevance' }
},
{
if: ctx => ctx.performance.clicks30 >= 100 && ctx.performance.conversions30 === 0,
action: 'review_targeting',
params: { focus: 'audience_refinement', reason: 'Clicks without conversions' }
},
{
if: ctx => ctx.performance.roas30 > 3.0 && ctx.performance.conversions30 >= 5,
action: 'graduate_to_growth',
params: { reason: 'Strong early performance warrants scaling' }
}
];
}
growthPhaseDecisions() {
return [
{
if: ctx => ctx.performance.roas30 > 4.0 && ctx.budget.utilization > 0.8,
action: 'aggressive_scale',
params: {
budgetMultiplier: 2.0,
bidIncrease: 0.2,
reason: 'High ROAS with full budget utilization'
}
},
{
if: ctx => ctx.performance.roas7 < ctx.performance.roas30 * 0.8,
action: 'performance_decline_response',
params: {
investigate: ['seasonality', 'competition', 'audience_saturation'],
reason: 'Recent performance decline detected'
}
},
{
if: ctx => ctx.trends.declining && ctx.competitivePosition.pressure > 0.7,
action: 'competitive_response',
params: {
strategy: 'defensive_bidding',
reason: 'High competitive pressure with declining trends'
}
}
];
}
maturityPhaseDecisions() {
return [
{
if: ctx => ctx.performance.roas30 < 2.0,
action: 'optimization_or_pause',
params: {
threshold: 1.5,
gracePeriod: 7,
reason: 'Consistently poor ROAS in mature campaign'
}
},
{
if: ctx => ctx.trends.stable && ctx.performance.roas30 > 3.0,
action: 'maintain_and_optimize',
params: {
focus: 'efficiency_improvements',
reason: 'Stable, profitable mature campaign'
}
},
{
if: ctx => ctx.trends.growth_opportunity,
action: 'strategic_expansion',
params: {
areas: ['new_keywords', 'audience_expansion', 'geo_expansion'],
reason: 'Growth opportunity identified in mature campaign'
}
}
];
}
executeAction(campaign, decision, context) {
Logger.log(`Executing: ${decision.action} for ${campaign.getName()}`);
Logger.log(`Reason: ${decision.params.reason}`);
switch (decision.action) {
case 'increase_budget':
this.increaseBudget(campaign, decision.params.multiplier);
break;
case 'aggressive_scale':
this.aggressiveScale(campaign, decision.params);
break;
case 'optimize_ads':
this.optimizeAds(campaign, decision.params);
break;
case 'competitive_response':
this.competitiveResponse(campaign, decision.params);
break;
case 'optimization_or_pause':
this.optimizationOrPause(campaign, decision.params, context);
break;
case 'maintain_and_optimize':
this.maintainAndOptimize(campaign, decision.params);
break;
default:
Logger.log(`Action not implemented: ${decision.action}`);
}
// Schedule follow-up evaluation
this.scheduleFollowUp(campaign, decision);
}
increaseBudget(campaign, multiplier) {
const currentBudget = campaign.getBudget();
const newBudget = Math.round(currentBudget * multiplier);
const maxBudget = 5000; // Safety limit
const finalBudget = Math.min(newBudget, maxBudget);
campaign.setBudget(finalBudget);
Logger.log(`Budget increased: €${currentBudget} → €${finalBudget}`);
}
aggressiveScale(campaign, params) {
// Budget scaling
this.increaseBudget(campaign, params.budgetMultiplier);
// Bid increases for top keywords
const keywords = campaign.keywords()
.withCondition('Status = ENABLED')
.orderBy('ConversionValue DESC')
.withLimit(20)
.get();
let keywordsOptimized = 0;
while (keywords.hasNext()) {
const keyword = keywords.next();
const currentBid = keyword.getMaxCpc();
const newBid = currentBid * (1 + params.bidIncrease);
keyword.setMaxCpc(newBid);
keywordsOptimized++;
}
Logger.log(`Aggressive scaling: ${keywordsOptimized} keywords optimized`);
}
}This advanced workflow demonstrates intelligent campaign lifecycle management with context-aware decision making, multi-stage optimization, and adaptive strategies.
// Advanced state management for workflow persistence
class WorkflowStateManager {
constructor() {
this.stateKey = 'workflow_state_v2';
this.maxStateAge = 30 * 24 * 60 * 60 * 1000; // 30 days
}
loadWorkflowState() {
try {
const stateJson = PropertiesService.getScriptProperties().getProperty(this.stateKey);
if (!stateJson) {
return this.createInitialState();
}
const state = JSON.parse(stateJson);
// Validate state age and structure
if (this.isStateValid(state)) {
Logger.log(`Loaded workflow state with ${Object.keys(state.campaigns).length} campaigns`);
return state;
} else {
Logger.log('Invalid or expired state, creating new state');
return this.createInitialState();
}
} catch (error) {
Logger.log(`Error loading state: ${error.message}`);
return this.createInitialState();
}
}
saveWorkflowState(state) {
try {
// Clean old entries before saving
const cleanedState = this.cleanOldEntries(state);
// Add metadata
cleanedState.lastUpdated = Date.now();
cleanedState.version = '2.0';
const stateJson = JSON.stringify(cleanedState);
// Check size limits (PropertiesService has 9KB limit per property)
if (stateJson.length > 8000) {
Logger.log('State too large, compressing...');
const compressedState = this.compressState(cleanedState);
PropertiesService.getScriptProperties().setProperty(this.stateKey, JSON.stringify(compressedState));
} else {
PropertiesService.getScriptProperties().setProperty(this.stateKey, stateJson);
}
Logger.log(`Workflow state saved (${stateJson.length} chars)`);
} catch (error) {
Logger.log(`Error saving state: ${error.message}`);
}
}
updateCampaignState(campaignId, updates) {
const state = this.loadWorkflowState();
if (!state.campaigns[campaignId]) {
state.campaigns[campaignId] = {
created: Date.now(),
stage: 'learning',
actions: [],
metrics: {}
};
}
// Merge updates
Object.assign(state.campaigns[campaignId], updates);
// Add timestamp
state.campaigns[campaignId].lastUpdated = Date.now();
this.saveWorkflowState(state);
}
logWorkflowAction(campaignId, action, result) {
const state = this.loadWorkflowState();
if (!state.campaigns[campaignId]) {
this.updateCampaignState(campaignId, {});
return this.logWorkflowAction(campaignId, action, result);
}
const actionLog = {
timestamp: Date.now(),
action: action.action,
params: action.params,
result: result,
success: result.success || false
};
if (!state.campaigns[campaignId].actions) {
state.campaigns[campaignId].actions = [];
}
state.campaigns[campaignId].actions.push(actionLog);
// Keep only last 50 actions per campaign
if (state.campaigns[campaignId].actions.length > 50) {
state.campaigns[campaignId].actions = state.campaigns[campaignId].actions.slice(-50);
}
this.saveWorkflowState(state);
}
getWorkflowHistory(campaignId, days = 30) {
const state = this.loadWorkflowState();
const campaign = state.campaigns[campaignId];
if (!campaign || !campaign.actions) {
return [];
}
const cutoffTime = Date.now() - (days * 24 * 60 * 60 * 1000);
return campaign.actions.filter(action => action.timestamp > cutoffTime);
}
createInitialState() {
return {
version: '2.0',
created: Date.now(),
lastUpdated: Date.now(),
campaigns: {},
globalSettings: {
maxCampaignAge: 90,
minConversionsForOptimization: 5,
roasThresholds: {
excellent: 4.0,
good: 3.0,
acceptable: 2.0,
poor: 1.5
}
}
};
}
isStateValid(state) {
if (!state || !state.version || !state.lastUpdated) {
return false;
}
const age = Date.now() - state.lastUpdated;
return age < this.maxStateAge;
}
compressState(state) {
// Simple compression: remove old action logs and compress metrics
const compressed = { ...state };
Object.keys(compressed.campaigns).forEach(campaignId => {
const campaign = compressed.campaigns[campaignId];
// Keep only last 10 actions
if (campaign.actions && campaign.actions.length > 10) {
campaign.actions = campaign.actions.slice(-10);
}
// Compress metrics (keep only essential data)
if (campaign.metrics) {
campaign.metrics = {
lastRoas: campaign.metrics.lastRoas,
lastUpdate: campaign.metrics.lastUpdate
};
}
});
return compressed;
}
}