Forecast Reach

Java

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.googleads.examples.planning;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.v16.common.DeviceInfo;
import com.google.ads.googleads.v16.common.GenderInfo;
import com.google.ads.googleads.v16.enums.DeviceEnum.Device;
import com.google.ads.googleads.v16.enums.GenderTypeEnum.GenderType;
import com.google.ads.googleads.v16.enums.ReachPlanAgeRangeEnum.ReachPlanAgeRange;
import com.google.ads.googleads.v16.errors.GoogleAdsError;
import com.google.ads.googleads.v16.errors.GoogleAdsException;
import com.google.ads.googleads.v16.services.CampaignDuration;
import com.google.ads.googleads.v16.services.GenerateReachForecastRequest;
import com.google.ads.googleads.v16.services.GenerateReachForecastResponse;
import com.google.ads.googleads.v16.services.ListPlannableLocationsRequest;
import com.google.ads.googleads.v16.services.ListPlannableLocationsResponse;
import com.google.ads.googleads.v16.services.ListPlannableProductsRequest;
import com.google.ads.googleads.v16.services.ListPlannableProductsResponse;
import com.google.ads.googleads.v16.services.PlannableLocation;
import com.google.ads.googleads.v16.services.PlannedProduct;
import com.google.ads.googleads.v16.services.PlannedProductReachForecast;
import com.google.ads.googleads.v16.services.ProductMetadata;
import com.google.ads.googleads.v16.services.ReachForecast;
import com.google.ads.googleads.v16.services.ReachPlanServiceClient;
import com.google.ads.googleads.v16.services.Targeting;
import com.google.common.base.Joiner;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Demonstrates how to interact with the ReachPlanService to find plannable locations and product
 * codes, build a media plan, and generate a video ads reach forecast.
 */
public class ForecastReach {

