Right panel updated
Browse files- app.py +47 -10
- app/ads1/campaign_doctor.py +3 -3
app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
print("APP STARTED", flush=True)
|
| 2 |
|
|
|
|
| 3 |
import gradio as gr
|
| 4 |
import spaces
|
| 5 |
|
|
@@ -936,7 +937,34 @@ SIGNAL_FLOW_HTML = """
|
|
| 936 |
"""
|
| 937 |
|
| 938 |
|
| 939 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 940 |
<div class="advisor-intel-title">
|
| 941 |
<div style="display:flex;align-items:center;gap:10px;">
|
| 942 |
<span class="material-symbols-outlined">bolt</span>
|
|
@@ -945,26 +973,29 @@ RIGHT_PANEL_HTML = """
|
|
| 945 |
<span class="pulse-dot"></span>
|
| 946 |
</div>
|
| 947 |
<div class="intel-section">
|
| 948 |
-
<div class="intel-label">
|
| 949 |
<div class="intel-box">
|
| 950 |
-
|
| 951 |
</div>
|
| 952 |
</div>
|
| 953 |
<div class="intel-section">
|
| 954 |
-
<div class="intel-label">
|
| 955 |
<div class="intel-box">
|
| 956 |
-
|
| 957 |
</div>
|
| 958 |
</div>
|
| 959 |
<div class="intel-section">
|
| 960 |
-
<div class="intel-label">
|
| 961 |
<div class="intel-box">
|
| 962 |
-
|
| 963 |
</div>
|
| 964 |
</div>
|
| 965 |
"""
|
| 966 |
|
| 967 |
|
|
|
|
|
|
|
|
|
|
| 968 |
def ai_card(title, body, onclick=""):
|
| 969 |
return f"""
|
| 970 |
<div class="ai-card-html clickable-card" onclick="{onclick}">
|
|
@@ -997,7 +1028,13 @@ def initial_data_load():
|
|
| 997 |
)
|
| 998 |
hero_html = build_hero_html(spend, leads, cpl, count)
|
| 999 |
kpi_html = build_kpi_html(spend, leads, cpl, count)
|
| 1000 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1001 |
|
| 1002 |
|
| 1003 |
# ==================================================
|
|
@@ -1170,7 +1207,7 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
|
|
| 1170 |
)
|
| 1171 |
|
| 1172 |
with gr.Column(elem_classes=["right-panel"]):
|
| 1173 |
-
gr.HTML(RIGHT_PANEL_HTML)
|
| 1174 |
|
| 1175 |
campaign_picker.change(
|
| 1176 |
fn=campaign_selected,
|
|
@@ -1210,7 +1247,7 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
|
|
| 1210 |
|
| 1211 |
demo.load(
|
| 1212 |
fn=initial_data_load,
|
| 1213 |
-
outputs=[full_state, campaign_picker, hero_html, kpi_html],
|
| 1214 |
)
|
| 1215 |
|
| 1216 |
demo.load(fn=startup)
|
|
|
|
| 1 |
print("APP STARTED", flush=True)
|
| 2 |
|
| 3 |
+
import html
|
| 4 |
import gradio as gr
|
| 5 |
import spaces
|
| 6 |
|
|
|
|
| 937 |
"""
|
| 938 |
|
| 939 |
|
| 940 |
+
def build_right_panel_html(doctor_result=None):
|
| 941 |
+
doctor_result = doctor_result or {}
|
| 942 |
+
|
| 943 |
+
def campaign_block(key, fallback_name, fallback_reason):
|
| 944 |
+
item = doctor_result.get(key) or {}
|
| 945 |
+
name = html.escape(str(item.get("name") or fallback_name))
|
| 946 |
+
reason = html.escape(str(item.get("reason") or fallback_reason))
|
| 947 |
+
return f'<span class="text-primary">{name}</span><br>{reason}'
|
| 948 |
+
|
| 949 |
+
best_html = campaign_block(
|
| 950 |
+
"best_campaign",
|
| 951 |
+
"Awaiting campaign data",
|
| 952 |
+
"Top performer will appear here after data loads.",
|
| 953 |
+
)
|
| 954 |
+
|
| 955 |
+
worst_html = campaign_block(
|
| 956 |
+
"budget_drain",
|
| 957 |
+
"Awaiting campaign data",
|
| 958 |
+
"Lowest-performing campaign will appear here.",
|
| 959 |
+
)
|
| 960 |
+
|
| 961 |
+
scale_html = campaign_block(
|
| 962 |
+
"scale_candidate",
|
| 963 |
+
"Awaiting campaign data",
|
| 964 |
+
"Best growth opportunity will appear here.",
|
| 965 |
+
)
|
| 966 |
+
|
| 967 |
+
return f"""
|
| 968 |
<div class="advisor-intel-title">
|
| 969 |
<div style="display:flex;align-items:center;gap:10px;">
|
| 970 |
<span class="material-symbols-outlined">bolt</span>
|
|
|
|
| 973 |
<span class="pulse-dot"></span>
|
| 974 |
</div>
|
| 975 |
<div class="intel-section">
|
| 976 |
+
<div class="intel-label">Best Campaign</div>
|
| 977 |
<div class="intel-box">
|
| 978 |
+
{best_html}
|
| 979 |
</div>
|
| 980 |
</div>
|
| 981 |
<div class="intel-section">
|
| 982 |
+
<div class="intel-label">Budget Drain</div>
|
| 983 |
<div class="intel-box">
|
| 984 |
+
{worst_html}
|
| 985 |
</div>
|
| 986 |
</div>
|
| 987 |
<div class="intel-section">
|
| 988 |
+
<div class="intel-label">Scalable Candidate</div>
|
| 989 |
<div class="intel-box">
|
| 990 |
+
{scale_html}
|
| 991 |
</div>
|
| 992 |
</div>
|
| 993 |
"""
|
| 994 |
|
| 995 |
|
| 996 |
+
RIGHT_PANEL_HTML = build_right_panel_html()
|
| 997 |
+
|
| 998 |
+
|
| 999 |
def ai_card(title, body, onclick=""):
|
| 1000 |
return f"""
|
| 1001 |
<div class="ai-card-html clickable-card" onclick="{onclick}">
|
|
|
|
| 1028 |
)
|
| 1029 |
hero_html = build_hero_html(spend, leads, cpl, count)
|
| 1030 |
kpi_html = build_kpi_html(spend, leads, cpl, count)
|
| 1031 |
+
try:
|
| 1032 |
+
doctor_result = run_campaign_doctor(dfs)
|
| 1033 |
+
except Exception as e:
|
| 1034 |
+
print(f"CAMPAIGN DOCTOR FAILED: {e}", flush=True)
|
| 1035 |
+
doctor_result = None
|
| 1036 |
+
right_panel_html = build_right_panel_html(doctor_result)
|
| 1037 |
+
return dfs, gr.update(choices=campaign_choices, value=None), hero_html, kpi_html, right_panel_html
|
| 1038 |
|
| 1039 |
|
| 1040 |
# ==================================================
|
|
|
|
| 1207 |
)
|
| 1208 |
|
| 1209 |
with gr.Column(elem_classes=["right-panel"]):
|
| 1210 |
+
right_panel_html = gr.HTML(RIGHT_PANEL_HTML)
|
| 1211 |
|
| 1212 |
campaign_picker.change(
|
| 1213 |
fn=campaign_selected,
|
|
|
|
| 1247 |
|
| 1248 |
demo.load(
|
| 1249 |
fn=initial_data_load,
|
| 1250 |
+
outputs=[full_state, campaign_picker, hero_html, kpi_html, right_panel_html],
|
| 1251 |
)
|
| 1252 |
|
| 1253 |
demo.load(fn=startup)
|
app/ads1/campaign_doctor.py
CHANGED
|
@@ -85,15 +85,15 @@ def run_campaign_doctor(dfs: dict) -> dict:
|
|
| 85 |
result = {
|
| 86 |
"best_campaign": {
|
| 87 |
"name": best["name"],
|
| 88 |
-
"reason": "
|
| 89 |
},
|
| 90 |
"budget_drain": {
|
| 91 |
"name": worst["name"],
|
| 92 |
-
"reason": "
|
| 93 |
},
|
| 94 |
"scale_candidate": {
|
| 95 |
"name": scale["name"],
|
| 96 |
-
"reason": "
|
| 97 |
}
|
| 98 |
}
|
| 99 |
|
|
|
|
| 85 |
result = {
|
| 86 |
"best_campaign": {
|
| 87 |
"name": best["name"],
|
| 88 |
+
"reason": "Most efficient converter"
|
| 89 |
},
|
| 90 |
"budget_drain": {
|
| 91 |
"name": worst["name"],
|
| 92 |
+
"reason": "Highest wasted spend"
|
| 93 |
},
|
| 94 |
"scale_candidate": {
|
| 95 |
"name": scale["name"],
|
| 96 |
+
"reason": "Strong growth potential"
|
| 97 |
}
|
| 98 |
}
|
| 99 |
|