mktgtech commited on
Commit
07f6c1a
ยท
verified ยท
1 Parent(s): b98d1d6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -114
app.py CHANGED
@@ -3,11 +3,8 @@ import time
3
  import requests
4
  import json
5
  import pandas as pd
6
- import math
7
  import gradio as gr
8
  import plotly.express as px
9
- from plotly.subplots import make_subplots
10
- import plotly.graph_objects as go
11
  from datetime import date, timedelta
12
 
13
  # =====================================================
@@ -88,78 +85,67 @@ def get_campaign_match(text, rules):
88
  def apply_business_logic(df):
89
  if df is None or df.empty: return df
90
 
91
- # 1. Quality Group
92
  if "lead_quality_score" in df.columns:
93
  df["Quality_Group"] = df["lead_quality_score"].apply(categorize_quality)
94
 
95
- # 2. Campaign Logic (Strictly tied to Landing Page now)
96
  rules = load_campaign_rules()
97
 
98
  def resolve_campaign(row):
99
  path = row.get("landing_page_path")
100
-
101
- # Exclude empty paths and generic homepages
102
  if path and path not in ["", "/", "/home"]:
103
  match = get_campaign_match(path, rules)
104
  if match:
105
  return match
106
-
107
- # If it doesn't match the landing page, it doesn't belong in the campaign
108
  return "Uncategorized"
109
 
110
  df["Campaign"] = df.apply(resolve_campaign, axis=1)
111
  return df
112
 
113
  # =====================================================
114
- # 2. PRESETS (UPDATED WITH COUNTRY/CITY/INDUSTRY)
115
  # =====================================================
116
  DASHBOARD_PRESETS = {
117
- # -- SPECIAL VIEWS --
118
- "Key Campaigns Bifurcation (MS Tech, Fintech, Adtech)": ("SPECIAL_KEY_BIFURCATION", None, None),
119
- "All Campaigns Performance": ("SPECIAL_ALL_PERFORMANCE", None, None),
120
-
121
- # -- CAMPAIGN & QUALITY --
122
  "Visits by Campaign": ("Campaign", "total_visits", "sum"),
123
  "Leads by Campaign": ("Campaign", "company_name", "count"),
124
  "Leads by Quality Group": ("Quality_Group", "company_name", "count"),
125
-
126
- # -- GEOGRAPHY --
127
- "Visits by Country": ("country", "total_visits", "sum"),
128
- "Leads by Country": ("country", "company_name", "count"),
129
  "Visits by City": ("city", "total_visits", "sum"),
130
  "Leads by City": ("city", "company_name", "count"),
131
-
132
- # -- INDUSTRY --
133
- "Visits by Industry": ("primary_industry", "total_visits", "sum"),
134
  "Leads by Industry": ("primary_industry", "company_name", "count"),
135
 
136
- # -- OTHERS --
 
 
 
137
  "Top Accounts by Visits": ("company_name", "total_visits", "sum"),
 
 
 
138
  }
139
 
140
  TREND_PRESETS = {
141
- # -- CAMPAIGN & QUALITY --
142
  "Visits Trend by Campaign": ("total_visits", "sum", "Campaign"),
143
  "Leads Trend by Campaign": ("company_name", "count", "Campaign"),
144
  "Visits Trend by Quality": ("total_visits", "sum", "Quality_Group"),
145
-
146
- # -- GEOGRAPHY --
147
- "Visits Trend by Country": ("total_visits", "sum", "country"),
148
- "Leads Trend by Country": ("company_name", "count", "country"),
149
  "Visits Trend by City": ("total_visits", "sum", "city"),
150
  "Leads Trend by City": ("company_name", "count", "city"),
151
-
152
- # -- INDUSTRY --
153
- "Visits Trend by Industry": ("total_visits", "sum", "primary_industry"),
154
  "Leads Trend by Industry": ("company_name", "count", "primary_industry"),
155
 
156
- # -- GENERAL --
157
  "Total Visits Trend": ("total_visits", "sum", None),
158
  "Active Accounts Trend": ("company_name", "count", None),
 
 
 
 
159
  }
