mktgtech commited on
Commit
65f029c
Β·
verified Β·
1 Parent(s): 312c7b1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -36
app.py CHANGED
@@ -1,4 +1,6 @@
1
  import os
 
 
2
  import requests
3
  import pandas as pd
4
  import gradio as gr
@@ -23,6 +25,23 @@ HEADERS = {
23
  "Accept": "application/json"
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  # =====================================================
27
  # DASHBOARD PRESETS
28
  # =====================================================
@@ -64,27 +83,24 @@ def authenticate(pwd):
64
  return True
65
 
66
  # =====================================================
67
- # VISITS β†’ LANDING & EXIT PAGE
68
  # =====================================================
69
  def fetch_landing_exit_pages(lead_id, start_date, end_date):
70
  try:
71
- r = requests.get(
72
  f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads/{lead_id}/visits",
73
- headers=HEADERS,
74
  params={
75
  "start_date": start_date,
76
  "end_date": end_date,
77
- "page[size]": 100
78
- },
79
- timeout=20
80
  )
81
- r.raise_for_status()
82
- visits = r.json().get("data", [])
83
 
 
84
  if not visits:
85
  return None, None
86
 
87
- visits = sorted(visits, key=lambda v: v["attributes"].get("started_at"))
88
 
89
  first_route = visits[0]["attributes"].get("visit_route", [])
90
  last_route = visits[-1]["attributes"].get("visit_route", [])
@@ -92,32 +108,32 @@ def fetch_landing_exit_pages(lead_id, start_date, end_date):
92
  landing = first_route[0]["page_path"] if first_route else None
93
  exit_page = last_route[-1]["page_path"] if last_route else None
94
 
 
 
 
95
  return landing, exit_page
96
 
97
  except Exception:
98
  return None, None
99
 
100
  # =====================================================
101
- # DATA FETCH (COMPANY + PAGE PATHS)
102
  # =====================================================
103
  def fetch_company_data(start_date, end_date):
104
  page, rows = 1, []
105
 
106
  while True:
107
- r = requests.get(
108
  f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads",
109
- headers=HEADERS,
110
  params={
111
  "start_date": start_date,
112
  "end_date": end_date,
113
  "page[number]": page,
114
  "page[size]": PAGE_SIZE
115
- },
116
- timeout=30
117
  )
118
- r.raise_for_status()
119
- js = r.json()
120
 
 
121
  data = js.get("data", [])
122
  if not data:
123
  break
@@ -133,7 +149,7 @@ def fetch_company_data(start_date, end_date):
133
  loc_id = lead.get("relationships", {}).get("location", {}).get("data", {}).get("id")
134
  loc = loc_map.get(loc_id, {})
135
 
136
- landing_page, exit_page = fetch_landing_exit_pages(
137
  lead.get("id"), start_date, end_date
138
  )
139
 
@@ -171,24 +187,26 @@ def fetch_company_data(start_date, end_date):
171
  "region": loc.get("region"),
172
  "city": loc.get("city"),
173
  "leadfeeder_url": a.get("view_in_leadfeeder"),
174
-
175
- # NEW PAGE-LEVEL FIELDS
176
- "landing_page_path": landing_page,
177
  "exit_page_path": exit_page,
178
  })
179
 
180
  page += 1
 
181
 
182
  df = pd.DataFrame(rows)
183
  df["last_visit_date"] = pd.to_datetime(df["last_visit_date"], errors="coerce")
184
  return df
185
 
186
  # =====================================================
187
- # LOAD WRAPPER
188
  # =====================================================
189
  def load_data_with_preview(start_date, end_date):
190
- df = fetch_company_data(start_date, end_date)
191
- return df, df.head(500), f"βœ… Loaded {len(df):,} companies"
 
 
 
192
 
193
  # =====================================================
194
  # EXPORT
@@ -216,6 +234,7 @@ def build_kpis(df):
216
  # =====================================================
217
  def build_dashboard(df, preset, top_n):
218
  dim, metric, agg = DASHBOARD_PRESETS[preset]
 
219
  if agg == "count":
220
  grouped = df.groupby(dim, dropna=False).size().reset_index(name="value")