  private static class ForecastReachParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
    private Long customerId;
  }

  public static void main(String[] args) {
    ForecastReachParams params = new ForecastReachParams();

    if (!params.parseArguments(args)) {

      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
    }

    GoogleAdsClient googleAdsClient;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      return;
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      return;
    }

    try {
      new ForecastReach().runExample(googleAdsClient, params.customerId);
    } catch (GoogleAdsException gae) {
      // GoogleAdsException is the base class for most exceptions thrown by an API request.
      // Instances of this exception have a message and a GoogleAdsFailure that contains a
      // collection of GoogleAdsErrors that indicate the underlying causes of the
      // GoogleAdsException.
      System.err.printf(
          "Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
          gae.getRequestId());
      int i = 0;
      for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
        System.err.printf("  Error %d: %s%n", i++, googleAdsError);
      }
    }
  }

  /**
   * Runs the example.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the customer ID for the reach forecast.
   */
  private void runExample(GoogleAdsClient googleAdsClient, long customerId) {
    String locationId = "2840"; // US
    String currencyCode = "USD";
    long budgetMicros = 5_000_000L; // $5 USD

    try (ReachPlanServiceClient reachPlanServiceClient =
        googleAdsClient.getLatestVersion().createReachPlanServiceClient()) {
      showPlannableLocations(reachPlanServiceClient);
      showPlannableProducts(reachPlanServiceClient, locationId);
      forecastManualMix(reachPlanServiceClient, customerId, locationId, currencyCode, budgetMicros);
    }
  }

  /**
   * Maps friendly names of plannable locations to location IDs usable with ReachPlanServiceClient.
   *
   * @param reachPlanServiceClient instance of Reach Plan Service client.
   */
  private void showPlannableLocations(ReachPlanServiceClient reachPlanServiceClient) {
    ListPlannableLocationsRequest request = ListPlannableLocationsRequest.newBuilder().build();
    ListPlannableLocationsResponse response =
        reachPlanServiceClient.listPlannableLocations(request);

    System.out.println("Plannable Locations:");
    for (PlannableLocation location : response.getPlannableLocationsList()) {
      System.out.printf(
          "Name: %s, ID: %s, ParentCountryId: %s%n",
          location.getName(), location.getId(), location.getParentCountryId());
    }
  }

  /**
   * Lists plannable products for a given location.
   *
   * @param reachPlanServiceClient instance of Reach Plan Service client.
   * @param locationId location ID to plan for. To find a valid location ID, either see
   *     https://developers.google.com/google-ads/api/reference/data/geotargets or call
   *     ReachPlanServiceClient.listPlannableLocations().
   */
  private void showPlannableProducts(
      ReachPlanServiceClient reachPlanServiceClient, String locationId) {
    ListPlannableProductsRequest request =
        ListPlannableProductsRequest.newBuilder().setPlannableLocationId(locationId).build();

    ListPlannableProductsResponse response = reachPlanServiceClient.listPlannableProducts(request);

    System.out.printf("Plannable Products for location %s:%n", locationId);
    for (ProductMetadata product : response.getProductMetadataList()) {
      System.out.printf("%s:%n", product.getPlannableProductCode());
      System.out.println("Age Ranges:");
      for (ReachPlanAgeRange ageRange : product.getPlannableTargeting().getAgeRangesList()) {
        System.out.printf("\t- %s%n", ageRange);
      }
      System.out.println("Genders:");
      for (GenderInfo gender : product.getPlannableTargeting().getGendersList()) {
        System.out.printf("\t- %s%n", gender.getType());
      }
      System.out.println("Devices:");
      for (DeviceInfo device : product.getPlannableTargeting().getDevicesList()) {
        System.out.printf("\t- %s%n", device.getType());
      }
    }
  }

  /**
   * Creates a base request to generate a reach forecast.
   *
   * @param customerId the customer ID for the reach forecast.
   * @param productMix the product mix for the reach forecast.
   * @param locationId location ID to plan for. To find a valid location ID, either see
   *     https://developers.google.com/google-ads/api/reference/data/geotargets or call
   *     ReachPlanServiceClient.ListPlannableLocations.
   * @param currencyCode three-character ISO 4217 currency code.
   * @return populated GenerateReachForecastRequest object.
   */
  private GenerateReachForecastRequest buildReachRequest(
      Long customerId, List<PlannedProduct> productMix, String locationId, String currencyCode) {
    CampaignDuration duration = CampaignDuration.newBuilder().setDurationInDays(28).build();

    List<GenderInfo> genders =
        Arrays.asList(
            GenderInfo.newBuilder().setType(GenderType.FEMALE).build(),
            GenderInfo.newBuilder().setType(GenderType.MALE).build());

    List<DeviceInfo> devices =
        Arrays.asList(
            DeviceInfo.newBuilder().setType(Device.DESKTOP).build(),
            DeviceInfo.newBuilder().setType(Device.MOBILE).build(),
            DeviceInfo.newBuilder().setType(Device.TABLET).build());

    Targeting targeting =
        Targeting.newBuilder()
            .setPlannableLocationId(locationId)
            .setAgeRange(ReachPlanAgeRange.AGE_RANGE_18_65_UP)
            .addAllGenders(genders)
            .addAllDevices(devices)
            .build();

    // See the docs for defaults and valid ranges:
    // https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
    return GenerateReachForecastRequest.newBuilder()
        .setCustomerId(Long.toString(customerId))
        .setCurrencyCode(currencyCode)
        .setCampaignDuration(duration)
        .setTargeting(targeting)
        .setMinEffectiveFrequency(1)
        .addAllPlannedProducts(productMix)
        .build();
  }

  /**
   * Pulls and prints the reach curve for the given request.
   *
   * @param reachPlanServiceClient instance of Reach Plan Service client.
   * @param request an already-populated reach curve request.
   */
  private void getReachCurve(
      ReachPlanServiceClient reachPlanServiceClient, GenerateReachForecastRequest request) {
    GenerateReachForecastResponse response = reachPlanServiceClient.generateReachForecast(request);
    System.out.println("Reach curve output:");
    System.out.println(
        "Currency, Cost Micros, On-Target Reach, On-Target Imprs, Total Reach, Total Imprs,"
            + " Products");
    for (ReachForecast point : response.getReachCurve().getReachForecastsList()) {
      System.out.printf(
          "%s, \"",
          Joiner.on(", ")
              .join(
                  request.getCurrencyCode(),
                  String.valueOf(point.getCostMicros()),
                  String.valueOf(point.getForecast().getOnTargetReach()),
                  String.valueOf(point.getForecast().getOnTargetImpressions()),
                  String.valueOf(point.getForecast().getTotalReach()),
                  String.valueOf(point.getForecast().getTotalImpressions())));
      for (PlannedProductReachForecast product : point.getPlannedProductReachForecastsList()) {
        System.out.printf("[Product: %s, ", product.getPlannableProductCode());
        System.out.printf("Budget Micros: %s]", product.getCostMicros());
      }
      System.out.printf("\"%n");
    }
  }

  /**
   * Pulls a forecast for a budget split 15% and 85% between two products.
   *
   * @param reachPlanServiceClient instance of Reach Plan Service client.
   * @param customerId the customer ID for the reach forecast.
   * @param locationId location ID to plan for. To find a valid location ID, either see
   *     https://developers.google.com/google-ads/api/reference/data/geotargets or call
   *     ReachPlanServiceClient.listPlannableLocations().
   * @param currencyCode three-character ISO 4217 currency code.
   * @param budgetMicros budget in currency to plan for.
   */
  private void forecastManualMix(
      ReachPlanServiceClient reachPlanServiceClient,
      long customerId,
      String locationId,
      String currencyCode,
      long budgetMicros) {
    List<PlannedProduct> productMix = new ArrayList<>();

    // Set up a ratio to split the budget between two products.
    double trueviewAllocation = 0.15;
    double bumperAllocation = 1 - trueviewAllocation;

    // See listPlannableProducts on ReachPlanService to retrieve a list
    // of valid PlannableProductCode's for a given location:
    // https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
    productMix.add(
        PlannedProduct.newBuilder()
            .setPlannableProductCode("TRUEVIEW_IN_STREAM")
            .setBudgetMicros((long) (budgetMicros * bumperAllocation))
            .build());
    productMix.add(
        PlannedProduct.newBuilder()
            .setPlannableProductCode("BUMPER")
            .setBudgetMicros((long) (budgetMicros * bumperAllocation))
            .build());

    GenerateReachForecastRequest request =
        buildReachRequest(customerId, productMix, locationId, currencyCode);

    getReachCurve(reachPlanServiceClient, request);
  }

}

      

C#

// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using CommandLine;
using Google.Ads.Gax.Examples;
using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V16.Common;
using Google.Ads.GoogleAds.V16.Errors;
using Google.Ads.GoogleAds.V16.Services;
using System;
using System.Collections.Generic;
using static Google.Ads.GoogleAds.V16.Enums.DeviceEnum.Types;
using static Google.Ads.GoogleAds.V16.Enums.GenderTypeEnum.Types;
using static Google.Ads.GoogleAds.V16.Enums.ReachPlanAgeRangeEnum.Types;

namespace Google.Ads.GoogleAds.Examples.V16
{
    /// <summary>
    /// This example demonstrates how to interact with the ReachPlanService to find plannable
    /// locations and product codes, build a media plan, and generate a video ads reach forecast.
    /// </summary>
    public class ForecastReach : ExampleBase
    {
        /// <summary>
        /// Command line options for running the <see cref="ForecastReach"/> example.
        /// </summary>
        public class Options : OptionsBase
        {
            /// <summary>
            /// The Google Ads customer ID for which the call is made.
            /// </summary>
            [Option("customerId", Required = true, HelpText =
                "The Google Ads customer ID for which the call is made.")]
            public long CustomerId { get; set; }
        }

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            Options options = ExampleUtilities.ParseCommandLine<Options>(args);

            ForecastReach codeExample = new ForecastReach();
            Console.WriteLine(codeExample.Description);
            codeExample.Run(new GoogleAdsClient(), options.CustomerId);
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description =>
            "This example demonstrates how to interact with the ReachPlanService to find " +
            "plannable locations and product codes, build a media plan, and generate a video ads " +
            "reach forecast.";

