ps1811 commited on
Commit
2585639
·
1 Parent(s): 182bdae

Added growth finder

Browse files
Files changed (2) hide show
  1. app.py +33 -3
  2. app/ads1/growth_finder.py +80 -0
app.py CHANGED
@@ -14,6 +14,8 @@ 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
  from app.ads1.budget_optimizer import run_budget_optimizer
 
 
17
 
18
  # ==================================================
19
  # ROMER / ADVISOR DASHBOARD THEME
@@ -1009,6 +1011,9 @@ def campaign_selected(campaign_name, full_state):
1009
  campaign_state = on_campaign_select(full_state, campaign_name)
1010
  return campaign_state, build_campaign_kpi_html(campaign_state.get("campaign_df"))
1011
 
 
 
 
1012
 
1013
  # ==================================================
1014
  # ADS ANALYST
@@ -1064,6 +1069,23 @@ def run_search_term_optimizer_card(state):
1064
 
1065
  except Exception as e:
1066
  return f"Search term optimization failed: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1067
 
1068
  @spaces.GPU(duration=120)
1069
  def run_keyword_inspector_card(state):
@@ -1137,9 +1159,11 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
1137
  value="Search Term Cleaner",
1138
  elem_classes=["ai-button-card", "search-term-cleaner-card"],
1139
  )
1140
- gr.HTML(ai_card("Growth Finder", "Where to scale?"))
1141
- gr.HTML(ai_card("Campaign Doctor", "What limits performance?"))
1142
-
 
 
1143
  ads_output = gr.Markdown(
1144
  value="Select a campaign and click Ads Analyst.",
1145
  elem_id="ads-output",
@@ -1172,6 +1196,12 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
1172
  outputs=ads_output,
1173
  )
1174
 
 
 
 
 
 
 
1175
  keyword_inspector_card.click(
1176
  fn=run_keyword_inspector_card,
1177
  inputs=campaign_state,
 
14
  from app.ads1.search_term_optimizer import run_search_term_optimizer
15
  from app.ads1.keyword_inspector import run_keyword_inspector
16
  from app.ads1.budget_optimizer import run_budget_optimizer
17
+ from app.ads1.growth_finder import run_growth_finder
18
+ from app.ads1.campaign_doctor import run_campaign_doctor
19
 
20
  # ==================================================
21
  # ROMER / ADVISOR DASHBOARD THEME
 
1011
  campaign_state = on_campaign_select(full_state, campaign_name)
1012
  return campaign_state, build_campaign_kpi_html(campaign_state.get("campaign_df"))
1013
 
1014
+ # def load_campaign_doctor(dfs):
1015
+ # result = run_campaign_doctor(dfs)
1016
+ # return update_right_panel(result)
1017
 
1018
  # ==================================================
1019
  # ADS ANALYST
 
1069
 
1070
  except Exception as e:
1071
  return f"Search term optimization failed: {e}"
1072
+
1073
+ @spaces.GPU(duration=120)
1074
+ def run_growth_finder_card(state):
1075
+ try:
1076
+ if not state:
1077
+ return "Select a campaign first."
1078
+
1079
+ dfs = state.get("full_dfs")
1080
+ campaign_name = state.get("campaign_name")
1081
+
1082
+ if dfs is None or campaign_name is None:
1083
+ return "Campaign state is not properly initialized."
1084
+
1085
+ return run_growth_finder(dfs, campaign_name=campaign_name)
1086
+
1087
+ except Exception as e:
1088
+ return f"Search term optimization failed: {e}"
1089
 
1090
  @spaces.GPU(duration=120)
1091
  def run_keyword_inspector_card(state):
 
1159
  value="Search Term Cleaner",
1160
  elem_classes=["ai-button-card", "search-term-cleaner-card"],
1161
  )
1162
+ growth_finder_card = gr.Button(
1163
+ value="Growth Finder",
1164
+ elem_classes=["ai-button-card", "growth-finder-card"],
1165
+ )
1166
+
1167
  ads_output = gr.Markdown(
1168
  value="Select a campaign and click Ads Analyst.",
1169
  elem_id="ads-output",
 
1196
  outputs=ads_output,
1197
  )