221
  else:
@@ -225,7 +244,7 @@ def build_dashboard(df, preset, top_n):
225
  return px.bar(grouped, x=dim, y="value", template="plotly_white", title=preset)
226
 
227
  # =====================================================
228
- # TREND ENGINE (BAR + TABLE)
229
  # =====================================================
230
  def build_trend(df, preset, grain):
231
  metric, agg, segment = TREND_PRESETS[preset]
@@ -266,20 +285,17 @@ with gr.Blocks(title="Leadfeeder Company-Level Analytics") as demo:
266
 
267
  with gr.Tabs():
268
 
269
- # DATA TAB
270
  with gr.Tab("πŸ“‹ Data"):
271
  start = gr.Textbox(value=(date.today() - timedelta(days=30)).isoformat())
272
  end = gr.Textbox(value=date.today().isoformat())
273
-
274
  load_btn = gr.Button("Load Data")
275
- preview_table = gr.Dataframe(interactive=True)
276
  download_btn = gr.Button("⬇️ Download Excel")
277
  download_file = gr.File()
278
 
279
- load_btn.click(load_data_with_preview, [start, end], [df_state, preview_table, status])
280
  download_btn.click(export_excel, df_state, download_file)
281
 
282
- # DASHBOARD TAB
283
  with gr.Tab("πŸ“Š Dashboard"):
284
  with gr.Row():
285
  k1 = gr.Number(label="Total Companies")
@@ -297,17 +313,12 @@ with gr.Blocks(title="Leadfeeder Company-Level Analytics") as demo:
297
 
298
  gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
299
 
300
- # TRENDS TAB
301
  with gr.Tab("πŸ“ˆ Trends"):
302
  trend_view = gr.Dropdown(choices=list(TREND_PRESETS.keys()))
303
  grain = gr.Radio(["Daily", "Weekly", "Monthly"], value="Daily")
304
  plot = gr.Plot()
305
- trend_table = gr.Dataframe(interactive=False)
306
 
307
- gr.Button("Show Trend").click(
308
- build_trend,
309
- [df_state, trend_view, grain],
310
- [plot, trend_table]
311
- )
312
 
313
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
 
1
  import os
2
+ import time
3
+ import random
4
  import requests
5
  import pandas as pd
6
  import gradio as gr
 
25
  "Accept": "application/json"
26
  }
27
 
28
+ # =====================================================
29
+ # SAFE REQUEST (RATE LIMIT HANDLING)
30
+ # =====================================================
31
+ def safe_get(url, params=None, max_retries=5):
32
+ for attempt in range(max_retries):
33
+ r = requests.get(url, headers=HEADERS, params=params, timeout=30)
34
+
35
+ if r.status_code == 429:
36
+ sleep_time = (2 ** attempt) + random.uniform(0.5, 1.5)
37
+ time.sleep(sleep_time)
38
+ continue
39
+
40
+ r.raise_for_status()
41
+ return r
42
+
43
+ raise RuntimeError("Leadfeeder API rate limit exceeded. Try smaller date range.")
44
+
45
  # =====================================================
46
  # DASHBOARD PRESETS
47
  # =====================================================
 
83
  return True
84
 
85
  # =====================================================
86
+ # VISITS β†’ LANDING & EXIT PAGE (THROTTLED)
87
  # =====================================================
88
  def fetch_landing_exit_pages(lead_id, start_date, end_date):
89
  try:
90
+ r = safe_get(
91
  f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads/{lead_id}/visits",
 
92
  params={
93
  "start_date": start_date,
94
  "end_date": end_date,
95
+ "page[size]": 50
96
+ }
 
97
  )
 
 
98
 
99
+ visits = r.json().get("data", [])
100
  if not visits:
101
  return None, None
102
 
103
+ visits = sorted(visits, key=lambda v: v["attributes"].get("started_at", ""))
104
 
105
  first_route = visits[0]["attributes"].get("visit_route", [])
106
  last_route = visits[-1]["attributes"].get("visit_route", [])
 
108
  landing = first_route[0]["page_path"] if first_route else None
109
  exit_page = last_route[-1]["page_path"] if last_route else None
110
 