160
 
161
  # =====================================================
162
- # 3. API HANDLING
163
  # =====================================================
164
  def make_request(url, params=None):
165
  retries = 3
@@ -319,11 +305,10 @@ def inspect_raw_json(lead_id, start_date, end_date):
319
  return str(e)
320
 
321
  # =====================================================
322
- # 5. CHART ENGINES
323
  # =====================================================
324
  def build_kpis(df):
325
- if df is None or df.empty: return 0, 0, 0, 0, 0, 0, 0
326
- if "Quality_Group" not in df.columns: df = apply_business_logic(df)
327
  return (
328
  len(df),
329
  df["total_visits"].gt(0).sum(),
@@ -331,95 +316,60 @@ def build_kpis(df):
331
  round(df["lead_quality_score"].mean(), 2),
332
  round(df["crm_organization_id"].notna().mean() * 100, 1),
333
  round(df["linkedin_url"].notna().mean() * 100, 1),
334
- df[df["Quality_Group"] == "High Quality (8-10)"].shape[0],
335
  )
336
 
337
  def build_dashboard(df, preset, top_n):
338
  if df is None or df.empty: return px.bar(title="No Data")
339
  if "Campaign" not in df.columns: df = apply_business_logic(df)
340
-
341
- if preset == "Key Campaigns Bifurcation (MS Tech, Fintech, Adtech)":
342
- target = ["MS Tech", "Fintech", "Adtech"]
343
- filtered = df[df["Campaign"].isin(target)].copy()
344
- if filtered.empty: return px.bar(title="No Data for Key Campaigns")
345
- agg = filtered.groupby("Campaign").agg(
346
- Leads_Count=("company_name", "count"),
347
- Total_Visits=("total_visits", "sum")
348
- ).reset_index()
349
- fig = go.Figure()
350
- fig.add_trace(go.Bar(x=agg["Campaign"], y=agg["Leads_Count"], name="No. of Leads", marker_color="#00C49F"))
351
- fig.add_trace(go.Bar(x=agg["Campaign"], y=agg["Total_Visits"], name="Total Visits", marker_color="#FFBB28"))
352
- fig.update_layout(title="Key Campaigns: Leads vs Visits", barmode='group')
353
- return fig
354
-
355
- if preset == "All Campaigns Performance":
356
- agg = df.groupby("Campaign").agg(
357
- Leads_Count=("company_name", "count"),
358
- Total_Visits=("total_visits", "sum")
359
- ).reset_index().sort_values("Leads_Count", ascending=False)
360
-
361
- fig = make_subplots(specs=[[{"secondary_y": True}]])
362
- fig.add_trace(go.Bar(x=agg["Campaign"], y=agg["Leads_Count"], name="Leads", marker_color="indigo"), secondary_y=False)
363
- fig.add_trace(go.Scatter(x=agg["Campaign"], y=agg["Total_Visits"], name="Visits", mode="lines+markers", line=dict(color="orange", width=3)), secondary_y=True)
364
- fig.update_layout(title_text="All Campaigns Performance")
365
- return fig
366
-
367
  dim, metric, agg = DASHBOARD_PRESETS[preset]
368
  if agg == "count":
369
  grouped = df.groupby(dim, dropna=False).size().reset_index(name="value")
370
  else:
371
  grouped = df.groupby(dim, dropna=False)[metric].agg(agg).reset_index(name="value")
372
- return px.bar(grouped.sort_values("value", ascending=False).head(top_n), x=dim, y="value", title=preset, color=dim)
373
 
374
  def build_trend(df, preset, grain, filter_values):
375
- if df is None or df.empty: return px.line(title="No Data")
376
- metric, agg, segment = TREND_PRESETS[preset]
377
- df_t = df.dropna(subset=["last_visit_date"]).copy()
378
 
379
- if segment and segment not in df_t.columns:
380
- df_t = apply_business_logic(df_t)
 
 
 
 
381
 
382
- if segment and filter_values:
383
- df_t = df_t[df_t[segment].isin(filter_values)]
384
-
385
  if grain == "Weekly":