        /// <summary>
        /// Runs the code example, showing a typical series of calls to the
        /// <see cref="Services.V16.ReachPlanService"/>.
        /// </summary>
        /// <param name="client">The Google Ads API client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        public void Run(GoogleAdsClient client, long customerId)
        {
            string locationId = "2840"; // US
            string currencyCode = "USD";
            long budgetMicros = 5_000_000L;
            ReachPlanServiceClient reachPlanService =
                client.GetService(Services.V16.ReachPlanService);

            try
            {
                ShowPlannableLocations(reachPlanService);
                ShowPlannableProducts(reachPlanService, locationId);
                ForecastMix(reachPlanService, customerId.ToString(), locationId, currencyCode,
                    budgetMicros);
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
                throw;
            }
        }

        /// <summary>
        /// Maps friendly names of plannable locations to location IDs usable with
        /// <see cref="ReachPlanServiceClient"/>.
        /// </summary>
        /// <param name="reachPlanService">Instance of Reach Plan Service client.</param>
        public void ShowPlannableLocations(ReachPlanServiceClient reachPlanService)
        {
            ListPlannableLocationsRequest request = new ListPlannableLocationsRequest();
            ListPlannableLocationsResponse response = reachPlanService.ListPlannableLocations(
                request);

            Console.WriteLine("Plannable Locations:");
            Console.WriteLine("Name,\tId,\t,ParentCountryId");
            foreach (PlannableLocation location in response.PlannableLocations)
            {
                Console.WriteLine(
                    $"\"{location.Name}\",\t{location.Id},{location.ParentCountryId}");
            }
        }

        /// <summary>
        /// Lists plannable products for a given location.
        /// </summary>
        /// <param name="reachPlanService">Instance of Reach Plan Service client.</param>
        /// <param name="locationId">Location ID to plan for. To find a valid location ID, either
        /// see https://developers.google.com/google-ads/api/reference/data/geotargets or call
        /// <see cref="ReachPlanServiceClient.ListPlannableLocations"/>.</param>
        public void ShowPlannableProducts(
            ReachPlanServiceClient reachPlanService, string locationId)
        {
            ListPlannableProductsRequest request = new ListPlannableProductsRequest
            {
                PlannableLocationId = locationId
            };
            ListPlannableProductsResponse response = reachPlanService.ListPlannableProducts(
                request);

            Console.WriteLine($"Plannable Products for location {locationId}:");
            foreach (ProductMetadata product in response.ProductMetadata)
            {
                Console.WriteLine($"{product.PlannableProductCode}:");
                Console.WriteLine("Age Ranges:");
                foreach (ReachPlanAgeRange ageRange in product.PlannableTargeting.AgeRanges)
                {
                    Console.WriteLine($"\t- {ageRange}");
                }

                Console.WriteLine("Genders:");
                foreach (GenderInfo gender in product.PlannableTargeting.Genders)
                {
                    Console.WriteLine($"\t- {gender.Type}");
                }

                Console.WriteLine("Devices:");
                foreach (DeviceInfo device in product.PlannableTargeting.Devices)
                {
                    Console.WriteLine($"\t- {device.Type}");
                }
            }
        }

        /// <summary>
        /// Create a base request to generate a reach forecast.
        /// </summary>
        /// <param name="customerId">The customer ID for the reach forecast.</param>
        /// <param name="productMix">The product mix for the reach forecast.</param>
        /// <param name="locationId">Location ID to plan for. To find a valid location ID, either
        /// see https://developers.google.com/google-ads/api/reference/data/geotargets or call
        /// <see cref="ReachPlanServiceClient.ListPlannableLocations"/>.</param>
        /// <param name="currencyCode">Three-character ISO 4217 currency code.</param>
        public GenerateReachForecastRequest BuildReachRequest(
            string customerId, List<PlannedProduct> productMix, string locationId,
            string currencyCode)
        {
            // Valid durations are between 1 and 90 days.
            CampaignDuration duration = new CampaignDuration
            {
                DurationInDays = 28
            };

            GenderInfo[] genders =
            {
                new GenderInfo {Type = GenderType.Female},
                new GenderInfo {Type = GenderType.Male}
            };

            DeviceInfo[] devices =
            {
                new DeviceInfo {Type = Device.Desktop},
                new DeviceInfo {Type = Device.Mobile},
                new DeviceInfo {Type = Device.Tablet}
            };

            Targeting targeting = new Targeting
            {
                PlannableLocationId = locationId,
                AgeRange = ReachPlanAgeRange.AgeRange1865Up,
            };
            targeting.Genders.AddRange(genders);
            targeting.Devices.AddRange(devices);

            // See the docs for defaults and valid ranges:
            // https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
            GenerateReachForecastRequest request = new GenerateReachForecastRequest
            {
                CustomerId = customerId,
                CurrencyCode = currencyCode,
                CampaignDuration = duration,
                Targeting = targeting,
                MinEffectiveFrequency = 1
            };

            request.PlannedProducts.AddRange(productMix);

            return request;
        }

        /// <summary>
        /// Retrieves and prints the reach curve for the given request.
        /// </summary>
        /// <param name="reachPlanService">Instance of Reach Plan Service client.</param>
        /// <param name="request">An already-populated reach curve request.</param>
        public void GetReachCurve(ReachPlanServiceClient reachPlanService,
            GenerateReachForecastRequest request)
        {
            GenerateReachForecastResponse response = reachPlanService.GenerateReachForecast(
                request);
            Console.WriteLine("Reach curve output:");
            Console.WriteLine(
                "Currency, Cost Micros, On-Target Reach, On-Target Impressions, Total Reach," +
                " Total Impressions, Products");
            foreach (ReachForecast point in response.ReachCurve.ReachForecasts)
            {
                Console.Write($"{request.CurrencyCode}, ");
                Console.Write($"{point.CostMicros}, ");
                Console.Write($"{point.Forecast.OnTargetReach}, ");
                Console.Write($"{point.Forecast.OnTargetImpressions}, ");
                Console.Write($"{point.Forecast.TotalReach}, ");
                Console.Write($"{point.Forecast.TotalImpressions}, ");
                Console.Write("\"[");
                foreach (PlannedProductReachForecast productReachForecast in
                    point.PlannedProductReachForecasts)
                {
                    Console.Write($"(Product: {productReachForecast.PlannableProductCode}, ");
                    Console.Write($"Budget Micros: {productReachForecast.CostMicros}), ");
                }

                Console.WriteLine("]\"");
            }
        }

