Jay-2776 commited on
Commit
12e1869
Β·
verified Β·
1 Parent(s): c84b858

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -44
app.py CHANGED
@@ -21,7 +21,7 @@ from google.analytics.data_v1beta.types import (
21
  warnings.filterwarnings("ignore", category=FutureWarning)
22
 
23
  # ==========================================================
24
- # ENVIRONMENT
25
  # ==========================================================
26
  SERVICE_ACCOUNT_JSON = json.loads(os.environ["SERVICE_ACCOUNT_JSON"])
27
  GA4_PROPERTY_ID = os.environ["GA4_PROPERTY_ID"]
@@ -44,8 +44,13 @@ def clean_path(url):
44
  return "/ (Home)"
45
  return path[:45] + "…" if len(path) > 45 else path
46
 
 
 
 
 
 
47
  # ==========================================================
48
- # PAGE PATH FETCH (CACHED + TOP PAGES)
49
  # ==========================================================
50
  @lru_cache(maxsize=3)
51
  def fetch_top_page_paths(start, end, limit=300):
@@ -60,9 +65,7 @@ def fetch_top_page_paths(start, end, limit=300):
60
  }],
61
  limit=limit
62
  )
63
-
64
  res = ga.run_report(req)
65
-
66
  return sorted({
67
  r.dimension_values[0].value
68
  for r in res.rows
@@ -93,7 +96,6 @@ def fetch_journey_paths(start, end, landing_page):
93
  ),
94
  limit=3000
95
  )
96
-
97
  res = ga.run_report(req)
98
 
99
  rows = []
@@ -143,12 +145,11 @@ def check_form_submission(start, end, landing_page):
143
  ])
144
  )
145
  )
146
-
147
  res = ga.run_report(req)
148
  return sum(int(r.metric_values[0].value) for r in res.rows)
149
 
150
  # ==========================================================
151
- # MAIN JOURNEY ANALYSIS
152
  # ==========================================================
153
  def run_journey(start, end, landing_page):
154
  df = fetch_journey_paths(start, end, landing_page)
@@ -156,22 +157,60 @@ def run_journey(start, end, landing_page):
156
  if df.empty:
157
  return None, pd.DataFrame(), "No journey data found."
158
 
159
- # Reduce noise (top 80%)
160
  df = df.sort_values("Views", ascending=False)
161
  df["cum_pct"] = df["Views"].cumsum() / df["Views"].sum()
162
  df = df[df["cum_pct"] <= 0.80]
163
 
164
- total_views = df["Views"].sum()
165
  form_yes = check_form_submission(start, end, landing_page)
166
- form_no = max(total_views - form_yes, 0)
 
 
 
 
 
 
167
 
168
- # Exit table
169
- exit_df = df.groupby("From")["Views"].sum().reset_index()
170
- exit_df.columns = ["Page", "Exit Views"]
171
 
172
- # =====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  # SANKEY VISUALIZATION
174
- # =====================
175
  NODE_COLORS = {
176
  "entry": "rgba(59,130,246,0.85)",
177
  "page": "rgba(107,114,128,0.75)",
@@ -179,15 +218,15 @@ def run_journey(start, end, landing_page):
179
  "form": "rgba(34,197,94,0.9)"
180
  }
181
 
182
- labels, node_colors, index = [], [], {}
183
 
184
  def add_node(name, t):
185
  name = clean_path(name)
186
- if name not in index:
187
- index[name] = len(labels)
188
  labels.append(name)
189
  node_colors.append(NODE_COLORS[t])
190
- return index[name]
191
 
192
  sources, targets, values, colors = [], [], [], []
193
 
@@ -202,39 +241,28 @@ def run_journey(start, end, landing_page):
202
  page_node = add_node(landing_page, "page")
203
 
204
  if form_yes > 0:
205
- form_node = add_node("Form Submitted", "form")
206
  sources.append(page_node)
207
- targets.append(form_node)
208
  values.append(form_yes)
209
  colors.append("rgba(34,197,94,0.6)")
210
 
211
- exit_node = add_node("Exit", "exit")
212
  sources.append(page_node)
213
- targets.append(exit_node)
214
  values.append(form_no)
215
  colors.append("rgba(239,68,68,0.6)")
216
 
217
  fig = go.Figure(go.Sankey(
218
  arrangement="snap",
219
- node=dict(
220
- label=labels,
221
- color=node_colors,
222
- pad=22,
223
- thickness=18
224
- ),
225
- link=dict(
226
- source=sources,
227
- target=targets,
228
- value=values,
229
- color=colors
230
- )
231
  ))
