ps1811 commited on
Commit
4690f5c
·
1 Parent(s): 696a2dc

Keyword inspector card added.Cleaned up Search term code

Browse files
app.py CHANGED
@@ -12,6 +12,7 @@ print("IMPORT 3 OK", flush=True)
12
  from app.ads1.ads_analyst import run_ads_analyst_card
13
  print("IMPORT 4 OK", flush=True)
14
  from app.ads1.search_term_optimizer import run_search_term_optimizer
 
15
 
16
  # ==================================================
17
  # ROMER / ADVISOR DASHBOARD THEME
@@ -978,6 +979,22 @@ def run_search_term_optimizer_card(state):
978
  except Exception as e:
979
  return f"Search term optimization failed: {e}"
980
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
981
 
982
  # ==================================================
983
  # UI
@@ -1021,7 +1038,10 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
1021
  elem_classes=["ai-button-card"],
1022
  )
1023
  gr.HTML(ai_card("Budget Optimizer", "Where to adjust spend?"))
1024
- gr.HTML(ai_card("Keyword Inspector", "Winning versus wasting keywords."))
 
 
 
1025
 
1026
  with gr.Row(elem_classes=["ai-row"]):
1027
  search_term_card = gr.Button(
@@ -1057,6 +1077,12 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
1057
  outputs=ads_output,
1058
  )
1059
 
 
 
 
 
 
 
1060
  demo.load(
1061
  fn=initial_data_load,
1062
  outputs=[full_state, campaign_picker, hero_html, kpi_html],
 
12
  from app.ads1.ads_analyst import run_ads_analyst_card
13
  print("IMPORT 4 OK", flush=True)
14
  from app.ads1.search_term_optimizer import run_search_term_optimizer
15
+ from app.ads1.keyword_inspector import run_keyword_inspector
16
 
17
  # ==================================================
18
  # ROMER / ADVISOR DASHBOARD THEME
 
979
  except Exception as e:
980
  return f"Search term optimization failed: {e}"
981
 
982
+ @spaces.GPU(duration=120)
983
+ def run_keyword_inspector_card(state):
984
+ try:
985
+ if not state:
986
+ return "Select a campaign first."
987
+
988
+ dfs = state.get("full_dfs")
989
+ campaign_name = state.get("campaign_name")
990
+
991
+ if dfs is None or campaign_name is None:
992
+ return "Campaign state is not properly initialized."
993
+
994
+ return run_keyword_inspector(dfs, campaign_name=campaign_name)
995
+
996
+ except Exception as e:
997
+ return f"Search term optimization failed: {e}"
998
 
999
  # ==================================================
1000
  # UI
 
1038
  elem_classes=["ai-button-card"],
1039
  )
1040
  gr.HTML(ai_card("Budget Optimizer", "Where to adjust spend?"))
1041
+ keyword_inspector_card = gr.Button(
1042
+ value="Keyword Inspector\n <hr> Winning versus wasting keywords..",
1043
+ elem_classes=["ai-button-card"],
1044
+ )
1045
 
1046
  with gr.Row(elem_classes=["ai-row"]):
1047
  search_term_card = gr.Button(
 
1077
  outputs=ads_output,
1078
  )
1079
 