        /// <summary>
        /// Gets a forecast for a budget split 15% and 85% between two products.
        /// </summary>
        /// <param name="reachPlanService">Instance of Reach Plan Service client.</param>
        /// <param name="customerId">The customer ID for the reach forecast.</param>
        /// <param name="locationId">Location ID to plan for. To find a valid location ID, either
        /// see https://developers.google.com/google-ads/api/reference/data/geotargets or call
        /// <see cref="ReachPlanServiceClient.ListPlannableLocations"/>.</param>
        /// <param name="currencyCode">Three-character ISO 4217 currency code.</param>
        /// <param name="budgetMicros">Budget in currency to plan for.</param>
        public void ForecastMix(ReachPlanServiceClient reachPlanService, string customerId,
            string locationId, string currencyCode, long budgetMicros)
        {
            List<PlannedProduct> productMix = new List<PlannedProduct>();

            // Set up a ratio to split the budget between two products.
            double trueviewAllocation = 0.15;
            double bumperAllocation = 1 - trueviewAllocation;

            // See listPlannableProducts on ReachPlanService to retrieve a list
            // of valid PlannableProductCode's for a given location:
            // https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
            productMix.Add(new PlannedProduct
            {
                PlannableProductCode = "TRUEVIEW_IN_STREAM",
                BudgetMicros = Convert.ToInt64(budgetMicros * trueviewAllocation)
            });
            productMix.Add(new PlannedProduct
            {
                PlannableProductCode = "BUMPER",
                BudgetMicros = Convert.ToInt64(budgetMicros * bumperAllocation)
            });

            GenerateReachForecastRequest request =
                BuildReachRequest(customerId, productMix, locationId, currencyCode);

            GetReachCurve(reachPlanService, request);
        }
    }
}

      

PHP

<?php

/**
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Google\Ads\GoogleAds\Examples\Planning;

require __DIR__ . '/../../vendor/autoload.php';

use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V16\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V16\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V16\GoogleAdsException;
use Google\Ads\GoogleAds\V16\Common\DeviceInfo;
use Google\Ads\GoogleAds\V16\Common\GenderInfo;
use Google\Ads\GoogleAds\V16\Enums\DeviceEnum\Device;
use Google\Ads\GoogleAds\V16\Enums\GenderTypeEnum\GenderType;
use Google\Ads\GoogleAds\V16\Enums\ReachPlanAgeRangeEnum\ReachPlanAgeRange;
use Google\Ads\GoogleAds\V16\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V16\Services\CampaignDuration;
use Google\Ads\GoogleAds\V16\Services\GenerateReachForecastRequest;
use Google\Ads\GoogleAds\V16\Services\ListPlannableLocationsRequest;
use Google\Ads\GoogleAds\V16\Services\ListPlannableProductsRequest;
use Google\Ads\GoogleAds\V16\Services\PlannableLocation;
use Google\Ads\GoogleAds\V16\Services\PlannedProduct;
use Google\Ads\GoogleAds\V16\Services\PlannedProductReachForecast;
use Google\Ads\GoogleAds\V16\Services\ProductMetadata;
use Google\Ads\GoogleAds\V16\Services\ReachForecast;
use Google\Ads\GoogleAds\V16\Services\Targeting;
use Google\ApiCore\ApiException;

/**
 * This example demonstrates how to interact with the ReachPlanService to find plannable
 * locations and product codes, build a media plan, and generate a video ads reach forecast.
 */
