// Documentation: https://adssystem.store/scripts/impression-share-loss-report/ // Writes: Nothing. Read-only. // Impression share loss report // Separates the two reasons a campaign is not showing: budget and ad rank. // They need opposite responses, and the account summary does not distinguish them. // Read-only. var DAYS = 7; var MIN_COST = 20; // ignore campaigns too small to act on var BUDGET_AT = 0.10; // flag when 10% or more of impression share is lost to budget var RANK_AT = 0.40; // flag when 40% or more is lost to rank var EMAIL = ''; function main() { var rows = AdsApp.search( 'SELECT campaign.name, campaign.advertising_channel_type, ' + ' metrics.cost_micros, metrics.conversions, ' + ' metrics.search_impression_share, ' + ' metrics.search_budget_lost_impression_share, ' + ' metrics.search_rank_lost_impression_share ' + 'FROM campaign ' + 'WHERE segments.date DURING LAST_' + DAYS + '_DAYS ' + ' AND campaign.status = "ENABLED"' ); var budgetBound = []; var rankBound = []; while (rows.hasNext()) { var r = rows.next(); var cost = Number(r.metrics.costMicros) / 1000000; if (cost < MIN_COST) { continue; } var is = num(r.metrics.searchImpressionShare); var lostBud = num(r.metrics.searchBudgetLostImpressionShare); var lostRank = num(r.metrics.searchRankLostImpressionShare); // Impression share metrics are not reported for every channel type. if (is === null && lostBud === null && lostRank === null) { continue; } var row = { name: String(r.campaign.name), type: String(r.campaign.advertisingChannelType), cost: cost, conv: Number(r.metrics.conversions), is: is, bud: lostBud, rank: lostRank }; if (lostBud !== null && lostBud >= BUDGET_AT) { budgetBound.push(row); } if (lostRank !== null && lostRank >= RANK_AT) { rankBound.push(row); } } if (!budgetBound.length && !rankBound.length) { Logger.log('No campaign over the thresholds in the last ' + DAYS + ' days.'); return; } budgetBound.sort(function (a, b) { return b.cost - a.cost; }); rankBound.sort(function (a, b) { return b.cost - a.cost; }); var lines = [AdsApp.currentAccount().getName(), 'Last ' + DAYS + ' days', '']; if (budgetBound.length) { lines.push('LOSING SHARE TO BUDGET (raise the budget, or accept the ceiling)'); lines.push('campaign\ttype\tcost\tconv\tIS\tlost to budget'); for (var i = 0; i < budgetBound.length; i++) { lines.push(fmt(budgetBound[i], 'bud')); } lines.push(''); } if (rankBound.length) { lines.push('LOSING SHARE TO RANK (relevance, bids or landing page, not budget)'); lines.push('campaign\ttype\tcost\tconv\tIS\tlost to rank'); for (var j = 0; j < rankBound.length; j++) { lines.push(fmt(rankBound[j], 'rank')); } } var body = lines.join('\n') + '\n\nShares above 90% are reported as 0.9 and below 10% as 0.1. ' + 'Impression share is not reported for every campaign type.'; Logger.log(body); if (EMAIL) { MailApp.sendEmail(EMAIL, 'Impression share loss - ' + AdsApp.currentAccount().getName(), body); } } function fmt(r, which) { return [ r.name, r.type, r.cost.toFixed(2), r.conv.toFixed(1), pct(r.is), pct(r[which]) ].join('\t'); } function num(v) { if (v === null || v === undefined || v === '') { return null; } var n = Number(v); return isNaN(n) ? null : n; } function pct(v) { return v === null ? '-' : (v * 100).toFixed(0) + '%'; }