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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -26
app.py CHANGED
@@ -25,6 +25,30 @@ HEADERS = {
25
  "Accept": "application/json"
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  # =====================================================
29
  # UTILS: RATE LIMIT HANDLER
30
  # =====================================================
@@ -45,9 +69,7 @@ def make_request(url, params=None):
45
  # DATA FETCHING
46
  # =====================================================
47
  def fetch_basic_leads(start_date, end_date):
48
- """
49
- Step 1: Fetch ALL company details (fast fetch).
50
- """
51
  page = 1
52
  rows = []
53
 
@@ -69,7 +91,6 @@ def fetch_basic_leads(start_date, end_date):
69
  if not data:
70
  break
71
 
72
- # Map Included Locations
73
  included = js.get("included", [])
74
  loc_map = {str(i["id"]): i["attributes"] for i in included if i["type"] == "locations"}
75
 
@@ -125,7 +146,7 @@ def fetch_basic_leads(start_date, end_date):
125
  def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=gr.Progress()):
126
  """
127
  Step 2: Go back and fetch the specific Visit URLs for each company.
128
- FIX: Now specifically parses 'visit_route' to find the Exit Page.
129
  """
130
  if df.empty:
131
  return df
@@ -159,25 +180,23 @@ def enrich_leads_with_visits(df, start_date, end_date, max_rows=None, progress=g
159
  visit = visits[0]
160
  v_attrs = visit.get("attributes", {})
161
 
162
- # --- STRATEGY 1: Direct Attributes ---
163
  landing = v_attrs.get("landing_page_path") or v_attrs.get("landing_page_url")
164
 
165
- # --- STRATEGY 2: Parse 'visit_route' (The FIX for Exit Page) ---
166
- # The visit_route is a list of all pages visited.
167
- # Last item in list = Exit Page.
168
  visit_route = v_attrs.get("visit_route", [])
169
  if visit_route and isinstance(visit_route, list):
170
- # Get Last Item for Exit Page
171
  last_step = visit_route[-1]
172
  if not exit_p:
173
  exit_p = last_step.get("page_path") or last_step.get("page_url")
174
 
175
- # Fallback for Landing Page (First Item)
176
  if not landing:
177
  first_step = visit_route[0]
178
  landing = first_step.get("page_path") or first_step.get("page_url")
179
 
180
- # --- STRATEGY 3: Deep Link via 'included' Page Views (Backup) ---
181
  if not landing or not exit_p:
182
  pv_map = {p["id"]: p["attributes"] for p in included if p["type"] == "page_views"}
183
  pv_ids = [r["id"] for r in visit.get("relationships", {}).get("page_views", {}).get("data", [])]
@@ -221,13 +240,14 @@ def inspect_raw_json(lead_id, start_date, end_date):
221
  return str(e)
222
 
223
  # =====================================================
224
- # UI LOGIC
225
  # =====================================================
226
  def load_preview(start, end):
227
  df = fetch_basic_leads(start, end)
228
  if df.empty:
229
  return df, pd.DataFrame(), "⚠️ No data found."
230
 
 
231
  df_preview = df.copy()
232
  df_preview = enrich_leads_with_visits(df_preview, start, end, max_rows=10)
233
 
@@ -236,26 +256,15 @@ def load_preview(start, end):
236
 
237
  def download_full_excel(df, start, end):
238
  if df is None or df.empty: return None
239
-
240
  print("⏳ Starting full enrichment for Excel export...")
241
  enriched_df = enrich_leads_with_visits(df.copy(), start, end)
242
-
243
  path = "/tmp/leadfeeder_company_data_full.xlsx"
244
  enriched_df.to_excel(path, index=False)
245
  return path
246
 
247
  # =====================================================
248
- # DASHBOARD LOGIC (KPIs)
249
  # =====================================================
250
- DASHBOARD_PRESETS = {
251
- "Total Visits by Industry": ("primary_industry", "total_visits", "sum"),
252
- "Total Visits by Country": ("country", "total_visits", "sum"),
253
- "Avg Quality by Industry": ("primary_industry", "lead_quality_score", "mean"),
254
- "Top Accounts by Visits": ("company_name", "total_visits", "sum"),
255
- "Companies by Country": ("country", "company_name", "count"),
256
- "Visits by City": ("city", "total_visits", "sum"),
257
- }
258
-
259
  def build_kpis(df):
260
  if df is None or df.empty: return 0, 0, 0, 0, 0, 0
261
  return (
@@ -276,6 +285,36 @@ def build_dashboard(df, preset, top_n):
276
  grouped = df.groupby(dim, dropna=False)[metric].agg(agg).reset_index(name="value")
277
  return px.bar(grouped.sort_values("value", ascending=False).head(top_n), x=dim, y="value", title=preset)
278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  # =====================================================
280
  # UI LAYOUT
281
  # =====================================================
@@ -318,7 +357,16 @@ with gr.Blocks(title="Leadfeeder Pro Analytics") as demo:
318
  chart = gr.Plot()
319
  gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
320
 
321
- # --- TAB 3: DEBUGGER ---
 
 
 
 
 
 
 
 
 
322
  with gr.Tab("🛠️ Debugger"):
323
  gr.Markdown("Use this to inspect raw API data for a specific lead if columns are empty.")
324
  dbg_id = gr.Textbox(label="Lead ID (Copy from Data tab)")
 
25
  "Accept": "application/json"
26
  }
27
 
28
+ # =====================================================
29
+ # PRESETS (DASHBOARD & TRENDS)
30
+ # =====================================================
31
+ DASHBOARD_PRESETS = {
32
+ "Total Visits by Industry": ("primary_industry", "total_visits", "sum"),
33
+ "Total Visits by Country": ("country", "total_visits", "sum"),
34
+ "Avg Quality by Industry": ("primary_industry", "lead_quality_score", "mean"),
35
+ "Top Accounts by Visits": ("company_name", "total_visits", "sum"),
36
+ "Companies by Country": ("country", "company_name", "count"),
37
+ "Visits by City": ("city", "total_visits", "sum"),
38
+ "Accounts by Assignee": ("assignee", "company_name", "count"),
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),
46
+ "Avg Lead Quality Trend": ("lead_quality_score", "mean", None),
47
+ "Visits Trend by Industry": ("total_visits", "sum", "primary_industry"),
48
+ "Visits Trend by Country": ("total_visits", "sum", "country"),
49
+ "Visits Trend by Assignee": ("total_visits", "sum", "assignee"),
50
+ }
51
+
52
  # =====================================================