class ForecastReach
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    private const CURRENCY_CODE = 'USD';
    // You can get a valid location ID from
    // https://developers.google.com/adwords/api/docs/appendix/geotargeting or by calling
    // ListPlannableLocations on the ReachPlanService.
    private const LOCATION_ID = '2840'; // US
    private const BUDGET_MICROS = 5000000; // 5

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert
        // them into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())
            ->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            // We set this value to true to show how to use GAPIC v2 source code. You can remove the
            // below line if you wish to use the old-style source code. Note that in that case, you
            // probably need to modify some parts of the code below to make it work.
            // For more information, see
            // https://developers.devsite.corp.google.com/google-ads/api/docs/client-libs/php/gapic.
            ->usingGapicV2Source(true)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     */
    public static function runExample(GoogleAdsClient $googleAdsClient, int $customerId)
    {
        self::showPlannableLocations($googleAdsClient);
        self::showPlannableProducts($googleAdsClient);
        self::forecastManualMix($googleAdsClient, $customerId);
    }

    /**
     * Shows map of plannable locations to their IDs.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     */
    private static function showPlannableLocations(GoogleAdsClient $googleAdsClient)
    {
        $response = $googleAdsClient->getReachPlanServiceClient()->listPlannableLocations(
            new ListPlannableLocationsRequest()
        );

        printf("Plannable Locations:%sName, Id, ParentCountryId%s", PHP_EOL, PHP_EOL);
        foreach ($response->getPlannableLocations() as $location) {
            /** @var PlannableLocation $location */
            printf(
                "'%s', %s, %s%s",
                $location->getName(),
                $location->getId(),
                $location->getParentCountryId(),
                PHP_EOL
            );
        }
    }

    /**
     * Lists plannable products for a given location.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     */
    private static function showPlannableProducts(GoogleAdsClient $googleAdsClient)
    {
        $response = $googleAdsClient->getReachPlanServiceClient()->listPlannableProducts(
            ListPlannableProductsRequest::build(self::LOCATION_ID)
        );

        print 'Plannable Products for Location ID ' . self::LOCATION_ID . ':' . PHP_EOL;
        foreach ($response->getProductMetadata() as $product) {
            /** @var ProductMetadata $product */
            print $product->getPlannableProductCode() . ':' . PHP_EOL;
            print 'Age Ranges:' . PHP_EOL;
            foreach ($product->getPlannableTargeting()->getAgeRanges() as $ageRange) {
                /** @var ReachPlanAgeRange $ageRange */
                printf("\t- %s%s", ReachPlanAgeRange::name($ageRange), PHP_EOL);
            }
            print 'Genders:' . PHP_EOL;
            foreach ($product->getPlannableTargeting()->getGenders() as $gender) {
                /** @var GenderInfo $gender */
                printf("\t- %s%s", GenderType::name($gender->getType()), PHP_EOL);
            }
            print 'Devices:' . PHP_EOL;
            foreach ($product->getPlannableTargeting()->getDevices() as $device) {
                /** @var DeviceInfo $device */
                printf("\t- %s%s", Device::name($device->getType()), PHP_EOL);
            }
        }
    }

    /**
     * Retrieves and prints the reach curve for a given product mix.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param array $productMix the product mix for the reach forecast
     * @param string $locationId the location ID to plan for. You can get a valid location ID from
     *     https://developers.google.com/adwords/api/docs/appendix/geotargeting or
     *     by calling ListPlannableLocations on the ReachPlanService.
     * @param string $currencyCode three-character ISO 4217 currency code
     */
    private static function getReachCurve(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        array $productMix,
        string $locationId,
        string $currencyCode
    ) {
        // Valid durations are between 1 and 90 days.
        $duration = new CampaignDuration(['duration_in_days' => 28]);
        $targeting = new Targeting([
            'plannable_location_id' => $locationId,
            'age_range' => ReachPlanAgeRange::AGE_RANGE_18_65_UP,
            'genders' => [
                new GenderInfo(['type' => GenderType::FEMALE]),
                new GenderInfo(['type' => GenderType::MALE])
            ],
            'devices' => [
                new DeviceInfo(['type' => Device::DESKTOP]),
                new DeviceInfo(['type' => Device::MOBILE]),
                new DeviceInfo(['type' => Device::TABLET])
            ]
        ]);

        // See the docs for defaults and valid ranges:
        // https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
        $response = $googleAdsClient->getReachPlanServiceClient()->generateReachForecast(
            GenerateReachForecastRequest::build($customerId, $duration, $productMix)
                ->setCurrencyCode($currencyCode)
                ->setTargeting($targeting)
        );

        printf(
            "Reach curve output:%sCurrency, Cost Micros, On-Target Reach, On-Target Imprs," .
                " Total Reach, Total Imprs, Products%s",
            PHP_EOL,
            PHP_EOL
        );
        foreach ($response->getReachCurve()->getReachForecasts() as $point) {
            $products = '';
            /** @var ReachForecast $point */
            foreach ($point->getPlannedProductReachForecasts() as $plannedProductReachForecast) {
                /** @var PlannedProductReachForecast $plannedProductReachForecast */
                $products .= sprintf(
                    '(Product: %s, Budget Micros: %s)',
                    $plannedProductReachForecast->getPlannableProductCode(),
                    $plannedProductReachForecast->getCostMicros()
                );
            }
            printf(
                "%s, %d, %d, %d, %d, %d, %s%s",
                $currencyCode,
                $point->getCostMicros(),
                $point->getForecast()->getOnTargetReach(),
                $point->getForecast()->getOnTargetImpressions(),
                $point->getForecast()->getTotalReach(),
                $point->getForecast()->getTotalImpressions(),
                $products,
                PHP_EOL
            );
        }
    }

    /**
     * Gets a forecast for product mix created manually.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     */
    private static function forecastManualMix(GoogleAdsClient $googleAdsClient, int $customerId)
    {
        // Set up a ratio to split the budget between two products.
        $trueviewAllocation = floatval(0.15);
        $bumperAllocation = floatval(1 - $trueviewAllocation);

        // See listPlannableProducts on ReachPlanService to retrieve a list
        // of valid PlannableProductCode's for a given location:
        // https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
        $productMix = [
            new PlannedProduct([
                'plannable_product_code' => 'TRUEVIEW_IN_STREAM',
                'budget_micros' => self::BUDGET_MICROS * $trueviewAllocation
            ]),
            new PlannedProduct([
                'plannable_product_code' => 'BUMPER',
                'budget_micros' => self::BUDGET_MICROS * $bumperAllocation
            ])
        ];

        self::getReachCurve(
            $googleAdsClient,
            $customerId,
            $productMix,
            self::LOCATION_ID,
            self::CURRENCY_CODE
        );
    }
}

ForecastReach::main();

      

Python

