Script Google AdsQuotidienLecture seule

Alerte de budget

Un e-mail quand la dépense du mois prend de l’avance sur le budget, quand la projection de fin de mois dérape, ou quand la veille a fait un pic.

Lecture seuleE-mail

Le problème que ça règle

Un budget mensuel se dépasse rarement d'un coup : c'est un rythme de dépense un peu trop rapide pendant trois semaines, ou un pic isolé un jour de forte demande, que personne ne remarque avant la facture. Google Ads plafonne la dépense par jour, pas par mois.

Ce que fait le script

  1. Compare la dépense du mois en cours au budget attendu à la date du jour.
  2. Projette la dépense de fin de mois au rythme actuel.
  3. Compare la dépense de la veille à la moyenne des sept derniers jours.
  4. Envoie un e-mail d'alerte quand un seuil est dépassé, avec les campagnes les plus dépensières, classiques et Performance Max. Optionnel : un point quotidien même sans alerte.

Installation

  1. Dans Google Ads : Outils et paramètres, Actions groupées, Scripts, puis le bouton « + ».
  2. Collez le code complet, remplacez les valeurs du bloc CONFIG en haut du script.
  3. Cliquez sur « Aperçu » : le script tourne sans rien envoyer et affiche son journal. Autorisez les accès demandés (Google Ads, e-mail, Google Sheets selon le script).
  4. Cliquez sur « Enregistrer », puis planifiez l’exécution avec l’icône horloge, à la fréquence indiquée.

Paramètres à adapter

ParamètreRôle
MONTHLY_BUDGETBudget média mensuel du compte, dans sa devise
PACE_ALERT_PCTSeuil d’avance sur le rythme normal, en pourcentage (110 = 10 % d’avance)
DAILY_SPIKE_PCTSeuil de pic de la veille par rapport à la moyenne des 7 jours
PROJECTION_ALERT_PCTSeuil sur la projection de fin de mois
EMAIL_RECIPIENTSAdresses destinataires, séparées par des virgules
ALWAYS_SENDtrue pour un e-mail chaque jour, false pour les alertes seulement

Le code

budget-pacing-alert.js
Télécharger
/**
 * Alerte de budget et de rythme de dépense (budget pacing)
 * Par Pierre Bordier — https://pierrebordier.com
 *
 * Chaque jour, ce script compare la dépense du mois en cours au budget mensuel
 * que vous avez fixé, projette la dépense de fin de mois au rythme actuel, et
 * repère les pics de dépense de la veille. Il envoie un e-mail uniquement quand
 * un seuil est dépassé (ou toujours, si vous préférez un point quotidien).
 * Lecture seule : il ne modifie jamais le compte.
 *
 * INSTALLATION
 * 1. Google Ads > Outils et paramètres > Actions groupées > Scripts > "+".
 * 2. Collez tout ce fichier, ajustez le bloc CONFIG ci-dessous.
 * 3. "Aperçu" pour un premier test (le journal affiche le résumé), puis
 *    "Enregistrer".
 * 4. Planifiez l'exécution (icône horloge) : quotidienne, tôt le matin
 *    (par exemple 07:00), pour que les données de la veille soient complètes.
 *
 * NOTES
 * - Les coûts sont dans la devise du compte. Sur un compte administrateur (MCC),
 *   installez le script dans chaque compte client, ou adaptez-le avec
 *   AdsManagerApp.accounts().
 * - Les campagnes Performance Max sont incluses (elles ont leur propre
 *   itérateur dans Google Ads Scripts).
 */

const CONFIG = {
  // Budget média mensuel du compte, dans la devise du compte.
  MONTHLY_BUDGET: 3000,

  // Alerte quand la dépense dépasse ce pourcentage du budget attendu à ce jour.
  // 110 = 10 % d'avance sur le rythme normal.
  PACE_ALERT_PCT: 110,

  // Alerte quand la dépense de la veille dépasse ce pourcentage de la moyenne
  // des 7 jours précédents. 150 = pic de 50 %.
  DAILY_SPIKE_PCT: 150,

  // Alerte quand la projection de fin de mois dépasse ce pourcentage du budget.
  PROJECTION_ALERT_PCT: 105,

  // Destinataires, séparés par des virgules.
  EMAIL_RECIPIENTS: 'vous@entreprise.com',

  // true : e-mail chaque jour même sans alerte (point quotidien).
  // false : e-mail uniquement en cas de dépassement.
  ALWAYS_SEND: false,

  // Nombre de campagnes détaillées dans l'e-mail (les plus dépensières).
  TOP_CAMPAIGNS: 10,
};

