Files changed (1) hide show
  1. app.py +64 -29
app.py CHANGED
@@ -1,6 +1,7 @@
1
  # app.py
2
- # Fixed Mutual Fund Churn Explorer - Gradio app
3
- # Fixes ValueError: numpy.float64 passed to layout.width by coercing to int and enforcing minimums.
 
4
 
5
  import gradio as gr
6
  import pandas as pd
@@ -85,6 +86,7 @@ def maps_from_dataframe(df, amc_col="AMC", company_col="Company", action_col="Ac
85
  elif act in ("fresh_buy", "fresh", "new"):
86
  fresh_buy[a].append(c)
87
  else:
 
88
  if "sell" in act:
89
  sell_map[a].append(c)
90
  elif "exit" in act:
@@ -180,13 +182,13 @@ def build_graph(AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY, in
180
  # Plotly visualizer (coerce width/height -> int with minimums)
181
  # ---------------------------
182
  def graph_to_plotly(G,
183
- node_color_amc="#9EC5FF",
184
- node_color_company="#FFCF9E",
185
  node_shape_amc="circle",
186
  node_shape_company="circle",
187
- edge_color_buy="#2ca02c",
188
- edge_color_sell="#d62728",
189
- edge_color_transfer="#888888",
190
  edge_thickness_base=1.2,
191
  show_labels=True,
192
  width=1400,
@@ -205,6 +207,7 @@ def graph_to_plotly(G,
205
  if height < 360:
206
  height = 360
207
 
 
208
  pos = nx.spring_layout(G, seed=42, k=1.4)
209
 
210
  node_x = []
@@ -233,12 +236,14 @@ def graph_to_plotly(G,
233
  hoverinfo='text'
234
  )
235
 
 
236
  edge_traces = []
237
  for u, v, attrs in G.edges(data=True):
238
  x0, y0 = pos[u]
239
  x1, y1 = pos[v]
240
  actions = attrs.get("actions", [])
241
  weight = float(attrs.get("weight", 1.0))
 
242
  if "complete_exit" in actions:
243
  color = edge_color_sell
244
  dash = "solid"
@@ -273,7 +278,7 @@ def graph_to_plotly(G,
273
 
274
  fig = go.Figure(data=edge_traces + [node_trace],
275
  layout=go.Layout(
276
- title_text="Mutual Fund Churn Network (AMCs: blue, Companies: orange)",
277
  title_x=0.5,
278
  showlegend=False,
279
  margin=dict(b=20,l=5,r=5,t=40),
@@ -354,35 +359,47 @@ def build_initial_graph_and_data():
354
  (AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY, G_initial, initial_fig) = build_initial_graph_and_data()
355
 
356
  # ---------------------------
357
- # Gradio UI
358
  # ---------------------------
359
- with gr.Blocks() as demo:
360
- gr.Markdown("# Mutual Fund Churn Explorer (fixed layout issue)")
 
 
 
361
  with gr.Row():
362
- with gr.Column(scale=3):
363
- csv_uploader = gr.File(label="Upload CSV (optional). Columns: AMC,Company,Action", file_types=['.csv'])
364
- node_color_company = gr.ColorPicker(value="#FFCF9E", label="Company node color")
365
- node_color_amc = gr.ColorPicker(value="#9EC5FF", label="AMC node color")
366
- node_shape_company = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="Company node shape")
367
- node_shape_amc = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="AMC node shape")
368
- edge_color_buy = gr.ColorPicker(value="#2ca02c", label="BUY edge color")
369
- edge_color_sell = gr.ColorPicker(value="#d62728", label="SELL edge color")
370
- edge_color_transfer = gr.ColorPicker(value="#888888", label="Transfer edge color")
371
- edge_thickness = gr.Slider(minimum=0.5, maximum=8.0, value=1.4, step=0.1, label="Edge thickness base")
372
- include_transfers_chk = gr.Checkbox(value=True, label="Infer AMC→AMC transfers (show loops)")
373
- update_btn = gr.Button("Update network")
374
- gr.Markdown("## Inspect")
375
- company_selector = gr.Dropdown(choices=COMPANIES, label="Select Company (show buyers/sellers)")
376
- amc_selector = gr.Dropdown(choices=AMCS, label="Select AMC (inferred transfers)")
377
- with gr.Column(scale=7):
 
 
 
 
 
 
 
378
  network_plot = gr.Plot(value=initial_fig, label="Network graph (drag to zoom)")
379
 
 
380
  company_plot = gr.Plot(label="Company trade summary")
381
  company_table = gr.Dataframe(headers=["Role","AMC"], interactive=False, label="Trades (company)")
382
  amc_plot = gr.Plot(label="AMC inferred transfers")
383
  amc_table = gr.Dataframe(headers=["security","buyer_amc"], interactive=False, label="Inferred transfers (AMC)")
384
  loops_text = gr.Markdown()
385
 
 
386
  def load_dataset_from_csv(file_obj):
387
  if file_obj is None:
388
  return AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY
@@ -410,6 +427,7 @@ with gr.Blocks() as demo:
410
  print("CSV load error:", e)
411
  return AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY
412
 
 
413
  def on_update(csv_file, node_color_company_val, node_color_amc_val, node_shape_company_val, node_shape_amc_val,
414
  edge_color_buy_val, edge_color_sell_val, edge_color_transfer_val, edge_thickness_val, include_transfers_val):
415
  amcs, companies, buy_map, sell_map, complete_exit, fresh_buy = load_dataset_from_csv(csv_file)
@@ -438,9 +456,23 @@ with gr.Blocks() as demo:
438
  edge_color_buy, edge_color_sell, edge_color_transfer, edge_thickness, include_transfers_chk],
439
  outputs=[network_plot, loops_text, company_selector, amc_selector])
440
 
 
 
 
 
 
 
 
 
 
 
 
441
  def on_company_sel(company_name, csv_file):
 
 
 
442
  amcs, companies, buy_map, sell_map, complete_exit, fresh_buy = load_dataset_from_csv(csv_file)
443
- fig, df = company_trade_summary(company_name, buy_map, sell_map, fresh_buy, complete_exit)
444
  if fig is None:
445
  return None, pd.DataFrame([], columns=["Role","AMC"])
446
  return fig, df
@@ -448,8 +480,11 @@ with gr.Blocks() as demo:
448
  company_selector.change(on_company_sel, inputs=[company_selector, csv_uploader], outputs=[company_plot, company_table])
449
 
450
  def on_amc_sel(amc_name, csv_file):
 
 
 
451
  amcs, companies, buy_map, sell_map, complete_exit, fresh_buy = load_dataset_from_csv(csv_file)
452
- fig, df = amc_transfer_summary(amc_name, buy_map, sell_map)
453
  if fig is None:
454
  return None, pd.DataFrame([], columns=["security","buyer_amc"])
455
  return fig, df
 
1
  # app.py
2
+ # Mutual Fund Churn Explorer - final version with collapsible sidebar + deep green theme
3
+ # Save as app.py and run with: python app.py
4
+ # requirements.txt should include: gradio, networkx, plotly, pandas, numpy
5
 
6
  import gradio as gr
7
  import pandas as pd
 
86
  elif act in ("fresh_buy", "fresh", "new"):
87
  fresh_buy[a].append(c)
88
  else:
89
+ # fallback heuristics
90
  if "sell" in act:
91
  sell_map[a].append(c)
92
  elif "exit" in act:
 
182
  # Plotly visualizer (coerce width/height -> int with minimums)
183
  # ---------------------------
184
  def graph_to_plotly(G,
185
+ node_color_amc="#0f5132", # deep green theme: use darker green for AMCs by default
186
+ node_color_company="#ffc107", # amber for companies — user can change
187
  node_shape_amc="circle",
188
  node_shape_company="circle",
189
+ edge_color_buy="#28a745",
190
+ edge_color_sell="#dc3545",
191
+ edge_color_transfer="#6c757d",
192
  edge_thickness_base=1.2,
193
  show_labels=True,
194
  width=1400,
 
207
  if height < 360:
208
  height = 360
209
 
210
+ # layout: spring layout for clarity
211
  pos = nx.spring_layout(G, seed=42, k=1.4)
212
 
213
  node_x = []
 
236
  hoverinfo='text'
237
  )
238
 
239
+ # edges rendered individually so they can be styled
240
  edge_traces = []
241
  for u, v, attrs in G.edges(data=True):
242
  x0, y0 = pos[u]
243
  x1, y1 = pos[v]
244
  actions = attrs.get("actions", [])
245
  weight = float(attrs.get("weight", 1.0))
246
+ # priority styling
247
  if "complete_exit" in actions:
248
  color = edge_color_sell
249
  dash = "solid"
 
278
 
279
  fig = go.Figure(data=edge_traces + [node_trace],
280
  layout=go.Layout(
281
+ title_text="Mutual Fund Churn Network (AMCs: green, Companies: amber)",
282
  title_x=0.5,
283
  showlegend=False,
284
  margin=dict(b=20,l=5,r=5,t=40),
 
359
  (AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY, G_initial, initial_fig) = build_initial_graph_and_data()
360
 
361
  # ---------------------------
362
+ # GRADIO UI: deep-green theme + collapsible sidebar
363
  # ---------------------------
364
+ # Create a deep green theme using gradio theme API (primary hue green, darker accents)
365
+ deep_green_theme = gr.themes.Soft(primary_hue="green", secondary_hue="teal", spacing_size="md")
366
+
367
+ with gr.Blocks(theme=deep_green_theme) as demo:
368
+ gr.Markdown("# Mutual Fund Churn Explorer — Deep Green Theme")
369
  with gr.Row():
370
+ # Collapsible sidebar using Accordion
371
+ with gr.Accordion("⚙️ Settings (click to expand / collapse)", open=False):
372
+ with gr.Column():
373
+ gr.Markdown("### Data Input")
374
+ csv_uploader = gr.File(label="Upload CSV (optional). Columns: AMC,Company,Action", file_types=['.csv'])
375
+ gr.Markdown("### Node Appearance")
376
+ node_color_company = gr.ColorPicker(value="#ffc107", label="Company node color")
377
+ node_color_amc = gr.ColorPicker(value="#0f5132", label="AMC node color")
378
+ node_shape_company = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="Company node shape")
379
+ node_shape_amc = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="AMC node shape")
380
+ gr.Markdown("### Edge Appearance")
381
+ edge_color_buy = gr.ColorPicker(value="#28a745", label="BUY edge color")
382
+ edge_color_sell = gr.ColorPicker(value="#dc3545", label="SELL edge color")
383
+ edge_color_transfer = gr.ColorPicker(value="#6c757d", label="Transfer edge color")
384
+ edge_thickness = gr.Slider(minimum=0.5, maximum=8.0, value=1.4, step=0.1, label="Edge thickness base")
385
+ include_transfers_chk = gr.Checkbox(value=True, label="Infer AMC→AMC transfers (show loops)")
386
+ update_btn = gr.Button("Update network")
387
+ gr.Markdown("### Inspect")
388
+ # do not set default value to avoid mismatch warnings after dropdown choices update
389
+ company_selector = gr.Dropdown(choices=COMPANIES, label="Select Company (show buyers/sellers)")
390
+ amc_selector = gr.Dropdown(choices=AMCS, label="Select AMC (inferred transfers)")
391
+ # Graph column
392
+ with gr.Column():
393
  network_plot = gr.Plot(value=initial_fig, label="Network graph (drag to zoom)")
394
 
395
+ # outputs for inspection
396
  company_plot = gr.Plot(label="Company trade summary")
397
  company_table = gr.Dataframe(headers=["Role","AMC"], interactive=False, label="Trades (company)")
398
  amc_plot = gr.Plot(label="AMC inferred transfers")
399
  amc_table = gr.Dataframe(headers=["security","buyer_amc"], interactive=False, label="Inferred transfers (AMC)")
400
  loops_text = gr.Markdown()
401
 
402
+ # CSV loader helper
403
  def load_dataset_from_csv(file_obj):
404
  if file_obj is None:
405
  return AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY
 
427
  print("CSV load error:", e)
428
  return AMCS, COMPANIES, BUY_MAP, SELL_MAP, COMPLETE_EXIT, FRESH_BUY
429
 
430
+ # Update callback builds new graph, detects loops and refreshes dropdown choices
431
  def on_update(csv_file, node_color_company_val, node_color_amc_val, node_shape_company_val, node_shape_amc_val,
432
  edge_color_buy_val, edge_color_sell_val, edge_color_transfer_val, edge_thickness_val, include_transfers_val):
433
  amcs, companies, buy_map, sell_map, complete_exit, fresh_buy = load_dataset_from_csv(csv_file)
 
456
  edge_color_buy, edge_color_sell, edge_color_transfer, edge_thickness, include_transfers_chk],
457
  outputs=[network_plot, loops_text, company_selector, amc_selector])
458
 
459
+ # Defensive normalizer for dropdown values (sometimes Gradio returns list)
460
+ def normalize_dropdown_value(val):
461
+ if val is None:
462
+ return None
463
+ if isinstance(val, list):
464
+ return val[0] if len(val) > 0 else None
465
+ try:
466
+ return str(val)
467
+ except Exception:
468
+ return None
469
+
470
  def on_company_sel(company_name, csv_file):
471
+ cname = normalize_dropdown_value(company_name)
472
+ if cname is None:
473
+ return None, pd.DataFrame([], columns=["Role","AMC"])
474
  amcs, companies, buy_map, sell_map, complete_exit, fresh_buy = load_dataset_from_csv(csv_file)
475
+ fig, df = company_trade_summary(cname, buy_map, sell_map, fresh_buy, complete_exit)
476
  if fig is None:
477
  return None, pd.DataFrame([], columns=["Role","AMC"])
478
  return fig, df
 
480
  company_selector.change(on_company_sel, inputs=[company_selector, csv_uploader], outputs=[company_plot, company_table])
481
 
482
  def on_amc_sel(amc_name, csv_file):
483
+ aname = normalize_dropdown_value(amc_name)
484
+ if aname is None:
485
+ return None, pd.DataFrame([], columns=["security","buyer_amc"])
486
  amcs, companies, buy_map, sell_map, complete_exit, fresh_buy = load_dataset_from_csv(csv_file)
487
+ fig, df = amc_transfer_summary(aname, buy_map, sell_map)
488
  if fig is None:
489
  return None, pd.DataFrame([], columns=["security","buyer_amc"])
490
  return fig, df