#!/usr/bin/env python
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This code example generates a video ads reach forecast."""

import argparse
import math
import sys

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException

ONE_MILLION = 1.0e6


def main(client, customer_id):
    """The main method that creates all necessary entities for the example.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
    """
    # You can review a list of valid location IDs by visiting:
    # https://developers.google.com/google-ads/api/reference/data/geotargets
    # or by calling the ListPlannableLocations method on ReachPlanService.
    location_id = "2840"  # US
    currency_code = "USD"
    budget = 500000

    show_plannable_locations(client)
    show_plannable_products(client, location_id)
    forecast_manual_mix(client, customer_id, location_id, currency_code, budget)


def show_plannable_locations(client):
    """Shows map of plannable locations to their IDs.

    Args:
        client: an initialized GoogleAdsClient instance.
    """
    reach_plan_service = client.get_service("ReachPlanService")
    response = reach_plan_service.list_plannable_locations()

    print("Plannable Locations")
    print("Name,\tId,\tParentCountryId")
    for location in response.plannable_locations:
        print(
            f"'{location.name}',\t{location.id},\t{location.parent_country_id}"
        )


def show_plannable_products(client, location_id):
    """Lists plannable products for a given location.

    Args:
        client: an initialized GoogleAdsClient instance.
        location_id: The location ID to plan for.
    """
    reach_plan_service = client.get_service("ReachPlanService")
    response = reach_plan_service.list_plannable_products(
        plannable_location_id=location_id
    )
    print(f"Plannable Products for Location ID {location_id}")

    for product_metadata in response.product_metadata:
        print(
            f"{product_metadata.plannable_product_code} : "
            f"{product_metadata.plannable_product_name}"
        )

        print("Age Ranges:")
        for age_range in product_metadata.plannable_targeting.age_ranges:
            print(f"\t- {age_range.name}")

        print("Genders:")
        for gender in product_metadata.plannable_targeting.genders:
            print(f"\t- {gender.type_.name}")

        print("Devices:")
        for device in product_metadata.plannable_targeting.devices:
            print(f"\t- {device.type_.name}")


def request_reach_curve(
    client, customer_id, product_mix, location_id, currency_code
):
    """Creates a sample request for a given product mix.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: The customer ID for the reach forecast.
        product_mix: The product mix for the reach forecast.
        location_id: The location ID to plan for.
        currency_code: Three-character ISO 4217 currency code.
    """
    # See the docs for defaults and valid ranges:
    # https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
    request = client.get_type("GenerateReachForecastRequest")
    request.customer_id = customer_id
    # Valid durations are between 1 and 90 days.
    request.campaign_duration.duration_in_days = 28
    request.currency_code = currency_code
    request.cookie_frequency_cap = 0
    request.min_effective_frequency = 1
    request.planned_products = product_mix

    request.targeting.plannable_location_id = location_id
    request.targeting.age_range = (
        client.enums.ReachPlanAgeRangeEnum.AGE_RANGE_18_65_UP
    )

    # Add gender targeting to the request.
    for gender_type in [
        client.enums.GenderTypeEnum.FEMALE,
        client.enums.GenderTypeEnum.MALE,
    ]:
        gender = client.get_type("GenderInfo")
        gender.type_ = gender_type
        request.targeting.genders.append(gender)

    # Add device targeting to the request.
    for device_type in [
        client.enums.DeviceEnum.DESKTOP,
        client.enums.DeviceEnum.MOBILE,
        client.enums.DeviceEnum.TABLET,
    ]:
        device = client.get_type("DeviceInfo")
        device.type_ = device_type
        request.targeting.devices.append(device)

    reach_plan_service = client.get_service("ReachPlanService")
    response = reach_plan_service.generate_reach_forecast(request=request)

    print(
        "Currency, Cost, On-Target Reach, On-Target Imprs, Total Reach,"
        " Total Imprs, Products"
    )
    for point in response.reach_curve.reach_forecasts:
        product_splits = []
        for p in point.planned_product_reach_forecasts:
            product_splits.append(
                {p.plannable_product_code: p.cost_micros / ONE_MILLION}
            )
        print(
            [
                currency_code,
                point.cost_micros / ONE_MILLION,
                point.forecast.on_target_reach,
                point.forecast.on_target_impressions,
                point.forecast.total_reach,
                point.forecast.total_impressions,
                product_splits,
            ]
        )


def forecast_manual_mix(
    client, customer_id, location_id, currency_code, budget
):
    """Pulls a forecast for product mix created manually.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: The customer ID for the reach forecast.
        product_mix: The product mix for the reach forecast.
        location_id: The location ID to plan for.
        currency_code: Three-character ISO 4217 currency code.
        budget: Budget to allocate to the plan.
    """
    product_mix = []
    trueview_allocation = 0.15
    bumper_allocation = 1 - trueview_allocation
    product_splits = [
        ("TRUEVIEW_IN_STREAM", trueview_allocation),
        ("BUMPER", bumper_allocation),
    ]
    for product, split in product_splits:
        planned_product = client.get_type("PlannedProduct")
        planned_product.plannable_product_code = product
        planned_product.budget_micros = math.trunc(budget * ONE_MILLION * split)
        product_mix.append(planned_product)

    request_reach_curve(
        client, customer_id, product_mix, location_id, currency_code
    )


if __name__ == "__main__":
    # GoogleAdsClient will read the google-ads.yaml configuration file in the
    # home directory if none is specified.
    googleads_client = GoogleAdsClient.load_from_storage(version="v16")

    parser = argparse.ArgumentParser(
        description="Generates video ads reach forecast."
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    args = parser.parse_args()

    try:
        main(googleads_client, args.customer_id)
    except GoogleAdsException as ex:
        print(
            'Request with ID "{}" failed with status "%s" and includes the '
            "following errors:".format(ex.request_id, ex.error.code().name)
        )
        for error in ex.failure.errors:
            print('\tError with message "{}".'.format(error.message))
            if error.location:
                for field_path_element in error.location.field_path_elements:
                    print(
                        "\t\tOn field: {}".format(field_path_element.field_name)
                    )
        sys.exit(1)

      

Ruby

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example demonstrates how to interact with the ReachPlanService to find
# plannable locations and product codes, build a media plan, and generate a
# video ads reach forecast.

require 'optparse'
require 'google/ads/google_ads'

def forecast_reach(customer_id)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  reach_plan_service = client.service.reach_plan

  show_plannable_locations(reach_plan_service)

  show_plannable_products(reach_plan_service)

  forecast_manual_mix(client, reach_plan_service, customer_id)
end

# Shows map of plannable locations to their IDs.
def show_plannable_locations(reach_plan_service)
  response = reach_plan_service.list_plannable_locations()

  puts "Plannable Locations:"
  puts "Name, Id, ParentCountryId"

  response.plannable_locations.each do |location|
    puts "'#{location.name}', #{location.id}, #{location.parent_country_id}"
  end
end

# Lists plannable products for a given location.
def show_plannable_products(reach_plan_service)
  response = reach_plan_service.list_plannable_products(
    plannable_location_id: LOCATION_ID,
  )

  puts "Plannable Products for Location ID #{LOCATION_ID}:"

  response.product_metadata.each do |product|
    puts "#{product.plannable_product_code}:"
    puts "Age Ranges:"
    product.plannable_targeting.age_ranges.each do |age_range|
      puts "\t- #{age_range}"
    end
    puts "Genders:"
    product.plannable_targeting.genders.each do |gender|
      puts "\t- #{gender.type}"
    end
    puts "Devices:"
    product.plannable_targeting.devices.each do |device|
      puts "\t- #{device.type}"
    end
  end
end

# Retrieves and prints the reach curve for a given product mix.
def get_reach_curve(
  client,
  reach_plan_service,
  customer_id,
  product_mix,
  location_id,
  currency_code)
  duration = client.resource.campaign_duration do |d|
    # Valid durations are between 1 and 90 days.
    d.duration_in_days = 28
  end

  targeting = client.resource.targeting do |t|
    t.plannable_location_id = location_id
    t.age_range = :AGE_RANGE_18_65_UP
    t.genders << client.resource.gender_info do |gender|
      gender.type = :FEMALE
    end
    t.genders << client.resource.gender_info do |gender|
      gender.type = :MALE
    end
    t.devices << client.resource.device_info do |device|
      device.type = :DESKTOP
    end
    t.devices << client.resource.device_info do |device|
      device.type = :MOBILE
    end
    t.devices << client.resource.device_info do |device|
      device.type = :TABLET
    end
  end

  # See the docs for defaults and valid ranges:
  # https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
  response = reach_plan_service.generate_reach_forecast(
    customer_id: customer_id,
    campaign_duration: duration,
    planned_products: product_mix,
    currency_code: currency_code,
    targeting: targeting,
  )

  puts "Reach curve output:"
  puts "Currency, Cost Micros, On-Target Reach, On-Target Imprs, " \
    "Total Reach, Total Imprs, Products"

  response.reach_curve.reach_forecasts.each do |point|
    products = ""
    point.planned_product_reach_forecasts.each do |product|
      products += "(Product: #{product.plannable_product_code}, "\
        "Cost Micros: #{product.cost_micros})"
    end
    puts "#{currency_code}, #{point.cost_micros}, " \
      "#{point.forecast.on_target_reach}, " \
      "#{point.forecast.on_target_impressions}, " \
      "#{point.forecast.total_reach}, " \
      "#{point.forecast.total_impressions}, " \
      "#{products}"
  end
end

# Gets a forecast for product mix created manually.
def forecast_manual_mix(client, reach_plan_service, customer_id)
  # Set up a ratio to split the budget between two products.
  trueview_allocation = 0.15
  bumper_allocation = 1 - trueview_allocation

  # See listPlannableProducts on ReachPlanService to retrieve a list
  # of valid PlannableProductCode's for a given location:
  # https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
  product_mix = []

  product_mix << client.resource.planned_product do |p|
    p.plannable_product_code = 'TRUEVIEW_IN_STREAM'
    p.budget_micros = BUDGET_MICROS * trueview_allocation
  end

  product_mix << client.resource.planned_product do |p|
    p.plannable_product_code = 'BUMPER'
    p.budget_micros = BUDGET_MICROS * bumper_allocation
  end

  get_reach_curve(
    client,
    reach_plan_service,
    customer_id,
    product_mix,
    LOCATION_ID,
    CURRENCY_CODE,
  )
end

if __FILE__ == $0
  CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
  CURRENCY_CODE = 'USD';
  # You can get a valid location ID from
  # https://developers.google.com/adwords/api/docs/appendix/geotargeting
  # or by calling list_plannable_locations on the reach_plan service.
  LOCATION_ID = '2840'; # US
  BUDGET_MICROS = 5_000_000; # 5

  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE'

  OptionParser.new do |opts|
    opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__))

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v|
      options[:customer_id] = v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  begin
    forecast_reach(options.fetch(:customer_id).tr("-", ""))
  rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
    e.failure.errors.each do |error|
      STDERR.printf("Error with message: %s\n", error.message)
      if error.location
        error.location.field_path_elements.each do |field_path_element|
          STDERR.printf("\tOn field: %s\n", field_path_element.field_name)
        end
      end
      error.error_code.to_h.each do |k, v|
        next if v == :UNSPECIFIED
        STDERR.printf("\tType: %s\n\tCode: %s\n", k, v)
      end
    end
    raise
  end
end

      

Perl

#!/usr/bin/perl -w
#
# Copyright 2019, Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example demonstrates how to interact with the ReachPlanService to find
# plannable locations and product codes, build a media plan, and generate a video
# ads reach forecast.

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::V16::Common::GenderInfo;
use Google::Ads::GoogleAds::V16::Common::DeviceInfo;
use Google::Ads::GoogleAds::V16::Enums::ReachPlanAdLengthEnum
  qw(FIFTEEN_OR_TWENTY_SECONDS);
use Google::Ads::GoogleAds::V16::Enums::GenderTypeEnum qw(MALE FEMALE);
use Google::Ads::GoogleAds::V16::Enums::DeviceEnum qw(DESKTOP MOBILE TABLET);
use Google::Ads::GoogleAds::V16::Enums::ReachPlanAgeRangeEnum
  qw(AGE_RANGE_18_65_UP);
use Google::Ads::GoogleAds::V16::Services::ReachPlanService::PlannedProduct;
use Google::Ads::GoogleAds::V16::Services::ReachPlanService::CampaignDuration;
use Google::Ads::GoogleAds::V16::Services::ReachPlanService::Targeting;
use
  Google::Ads::GoogleAds::V16::Services::ReachPlanService::GenerateReachForecastRequest;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd qw(abs_path);

# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
my $customer_id = "INSERT_CUSTOMER_ID_HERE";

sub forecast_reach {
  my ($api_client, $customer_id) = @_;

  # Location ID to plan for. You can get a valid location ID from
  # https://developers.google.com/google-ads/api/reference/data/geotargets or by
  # calling list_plannable_locations on the ReachPlanService.
  # Location ID 2840 is for USA.
  my $location_id   = "2840";
  my $currency_code = "USD";
  my $budget_micros = 500_000_000_000;

  my $reach_plan_service = $api_client->ReachPlanService();

  show_plannable_locations($reach_plan_service);
  show_plannable_products($reach_plan_service, $location_id);
  forecast_mix(
    $reach_plan_service, $customer_id, $location_id,
    $currency_code,      $budget_micros
  );

  return 1;
}

# Maps friendly names of plannable locations to location IDs usable with
# ReachPlanService.
sub show_plannable_locations {
  my ($reach_plan_service) = @_;

  my $response = $reach_plan_service->list_plannable_locations();

  print "Plannable Locations:\n";
  print "Name,\tId,\tParentCountryId\n";
  foreach my $location (@{$response->{plannableLocations}}) {
    printf "'%s',\t%s,\t%d\n", $location->{name}, $location->{id},
      $location->{parentCountryId} ? $location->{parentCountryId} : 0;
  }
}

# Lists plannable products for a given location.
sub show_plannable_products {
  my ($reach_plan_service, $location_id) = @_;

  my $response = $reach_plan_service->list_plannable_products({
    plannableLocationId => $location_id
  });

  printf "Plannable Products for location %d:\n", $location_id;
  foreach my $product (@{$response->{productMetadata}}) {
    printf "%s : '%s'\n", $product->{plannableProductCode},
      $product->{plannableProductName};
    print "Age Ranges:\n";
    foreach my $age_range (@{$product->{plannableTargeting}{ageRanges}}) {
      printf "\t- %s\n", $age_range;
    }
    print "Genders:\n";
    foreach my $gender (@{$product->{plannableTargeting}{genders}}) {
      printf "\t- %s\n", $gender->{type};
    }
    print "Devices:\n";
    foreach my $device (@{$product->{plannableTargeting}{devices}}) {
      printf "\t- %s\n", $device->{type};
    }
  }
}

# Pulls a forecast for a budget split 15% and 85% between two products.
sub forecast_mix {
  my (
    $reach_plan_service, $customer_id, $location_id,
    $currency_code,      $budget_micros
  ) = @_;

  my $product_mix = [];

  # Set up a ratio to split the budget between two products.
  my $trueview_allocation = 0.15;
  my $bumper_allocation   = 1 - $trueview_allocation;

  # See list_plannable_products on ReachPlanService to retrieve a list of valid
  # plannable product codes for a given location:
  # https://developers.google.com/google-ads/api/reference/rpc/latest/ReachPlanService
  push @$product_mix,
    Google::Ads::GoogleAds::V16::Services::ReachPlanService::PlannedProduct->
    new({
      plannableProductCode => "TRUEVIEW_IN_STREAM",
      budgetMicros         => int($budget_micros * $trueview_allocation)});
  push @$product_mix,
    Google::Ads::GoogleAds::V16::Services::ReachPlanService::PlannedProduct->
    new({
      plannableProductCode => "BUMPER",
      budgetMicros         => int($budget_micros * $bumper_allocation)});

  my $reach_request =
    build_reach_request($customer_id, $product_mix, $location_id,
    $currency_code);

  pull_reach_curve($reach_plan_service, $reach_request);
}

# Create a base request to generate a reach forecast.
sub build_reach_request {
  my ($customer_id, $product_mix, $location_id, $currency_code) = @_;

  # Valid durations are between 1 and 90 days.
  my $duration =
    Google::Ads::GoogleAds::V16::Services::ReachPlanService::CampaignDuration->
    new({
      durationInDays => 28
    });

  my $genders = [
    Google::Ads::GoogleAds::V16::Common::GenderInfo->new({
        type => FEMALE
      }
    ),
    Google::Ads::GoogleAds::V16::Common::GenderInfo->new({
        type => MALE
      })];

  my $devices = [
    Google::Ads::GoogleAds::V16::Common::DeviceInfo->new({
        type => DESKTOP
      }
    ),
    Google::Ads::GoogleAds::V16::Common::DeviceInfo->new({
        type => MOBILE
      }
    ),
    Google::Ads::GoogleAds::V16::Common::DeviceInfo->new({
        type => TABLET
      })];

  my $targeting =
    Google::Ads::GoogleAds::V16::Services::ReachPlanService::Targeting->new({
      plannableLocationId => $location_id,
      ageRange            => AGE_RANGE_18_65_UP,
      genders             => $genders,
      devices             => $devices
    });

  # See the docs for defaults and valid ranges:
  # https://developers.google.com/google-ads/api/reference/rpc/latest/GenerateReachForecastRequest
  return
    Google::Ads::GoogleAds::V16::Services::ReachPlanService::GenerateReachForecastRequest
    ->new({
      customerId            => $customer_id,
      currencyCode          => $currency_code,
      campaignDuration      => $duration,
      cookieFrequencyCap    => 0,
      minEffectiveFrequency => 1,
      targeting             => $targeting,
      plannedProducts       => $product_mix
    });
}

# Pulls and prints the reach curve for the given request.
sub pull_reach_curve {
  my ($reach_plan_service, $reach_request) = @_;

  my $response = $reach_plan_service->generate_reach_forecast($reach_request);
  print "Reach curve output:\n";
  print "Currency,\tCost Micros,\tOn-Target Reach,\tOn-Target Imprs,\t" .
    "Total Reach,\tTotal Imprs,\tProducts\n";
  foreach my $point (@{$response->{reachCurve}{reachForecasts}}) {
    printf "%s,\t%d,\t%d,\t%d,\t%d,\t%d,\t'[", $reach_request->{currencyCode},
      $point->{costMicros}, $point->{forecast}{onTargetReach},
      $point->{forecast}{onTargetImpressions}, $point->{forecast}{totalReach},
      $point->{forecast}{totalImpressions};
    foreach my $productReachForecast (@{$point->{plannedProductReachForecasts}})
    {
      printf "(Product: %s, Budget Micros: %d), ",
        $productReachForecast->{plannableProductCode},
        $productReachForecast->{costMicros};
    }
    print "]'\n";
  }
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

# By default examples are set to die on any server returned fault.
$api_client->set_die_on_faults(1);

# Parameters passed on the command line will override any parameters set in code.
GetOptions("customer_id=s" => \$customer_id);

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2) if not check_params($customer_id);

# Call the example.
forecast_reach($api_client, $customer_id =~ s/-//gr);

=pod

=head1 NAME

forecast_reach

=head1 DESCRIPTION

This example demonstrates how to interact with the ReachPlanService to find plannable
locations and product codes, build a media plan, and generate a video ads reach forecast.

=head1 SYNOPSIS

forecast_reach.pl [options]

    -help                       Show the help message.
    -customer_id                The Google Ads customer ID.

=cut