from pydantic import BaseModel from typing import List, Optional from google.ads.googleads.client import GoogleAdsClient from google.ads.googleads.errors import GoogleAdsException import numpy as np import os # Read yaml from Secrets in HF yaml_str = os.environ["GOOGLE_ADS_YAML"] # Write YAML to a temporary file with open("google-ads.yaml", "w") as f: f.write(yaml_str) client = GoogleAdsClient.load_from_storage("google-ads.yaml") keyword_plan_idea_service = client.get_service("KeywordPlanIdeaService") class KeywordRequestModel(BaseModel): customer_id: str keywords: List[str] geo_target: Optional[str] = None language: Optional[str] = None # --- Call Google Ads API def fetch_keyword_ideas(request: KeywordRequestModel): """ Fetch keyword ideas from the Google Ads API based on the given parameters. Args: request (KeywordRequestModel): - customer_id (str): Google Ads customer ID. - keywords (List[str]): A list of seed keywords to generate ideas from. - geo_target (Optional[str]): (Optional) Geo target constant ID. - language (Optional[str]): (Optional) Language constant ID. Returns: List[dict]: A list of keyword idea results, where each item contains: - "keyword" (str): The suggested keyword text. - "avg_monthly_searches" (int): Average monthly search volume. - "competition" (str): Competition level (LOW / MEDIUM / HIGH). - "competition_index" (int): Numerical competition index. If an error occurs, an empty list is returned. """ req = client.get_type("GenerateKeywordIdeasRequest") req.customer_id = request.customer_id req.keyword_seed.keywords.extend(request.keywords) if request.geo_target: req.geo_target_constants.append( client.get_service("GoogleAdsService").geo_target_constant_path(request.geo_target) ) if request.language: req.language = client.get_service("GoogleAdsService").language_constant_path(request.language) try: response = keyword_plan_idea_service.generate_keyword_ideas(request=req) result_items = [] # --- Loop results --- for idea in response.results: metrics = idea.keyword_idea_metrics # Monthly search volumes monthly_volumes = [] if metrics.monthly_search_volumes: for mv in metrics.monthly_search_volumes: monthly_volumes.append({ "month": mv.month.name, "year": mv.year, "monthly_searches": mv.monthly_searches }) # According To "Rule Book For GKP With Code" Transform Some Values raw_cpc = (((metrics.low_top_of_page_bid_micros) + (metrics.high_top_of_page_bid_micros)) / 2) / 1000000 log_cpc = np.log(raw_cpc + 1) result_items.append({ "keyword": idea.text, "avg_monthly_searches": metrics.avg_monthly_searches, "competition": metrics.competition.name, "competition_index": metrics.competition_index, "low_top_of_page_bid_micros": metrics.low_top_of_page_bid_micros / 1000000, "high_top_of_page_bid_micros": metrics.high_top_of_page_bid_micros / 1000000, # "average_cpc_micros": metrics.average_cpc_micros, "monthly_search_volumes": monthly_volumes, ##Created Features "log_cpc": log_cpc }) #Normalize the Log CPC Values log_values = [item["log_cpc"] for item in result_items] min_log = min(log_values) max_log = max(log_values) def scale_0_100(x, min_val, max_val): if max_val == min_val: return 0 return ((x - min_val) / (max_val - min_val)) * 100 #Adding "log_cpc_norm_0_100" to Results_Items for item in result_items: item["log_cpc_norm_0_100"] = scale_0_100(item["log_cpc"], min_log, max_log) # --- Aggregate results --- aggregate = response.aggregate_metric_results aggregate_device = [] if aggregate and aggregate.device_searches: for d in aggregate.device_searches: aggregate_device.append({ "device": d.device.name, "search_count": d.search_count }) # return response return { "results": result_items, "aggregateMetricResults": { "device_searches": aggregate_device } } except Exception as ex: print(f"Error: {ex}") return { "results": [], "aggregateMetricResults": {} } # --- Keyword selection def rank_and_filter_keywords(api_response: dict, limit: int = 20): """ Rank and filter keyword results based on average monthly searches. Args: api_response (dict): A dict returned from fetch_keyword_ideas(), expected to have a "results" key. limit (int, optional): Maximum number of top keywords to return. Defaults to 20. Returns: list: A list of up to `limit` keyword dictionaries, sorted in descending order by their avg_monthly_searches value. """ # Check if it includs results key if "results" not in api_response or not isinstance(api_response["results"], list): print("Error: No results found in API response") return [] keyword_list = api_response["results"] # Filter out entries where avg_monthly_searches is None (just in case) filtered = [k for k in keyword_list if k.get("avg_monthly_searches") is not None] # Sort by avg_monthly_searches in descending order sorted_keywords = sorted( filtered, key=lambda x: x["avg_monthly_searches"], reverse=True ) return sorted_keywords[:limit]