386
- df_t["period"] = df_t["last_visit_date"].dt.to_period("W").astype(str)
 
 
387
  else:
388
- df_t["period"] = df_t["last_visit_date"].dt.date
389
-
390
  val = metric
391
  if agg == "count":
392
- df_t["_v"] = 1
393
  val = "_v"
394
-
395
  if segment:
396
- ts = df_t.groupby(["period", segment])[val].agg(agg).reset_index()
397
- return px.line(ts, x="period", y=val, color=segment, title=preset, markers=True)
398
-
399
- ts = df_t.groupby("period")[val].agg(agg).reset_index()
400
- return px.line(ts, x="period", y=val, markers=True, title=preset)
 
401
 
402
  def get_trend_filter_options(df, preset):
403
  if df is None or df.empty: return gr.update(choices=[], value=None, visible=False)
 
404
 
405
  metric, agg, segment = TREND_PRESETS[preset]
406
  if not segment: return gr.update(choices=[], value=None, visible=False)
407
 
408
- # FORCE LOGIC
409
- if segment not in df.columns:
410
- df = apply_business_logic(df)
411
-
412
  options = sorted(df[segment].astype(str).unique().tolist())
413
-
414
- # Smart Defaults for specific columns
415
- if segment == "Campaign":
416
- defaults = [o for o in options if o in ["MS Tech", "Fintech", "Adtech"]]
417
- else:
418
- defaults = options[:5] # Top 5 for City/Country/Industry
419
-
420
- if not defaults: defaults = options[:5]
421
-
422
- return gr.update(choices=options, value=defaults, visible=True, label=f"Filter {segment}")
423
 
424
  # =====================================================
425
  # 6. UI LAYOUT
@@ -431,9 +381,8 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
431
  pwd = gr.Textbox(type="password", label="App Password")
432
  gr.Button("Auth").click(lambda p: gr.Info("Success") if p==APP_PASSWORD else gr.Error("Invalid"), pwd, None)
433
  df_state = gr.State()
434
-
435
- status = gr.Markdown()
436
-
437
  with gr.Tabs():
438
  # --- TAB 1: DATA ---
439
  with gr.Tab("๐Ÿ“‹ Data & Report"):
@@ -444,19 +393,18 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
444
  btn_load = gr.Button("1. Load Data (Preview)", variant="primary")
445
  btn_dl = gr.Button("2. Enrich & Download Full Excel")
446
  file_dl = gr.File(label="Download Excel")
447
-
448
  table = gr.Dataframe(label="Preview (Top 50 Enriched)", interactive=True)
449
  btn_load.click(load_preview, [start, end], [df_state, table, status])
450
  btn_dl.click(download_full_excel, [df_state, start, end], file_dl)
451
 
452
  # --- TAB 2: DASHBOARD ---
453
  with gr.Tab("๐Ÿ“Š Dashboard"):
454
- kpis = [gr.Number(label=l) for l in ["Companies", "Active", "Visits", "Avg Quality", "CRM %", "LinkedIn %", "High Quality (8-10)"]]
455
  gr.Button("Refresh KPIs").click(build_kpis, df_state, kpis)
456
 
457
- gr.Markdown("### ๐Ÿ“Š Charts")
458
  with gr.Row():
459
- preset = gr.Dropdown(choices=list(DASHBOARD_PRESETS.keys()), label="Chart View", value="Key Campaigns Bifurcation (MS Tech, Fintech, Adtech)")
460
  top_n = gr.Slider(5, 50, value=10, label="Top N Items")
461
  chart = gr.Plot()
462
  gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
@@ -476,7 +424,6 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
476
  gr.Markdown("### Manage Campaign Groups")
477
  gr.Markdown("Select a campaign below and paste your keywords separated by commas.")
478
 
479
- # Load initial choices
480
  init_rules = load_campaign_rules()
481
  camp_choices = list(init_rules.keys()) + ["+ Create New Campaign"]
482
 
@@ -486,7 +433,6 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
486
 
487
  kw_input = gr.Textbox(label="Keywords (comma separated)", lines=5, value=", ".join(init_rules.get(camp_choices[0], [])))
488
 