232
 
233
  fig.update_layout(
234
  title="Customer Journey Flow<br><sup>Entry β†’ Page β†’ Exit / Form</sup>",
235
  height=650,
236
- font=dict(size=11),
237
- margin=dict(l=10, r=10, t=60, b=10)
238
  )
239
 
240
  summary = f"""
@@ -246,7 +274,7 @@ def run_journey(start, end, landing_page):
246
  - ❌ **No Form Before Exit:** {form_no}
247
  """
248
 
249
- return fig, exit_df, summary
250
 
251
  # ==========================================================
252
  # UI
@@ -255,8 +283,8 @@ with gr.Blocks() as app:
255
  gr.Markdown("## πŸš€ Customer Journey Explorer (GA4)")
256
 
257
  with gr.Row():
258
- sd = gr.Textbox(value=str(date.today() - timedelta(days=30)), label="Start Date")
259
- ed = gr.Textbox(value=str(date.today()), label="End Date")
260
 
261
  page_dd = gr.Dropdown(
262
  label="Starting Page Path",
@@ -266,14 +294,14 @@ with gr.Blocks() as app:
266
  )
267
 
268
  with gr.Row():
269
- load_pages = gr.Button("⚑ Load Top Pages")
270
  run_btn = gr.Button("Analyze Journey", variant="primary")
271
 
272
  chart = gr.Plot()
273
- exit_table = gr.Dataframe()
274
  summary_md = gr.Markdown()
275
 
276
- load_pages.click(
277
  lambda s, e: gr.update(choices=fetch_top_page_paths(s, e)),
278
  inputs=[sd, ed],
279
  outputs=page_dd
@@ -282,7 +310,7 @@ with gr.Blocks() as app:
282
  run_btn.click(
283
  run_journey,
284
  inputs=[sd, ed, page_dd],
285
- outputs=[chart, exit_table, summary_md]
286
  )
287
 
288
  # ==========================================================
 
21
  warnings.filterwarnings("ignore", category=FutureWarning)
22
 
23
  # ==========================================================
24
+ # ENV
25
  # ==========================================================
26
  SERVICE_ACCOUNT_JSON = json.loads(os.environ["SERVICE_ACCOUNT_JSON"])
27
  GA4_PROPERTY_ID = os.environ["GA4_PROPERTY_ID"]
 
44
  return "/ (Home)"
45
  return path[:45] + "…" if len(path) > 45 else path
46
 
47
+ def classify_source(src):
48
+ if src.startswith("http") or src == "(direct)":
49
+ return "External"
50
+ return "Internal"
51
+
52
  # ==========================================================
53
+ # PAGE PATH FETCH (FAST + CACHED)
54
  # ==========================================================
55
  @lru_cache(maxsize=3)
56
  def fetch_top_page_paths(start, end, limit=300):
 
65
  }],
66
  limit=limit
67
  )
 
68
  res = ga.run_report(req)
 
69
  return sorted({
70
  r.dimension_values[0].value
71
  for r in res.rows
 
96
  ),
97
  limit=3000
98
  )
 
99
  res = ga.run_report(req)
100
 
101
  rows = []
 
145
  ])
146
  )
147
  )
 
148
  res = ga.run_report(req)
149
  return sum(int(r.metric_values[0].value) for r in res.rows)
150
 
151
  # ==========================================================
152
+ # MAIN JOURNEY LOGIC
153
  # ==========================================================
154
  def run_journey(start, end, landing_page):
155
  df = fetch_journey_paths(start, end, landing_page)
 
157
  if df.empty:
158
  return None, pd.DataFrame(), "No journey data found."
159
 
160
+ # Reduce noise (Top 80% traffic)
161
  df = df.sort_values("Views", ascending=False)
162
  df["cum_pct"] = df["Views"].cumsum() / df["Views"].sum()
163
  df = df[df["cum_pct"] <= 0.80]
164
 
165
+ total_users = df["Views"].sum()
166
  form_yes = check_form_submission(start, end, landing_page)
167
+ form_no = max(total_users - form_yes, 0)
168
+
169
+ # ======================================================
170
+ # ENHANCED EXIT TABLE (BUSINESS FRIENDLY)
171
+ # ======================================================
172
+ entered_df = df.groupby("From")["Views"].sum().reset_index()
173
+ entered_df.columns = ["Source", "Users Entered"]
174
 
175
+ exited_df = df.groupby("From")["Views"].sum().reset_index()
176
+ exited_df.columns = ["Source", "Users Exited"]
 
177
 
