mktgtech commited on
Commit
1061930
·
verified ·
1 Parent(s): b1676f4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -37
app.py CHANGED
@@ -18,7 +18,7 @@ if not API_TOKEN or not APP_PASSWORD:
18
 
19
  ACCOUNT_ID = "255333" # PREMIUM account
20
  BASE_URL = "https://api.leadfeeder.com"
21
- PAGE_SIZE = 50
22
 
23
  HEADERS = {
24
  "Authorization": f"Token token={API_TOKEN}",
@@ -26,7 +26,7 @@ HEADERS = {
26
  }
27
 
28
  # =====================================================
29
- # PRESETS (DASHBOARD & TRENDS)
30
  # =====================================================
31
  DASHBOARD_PRESETS = {
32
  "Total Visits by Industry": ("primary_industry", "total_visits", "sum"),
@@ -39,7 +39,6 @@ DASHBOARD_PRESETS = {
39
  "Visits by Assignee": ("assignee", "total_visits", "sum"),
40
  }
41
 
42
- # --- RESTORED TREND PRESETS ---
43
  TREND_PRESETS = {
44
  "Total Visits Trend": ("total_visits", "sum", None),
45
  "Active Accounts Trend": ("company_name", "count", None),
@@ -144,21 +143,17 @@ def fetch_basic_leads(start_date, end_date):
144
  return df
145
 
146
  def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=gr.Progress()):
147
- """
148
- Step 2: Go back and fetch the specific Visit URLs for each company.
149
- Includes logic to parse 'visit_route' for the correct Exit Page.
150
- """
151
  if df.empty:
152
  return df
153
 
154
  target_df = df.head(max_rows) if max_rows else df
155
  total = len(target_df)
156
 
157
- print(f"🕵️ Deep enriching {total} rows with visit data ({start_date} to {end_date})...")
158
 
159
  for index, row in target_df.iterrows():
160
  lead_id = row["lead_id"]
161
-
162
  try:
163
  visit_data = make_request(
164
  f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads/{lead_id}/visits",
@@ -172,7 +167,6 @@ def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=g
172
 
173
  visits = visit_data.get("data", [])
174
  included = visit_data.get("included", [])
175
-
176
  landing = None
177
  exit_p = None
178
 
@@ -180,23 +174,17 @@ def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=g
180
  visit = visits[0]
181
  v_attrs = visit.get("attributes", {})
182
 
183
- # A. Direct Attributes (Often has Landing, rarely Exit)
184
  landing = v_attrs.get("landing_page_path") or v_attrs.get("landing_page_url")
185
 
186
- # B. Parse 'visit_route' (CRITICAL FIX for Exit Page)
187
  visit_route = v_attrs.get("visit_route", [])
188
  if visit_route and isinstance(visit_route, list):
189
- # Last item = Exit Page
190
  last_step = visit_route[-1]
191
- if not exit_p:
192
- exit_p = last_step.get("page_path") or last_step.get("page_url")
193
-
194
- # First item = Fallback Landing Page
195
- if not landing:
196
- first_step = visit_route[0]
197
- landing = first_step.get("page_path") or first_step.get("page_url")
198
 
199
- # C. Deep Link via 'included' Page Views (Backup)
200
  if not landing or not exit_p:
201
  pv_map = {p["id"]: p["attributes"] for p in included if p["type"] == "page_views"}
202
  pv_ids = [r["id"] for r in visit.get("relationships", {}).get("page_views", {}).get("data", [])]
@@ -205,14 +193,12 @@ def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=g
205
  if not landing:
206
  first_pv = pv_map.get(pv_ids[0])
207
  if first_pv: landing = first_pv.get("url") or first_pv.get("path")
208
-
209
  if not exit_p:
210
  last_pv = pv_map.get(pv_ids[-1])
211
  if last_pv: exit_p = last_pv.get("url") or last_pv.get("path")
212
 
213
  df.at[index, "landing_page_path"] = landing
214
  df.at[index, "exit_page_path"] = exit_p
215
-
216
  except Exception as e:
217
  print(f"Failed to enrich lead {lead_id}: {e}")
218
 
@@ -222,7 +208,7 @@ def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=g
222
  return df
223
 
224
  # =====================================================
225
- # DEBUGGER TOOL
226
  # =====================================================
227
  def inspect_raw_json(lead_id, start_date, end_date):
228
  if not lead_id: return "Please enter a Lead ID"
@@ -240,14 +226,12 @@ def inspect_raw_json(lead_id, start_date, end_date):
240
  return str(e)
241
 
242
  # =====================================================
243
- # UI WRAPPERS
244
  # =====================================================
245
  def load_preview(start, end):
246
  df = fetch_basic_leads(start, end)
247
- if df.empty:
248
- return df, pd.DataFrame(), "⚠️ No data found."
249
 
250
- # Preview enrichment
251
  df_preview = df.copy()
252
  df_preview = enrich_leads_with_visits(df_preview, start, end, max_rows=10)
253
 
@@ -263,7 +247,7 @@ def download_full_excel(df, start, end):
263
  return path
264
 
265
  # =====================================================
266
- # CHART ENGINES
267
  # =====================================================
268
  def build_kpis(df):
269
  if df is None or df.empty: return 0, 0, 0, 0, 0, 0
@@ -285,16 +269,41 @@ def build_dashboard(df, preset, top_n):
285
  grouped = df.groupby(dim, dropna=False)[metric].agg(agg).reset_index(name="value")
286
  return px.bar(grouped.sort_values("value", ascending=False).head(top_n), x=dim, y="value", title=preset)
287
 
288
- # --- RESTORED TREND ENGINE ---
289
- def build_trend(df, preset, grain):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  if df is None or df.empty:
291
  return px.line(title="No Data Loaded")
292
 
293
  metric, agg, segment = TREND_PRESETS[preset]
294
- # Ensure date column is valid
295
  df = df.dropna(subset=["last_visit_date"]).copy()
296
 
297
- # Create granularity column
 
 
 
 
 
 
298
  if grain == "Weekly":
299
  df["period"] = df["last_visit_date"].dt.to_period("W").astype(str)
300
  elif grain == "Monthly":
@@ -308,9 +317,11 @@ def build_trend(df, preset, grain):
308
  else:
309
  val = metric
310
 
 
311
  if segment:
312
  ts = df.groupby(["period", segment])[val].agg(agg).reset_index()
313
- return px.line(ts, x="period", y=val, color=segment, template="plotly_white", title=preset)
 
314
 
315
  ts = df.groupby("period")[val].agg(agg).reset_index()
316
  return px.line(ts, x="period", y=val, markers=True, template="plotly_white", title=preset)
@@ -357,19 +368,31 @@ with gr.Blocks(title="Leadfeeder Pro Analytics") as demo:
357
  chart = gr.Plot()
358
  gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
359
 
360
- # --- TAB 3: TRENDS (RESTORED) ---
361
  with gr.Tab("📈 Trends"):
362
  with gr.Row():
363
  trend_view = gr.Dropdown(choices=list(TREND_PRESETS.keys()), label="Select Trend")
364
  grain = gr.Radio(["Daily", "Weekly", "Monthly"], value="Daily", label="Granularity")
365
 
 
 
 
366
  plot = gr.Plot()
367
- gr.Button("Show Trend", variant="primary").click(build_trend, [df_state, trend_view, grain], plot)
 
 
 
 
 
 
 
 
 
368
 
369
  # --- TAB 4: DEBUGGER ---
370
  with gr.Tab("🛠️ Debugger"):
371
  gr.Markdown("Use this to inspect raw API data for a specific lead if columns are empty.")
372
- dbg_id = gr.Textbox(label="Lead ID (Copy from Data tab)")
373
  dbg_btn = gr.Button("Inspect Raw JSON")
374
  dbg_out = gr.Code(language="json")
375
  dbg_btn.click(inspect_raw_json, [dbg_id, start, end], dbg_out)
 
18
 
19
  ACCOUNT_ID = "255333" # PREMIUM account
20
  BASE_URL = "https://api.leadfeeder.com"
21
+ PAGE_SIZE = 100
22
 
23
  HEADERS = {
24
  "Authorization": f"Token token={API_TOKEN}",
 
26
  }
27
 
28
  # =====================================================
29
+ # PRESETS
30
  # =====================================================
31
  DASHBOARD_PRESETS = {
32
  "Total Visits by Industry": ("primary_industry", "total_visits", "sum"),
 
39
  "Visits by Assignee": ("assignee", "total_visits", "sum"),
40
  }
41
 
 
42
  TREND_PRESETS = {
43
  "Total Visits Trend": ("total_visits", "sum", None),
44
  "Active Accounts Trend": ("company_name", "count", None),
 
143
  return df
144
 
145
  def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=gr.Progress()):
146
+ """ Step 2: Deep enrich with visit_route parsing for Exit Page """
 
 
 
147
  if df.empty:
148
  return df
149
 
150
  target_df = df.head(max_rows) if max_rows else df
151
  total = len(target_df)
152
 
153
+ print(f"🕵️ Deep enriching {total} rows ({start_date} to {end_date})...")
154
 
155
  for index, row in target_df.iterrows():
156
  lead_id = row["lead_id"]
 
157
  try:
158
  visit_data = make_request(
159
  f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads/{lead_id}/visits",
 
167
 
168
  visits = visit_data.get("data", [])
169
  included = visit_data.get("included", [])
 
170
  landing = None
171
  exit_p = None
172
 
 
174
  visit = visits[0]
175
  v_attrs = visit.get("attributes", {})
176
 
177
+ # A. Direct Attributes
178
  landing = v_attrs.get("landing_page_path") or v_attrs.get("landing_page_url")
179
 
180
+ # B. Parse 'visit_route'
181
  visit_route = v_attrs.get("visit_route", [])
182
  if visit_route and isinstance(visit_route, list):
 
183
  last_step = visit_route[-1]
184
+ if not exit_p: exit_p = last_step.get("page_path") or last_step.get("page_url")
185
+ if not landing: landing = visit_route[0].get("page_path") or visit_route[0].get("page_url")
 
 
 
 
 
186
 
187
+ # C. Deep Link via 'included' Page Views
188
  if not landing or not exit_p:
189
  pv_map = {p["id"]: p["attributes"] for p in included if p["type"] == "page_views"}
190
  pv_ids = [r["id"] for r in visit.get("relationships", {}).get("page_views", {}).get("data", [])]
 
193
  if not landing:
194
  first_pv = pv_map.get(pv_ids[0])
195
  if first_pv: landing = first_pv.get("url") or first_pv.get("path")
 
196
  if not exit_p:
197
  last_pv = pv_map.get(pv_ids[-1])
198
  if last_pv: exit_p = last_pv.get("url") or last_pv.get("path")
199
 
200
  df.at[index, "landing_page_path"] = landing
201
  df.at[index, "exit_page_path"] = exit_p
 
202
  except Exception as e:
203
  print(f"Failed to enrich lead {lead_id}: {e}")
204
 
 
208
  return df
209
 
210
  # =====================================================
211
+ # DEBUGGER
212
  # =====================================================
213
  def inspect_raw_json(lead_id, start_date, end_date):
214
  if not lead_id: return "Please enter a Lead ID"
 
226
  return str(e)
227
 
228
  # =====================================================
229
+ # DATA WRAPPERS
230
  # =====================================================
231
  def load_preview(start, end):
232
  df = fetch_basic_leads(start, end)
233
+ if df.empty: return df, pd.DataFrame(), "⚠️ No data found."
 
234
 
 
235
  df_preview = df.copy()
236
  df_preview = enrich_leads_with_visits(df_preview, start, end, max_rows=10)
237
 
 
247
  return path
248
 
249
  # =====================================================
250
+ # DASHBOARD LOGIC
251
  # =====================================================
252
  def build_kpis(df):
253
  if df is None or df.empty: return 0, 0, 0, 0, 0, 0
 
269
  grouped = df.groupby(dim, dropna=False)[metric].agg(agg).reset_index(name="value")
270
  return px.bar(grouped.sort_values("value", ascending=False).head(top_n), x=dim, y="value", title=preset)
271
 
272
+ # =====================================================
273
+ # TREND ENGINE (UPDATED WITH FILTERING)
274
+ # =====================================================
275
+ def get_trend_filter_options(df, preset):
276
+ """Dynamically returns available options for the selected segment"""
277
+ if df is None or df.empty:
278
+ return gr.update(choices=[], value=None, visible=False)
279
+
280
+ metric, agg, segment = TREND_PRESETS[preset]
281
+
282
+ if not segment:
283
+ return gr.update(choices=[], value=None, visible=False)
284
+
285
+ # Get unique values for the segment (e.g. all Industries)
286
+ options = sorted(df[segment].dropna().unique().tolist())
287
+
288
+ # Default to top 5 most frequent to avoid clutter
289
+ top_5 = df[segment].value_counts().head(5).index.tolist()
290
+
291
+ return gr.update(choices=options, value=top_5, visible=True, label=f"Filter {segment}")
292
+
293
+ def build_trend(df, preset, grain, filter_values):
294
  if df is None or df.empty:
295
  return px.line(title="No Data Loaded")
296
 
297
  metric, agg, segment = TREND_PRESETS[preset]
 
298
  df = df.dropna(subset=["last_visit_date"]).copy()
299
 
300
+ # --- APPLY FILTER IF SEGMENT EXISTS ---
301
+ if segment and filter_values:
302
+ df = df[df[segment].isin(filter_values)]
303
+ if df.empty:
304
+ return px.line(title="No data for selected filters")
305
+
306
+ # Grain Logic
307
  if grain == "Weekly":
308
  df["period"] = df["last_visit_date"].dt.to_period("W").astype(str)
309
  elif grain == "Monthly":
 
317
  else:
318
  val = metric
319
 
320
+ # Plotting
321
  if segment:
322
  ts = df.groupby(["period", segment])[val].agg(agg).reset_index()
323
+ title = f"{preset} (Filtered by {len(filter_values)} items)" if filter_values else preset
324
+ return px.line(ts, x="period", y=val, color=segment, template="plotly_white", title=title)
325
 
326
  ts = df.groupby("period")[val].agg(agg).reset_index()
327
  return px.line(ts, x="period", y=val, markers=True, template="plotly_white", title=preset)
 
368
  chart = gr.Plot()
369
  gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
370
 
371
+ # --- TAB 3: TRENDS (UPDATED) ---
372
  with gr.Tab("📈 Trends"):
373
  with gr.Row():
374
  trend_view = gr.Dropdown(choices=list(TREND_PRESETS.keys()), label="Select Trend")
375
  grain = gr.Radio(["Daily", "Weekly", "Monthly"], value="Daily", label="Granularity")
376
 
377
+ # Dynamic Filter Dropdown (Hidden initially)
378
+ filter_dropdown = gr.Dropdown(multiselect=True, visible=False, label="Filter Segments")
379
+
380
  plot = gr.Plot()
381
+
382
+ # Logic: When trend view changes -> Update filter options
383
+ trend_view.change(get_trend_filter_options, [df_state, trend_view], filter_dropdown)
384
+
385
+ # Logic: When Button Clicked -> Build Chart with Filters
386
+ gr.Button("Show Trend", variant="primary").click(
387
+ build_trend,
388
+ [df_state, trend_view, grain, filter_dropdown],
389
+ plot
390
+ )
391
 
392
  # --- TAB 4: DEBUGGER ---
393
  with gr.Tab("🛠️ Debugger"):
394
  gr.Markdown("Use this to inspect raw API data for a specific lead if columns are empty.")
395
+ dbg_id = gr.Textbox(label="Lead ID")
396
  dbg_btn = gr.Button("Inspect Raw JSON")
397
  dbg_out = gr.Code(language="json")
398
  dbg_btn.click(inspect_raw_json, [dbg_id, start, end], dbg_out)