Production-ready prompts, scripts, frameworks and AI agents for Google Ads professionals. No payment required.
Auction Insights is the most useful Google Ads UI report and the worst tracked. This pulls it into a sheet so I see week-over-week shifts before they cost me impression share.
Auction Insights ships as a one-shot UI report. You can see who outranks you today, but you cannot see whose impression share is creeping up week by week, which is what actually matters. This script exports it to a sheet, computes the delta vs the previous period, and tags every competitor with a threat level so you know who to watch.
SPREADSHEET_URL as 'CREATE_NEW' for a fresh sheet, set DATE_RANGE (default LAST_30_DAYS), and tune THREAT_THRESHOLD (default 5 percent IS gain) plus MIN_IMPRESSION_SHARE to filter out noise. Optional: add EMAIL_RECIPIENTS or a SLACK_WEBHOOK_URL./******************************************************************************
* COMPETITOR AUCTION INSIGHTS EXPORT
*
* Generated by PPC.io Script Engine
* https://ppc.io
*
* Purpose: Export auction insights data for competitive analysis
* Author: PPC.io
* Version: 2.0
* Updated: 2025-01-13
*
* SETUP INSTRUCTIONS:
* 1. Set SPREADSHEET_URL to 'CREATE_NEW' or paste existing URL
* 2. Run in Preview mode first to verify
* 3. Schedule: Weekly recommended for trend analysis
*
* USE CASE: "Analyze my competitive position and flag any threats."
*
* OUTPUTS:
* - 1. Summary: Your position and AI prompts
* - 2. Competitor Overview: All competitors with metrics
* - 3. By Campaign: Auction insights per campaign
* - 4. Threats: Competitors gaining impression share
*
* CHANGELOG:
* v2.0 - Added numbered sheets, enhanced AI prompts
* v1.0 - Initial release
*
******************************************************************************/
/******************************************************************************
* CONFIGURATION - Adjust these values for your account
******************************************************************************/
var CONFIG = {
// ═══════════════════════════════════════════════════════════════════════════
// OUTPUT SETTINGS
// ═══════════════════════════════════════════════════════════════════════════
SPREADSHEET_URL: 'CREATE_NEW', // Or paste existing spreadsheet URL
// Email alerts (leave empty array to disable)
EMAIL_RECIPIENTS: [],
// Slack webhook (leave empty to disable)
SLACK_WEBHOOK_URL: '',
// ═══════════════════════════════════════════════════════════════════════════
// DATE RANGES
// ═══════════════════════════════════════════════════════════════════════════
// Main analysis period
DATE_RANGE: 'LAST_30_DAYS',
// For trend comparison (compare current period vs this)
COMPARISON_DATE_RANGE: 'LAST_30_DAYS', // Will use previous 30 days
// ═══════════════════════════════════════════════════════════════════════════
// FILTERS
// ═══════════════════════════════════════════════════════════════════════════
CAMPAIGN_NAME_CONTAINS: '', // Filter campaigns (empty = all)
CAMPAIGN_NAME_EXCLUDES: '', // Exclude campaigns containing this
INCLUDE_PAUSED_CAMPAIGNS: false,
// ═══════════════════════════════════════════════════════════════════════════
// ANALYSIS SETTINGS
// ═══════════════════════════════════════════════════════════════════════════
// Minimum impression share to include competitor
MIN_IMPRESSION_SHARE: 0.01, // 1%
// Flag competitors gaining more than this %
THREAT_THRESHOLD: 0.05, // 5% gain
// Top competitors to highlight
TOP_COMPETITORS_COUNT: 10,
// ═══════════════════════════════════════════════════════════════════════════
// EXECUTION SETTINGS
// ═══════════════════════════════════════════════════════════════════════════
LOG_LEVEL: 'INFO', // DEBUG, INFO, WARN, ERROR
TIME_LIMIT_MINUTES: 25, // Exit gracefully before this (max 30)
BATCH_SIZE: 500 // Rows per spreadsheet write
};
/******************************************************************************
* MAIN EXECUTION
******************************************************************************/
function main() {
var startTime = new Date();
log('INFO', 'Competitor Auction Insights Export started: ' + startTime.toISOString());
try {
var ss = initializeSpreadsheet();
var results = collectAuctionInsights(startTime);
writeAllSheets(ss, results);
sendNotifications(results, ss.getUrl(), startTime);
logSummary(results, startTime);
} catch (error) {
handleFatalError(error, startTime);
}
}
/******************************************************************************
* DATA COLLECTION
******************************************************************************/
function collectAuctionInsights(startTime) {
var results = {
competitorOverview: [],
campaignLevel: [],
trends: [],
threats: [],
yourPerformance: {
impressionShare: 0,
overlapRate: 0,
positionAboveRate: 0,
topOfPageRate: 0,
absTopOfPageRate: 0,
outRankingShare: 0
},
summary: {
totalCompetitors: 0,
topCompetitors: [],
biggestThreats: [],
losingGroundTo: [],
gainingGroundOn: []
}
};
// Get current period auction insights
var currentData = getAuctionInsightsData(CONFIG.DATE_RANGE, startTime);
// Get previous period for comparison
var previousData = getAuctionInsightsData(getPreviousPeriod(), startTime);
// Process and combine data
processAuctionData(results, currentData, previousData);
return results;
}
function getAuctionInsightsData(dateRange, startTime) {
var data = {
byCompetitor: {},
byCampaign: {}
};
// Query auction insights at campaign level
var query = "SELECT " +
"campaign.name, " +
"campaign.id, " +
"auction_insight.domain, " +
"metrics.auction_insight_search_impression_share, " +
"metrics.auction_insight_search_overlap_rate, " +
"metrics.auction_insight_search_position_above_rate, " +
"metrics.auction_insight_search_top_of_page_rate, " +
"metrics.auction_insight_search_absolute_top_of_page_rate, " +
"metrics.auction_insight_search_outranking_share " +
"FROM campaign " +
"WHERE campaign.advertising_channel_type = 'SEARCH' " +
"AND segments.date DURING " + dateRange;
if (!CONFIG.INCLUDE_PAUSED_CAMPAIGNS) {
query += " AND campaign.status = 'ENABLED'";
}
try {
var report = AdsApp.report(query);
var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var campaignName = row['campaign.name'];
var domain = row['auction_insight.domain'];
// Apply campaign filters
if (CONFIG.CAMPAIGN_NAME_CONTAINS &&
campaignName.toLowerCase().indexOf(CONFIG.CAMPAIGN_NAME_CONTAINS.toLowerCase()) === -1) {
continue;
}
if (CONFIG.CAMPAIGN_NAME_EXCLUDES &&
campaignName.toLowerCase().indexOf(CONFIG.CAMPAIGN_NAME_EXCLUDES.toLowerCase()) !== -1) {
continue;
}
var impressionShare = parseFloat(row['metrics.auction_insight_search_impression_share']) || 0;
var overlapRate = parseFloat(row['metrics.auction_insight_search_overlap_rate']) || 0;
var positionAboveRate = parseFloat(row['metrics.auction_insight_search_position_above_rate']) || 0;
var topOfPageRate = parseFloat(row['metrics.auction_insight_search_top_of_page_rate']) || 0;
var absTopOfPageRate = parseFloat(row['metrics.auction_insight_search_absolute_top_of_page_rate']) || 0;
var outRankingShare = parseFloat(row['metrics.auction_insight_search_outranking_share']) || 0;
// By competitor (aggregate)
if (!data.byCompetitor[domain]) {
data.byCompetitor[domain] = {
domain: domain,
impressionShare: [],
overlapRate: [],
positionAboveRate: [],
topOfPageRate: [],
absTopOfPageRate: [],
outRankingShare: [],
campaigns: {}
};
}
data.byCompetitor[domain].impressionShare.push(impressionShare);
data.byCompetitor[domain].overlapRate.push(overlapRate);
data.byCompetitor[domain].positionAboveRate.push(positionAboveRate);
data.byCompetitor[domain].topOfPageRate.push(topOfPageRate);
data.byCompetitor[domain].absTopOfPageRate.push(absTopOfPageRate);
data.byCompetitor[domain].outRankingShare.push(outRankingShare);
data.byCompetitor[domain].campaigns[campaignName] = true;
// By campaign
var campKey = campaignName + '|' + domain;
data.byCampaign[campKey] = {
campaign: campaignName,
domain: domain,
impressionShare: impressionShare,
overlapRate: overlapRate,
positionAboveRate: positionAboveRate,
topOfPageRate: topOfPageRate,
absTopOfPageRate: absTopOfPageRate,
outRankingShare: outRankingShare
};
checkTimeLimit(startTime);
}
} catch (e) {
log('ERROR', 'Auction insights query failed: ' + e.message);
// Fall back to alternative data collection
collectAuctionInsightsFallback(data, dateRange, startTime);
}
return data;
}
function collectAuctionInsightsFallback(data, dateRange, startTime) {
log('INFO', 'Using fallback method for auction insights');
// Use campaign stats and estimate competitive position
var campaigns = AdsApp.campaigns()
.withCondition('AdvertisingChannelType = SEARCH')
.withCondition('Status = ENABLED')
.forDateRange(dateRange)
.get();
while (campaigns.hasNext()) {
var campaign = campaigns.next();
var name = campaign.getName();
if (CONFIG.CAMPAIGN_NAME_CONTAINS &&
name.toLowerCase().indexOf(CONFIG.CAMPAIGN_NAME_CONTAINS.toLowerCase()) === -1) {
continue;
}
var stats = campaign.getStatsFor(dateRange);
// Store campaign performance (we can't get competitor data via fallback)
data.byCampaign[name + '|Your Account'] = {
campaign: name,
domain: 'Your Account',
impressionShare: 0, // Would need Search Campaign report
overlapRate: 0,
positionAboveRate: 0,
topOfPageRate: 0,
absTopOfPageRate: 0,
outRankingShare: 0,
impressions: stats.getImpressions(),
clicks: stats.getClicks()
};
}
}
function getPreviousPeriod() {
// Calculate previous period based on current DATE_RANGE
var periodMap = {
'LAST_7_DAYS': 'LAST_14_DAYS',
'LAST_14_DAYS': 'LAST_30_DAYS',
'LAST_30_DAYS': 'LAST_30_DAYS'
};
// Return the same period for comparison
// In practice, you'd want custom date ranges
return CONFIG.COMPARISON_DATE_RANGE;
}
function processAuctionData(results, currentData, previousData) {
var competitors = {};
// Process current period data
for (var domain in currentData.byCompetitor) {
var comp = currentData.byCompetitor[domain];
// Calculate averages
var avgImpressionShare = average(comp.impressionShare);
var avgOverlapRate = average(comp.overlapRate);
var avgPositionAboveRate = average(comp.positionAboveRate);
var avgTopOfPageRate = average(comp.topOfPageRate);
var avgAbsTopOfPageRate = average(comp.absTopOfPageRate);
var avgOutRankingShare = average(comp.outRankingShare);
if (avgImpressionShare < CONFIG.MIN_IMPRESSION_SHARE) {
continue;
}
// Check if "you" (the account)
var isYou = domain.toLowerCase().indexOf('you') !== -1 ||
domain === AdsApp.currentAccount().getName();
if (isYou) {
results.yourPerformance = {
impressionShare: avgImpressionShare,
overlapRate: avgOverlapRate,
positionAboveRate: avgPositionAboveRate,
topOfPageRate: avgTopOfPageRate,
absTopOfPageRate: avgAbsTopOfPageRate,
outRankingShare: avgOutRankingShare
};
continue;
}
// Get previous period data for comparison
var prevComp = previousData.byCompetitor[domain];
var prevImpressionShare = prevComp ? average(prevComp.impressionShare) : 0;
var impressionShareChange = avgImpressionShare - prevImpressionShare;
competitors[domain] = {
'Competitor': domain,
'Impression Share': avgImpressionShare,
'Overlap Rate': avgOverlapRate,
'Position Above Rate': avgPositionAboveRate,
'Top of Page Rate': avgTopOfPageRate,
'Abs Top of Page Rate': avgAbsTopOfPageRate,
'Outranking Share': avgOutRankingShare,
'IS Change': impressionShareChange,
'Campaigns Competing': Object.keys(comp.campaigns).length,
'Threat Level': getThreatLevel(avgImpressionShare, impressionShareChange, avgPositionAboveRate)
};
// Track threats
if (impressionShareChange >= CONFIG.THREAT_THRESHOLD) {
results.threats.push({
domain: domain,
currentIS: avgImpressionShare,
change: impressionShareChange,
positionAboveRate: avgPositionAboveRate
});
}
}
// Convert to array and sort by impression share
results.competitorOverview = Object.keys(competitors)
.map(function(d) { return competitors[d]; })
.sort(function(a, b) { return b['Impression Share'] - a['Impression Share']; });
results.summary.totalCompetitors = results.competitorOverview.length;
// Get top competitors
results.summary.topCompetitors = results.competitorOverview
.slice(0, CONFIG.TOP_COMPETITORS_COUNT)
.map(function(c) { return c.Competitor; });
// Identify biggest threats (gaining IS)
results.summary.biggestThreats = results.competitorOverview
.filter(function(c) { return c['IS Change'] > 0; })
.sort(function(a, b) { return b['IS Change'] - a['IS Change']; })
.slice(0, 5)
.map(function(c) { return { name: c.Competitor, change: c['IS Change'] }; });
// Identify where you're losing ground
results.summary.losingGroundTo = results.competitorOverview
.filter(function(c) { return c['Position Above Rate'] > 0.5; })
.slice(0, 5)
.map(function(c) { return { name: c.Competitor, rate: c['Position Above Rate'] }; });
// Campaign level data
for (var key in currentData.byCampaign) {
results.campaignLevel.push(currentData.byCampaign[key]);
}
// Sort campaign level by impression share
results.campaignLevel.sort(function(a, b) { return b.impressionShare - a.impressionShare; });
log('INFO', 'Processed ' + results.summary.totalCompetitors + ' competitors');
}
function getThreatLevel(impressionShare, isChange, positionAboveRate) {
var score = 0;
// High impression share = threat
if (impressionShare > 0.5) score += 3;
else if (impressionShare > 0.3) score += 2;
else if (impressionShare > 0.1) score += 1;
// Gaining impression share = threat
if (isChange > 0.1) score += 3;
else if (isChange > 0.05) score += 2;
else if (isChange > 0.02) score += 1;
// Often above you = threat
if (positionAboveRate > 0.5) score += 2;
else if (positionAboveRate > 0.3) score += 1;
if (score >= 6) return 'HIGH';
if (score >= 3) return 'MEDIUM';
return 'LOW';
}
function average(arr) {
if (!arr || arr.length === 0) return 0;
var sum = arr.reduce(function(a, b) { return a + b; }, 0);
return sum / arr.length;
}
/******************************************************************************
* OUTPUT FUNCTIONS
******************************************************************************/
function initializeSpreadsheet() {
var ss;
if (!CONFIG.SPREADSHEET_URL || CONFIG.SPREADSHEET_URL === 'YOUR_SPREADSHEET_URL_HERE' || CONFIG.SPREADSHEET_URL === 'CREATE_NEW') {
ss = SpreadsheetApp.create('PPC.io Auction Insights - ' +
AdsApp.currentAccount().getName() + ' - ' +
formatDate(new Date()));
log('INFO', 'Created spreadsheet: ' + ss.getUrl());
} else {
ss = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
}
return ss;
}
function writeAllSheets(ss, results) {
// Summary sheet first (position 0)
writeSummarySheet(ss, results);
// Numbered sheets for clear navigation
if (results.competitorOverview.length > 0) {
writeSheet(ss, '2. Competitor Overview', results.competitorOverview,
['Competitor', 'Impression Share', 'Overlap Rate', 'Position Above Rate',
'Top of Page Rate', 'Abs Top of Page Rate', 'Outranking Share',
'IS Change', 'Campaigns Competing', 'Threat Level']);
}
if (results.campaignLevel.length > 0) {
var campaignData = results.campaignLevel.map(function(c) {
return {
'Campaign': c.campaign,
'Competitor': c.domain,
'Impression Share': c.impressionShare,
'Overlap Rate': c.overlapRate,
'Position Above Rate': c.positionAboveRate,
'Top of Page Rate': c.topOfPageRate,
'Abs Top of Page': c.absTopOfPageRate,
'Outranking Share': c.outRankingShare
};
});
writeSheet(ss, '3. By Campaign', campaignData,
['Campaign', 'Competitor', 'Impression Share', 'Overlap Rate',
'Position Above Rate', 'Top of Page Rate', 'Abs Top of Page', 'Outranking Share']);
}
if (results.threats.length > 0) {
var threatData = results.threats.map(function(t) {
return {
'Competitor': t.domain,
'Current IS': t.currentIS,
'IS Change': t.change,
'Position Above Rate': t.positionAboveRate,
'Action': 'Monitor closely - gaining ground'
};
});
writeSheet(ss, '4. Threats', threatData,
['Competitor', 'Current IS', 'IS Change', 'Position Above Rate', 'Action']);
}
}
function writeSummarySheet(ss, results) {
var sheet = ss.getSheetByName('1. Summary');
if (!sheet) {
sheet = ss.insertSheet('1. Summary', 0);
} else {
sheet.clear();
}
var data = [
['COMPETITOR AUCTION INSIGHTS', ''],
['Generated by PPC.io Script Engine', ''],
['https://ppc.io', ''],
['', ''],
['Account: ' + AdsApp.currentAccount().getName(), ''],
['Date Range: ' + CONFIG.DATE_RANGE, ''],
['Export Date: ' + new Date().toISOString(), ''],
['', ''],
['═══════════════════════════════════════════════════════════════', ''],
['YOUR POSITION', ''],
['═══════════════════════════════════════════════════════════════', ''],
['Your Impression Share', formatPercent(results.yourPerformance.impressionShare)],
['Your Top of Page Rate', formatPercent(results.yourPerformance.topOfPageRate)],
['Your Abs Top of Page Rate', formatPercent(results.yourPerformance.absTopOfPageRate)],
['Your Outranking Share', formatPercent(results.yourPerformance.outRankingShare)],
['', ''],
['═══════════════════════════════════════════════════════════════', ''],
['COMPETITIVE LANDSCAPE', ''],
['═══════════════════════════════════════════════════════════════', ''],
['Total Competitors', results.summary.totalCompetitors],
['Threats Identified', results.threats.length],
['', ''],
['Top 5 Competitors by Impression Share:', '']
];
for (var i = 0; i < Math.min(5, results.summary.topCompetitors.length); i++) {
var comp = results.competitorOverview[i];
data.push([
(i + 1) + '. ' + comp.Competitor,
formatPercent(comp['Impression Share']) + ' IS'
]);
}
data.push(['', '']);
data.push(['═══════════════════════════════════════════════════════════════', '']);
data.push(['BIGGEST THREATS (Gaining Impression Share)', '']);
data.push(['═══════════════════════════════════════════════════════════════', '']);
if (results.summary.biggestThreats.length === 0) {
data.push(['No significant threats identified', '']);
} else {
for (var j = 0; j < results.summary.biggestThreats.length; j++) {
var threat = results.summary.biggestThreats[j];
data.push([
threat.name,
'+' + formatPercent(threat.change) + ' IS gain'
]);
}
}
data.push(['', '']);
data.push(['═══════════════════════════════════════════════════════════════', '']);
data.push(['LOSING GROUND TO (High Position Above Rate)', '']);
data.push(['═══════════════════════════════════════════════════════════════', '']);
if (results.summary.losingGroundTo.length === 0) {
data.push(['Not significantly losing position to any competitor', '']);
} else {
for (var k = 0; k < results.summary.losingGroundTo.length; k++) {
var loser = results.summary.losingGroundTo[k];
data.push([
loser.name,
'Above you ' + formatPercent(loser.rate) + ' of the time'
]);
}
}
data.push(['', '']);
data.push(['═══════════════════════════════════════════════════════════════', '']);
data.push(['AI ANALYSIS PROMPTS', '']);
data.push(['═══════════════════════════════════════════════════════════════', '']);
data.push(['Paste the competitor data into Claude with these prompts:', '']);
data.push(['', '']);
data.push(['1. "Analyze my competitive position. Who are my biggest threats?"', '']);
data.push(['2. "Which competitors should I focus on outranking first?"', '']);
data.push(['3. "Based on these trends, predict who will gain/lose share next quarter."', '']);
data.push(['4. "Recommend bid strategy adjustments based on competitive landscape."', '']);
data.push(['5. "Identify campaigns where I\'m most/least competitive."', '']);
sheet.getRange(1, 1, data.length, 2).setValues(data);
sheet.getRange(1, 1).setFontWeight('bold').setFontSize(14);
sheet.setColumnWidth(1, 400);
sheet.setColumnWidth(2, 250);
}
function writeSheet(ss, sheetName, data, columns) {
if (!data || data.length === 0) return;
var sheet = ss.getSheetByName(sheetName);
if (!sheet) {
sheet = ss.insertSheet(sheetName);
} else {
sheet.clear();
}
sheet.getRange(1, 1, 1, columns.length).setValues([columns]).setFontWeight('bold');
sheet.setFrozenRows(1);
var rows = data.map(function(row) {
return columns.map(function(col) {
var val = row[col];
return val !== null && val !== undefined ? val : '';
});
});
for (var i = 0; i < rows.length; i += CONFIG.BATCH_SIZE) {
var batch = rows.slice(i, Math.min(i + CONFIG.BATCH_SIZE, rows.length));
sheet.getRange(2 + i, 1, batch.length, columns.length).setValues(batch);
}
// Format percentage columns
var pctCols = ['Impression Share', 'Overlap Rate', 'Position Above Rate',
'Top of Page Rate', 'Abs Top of Page Rate', 'Outranking Share',
'IS Change', 'Current IS', 'Abs Top of Page'];
for (var p = 0; p < pctCols.length; p++) {
formatColumn(sheet, columns, pctCols[p], '0.00%', rows.length);
}
// Color code threat level
applyThreatFormatting(sheet, columns, rows.length);
for (var col = 1; col <= Math.min(columns.length, 10); col++) {
sheet.autoResizeColumn(col);
}
}
function applyThreatFormatting(sheet, headers, numRows) {
var threatCol = headers.indexOf('Threat Level') + 1;
if (threatCol === 0) return;
var range = sheet.getRange(2, threatCol, numRows, 1);
var values = range.getValues();
var colors = values.map(function(row) {
if (row[0] === 'HIGH') return ['#f8d7da'];
if (row[0] === 'MEDIUM') return ['#fff3cd'];
return ['#d4edda'];
});
range.setBackgrounds(colors);
}
function formatColumn(sheet, headers, columnName, format, numRows) {
var colIndex = headers.indexOf(columnName);
if (colIndex !== -1) {
sheet.getRange(2, colIndex + 1, numRows, 1).setNumberFormat(format);
}
}
function formatPercent(val) {
if (!val && val !== 0) return '-';
return (val * 100).toFixed(2) + '%';
}
/******************************************************************************
* NOTIFICATION FUNCTIONS
******************************************************************************/
function sendNotifications(results, spreadsheetUrl, startTime) {
var duration = ((new Date() - startTime) / 1000).toFixed(1);
var message = [
'Competitor Auction Insights Complete',
'',
'Account: ' + AdsApp.currentAccount().getName(),
'Duration: ' + duration + 's',
'',
'Your Position:',
'- Impression Share: ' + formatPercent(results.yourPerformance.impressionShare),
'- Top of Page Rate: ' + formatPercent(results.yourPerformance.topOfPageRate),
'',
'Competitive Landscape:',
'- Total Competitors: ' + results.summary.totalCompetitors,
'- Threats (gaining IS): ' + results.threats.length,
'',
'Report: ' + spreadsheetUrl,
'',
'--',
'Generated by PPC.io Script Engine'
].join('\n');
if (CONFIG.EMAIL_RECIPIENTS && CONFIG.EMAIL_RECIPIENTS.length > 0) {
try {
MailApp.sendEmail({
to: CONFIG.EMAIL_RECIPIENTS.join(','),
subject: '[PPC.io] Auction Insights - ' + results.summary.totalCompetitors + ' competitors',
body: message
});
log('INFO', 'Email sent');
} catch (e) {
log('ERROR', 'Failed to send email: ' + e.message);
}
}
if (CONFIG.SLACK_WEBHOOK_URL) {
var emoji = results.threats.length > 0 ? ':warning:' : ':chart_with_upwards_trend:';
try {
UrlFetchApp.fetch(CONFIG.SLACK_WEBHOOK_URL, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({
text: emoji + ' *PPC.io Auction Insights*\n```' + message + '```'
})
});
log('INFO', 'Slack sent');
} catch (e) {
log('ERROR', 'Failed to send Slack: ' + e.message);
}
}
}
/******************************************************************************
* UTILITY FUNCTIONS
******************************************************************************/
function checkTimeLimit(startTime) {
var elapsed = (new Date() - startTime) / 1000 / 60;
if (elapsed > CONFIG.TIME_LIMIT_MINUTES) {
throw new Error('TIME_LIMIT: Stopped after ' + elapsed.toFixed(1) + ' minutes.');
}
}
function log(level, message) {
var levels = { 'DEBUG': 0, 'INFO': 1, 'WARN': 2, 'ERROR': 3 };
if (levels[level] >= levels[CONFIG.LOG_LEVEL]) {
Logger.log('[' + level + '] ' + message);
}
}
function logSummary(results, startTime) {
var duration = ((new Date() - startTime) / 1000).toFixed(1);
log('INFO', '════════════════════════════════════════');
log('INFO', 'AUCTION INSIGHTS COMPLETE');
log('INFO', 'Duration: ' + duration + ' seconds');
log('INFO', 'Competitors: ' + results.summary.totalCompetitors);
log('INFO', 'Threats: ' + results.threats.length);
log('INFO', '════════════════════════════════════════');
}
function handleFatalError(error, startTime) {
log('ERROR', '════════════════════════════════════════');
log('ERROR', 'FATAL ERROR: ' + error.message);
log('ERROR', 'Stack: ' + error.stack);
log('ERROR', '════════════════════════════════════════');
if (CONFIG.EMAIL_RECIPIENTS && CONFIG.EMAIL_RECIPIENTS.length > 0) {
try {
MailApp.sendEmail({
to: CONFIG.EMAIL_RECIPIENTS.join(','),
subject: '[PPC.io ERROR] Auction Insights Failed',
body: 'Error: ' + error.message + '\n\nStack:\n' + error.stack
});
} catch (e) {
log('ERROR', 'Could not send error email: ' + e.message);
}
}
}
function formatDate(date) {
return Utilities.formatDate(date, AdsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd');
}
Open the 1. Summary tab first. Your impression share, top-of-page rate, and outranking share are at the top. Below that, the BIGGEST THREATS block lists competitors who gained more than 5 percent IS vs the previous period, and LOSING GROUND TO lists competitors above you on more than half the impressions you both showed on. The 2. Competitor Overview tab is the full table sorted by IS, with a HIGH or MEDIUM or LOW threat tag (red, yellow, green) per competitor. Anyone red and trending up is who you talk about on the next client call.