178
+ exit_table = pd.merge(entered_df, exited_df, on="Source")
179
+
180
+ exit_table["Exit Rate %"] = (
181
+ exit_table["Users Exited"] / exit_table["Users Entered"] * 100
182
+ ).round(1)
183
+
184
+ exit_table["Source Type"] = exit_table["Source"].apply(classify_source)
185
+ exit_table["Form Submitted?"] = (
186
+ "Yes" if form_yes > 0 else "No"
187
+ )
188
+ exit_table["Exit Source / Page"] = exit_table["Source"].apply(clean_path)
189
+
190
+ def insight(row):
191
+ if row["Source Type"] == "External" and row["Exit Rate %"] >= 40:
192
+ return "High exit – landing intent mismatch"
193
+ if row["Source Type"] == "Internal" and row["Exit Rate %"] >= 30:
194
+ return "Conversion leak – improve CTA"
195
+ return "Normal behavior"
196
+
197
+ exit_table["Insight"] = exit_table.apply(insight, axis=1)
198
+
199
+ exit_table = exit_table[
200
+ [
201
+ "Exit Source / Page",
202
+ "Source Type",
203
+ "Users Entered",
204
+ "Users Exited",
205
+ "Exit Rate %",
206
+ "Form Submitted?",
207
+ "Insight"
208
+ ]
209
+ ].sort_values("Users Exited", ascending=False)
210
+
211
+ # ======================================================
212
  # SANKEY VISUALIZATION
213
+ # ======================================================
214
  NODE_COLORS = {
215
  "entry": "rgba(59,130,246,0.85)",
216
  "page": "rgba(107,114,128,0.75)",
 
218
  "form": "rgba(34,197,94,0.9)"
219
  }
220
 
221
+ labels, node_colors, idx = [], [], {}
222
 
223
  def add_node(name, t):
224
  name = clean_path(name)
225
+ if name not in idx:
226
+ idx[name] = len(labels)
227
  labels.append(name)
228
  node_colors.append(NODE_COLORS[t])
229
+ return idx[name]
230
 
231
  sources, targets, values, colors = [], [], [], []
232
 
 
241
  page_node = add_node(landing_page, "page")
242
 
243
  if form_yes > 0:
244
+ f_node = add_node("Form Submitted", "form")
245
  sources.append(page_node)
246
+ targets.append(f_node)
247
  values.append(form_yes)
248
  colors.append("rgba(34,197,94,0.6)")
249
 
250
+ e_node = add_node("Exit", "exit")
251
  sources.append(page_node)
252
+ targets.append(e_node)
253
  values.append(form_no)
254
  colors.append("rgba(239,68,68,0.6)")
255
 
256
  fig = go.Figure(go.Sankey(
257
  arrangement="snap",
258
+ node=dict(label=labels, color=node_colors, pad=22, thickness=18),
259
+ link=dict(source=sources, target=targets, value=values, color=colors)
 
 
 
 
 
 
 
 
 
 
260
  ))
261
 
262
  fig.update_layout(
263
  title="Customer Journey Flow<br><sup>Entry β†’ Page β†’ Exit / Form</sup>",
264
  height=650,
265
+ font=dict(size=11)
 
266
  )
267
 
268
  summary = f"""
 
274
  - ❌ **No Form Before Exit:** {form_no}
275
  """
276
 
277
+ return fig, exit_table, summary
278
 
279
  # ==========================================================
280
  # UI
 
283
  gr.Markdown("## πŸš€ Customer Journey Explorer (GA4)")
284
 
285
  with gr.Row():
286
+ sd = gr.Textbox(label="Start Date", value=str(date.today() - timedelta(days=30)))
287
+ ed = gr.Textbox(label="End Date", value=str(date.today()))
288
 
289
  page_dd = gr.Dropdown(
290
  label="Starting Page Path",
 
294
  )
295
 
296
  with gr.Row():
297
+ load_btn = gr.Button("⚑ Load Top Pages")
298
  run_btn = gr.Button("Analyze Journey", variant="primary")
299
 
300
  chart = gr.Plot()
301
+ exit_df = gr.Dataframe()
302
  summary_md = gr.Markdown()
303
 
304
+ load_btn.click(
305
  lambda s, e: gr.update(choices=fetch_top_page_paths(s, e)),
306
  inputs=[sd, ed],
307
  outputs=page_dd
 
310
  run_btn.click(
311
  run_journey,
312
  inputs=[sd, ed, page_dd],
313
+ outputs=[chart, exit_df, summary_md]
314
  )
315
 
316
  # ==========================================================