1198
 
1199
+ growth_finder_card.click(
1200
+ fn=run_growth_finder_card,
1201
+ inputs=campaign_state,
1202
+ outputs=ads_output,
1203
+ )
1204
+
1205
  keyword_inspector_card.click(
1206
  fn=run_keyword_inspector_card,
1207
  inputs=campaign_state,
app/ads1/growth_finder.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import pandas as pd
3
+ from app.recs.generate import generate_explanation, is_bad_llm_output
4
+
5
+ def build_growth_finder_features(df: pd.DataFrame) -> pd.DataFrame:
6
+ df = df.copy()
7
+
8
+ df["cost"] = df["cost"].fillna(0)
9
+ df["clicks"] = df["clicks"].fillna(0)
10
+ df["impressions"] = df["impressions"].fillna(0)
11
+ df["conversions"] = df.get("conversions", 0).fillna(0)
12
+
13
+ # Core signals
14
+ df["ctr"] = (df["clicks"] / df["impressions"].replace(0, 1)) * 100
15
+ df["cvr"] = (df["conversions"] / df["clicks"].replace(0, 1)) * 100
16
+ df["cpa"] = df["cost"] / df["conversions"].replace(0, 1)
17
+
18
+ # Growth signal (IMPORTANT)
19
+ df["efficiency_score"] = df["conversions"] / df["cost"].replace(0, 1)
20
+
21
+ return df
22
+
23
+ def build_growth_finder_context(dfs: dict, campaign_name: str | None = None):
24
+ df = dfs["keywords"].copy()
25
+
26
+ if campaign_name and "campaign_name" in df.columns:
27
+ df = df[df["campaign_name"] == campaign_name]
28
+
29
+ df = build_growth_finder_features(df)
30
+
31
+ # keep high-signal subset only (not rule-based, just signal control)
32
+ df = df.sort_values("cost", ascending=False).head(200)
33
+
34
+ return {
35
+ "campaign_name": campaign_name,
36
+ "keywords": df.to_dict("records")
37
+ }
38
+
39
+ def build_growth_finder_prompt(context: dict) -> str:
40
+ payload = json.dumps(context, indent=2, default=str)
41
+ name = context.get("campaign_name", "this account")
42
+
43
+ return (
44
+ f"Write 3 to 5 bullet points of actionable Google Ads scaling opportunities for {name}.\n"
45
+ "Use simple language. One insight per bullet. Start each line with '- '. No intro sentence.\n\n"
46
+ "Focus on:\n"
47
+ "- keywords/ad groups that can scale budget profitably\n"
48
+ "- patterns that show strong conversion efficiency\n"
49
+ "- underfunded high-performing segments\n"
50
+ "- expansion opportunities (similar keywords, match types, themes)\n"
51
+ "- signals of demand that are not fully exploited\n\n"
52
+ "- Only suggest scaling where performance supports it\n"
53
+ f"Data (JSON):\n{payload}"
54
+ )
55
+
56
+ def run_growth_finder(dfs: dict, campaign_name: str | None = None) -> str:
57
+ print("\n🚀 [growth_finder] STARTED", flush=True)
58
+
59
+ if not dfs or "keywords" not in dfs:
60
+ return "⚠️ No keyword data available."
61
+
62
+ context = build_growth_finder_context(dfs, campaign_name)
63
+
64
+ print("🧠 [growth_finder] context built", flush=True)
65
+
66
+ prompt = build_growth_finder_prompt(context)
67
+
68
+ print("✍️ [growth_finder] prompt built", flush=True)
69
+
70
+ result = generate_explanation(prompt)
71
+
72
+ if is_bad_llm_output(result):
73
+ print("⚠️ [growth_finder] fallback triggered", flush=True)
74
+ return (
75
+ "- Unable to generate scaling opportunities right now.\n"
76
+ "- Try again or check data quality."
77
+ )
78
+
79
+ print("📤 [growth_finder] result received", flush=True)
80
+ return result