489
- # Function to update the keyword box when a user selects a different campaign
490
  def update_ui_on_select(selected_camp):
491
  rules = load_campaign_rules()
492
  if selected_camp == "+ Create New Campaign":
@@ -500,26 +446,18 @@ with gr.Blocks(title="Leadfeeder Campaign Pro") as demo:
500
  save_config_btn = gr.Button("๐Ÿ’พ Save Configuration", variant="primary")
501
  config_status = gr.Markdown()
502
 
503
- # Function to save the comma separated string back to the JSON file
504
  def save_easy_config(selected_camp, new_name, kw_string):
505
  rules = load_campaign_rules()
506
-
507
- # Clean and parse the string into a list
508
  raw_kws = kw_string.split(",")
509
  clean_kws = [k.strip().lower() for k in raw_kws if k.strip()]
510
-
511
- # Determine target campaign name
512
  target_camp = new_name.strip() if selected_camp == "+ Create New Campaign" else selected_camp
513
-
514
  if not target_camp:
515
  return "โŒ Error: Campaign name cannot be empty.", gr.update()
516
 
517
- # Update Dictionary and Save
518
  rules[target_camp] = clean_kws
519
  with open(CAMPAIGN_CONFIG_FILE, "w") as f:
520
  json.dump(rules, f, indent=4)
521
 
522
- # Refresh dropdown choices
523
  updated_choices = list(rules.keys()) + ["+ Create New Campaign"]
524
  return f"โœ… Saved successfully! Updated keywords for '{target_camp}'.", gr.update(choices=updated_choices, value=target_camp)
525
 
 
3
  import requests
4
  import json
5
  import pandas as pd
 
6
  import gradio as gr
7
  import plotly.express as px
 
 
8
  from datetime import date, timedelta
9
 
10
  # =====================================================
 
85
  def apply_business_logic(df):
86
  if df is None or df.empty: return df
87
 
 
88
  if "lead_quality_score" in df.columns:
89
  df["Quality_Group"] = df["lead_quality_score"].apply(categorize_quality)
90
 
 
91
  rules = load_campaign_rules()
92
 
93
  def resolve_campaign(row):
94
  path = row.get("landing_page_path")
95
+ # Strictly check landing page to avoid generic categorizations
 
96
  if path and path not in ["", "/", "/home"]:
97
  match = get_campaign_match(path, rules)
98
  if match:
99
  return match
 
 
100
  return "Uncategorized"
101
 
102
  df["Campaign"] = df.apply(resolve_campaign, axis=1)
103
  return df
104
 
105
  # =====================================================
106
+ # 2. PRESETS (Original structure preserved)
107
  # =====================================================
108
  DASHBOARD_PRESETS = {
109
+ # --- Added New Presets ---
 
 
 
 
110
  "Visits by Campaign": ("Campaign", "total_visits", "sum"),
111
  "Leads by Campaign": ("Campaign", "company_name", "count"),
112
  "Leads by Quality Group": ("Quality_Group", "company_name", "count"),
 
 
 
 
113
  "Visits by City": ("city", "total_visits", "sum"),
114
  "Leads by City": ("city", "company_name", "count"),
115
+ "Leads by Country": ("country", "company_name", "count"),
 
 
116
  "Leads by Industry": ("primary_industry", "company_name", "count"),
117
 
118
+ # --- Original Presets ---
119
+ "Total Visits by Industry": ("primary_industry", "total_visits", "sum"),
120
+ "Total Visits by Country": ("country", "total_visits", "sum"),
121
+ "Avg Quality by Industry": ("primary_industry", "lead_quality_score", "mean"),
122
  "Top Accounts by Visits": ("company_name", "total_visits", "sum"),
123
+ "Companies by Country": ("country", "company_name", "count"),
124
+ "Accounts by Assignee": ("assignee", "company_name", "count"),
125
+ "Visits by Assignee": ("assignee", "total_visits", "sum"),
126
  }
127
 
