ps1811 commited on
Commit
f7d4e07
·
1 Parent(s): 7f4e95a

Clicks trend graph added

Browse files
Files changed (1) hide show
  1. app.py +135 -3
app.py CHANGED
@@ -2,6 +2,7 @@ print("APP STARTED", flush=True)
2
 
3
  import html
4
  import gradio as gr
 
5
  import spaces
6
 
7
  from app.db.repo import init_db
@@ -937,6 +938,132 @@ SIGNAL_FLOW_HTML = """
937
  """
938
 
939
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940
  def build_right_panel_html(doctor_result=None):
941
  doctor_result = doctor_result or {}
942
 
@@ -1028,13 +1155,18 @@ def initial_data_load():
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
  # ==================================================
@@ -1175,7 +1307,7 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
1175
 
1176
  with gr.Column(scale=1, elem_classes=["center-workspace"]):
1177
  kpi_html = gr.HTML()
1178
- gr.HTML(SIGNAL_FLOW_HTML)
1179
 
1180
  with gr.Row(elem_classes=["ai-row"]):
1181
  analyze_ads_card = gr.Button(
@@ -1247,7 +1379,7 @@ with gr.Blocks(fill_height=True, fill_width=True, css=CSS) as demo:
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)
 
2
 
3
  import html
4
  import gradio as gr
5
+ import pandas as pd
6
  import spaces
7
 
8
  from app.db.repo import init_db
 