1080
+ keyword_inspector_card.click(
1081
+ fn=run_keyword_inspector_card,
1082
+ inputs=campaign_state,
1083
+ outputs=ads_output,
1084
+ )
1085
+
1086
  demo.load(
1087
  fn=initial_data_load,
1088
  outputs=[full_state, campaign_picker, hero_html, kpi_html],
app/ads1/keyword_inspector.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import pandas as pd
3
+
4
+ from app.recs.generate import generate_explanation, is_bad_llm_output
5
+
6
+
7
+ # -------------------------
8
+ # Feature engineering only
9
+ # -------------------------
10
+ def build_keyword_features(df: pd.DataFrame) -> pd.DataFrame:
11
+ df = df.copy()
12
+
13
+ df["cost"] = df["cost"].fillna(0)
14
+ df["clicks"] = df["clicks"].fillna(0)
15
+ df["impressions"] = df["impressions"].fillna(0)
16
+ df["conversions"] = df.get("conversions", 0).fillna(0)
17
+
18
+ df["ctr"] = (df["clicks"] / df["impressions"].replace(0, 1)) * 100
19
+ df["cpa"] = df["cost"] / df["conversions"].replace(0, 1)
20
+
21
+ return df
22
+
23
+
24
+ # -------------------------
25
+ # Prompt (simplified + stronger reasoning)
26
+ # -------------------------
27
+ def build_keyword_prompt(context: dict) -> str:
28
+ payload = json.dumps(context, indent=2, default=str)
29
+
30
+ return f"""
31
+ You are an expert Google Ads performance strategist for a preschool business.
32
+
33
+ Campaign: {context.get("campaign_name", "ALL CAMPAIGNS")}
34
+
35
+ Your job:
36
+ Analyze keyword performance and identify:
37
+ - Winning keywords (high intent + conversions)
38
+ - Wasted spend keywords (cost but no conversions)
39
+ - Keywords to scale
40
+ - Keywords to pause or reduce bids
41
+ - Any CTR / conversion anomalies
42
+
43
+ IMPORTANT:
44
+ - Do NOT assume CRM / SaaS context
45
+ - Assume all data relates to preschool admissions, daycare, or childcare services
46
+ - Think like a preschool marketing expert
47
+
48
+ DATA:
49
+ {payload}
50
+
51
+ Return 5 bullet points.
52
+ Each bullet must start with "- ".
53
+ Be direct, business-focused, no intro text.
54
+ """
55
+
56
+
57
+ # -------------------------
58
+ # Main runner
59
+ # -------------------------
60
+ def run_keyword_inspector(dfs: dict, campaign_name: str | None = None) -> str:
61
+ print("\n🚀 [keyword_inspector] STARTED", flush=True)
62
+
63
+ if not dfs or "keywords" not in dfs:
64
+ return "⚠️ No keyword data available."
65
+
66
+ df = dfs["keywords"].copy()
67
+ df = build_keyword_features(df)
68
+
69
+ # optional campaign filter (safe, not destructive)
70
+ if campaign_name and "campaign_name" in df.columns:
71
+ df = df[df["campaign_name"] == campaign_name]
72
+
73
+ context = {
74
+ "campaign_name": campaign_name,
75
+ "keywords": df.to_dict("records") # FULL DATA given to LLM
76
+ }
77
+
78
+ print("🧠 [keyword_inspector] context built", flush=True)
79
+
80
+ prompt = build_keyword_prompt(context)
81
+ print("✍️ [keyword_inspector] prompt built", flush=True)
82
+
83
+ result = generate_explanation(prompt)
84
+
85
+ if is_bad_llm_output(result):
86
+ print("⚠️ [keyword_inspector] LLM fallback triggered", flush=True)
87
+ return (
88
+ "- Unable to generate LLM insights right now.\n"
89
+ "- Check keyword data quality or retry."
90
+ )
91
+
92
+ print("📤 [keyword_inspector] result received", flush=True)
93
+ return result
app/ads1/search_term_optimizer.py CHANGED
@@ -43,20 +43,6 @@ def detect_review_terms(df):
43
  def detect_scaling_terms(df):
44
  return df[(df["conversions"] > 0)].sort_values("cpa", ascending=True)
45
 
46
- # def classify_search_term(dfs: dict) -> dict:
47
- # df = dfs["search_terms"].copy()
48
- # df = build_search_term_features(df)
49
-
50
- # negatives = detect_negative_terms(df)
51
- # review = detect_review_terms(df)
52
- # winners = detect_scaling_terms(df)
53
-
54
- # return {
55
- # "negative_keywords": negatives.to_dict("records"),
56
- # "review_terms": review.to_dict("records"),
57
- # "winning_terms": winners.head(10).to_dict("records"),
58
- # }
59
-
60
  def build_search_optimizer_prompt(context: dict) -> str:
61
  # Convert data to clean JSON string strings for better LLM readability
62
  neg_json = json.dumps(context['negative_keywords'], indent=2)
 
43
  def detect_scaling_terms(df):
44
  return df[(df["conversions"] > 0)].sort_values("cpa", ascending=True)
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def build_search_optimizer_prompt(context: dict) -> str:
47
  # Convert data to clean JSON string strings for better LLM readability
48
  neg_json = json.dumps(context['negative_keywords'], indent=2)