128
  TREND_PRESETS = {
129
+ # --- Added New Presets ---
130
  "Visits Trend by Campaign": ("total_visits", "sum", "Campaign"),
131
  "Leads Trend by Campaign": ("company_name", "count", "Campaign"),
132
  "Visits Trend by Quality": ("total_visits", "sum", "Quality_Group"),
 
 
 
 
133
  "Visits Trend by City": ("total_visits", "sum", "city"),
134
  "Leads Trend by City": ("company_name", "count", "city"),
135
+ "Leads Trend by Country": ("company_name", "count", "country"),
 
 
136
  "Leads Trend by Industry": ("company_name", "count", "primary_industry"),
137
 
138
+ # --- Original Presets ---
139
  "Total Visits Trend": ("total_visits", "sum", None),
140
  "Active Accounts Trend": ("company_name", "count", None),
141
+ "Avg Lead Quality Trend": ("lead_quality_score", "mean", None),
142
+ "Visits Trend by Industry": ("total_visits", "sum", "primary_industry"),
143
+ "Visits Trend by Country": ("total_visits", "sum", "country"),
144
+ "Visits Trend by Assignee": ("total_visits", "sum", "assignee"),
145
  }
146
 
147
  # =====================================================
148
+ # 3. API HANDLING (Original structure preserved)
149
  # =====================================================
150
  def make_request(url, params=None):
151
  retries = 3
 
305
  return str(e)
306
 
307
  # =====================================================
308
+ # 5. CHART ENGINES (Restored to exact original logic)
309
  # =====================================================
310
  def build_kpis(df):
311
+ if df is None or df.empty: return 0, 0, 0, 0, 0, 0
 
312
  return (
313
  len(df),
314
  df["total_visits"].gt(0).sum(),
 
316
  round(df["lead_quality_score"].mean(), 2),
317
  round(df["crm_organization_id"].notna().mean() * 100, 1),
318
  round(df["linkedin_url"].notna().mean() * 100, 1),
 
319
  )
320
 
321
  def build_dashboard(df, preset, top_n):
322
  if df is None or df.empty: return px.bar(title="No Data")
323
  if "Campaign" not in df.columns: df = apply_business_logic(df)
324
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  dim, metric, agg = DASHBOARD_PRESETS[preset]
326
  if agg == "count":
327
  grouped = df.groupby(dim, dropna=False).size().reset_index(name="value")
328
  else:
329
  grouped = df.groupby(dim, dropna=False)[metric].agg(agg).reset_index(name="value")
330
+ return px.bar(grouped.sort_values("value", ascending=False).head(top_n), x=dim, y="value", title=preset)
331
 
332
  def build_trend(df, preset, grain, filter_values):
333
+ if df is None or df.empty: return px.line(title="No Data Loaded")
334
+ if "Campaign" not in df.columns: df = apply_business_logic(df)
 
335
 
336
+ metric, agg, segment = TREND_PRESETS[preset]
337
+ df = df.dropna(subset=["last_visit_date"]).copy()
338
+
339
+ if segment and filter_values:
340
+ df = df[df[segment].isin(filter_values)]
341
+ if df.empty: return px.line(title="No data for selected filters")
342
 
 
 
 
343
  if grain == "Weekly":
344
+ df["period"] = df["last_visit_date"].dt.to_period("W").astype(str)
345
+ elif grain == "Monthly":
346
+ df["period"] = df["last_visit_date"].dt.to_period("M").astype(str)
347
  else:
348
+ df["period"] = df["last_visit_date"].dt.date
349
+
350
  val = metric
351
  if agg == "count":
352
+ df["_v"] = df[metric].notna().astype(int)
353
  val = "_v"
354
+
355
  if segment:
356
+ ts = df.groupby(["period", segment])[val].agg(agg).reset_index()
357
+ title = f"{preset} (Filtered)" if filter_values else preset
358
+ return px.line(ts, x="period", y=val, color=segment, template="plotly_white", title=title)
359
+
360
+ ts = df.groupby("period")[val].agg(agg).reset_index()
361
+ return px.line(ts, x="period", y=val, markers=True, template="plotly_white", title=preset)
362
 
363
  def get_trend_filter_options(df, preset):
364
  if df is None or df.empty: return gr.update(choices=[], value=None, visible=False)