938
  """
939
 
940
 
941
+ def build_signal_flow_html(dfs=None):
942
+ if not dfs or "hourly" not in dfs or dfs["hourly"] is None or dfs["hourly"].empty:
943
+ return """
944
+ <div class="signal-card">
945
+ <div class="signal-header">
946
+ <div>
947
+ <span class="signal-title">Traffic Trend</span>
948
+ <span class="kpi-status"> [WAITING]</span>
949
+ </div>
950
+ </div>
951
+ <div class="signal-box" style="display:flex;align-items:center;justify-content:center;color:var(--custom-text-muted);font-size:14px;">
952
+ Waiting for Google Ads API data streaming...
953
+ </div>
954
+ </div>
955
+ """
956
+
957
+ hourly_df = dfs["hourly"].copy()
958
+ required_columns = {"date", "clicks", "cost"}
959
+ if not required_columns.issubset(hourly_df.columns):
960
+ return """
961
+ <div class="signal-card">
962
+ <div class="signal-header">
963
+ <div>
964
+ <span class="signal-title">Traffic Trend</span>
965
+ <span class="kpi-status"> [DATA CHECK]</span>
966
+ </div>
967
+ </div>
968
+ <div class="signal-box" style="display:flex;align-items:center;justify-content:center;color:var(--custom-text-muted);font-size:14px;">
969
+ Hourly data is missing date, clicks, or cost columns.
970
+ </div>
971
+ </div>
972
+ """
973
+
974
+ hourly_df["date"] = pd.to_datetime(hourly_df["date"], errors="coerce").dt.date
975
+ hourly_df["clicks"] = pd.to_numeric(hourly_df["clicks"], errors="coerce").fillna(0)
976
+ hourly_df["cost"] = pd.to_numeric(hourly_df["cost"], errors="coerce").fillna(0)
977
+ hourly_df = hourly_df.dropna(subset=["date"])
978
+
979
+ df_daily = (
980
+ hourly_df.groupby("date", as_index=False)
981
+ .agg({"clicks": "sum", "cost": "sum"})
982
+ .sort_values("date")
983
+ .tail(14)
984
+ .reset_index(drop=True)
985
+ )
986
+
987
+ if df_daily.empty:
988
+ return build_signal_flow_html(None)
989
+
990
+ total_days = len(df_daily)
991
+ total_clicks = int(df_daily["clicks"].sum())
992
+ total_cost = float(df_daily["cost"].sum())
993
+
994
+ if (
995
+ "campaigns" in dfs
996
+ and dfs["campaigns"] is not None
997
+ and not dfs["campaigns"].empty
998
+ and "conversions" in dfs["campaigns"].columns
999
+ ):
1000
+ total_tours = int(dfs["campaigns"]["conversions"].fillna(0).sum())
1001
+ else:
1002
+ total_tours = int(total_clicks * 0.06)
1003
+
1004
+ max_clicks = df_daily["clicks"].max()
1005
+ max_clicks = max_clicks if max_clicks > 0 else 1
1006
+
1007
+ points = []
1008
+ for idx, row in df_daily.iterrows():
1009
+ x = (idx / (total_days - 1)) * 100 if total_days > 1 else 50
1010
+ y = 100 - ((row["clicks"] / max_clicks) * 75 + 15)
1011
+ points.append((x, y))
1012
+
1013
+ line_path = "M " + " L ".join(f"{x:.1f},{y:.1f}" for x, y in points)
1014
+ first_x = points[0][0]
1015
+ last_x = points[-1][0]
1016
+ area_path = f"{line_path} L {last_x:.1f},100 L {first_x:.1f},100 Z"
1017
+
1018
+ peak_idx = int(df_daily["clicks"].idxmax())
1019
+ dot_indices = sorted({0, peak_idx, total_days - 1})
1020
+ dots_html = "".join(
1021
+ f'<circle cx="{points[i][0]:.1f}" cy="{points[i][1]:.1f}" fill="#5E6BFF" r="1.8" stroke="white" stroke-width="0.6"></circle>'
1022
+ for i in dot_indices
1023
+ if i < len(points)
1024
+ )
1025
+
1026
+ return f"""
1027
+ <div class="signal-card">
1028
+ <div class="signal-header">
1029
+ <div>
1030
+ <span class="signal-title">Traffic Trend</span>
1031
+ <span class="kpi-status"></span>
1032
+ </div>
1033
+ <div class="kpi-status">
1034
+ <span style="color: var(--primary);">{format_number(total_clicks)} Clicks</span>
1035
+ <span style="margin-left: 16px; color: var(--secondary);">{format_number(total_tours)} Tours Booked</span>
1036
+ <span style="margin-left: 16px; color: var(--custom-text-muted);">{format_money(total_cost)} Spent</span>
1037
+ </div>
1038
+ </div>
1039
+ <div class="signal-box">
1040
+ <div class="grid-lines"><span></span><span></span><span></span><span></span></div>
1041
+ <div class="vertical-lines"><span></span><span></span><span></span><span></span><span></span><span></span></div>
1042
+ <svg preserveAspectRatio="none" viewBox="0 0 100 100">
1043
+ <defs>
1044
+ <linearGradient id="gradient-flow-daily" x1="0%" x2="0%" y1="0%" y2="100%">
1045
+ <stop offset="0%" stop-color="#5E6BFF" stop-opacity="0.25"></stop>
1046
+ <stop offset="100%" stop-color="#5E6BFF" stop-opacity="0"></stop>
1047
+ </linearGradient>
1048
+ <filter height="140%" id="glow-daily" width="140%" x="-20%" y="-20%">
1049
+ <feGaussianBlur result="blur" stdDeviation="1"></feGaussianBlur>
1050
+ <feComposite in="SourceGraphic" in2="blur" operator="over"></feComposite>
1051
+ </filter>
1052
+ </defs>
1053
+ <path d="{area_path}" fill="url(#gradient-flow-daily)"></path>
1054
+ <path d="{line_path}" fill="none" filter="url(#glow-daily)" stroke="#5E6BFF" stroke-linecap="round" stroke-width="1.2"></path>
1055
+ {dots_html}
1056
+ </svg>
1057
+ <div style="position: absolute; bottom: 6px; right: 10px; color: rgba(154,157,163,0.4); font-size: 10px; font-weight: 500;">Last 14 days</div>
1058
+ <div style="position: absolute; top: 6px; left: 10px; color: rgba(154,157,163,0.4); font-size: 10px; font-weight: 500;">Daily Clicks (Last 14 Days)</div>
1059
+ </div>
1060
+ </div>
1061
+ """
1062
+
1063
+
1064
+ SIGNAL_FLOW_HTML = build_signal_flow_html()
1065
+
1066
+
1067
  def build_right_panel_html(doctor_result=None):
1068
  doctor_result = doctor_result or {}
1069
 
 
1155
  )
1156
  hero_html = build_hero_html(spend, leads, cpl, count)
1157
  kpi_html = build_kpi_html(spend, leads, cpl, count)
1158
+ try:
1159
+ signal_flow_html = build_signal_flow_html(dfs)
1160
+ except Exception as e:
1161
+ print(f"SIGNAL FLOW CHART FAILED: {e}", flush=True)
1162
+ signal_flow_html = build_signal_flow_html()
1163
  try:
1164
  doctor_result = run_campaign_doctor(dfs)
1165
  except Exception as e:
1166
  print(f"CAMPAIGN DOCTOR FAILED: {e}", flush=True)
1167
  doctor_result = None
1168
  right_panel_html = build_right_panel_html(doctor_result)
1169
+ return dfs, gr.update(choices=campaign_choices, value=None), hero_html, kpi_html, signal_flow_html, right_panel_html
1170
 
1171
 
1172
  # ==================================================
 
1307
 
1308
  with gr.Column(scale=1, elem_classes=["center-workspace"]):
1309
  kpi_html = gr.HTML()
1310
+ signal_flow_html = gr.HTML(SIGNAL_FLOW_HTML)
1311
 
1312
  with gr.Row(elem_classes=["ai-row"]):
1313
  analyze_ads_card = gr.Button(
 
1379
 
1380
  demo.load(
1381
  fn=initial_data_load,
1382
+ outputs=[full_state, campaign_picker, hero_html, kpi_html, signal_flow_html, right_panel_html],
1383
  )
1384
 
1385
  demo.load(fn=startup)