function main() {
  const account = AdsApp.currentAccount();
  const tz = account.getTimeZone();
  const currency = account.getCurrencyCode();
  const now = new Date();

  // Bornes du mois en cours, au format attendu par getStatsFor (yyyyMMdd).
  const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
  const from = Utilities.formatDate(monthStart, tz, 'yyyyMMdd');
  const to = Utilities.formatDate(now, tz, 'yyyyMMdd');
  const dayOfMonth = parseInt(Utilities.formatDate(now, tz, 'd'), 10);
  const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();

  const costMonth = account.getStatsFor(from, to).getCost();
  const costYesterday = account.getStatsFor('YESTERDAY').getCost();
  const costLast7 = account.getStatsFor('LAST_7_DAYS').getCost();
  const avg7 = costLast7 / 7;

  const expectedToDate = CONFIG.MONTHLY_BUDGET * dayOfMonth / daysInMonth;
  const pacePct = expectedToDate > 0 ? (costMonth / expectedToDate) * 100 : 0;
  const projection = dayOfMonth > 0 ? (costMonth / dayOfMonth) * daysInMonth : 0;
  const projectionPct = CONFIG.MONTHLY_BUDGET > 0 ? (projection / CONFIG.MONTHLY_BUDGET) * 100 : 0;
  const spikePct = avg7 > 0 ? (costYesterday / avg7) * 100 : 0;

  const alerts = [];
  if (pacePct > CONFIG.PACE_ALERT_PCT) {
    alerts.push('Rythme de dépense : ' + pacePct.toFixed(0) + ' % du budget attendu à ce jour (seuil ' + CONFIG.PACE_ALERT_PCT + ' %).');
  }
  if (projectionPct > CONFIG.PROJECTION_ALERT_PCT) {
    alerts.push('Projection fin de mois : ' + fmt(projection) + ' ' + currency + ', soit ' + projectionPct.toFixed(0) + ' % du budget (seuil ' + CONFIG.PROJECTION_ALERT_PCT + ' %).');
  }
  if (avg7 > 0 && spikePct > CONFIG.DAILY_SPIKE_PCT) {
    alerts.push('Pic hier : ' + fmt(costYesterday) + ' ' + currency + ', soit ' + spikePct.toFixed(0) + ' % de la moyenne des 7 derniers jours (seuil ' + CONFIG.DAILY_SPIKE_PCT + ' %).');
  }

  const campaigns = topCampaigns(from, to, CONFIG.TOP_CAMPAIGNS);

  const summary = [
    'Compte ' + account.getName() + ' (' + account.getCustomerId() + ')',
    'Dépense du mois : ' + fmt(costMonth) + ' ' + currency + ' sur ' + fmt(CONFIG.MONTHLY_BUDGET) + ' (jour ' + dayOfMonth + '/' + daysInMonth + ')',
    'Attendu à ce jour : ' + fmt(expectedToDate) + ' ' + currency + ' -> rythme ' + pacePct.toFixed(0) + ' %',
    'Projection fin de mois : ' + fmt(projection) + ' ' + currency + ' (' + projectionPct.toFixed(0) + ' %)',
    'Hier : ' + fmt(costYesterday) + ' ' + currency + ' (moyenne 7 jours : ' + fmt(avg7) + ')',
  ];
  summary.forEach(function (line) { Logger.log(line); });
  alerts.forEach(function (line) { Logger.log('ALERTE : ' + line); });

  if (alerts.length === 0 && !CONFIG.ALWAYS_SEND) {
    Logger.log('Aucune alerte, pas d\'e-mail.');
    return;
  }

  const subject = (alerts.length ? '[Google Ads] Alerte budget : ' : '[Google Ads] Point budget : ')
    + account.getName() + ' - ' + pacePct.toFixed(0) + ' % du rythme';
  MailApp.sendEmail({
    to: CONFIG.EMAIL_RECIPIENTS,
    subject: subject,
    htmlBody: buildHtml(summary, alerts, campaigns, currency),
  });
  Logger.log('E-mail envoyé à ' + CONFIG.EMAIL_RECIPIENTS);
}

// Campagnes classiques + Performance Max, triées par dépense du mois.
function topCampaigns(from, to, limit) {
  const rows = [];
  const iterators = [
    AdsApp.campaigns().withCondition('Status = ENABLED').get(),
    AdsApp.performanceMaxCampaigns().withCondition('Status = ENABLED').get(),
  ];
  iterators.forEach(function (it) {
    while (it.hasNext()) {
      const c = it.next();
      const cost = c.getStatsFor(from, to).getCost();
      let daily = null;
      try { daily = c.getBudget().getAmount(); } catch (e) { daily = null; }
      rows.push({ name: c.getName(), cost: cost, daily: daily });
    }
  });
  rows.sort(function (a, b) { return b.cost - a.cost; });
  return rows.slice(0, limit);
}

function buildHtml(summary, alerts, campaigns, currency) {
  let html = '<div style="font-family:Arial,sans-serif;font-size:14px">';
  if (alerts.length) {
    html += '<h2 style="color:#b32d2e;font-size:16px">Alertes</h2><ul>'
      + alerts.map(function (a) { return '<li>' + escapeHtml(a) + '</li>'; }).join('') + '</ul>';
  }
  html += '<h2 style="font-size:16px">Résumé</h2><ul>'
    + summary.map(function (s) { return '<li>' + escapeHtml(s) + '</li>'; }).join('') + '</ul>';
  if (campaigns.length) {
    html += '<h2 style="font-size:16px">Campagnes les plus dépensières ce mois</h2>'
      + '<table border="1" cellpadding="5" style="border-collapse:collapse"><tr><th align="left">Campagne</th><th align="right">Dépense</th><th align="right">Budget / jour</th></tr>';
    campaigns.forEach(function (c) {
      html += '<tr><td>' + escapeHtml(c.name) + '</td><td align="right">' + fmt(c.cost) + ' ' + currency + '</td>'
        + '<td align="right">' + (c.daily === null ? '-' : fmt(c.daily) + ' ' + currency) + '</td></tr>';
    });
    html += '</table>';
  }
  html += '<p style="color:#888;font-size:12px">Script "Alerte de budget" par Pierre Bordier, pierrebordier.com. Lecture seule.</p></div>';
  return html;
}

function fmt(n) {
  return Number(n || 0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, "'");
}

function escapeHtml(s) {
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

Limites connues

  • Le script travaille au niveau d'un compte : sur un compte administrateur, installez-le dans chaque compte client.
  • Planifiez-le tôt le matin pour que les données de la veille soient complètes.