365
+ if "Campaign" not in df.columns: df = apply_business_logic(df)
366
 
367
  metric, agg, segment = TREND_PRESETS[preset]
368
  if not segment: return gr.update(choices=[], value=None, visible=False)
369
 
 
 
 
 
370
  options = sorted(df[segment].astype(str).unique().tolist())
371
+ top_5 = df[segment].value_counts().head(5).index.tolist()
372
+ return gr.update(choices=options, value=top_5, visible=True, label=f"Filter {segment}")
 
 
 
 
 
 
 
 
373
 
374
  # =====================================================
375
  # 6. UI LAYOUT
 
381
  pwd = gr.Textbox(type="password", label="App Password")
382
  gr.Button("Auth").click(lambda p: gr.Info("Success") if p==APP_PASSWORD else gr.Error("Invalid"), pwd, None)
383
  df_state = gr.State()
384
+ status = gr.Markdown()
385
+
 
386
  with gr.Tabs():
387
  # --- TAB 1: DATA ---
388
  with gr.Tab("๐Ÿ“‹ Data & Report"):
 
393
  btn_load = gr.Button("1. Load Data (Preview)", variant="primary")
394
  btn_dl = gr.Button("2. Enrich & Download Full Excel")
395
  file_dl = gr.File(label="Download Excel")
396
+
397
  table = gr.Dataframe(label="Preview (Top 50 Enriched)", interactive=True)
398
  btn_load.click(load_preview, [start, end], [df_state, table, status])
399
  btn_dl.click(download_full_excel, [df_state, start, end], file_dl)
400
 
401
  # --- TAB 2: DASHBOARD ---
402
  with gr.Tab("๐Ÿ“Š Dashboard"):
403
+ kpis = [gr.Number(label=l) for l in ["Total Companies", "Active Companies", "Total Visits", "Avg Quality", "CRM %", "LinkedIn %"]]
404
  gr.Button("Refresh KPIs").click(build_kpis, df_state, kpis)
405
 
 
406
  with gr.Row():
407
+ preset = gr.Dropdown(choices=list(DASHBOARD_PRESETS.keys()), label="Chart View", value="Visits by Campaign")
408
  top_n = gr.Slider(5, 50, value=10, label="Top N Items")
409
  chart = gr.Plot()
410
  gr.Button("Build View").click(build_dashboard, [df_state, preset, top_n], chart)
 
424
  gr.Markdown("### Manage Campaign Groups")
425
  gr.Markdown("Select a campaign below and paste your keywords separated by commas.")
426
 
 
427
  init_rules = load_campaign_rules()
428
  camp_choices = list(init_rules.keys()) + ["+ Create New Campaign"]
429
 
 
433
 
434
  kw_input = gr.Textbox(label="Keywords (comma separated)", lines=5, value=", ".join(init_rules.get(camp_choices[0], [])))
435
 
 
436
  def update_ui_on_select(selected_camp):
437
  rules = load_campaign_rules()
438
  if selected_camp == "+ Create New Campaign":
 
446
  save_config_btn = gr.Button("๐Ÿ’พ Save Configuration", variant="primary")
447
  config_status = gr.Markdown()
448
 
 
449
  def save_easy_config(selected_camp, new_name, kw_string):
450
  rules = load_campaign_rules()
 
 
451
  raw_kws = kw_string.split(",")
452
  clean_kws = [k.strip().lower() for k in raw_kws if k.strip()]
 
 
453
  target_camp = new_name.strip() if selected_camp == "+ Create New Campaign" else selected_camp
 
454
  if not target_camp:
455
  return "โŒ Error: Campaign name cannot be empty.", gr.update()
456
 
 
457
  rules[target_camp] = clean_kws
458
  with open(CAMPAIGN_CONFIG_FILE, "w") as f:
459
  json.dump(rules, f, indent=4)
460
 
 
461
  updated_choices = list(rules.keys()) + ["+ Create New Campaign"]
462
  return f"โœ… Saved successfully! Updated keywords for '{target_camp}'.", gr.update(choices=updated_choices, value=target_camp)
463