53
  # UTILS: RATE LIMIT HANDLER
54
  # =====================================================
 
69
  # DATA FETCHING
70
  # =====================================================
71
  def fetch_basic_leads(start_date, end_date):
72
+ """ Step 1: Fetch ALL company details (fast fetch). """
 
 
73
  page = 1
74
  rows = []
75
 
 
91
  if not data:
92
  break
93
 
 
94
  included = js.get("included", [])
95
  loc_map = {str(i["id"]): i["attributes"] for i in included if i["type"] == "locations"}
96
 
 
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
 
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", [])]
 
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
 
 
256
 
257
  def download_full_excel(df, start, end):
258
  if df is None or df.empty: return None
 
259
  print("⏳ Starting full enrichment for Excel export...")
260
  enriched_df = enrich_leads_with_visits(df.copy(), start, end)
 
261
  path = "/tmp/leadfeeder_company_data_full.xlsx"
262
  enriched_df.to_excel(path, index=False)
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
270
  return (
 
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":
301
+ df["period"] = df["last_visit_date"].dt.to_period("M").astype(str)
302
+ else:
303
+ df["period"] = df["last_visit_date"].dt.date
304
+
305
+ if agg == "count":
306
+ df["_v"] = df[metric].notna().astype(int)
307
+ val = "_v"
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)
317
+
318
  # =====================================================
319
  # UI LAYOUT
320
  # =====================================================
 
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)")