111
+ # IMPORTANT: throttle visit-level calls
112
+ time.sleep(0.25)
113
+
114
  return landing, exit_page
115
 
116
  except Exception:
117
  return None, None
118
 
119
  # =====================================================
120
+ # DATA FETCH (COMPANY LEVEL – RATE SAFE)
121
  # =====================================================
122
  def fetch_company_data(start_date, end_date):
123
  page, rows = 1, []
124
 
125
  while True:
126
+ r = safe_get(
127
  f"{BASE_URL}/accounts/{ACCOUNT_ID}/leads",
 
128
  params={
129
  "start_date": start_date,
130
  "end_date": end_date,
131
  "page[number]": page,
132
  "page[size]": PAGE_SIZE
133
+ }
 
134
  )
 
 
135
 
136
+ js = r.json()
137
  data = js.get("data", [])
138
  if not data:
139
  break
 
149
  loc_id = lead.get("relationships", {}).get("location", {}).get("data", {}).get("id")
150
  loc = loc_map.get(loc_id, {})
151
 
152
+ landing, exit_page = fetch_landing_exit_pages(
153
  lead.get("id"), start_date, end_date
154
  )
155
 
 
187
  "region": loc.get("region"),
188
  "city": loc.get("city"),
189
  "leadfeeder_url": a.get("view_in_leadfeeder"),
190
+ "landing_page_path": landing,
 
 
191
  "exit_page_path": exit_page,
192
  })
193
 
194
  page += 1
195
+ time.sleep(0.3) # IMPORTANT pagination throttle
196
 
197
  df = pd.DataFrame(rows)
198
  df["last_visit_date"] = pd.to_datetime(df["last_visit_date"], errors="coerce")
199
  return df
200
 
201
  # =====================================================
202
+ # LOAD WRAPPER (NO CRASH)
203
  # =====================================================
204
  def load_data_with_preview(start_date, end_date):
205
+ try:
206
+ df = fetch_company_data(start_date, end_date)
207
+ return df, df.head(500), f"βœ… Loaded {len(df):,} companies"
208
+ except Exception as e:
209
+ return None, None, f"❌ {str(e)}"
210
 
211
  # =====================================================
212
  # EXPORT
 
234
  # =====================================================
235
  def build_dashboard(df, preset, top_n):
236
  dim, metric, agg = DASHBOARD_PRESETS[preset]
237
+
238
  if agg == "count":
239
  grouped = df.groupby(dim, dropna=False).size().reset_index(name="value")
240
  else:
 
244
  return px.bar(grouped, x=dim, y="value", template="plotly_white", title=preset)
245
 
246
  # =====================================================
247
+ # TREND ENGINE (CHART + TABLE)
248
  # =====================================================
249
  def build_trend(df, preset, grain):
250
  metric, agg, segment = TREND_PRESETS[preset]
 
285
 
286
  with gr.Tabs():
287
 
 
288
  with gr.Tab("πŸ“‹ Data"):
289
  start = gr.Textbox(value=(date.today() - timedelta(days=30)).isoformat())
290
  end = gr.Textbox(value=date.today().isoformat())
 
291
  load_btn = gr.Button("Load Data")
292
+ preview = gr.Dataframe(interactive=True)
293
  download_btn = gr.Button("⬇️ Download Excel")
294
  download_file = gr.File()
295
 
296
+ load_btn.click(load_data_with_preview, [start, end], [df_state, preview, status])
297
  download_btn.click(export_excel, df_state, download_file)
298
 
 
299
  with gr.Tab("πŸ“Š Dashboard"):
300
  with gr.Row():
301
  k1 = gr.Number(label="Total Companies")
 
313
 
314
  gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
315
 
 
316
  with gr.Tab("πŸ“ˆ Trends"):
317
  trend_view = gr.Dropdown(choices=list(TREND_PRESETS.keys()))
318
  grain = gr.Radio(["Daily", "Weekly", "Monthly"], value="Daily")
319
  plot = gr.Plot()
320
+ table = gr.Dataframe()
321
 
322
+ gr.Button("Show Trend").click(build_trend, [df_state, trend_view, grain], [plot, table])
 
 
 
 
323
 
324
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)