singhn9 commited on
Commit
1ac75ec
·
verified ·
1 Parent(s): 82184d1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +248 -236
app.py CHANGED
@@ -1,282 +1,294 @@
1
- # app.py
2
- # Mutual Fund Churn Explorer — Custom Modal (no Gradio.Modal required)
3
- # Works on any Gradio version, including Hugging Face default
4
-
5
  import gradio as gr
6
  import pandas as pd
7
  import networkx as nx
8
  import plotly.graph_objects as go
9
  import numpy as np
10
  from collections import defaultdict
11
- import io
12
-
13
- ########################################
14
- # DATA + LOGIC (unchanged from before)
15
- ########################################
16
 
17
- DEFAULT_AMCS = [
18
- "SBI MF","ICICI Pru MF","HDFC MF","Nippon India MF","Kotak MF",
19
- "UTI MF","Axis MF","Aditya Birla SL MF","Mirae MF","DSP MF"
 
 
 
20
  ]
21
 
22
- DEFAULT_COMPANIES = [
23
- "HDFC Bank","ICICI Bank","Bajaj Finance","Bajaj Finserv","Adani Ports",
24
- "Tata Motors","Shriram Finance","HAL","TCS","AU Small Finance Bank",
25
- "Pearl Global","Hindalco","Tata Elxsi","Cummins India","Vedanta"
26
  ]
27
 
28
- SAMPLE_BUY = {
29
- "SBI MF":["Bajaj Finance","AU Small Finance Bank"],
30
- "ICICI Pru MF":["HDFC Bank"],
31
- "HDFC MF":["Tata Elxsi","TCS"],
32
- "Nippon India MF":["Hindalco"],
33
- "Kotak MF":["Bajaj Finance"],
34
- "UTI MF":["Adani Ports","Shriram Finance"],
35
- "Axis MF":["Tata Motors","Shriram Finance"],
36
- "Aditya Birla SL MF":["AU Small Finance Bank"],
37
- "Mirae MF":["Bajaj Finance","HAL"],
38
- "DSP MF":["Tata Motors","Bajaj Finserv"]
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
 
41
- SAMPLE_SELL = {
42
- "SBI MF":["Tata Motors"],
43
- "ICICI Pru MF":["Bajaj Finance","Adani Ports"],
44
- "HDFC MF":["HDFC Bank"],
45
- "Nippon India MF":["Hindalco"],
46
- "Kotak MF":["AU Small Finance Bank"],
47
- "UTI MF":["Hindalco","TCS"],
48
- "Axis MF":["TCS"],
49
- "Aditya Birla SL MF":["Adani Ports"],
50
- "Mirae MF":["TCS"],
51
- "DSP MF":["HAL","Shriram Finance"]
52
  }
53
 
54
- SAMPLE_COMPLETE_EXIT = {"DSP MF":["Shriram Finance"]}
55
- SAMPLE_FRESH_BUY = {"HDFC MF":["Tata Elxsi"],"UTI MF":["Adani Ports"],"Mirae MF":["HAL"]}
 
 
 
56
 
57
- def sanitize_map(m, companies):
58
  out = {}
59
- for k,v in m.items():
60
- out[k] = [x for x in v if x in companies]
61
  return out
62
 
63
- def load_default_dataset():
64
- AMCS = DEFAULT_AMCS.copy()
65
- COMPANIES = DEFAULT_COMPANIES.copy()
66
- BUY = sanitize_map(SAMPLE_BUY, COMPANIES)
67
- SELL = sanitize_map(SAMPLE_SELL, COMPANIES)
68
- CEXIT = sanitize_map(SAMPLE_COMPLETE_EXIT, COMPANIES)
69
- FBUY = sanitize_map(SAMPLE_FRESH_BUY, COMPANIES)
70
- return AMCS, COMPANIES, BUY, SELL, CEXIT, FBUY
71
-
72
- def infer_transfers(buy_map, sell_map):
 
 
 
 
 
 
 
 
 
 
73
  transfers = defaultdict(int)
74
- comp_to_sellers = defaultdict(list)
75
- comp_to_buyers = defaultdict(list)
76
-
77
- for a, comps in sell_map.items():
78
- for c in comps: comp_to_sellers[c].append(a)
79
- for a, comps in buy_map.items():
80
- for c in comps: comp_to_buyers[c].append(a)
81
-
82
- for c in set(list(comp_to_sellers.keys())+list(comp_to_buyers.keys())):
83
- for s in comp_to_sellers[c]:
84
- for b in comp_to_buyers[c]:
 
 
85
  transfers[(s,b)] += 1
 
 
 
 
86
 
87
- edges = []
88
- for (s,b),w in transfers.items():
89
- edges.append((s,b,{"action":"transfer","weight":w}))
90
- return edges
91
 
92
- def build_graph(AMCS, COMPANIES, BUY, SELL, CEXIT, FBUY, include_transfers):
93
  G = nx.DiGraph()
94
- for a in AMCS: G.add_node(a,type="amc")
95
- for c in COMPANIES: G.add_node(c,type="company")
96
-
97
- def add(a,c,action,weight):
98
- if not(G.has_node(a) and G.has_node(c)): return
 
 
99
  if G.has_edge(a,c):
100
- G[a][c]["weight"] += weight
101
- G[a][c]["actions"].append(action)
102
  else:
103
- G.add_edge(a,c,weight=weight,actions=[action])
104
-
105
- for a,cs in BUY.items(): [add(a,c,"buy",1) for c in cs]
106
- for a,cs in SELL.items(): [add(a,c,"sell",1) for c in cs]
107
- for a,cs in CEXIT.items():[add(a,c,"complete_exit",3) for c in cs]
108
- for a,cs in FBUY.items(): [add(a,c,"fresh_buy",3) for c in cs]
109
-
110
  if include_transfers:
111
- tr = infer_transfers(BUY,SELL)
112
- for s,b,d in tr:
 
113
  if G.has_edge(s,b):
114
- G[s][b]["weight"] += d["weight"]
115
  G[s][b]["actions"].append("transfer")
116
  else:
117
- G.add_edge(s,b,weight=d["weight"],actions=["transfer"])
118
  return G
119
 
 
 
 
120
  def graph_to_plotly(G,
121
- node_color_amc="#0f5132",
122
- node_color_company="#ffc107",
123
- edge_color_buy="#28a745",
124
- edge_color_sell="#dc3545",
125
- edge_color_transfer="#6c757d"):
126
-
127
- pos = nx.spring_layout(G, seed=42, k=1.4)
128
-
129
- xs,ys,cols,txt,size=[],[],[],[],[]
130
- for n,d in G.nodes(data=True):
131
- x,y=pos[n]
132
- xs.append(x); ys.append(y)
133
- txt.append(n)
134
- if d["type"]=="amc":
135
- cols.append(node_color_amc); size.append(40)
 
136
  else:
137
- cols.append(node_color_company); size.append(60)
138
 
139
- nodes = go.Scatter(
140
- x=xs,y=ys,mode="markers+text",
141
- marker=dict(color=cols,size=size,line=dict(width=2,color="black")),
142
- text=txt,textposition="top center"
 
143
  )
144
 
145
- edge_traces=[]
146
- for u,v,d in G.edges(data=True):
147
- x0,y0 = pos[u]; x1,y1=pos[v]
148
- acts = d.get("actions",[])
149
- if "complete_exit" in acts:
150
- color=edge_color_sell; dash="solid"; w=4
151
- elif "fresh_buy" in acts:
152
- color=edge_color_buy; dash="solid"; w=4
153
- elif "transfer" in acts:
154
- color=edge_color_transfer; dash="dash"; w=2
155
- elif "sell" in acts:
156
- color=edge_color_sell; dash="dot"; w=2
 
157
  else:
158
- color=edge_color_buy; dash="solid"; w=2
159
-
160
- edge_traces.append(go.Scatter(
161
- x=[x0,x1,None], y=[y0,y1,None],
162
- mode="lines",
163
- line=dict(color=color,width=w,dash=dash),
164
- hoverinfo="text", text=", ".join(acts)
165
- ))
166
-
167
- fig = go.Figure(data=edge_traces+[nodes],
168
- layout=go.Layout(
169
- width=1400,height=800,
170
- showlegend=False,
171
- xaxis=dict(visible=False),
172
- yaxis=dict(visible=False),
173
- margin=dict(t=50,l=10,r=10,b=10)
174
- )
175
- )
176
  return fig
177
 
178
- #######################################
179
- # Modal-free UI
180
- #######################################
181
-
182
- AMCS,COMPANIES,BUY,SELL,CEXIT,FBUY = load_default_dataset()
183
- G0 = build_graph(AMCS,COMPANIES,BUY,SELL,CEXIT,FBUY,True)
184
- FIG0 = graph_to_plotly(G0)
185
-
186
- deep_theme = gr.themes.Soft(primary_hue="green", secondary_hue="teal")
187
-
188
- with gr.Blocks(theme=deep_theme, css="""
189
- /* Modal overlay */
190
- #custom_modal_bg {
191
- display:none;
192
- position:fixed;
193
- top:0; left:0;
194
- width:100%; height:100%;
195
- background:rgba(0,0,0,0.55);
196
- z-index:9998;
197
- }
198
- /* Modal box */
199
- #custom_modal {
200
- display:none;
201
- position:fixed;
202
- top:10%; left:50%;
203
- transform:translateX(-50%);
204
- width:420px;
205
- max-height:80%;
206
- overflow-y:auto;
207
- background:white;
208
- border-radius:12px;
209
- padding:20px;
210
- z-index:9999;
211
- box-shadow:0 0 20px rgba(0,0,0,0.4);
212
- }
213
- #settings_btn {
214
- cursor:pointer;
215
- }
216
- """) as demo:
217
-
218
- gr.Markdown("# Mutual Fund Churn Explorer")
219
-
 
 
220
  with gr.Row():
221
- with gr.Column(scale=1, min_width=80):
222
- settings_btn = gr.Button("⚙️ Settings", elem_id="settings_btn")
223
- with gr.Column(scale=11):
224
- plot = gr.Plot(value=FIG0, label="Network Graph")
225
-
226
- # Invisible modal + background mask
227
- modal_bg = gr.HTML('<div id="custom_modal_bg"></div>')
228
- modal_html = gr.HTML('<div id="custom_modal"></div>')
229
-
230
- # All settings components (hidden; rendered inside modal via JS)
231
- with gr.Column(visible=False) as settings_contents:
232
- csv_up = gr.File(label="Upload CSV")
233
- node_col_amc = gr.ColorPicker(value="#0f5132", label="AMC Node Color")
234
- node_col_cmp = gr.ColorPicker(value="#ffc107", label="Company Node Color")
235
- edge_col_buy = gr.ColorPicker(value="#28a745", label="BUY Color")
236
- edge_col_sell = gr.ColorPicker(value="#dc3545", label="SELL Color")
237
- edge_col_trans = gr.ColorPicker(value="#6c757d", label="TRANSFER Color")
238
- include_trans = gr.Checkbox(value=True, label="Infer Transfers")
239
- update_btn = gr.Button("Update Graph")
240
-
241
- # JavaScript: show modal by copying the settings block inside popup
242
- demo.load(None, None, None, _js="""
243
- (() => {
244
- const btn = document.querySelector('#settings_btn');
245
- const bg = document.querySelector('#custom_modal_bg');
246
- const mod = document.querySelector('#custom_modal');
247
- const src = document.querySelector('[data-testid="block-settings_contents"]');
248
-
249
- btn.onclick = () => {
250
- mod.innerHTML = src.innerHTML; // copy settings UI into modal
251
- bg.style.display = 'block';
252
- mod.style.display = 'block';
253
- // close when clicking outside
254
- bg.onclick = () => {
255
- mod.style.display = 'none';
256
- bg.style.display = 'none';
257
- };
258
- };
259
- })();
260
- """)
261
-
262
- # When user presses Update Graph inside modal
263
- def update_graph(csvfile, colA, colC, buyC, sellC, transC, use_trans):
264
- AM,CP,BY,SL,CE,FB = load_default_dataset()
265
- G = build_graph(AM,CP,BY,SL,CE,FB,use_trans)
266
  fig = graph_to_plotly(G,
267
- node_color_amc=colA,
268
- node_color_company=colC,
269
- edge_color_buy=buyC,
270
- edge_color_sell=sellC,
271
- edge_color_transfer=transC
272
- )
 
 
 
273
  return fig
274
 
275
- update_btn.click(
276
- fn=update_graph,
277
- inputs=[csv_up,node_col_amc,node_col_cmp,edge_col_buy,edge_col_sell,edge_col_trans,include_trans],
278
- outputs=[plot]
279
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
  if __name__ == "__main__":
282
- demo.queue().launch()
 
1
+ # app.py (fixed)
 
 
 
2
  import gradio as gr
3
  import pandas as pd
4
  import networkx as nx
5
  import plotly.graph_objects as go
6
  import numpy as np
7
  from collections import defaultdict
 
 
 
 
 
8
 
9
+ # ---------------------------
10
+ # Data (same as before)
11
+ # ---------------------------
12
+ AMCS = [
13
+ "SBI MF", "ICICI Pru MF", "HDFC MF", "Nippon India MF", "Kotak MF",
14
+ "UTI MF", "Axis MF", "Aditya Birla SL MF", "Mirae MF", "DSP MF"
15
  ]
16
 
17
+ COMPANIES = [
18
+ "HDFC Bank", "ICICI Bank", "Bajaj Finance", "Bajaj Finserv", "Adani Ports",
19
+ "Tata Motors", "Shriram Finance", "HAL", "TCS", "AU Small Finance Bank",
20
+ "Pearl Global", "Hindalco", "Tata Elxsi", "Cummins India", "Vedanta"
21
  ]
22
 
23
+ BUY_MAP = {
24
+ "SBI MF": ["Bajaj Finance", "AU Small Finance Bank"],
25
+ "ICICI Pru MF": ["HDFC Bank"],
26
+ "HDFC MF": ["Tata Elxsi", "TCS"],
27
+ "Nippon India MF": ["Hindalco"],
28
+ "Kotak MF": ["Bajaj Finance"],
29
+ "UTI MF": ["Adani Ports", "Shriram Finance"],
30
+ "Axis MF": ["Tata Motors", "Shriram Finance"],
31
+ "Aditya Birla SL MF": ["AU Small Finance Bank"],
32
+ "Mirae MF": ["Bajaj Finance", "HAL"],
33
+ "DSP MF": ["Tata Motors", "Bajaj Finserv"]
34
+ }
35
+
36
+ SELL_MAP = {
37
+ "SBI MF": ["Tata Motors"],
38
+ "ICICI Pru MF": ["Bajaj Finance", "Adani Ports"],
39
+ "HDFC MF": ["HDFC Bank"],
40
+ "Nippon India MF": ["Hindalco"],
41
+ "Kotak MF": ["AU Small Finance Bank"],
42
+ "UTI MF": ["Hindalco", "TCS"],
43
+ "Axis MF": ["TCS"],
44
+ "Aditya Birla SL MF": ["Adani Ports"],
45
+ "Mirae MF": ["TCS"],
46
+ "DSP MF": ["HAL", "Shriram Finance"]
47
  }
48
 
49
+ COMPLETE_EXIT = {
50
+ "DSP MF": ["Shriram Finance"],
 
 
 
 
 
 
 
 
 
51
  }
52
 
53
+ FRESH_BUY = {
54
+ "HDFC MF": ["Tata Elxsi"],
55
+ "UTI MF": ["Adani Ports"],
56
+ "Mirae MF": ["HAL"]
57
+ }
58
 
59
+ def sanitize_map(m):
60
  out = {}
61
+ for k, vals in m.items():
62
+ out[k] = [v for v in vals if v in COMPANIES]
63
  return out
64
 
65
+ BUY_MAP = sanitize_map(BUY_MAP)
66
+ SELL_MAP = sanitize_map(SELL_MAP)
67
+ COMPLETE_EXIT = sanitize_map(COMPLETE_EXIT)
68
+ FRESH_BUY = sanitize_map(FRESH_BUY)
69
+
70
+ company_edges = []
71
+ for amc, comps in BUY_MAP.items():
72
+ for c in comps:
73
+ company_edges.append((amc, c, {"action": "buy", "weight": 1}))
74
+ for amc, comps in SELL_MAP.items():
75
+ for c in comps:
76
+ company_edges.append((amc, c, {"action": "sell", "weight": 1}))
77
+ for amc, comps in COMPLETE_EXIT.items():
78
+ for c in comps:
79
+ company_edges.append((amc, c, {"action": "complete_exit", "weight": 3}))
80
+ for amc, comps in FRESH_BUY.items():
81
+ for c in comps:
82
+ company_edges.append((amc, c, {"action": "fresh_buy", "weight": 3}))
83
+
84
+ def infer_amc_transfers(buy_map, sell_map):
85
  transfers = defaultdict(int)
86
+ company_to_sellers = defaultdict(list)
87
+ company_to_buyers = defaultdict(list)
88
+ for amc, comps in sell_map.items():
89
+ for c in comps:
90
+ company_to_sellers[c].append(amc)
91
+ for amc, comps in buy_map.items():
92
+ for c in comps:
93
+ company_to_buyers[c].append(amc)
94
+ for c in set(list(company_to_sellers.keys()) + list(company_to_buyers.keys())):
95
+ sellers = company_to_sellers.get(c, [])
96
+ buyers = company_to_buyers.get(c, [])
97
+ for s in sellers:
98
+ for b in buyers:
99
  transfers[(s,b)] += 1
100
+ edge_list = []
101
+ for (s,b), w in transfers.items():
102
+ edge_list.append((s,b, {"action": "transfer", "weight": w, "company_count": w}))
103
+ return edge_list
104
 
105
+ transfer_edges = infer_amc_transfers(BUY_MAP, SELL_MAP)
 
 
 
106
 
107
+ def build_graph(include_transfers=True):
108
  G = nx.DiGraph()
109
+ for a in AMCS:
110
+ G.add_node(a, type="amc", label=a)
111
+ for c in COMPANIES:
112
+ G.add_node(c, type="company", label=c)
113
+ for a, c, attrs in company_edges:
114
+ if not G.has_node(a) or not G.has_node(c):
115
+ continue
116
  if G.has_edge(a,c):
117
+ G[a][c]["weight"] += attrs.get("weight",1)
118
+ G[a][c]["actions"].append(attrs["action"])
119
  else:
120
+ G.add_edge(a, c, weight=attrs.get("weight",1), actions=[attrs["action"]])
 
 
 
 
 
 
121
  if include_transfers:
122
+ for s,b,attrs in transfer_edges:
123
+ if not G.has_node(s) or not G.has_node(b):
124
+ continue
125
  if G.has_edge(s,b):
126
+ G[s][b]["weight"] += attrs.get("weight",1)
127
  G[s][b]["actions"].append("transfer")
128
  else:
129
+ G.add_edge(s, b, weight=attrs.get("weight",1), actions=["transfer"])
130
  return G
131
 
132
+ # ---------------------------
133
+ # Visualization
134
+ # ---------------------------
135
  def graph_to_plotly(G,
136
+ node_color_amc="#9EC5FF",
137
+ node_color_company="#FFCF9E",
138
+ node_shape_amc="circle",
139
+ node_shape_company="circle",
140
+ edge_color_buy="#2ca02c",
141
+ edge_color_sell="#d62728",
142
+ edge_color_transfer="#888888",
143
+ edge_thickness_base=1.2,
144
+ show_labels=True):
145
+ pos = nx.spring_layout(G, seed=42, k=1.2)
146
+ node_x, node_y, node_text, node_color, node_size = [], [], [], [], []
147
+ for n, d in G.nodes(data=True):
148
+ x, y = pos[n]
149
+ node_x.append(x); node_y.append(y); node_text.append(n)
150
+ if d["type"] == "amc":
151
+ node_color.append(node_color_amc); node_size.append(40)
152
  else:
153
+ node_color.append(node_color_company); node_size.append(60)
154
 
155
+ node_trace = go.Scatter(
156
+ x=node_x, y=node_y,
157
+ mode='markers+text' if show_labels else 'markers',
158
+ marker=dict(color=node_color, size=node_size, line=dict(width=2, color="#222")),
159
+ text=node_text if show_labels else None, textposition="top center", hoverinfo='text'
160
  )
161
 
162
+ edge_traces = []
163
+ for u, v, attrs in G.edges(data=True):
164
+ actions = attrs.get("actions",[])
165
+ weight = attrs.get("weight",1)
166
+ x0, y0 = pos[u]; x1, y1 = pos[v]
167
+ if "complete_exit" in actions:
168
+ color = edge_color_sell; dash = "solid"; width = max(edge_thickness_base * 3, 3)
169
+ elif "fresh_buy" in actions:
170
+ color = edge_color_buy; dash = "solid"; width = max(edge_thickness_base * 3, 3)
171
+ elif "transfer" in actions:
172
+ color = edge_color_transfer; dash = "dash"; width = max(edge_thickness_base * (1 + np.log1p(weight)), 1.5)
173
+ elif "sell" in actions:
174
+ color = edge_color_sell; dash = "dot"; width = max(edge_thickness_base * (1 + np.log1p(weight)), 1)
175
  else:
176
+ color = edge_color_buy; dash = "solid"; width = max(edge_thickness_base * (1 + np.log1p(weight)), 1)
177
+ trace = go.Scatter(x=[x0, x1, None], y=[y0, y1, None],
178
+ line=dict(width=width, color=color, dash=dash),
179
+ hoverinfo='none', mode='lines')
180
+ edge_traces.append(trace)
181
+
182
+ fig = go.Figure(data=edge_traces + [node_trace],
183
+ layout=go.Layout(showlegend=False, margin=dict(b=20,l=5,r=5,t=40),
184
+ xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
185
+ yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
186
+ height=900, width=1400))
 
 
 
 
 
 
 
187
  return fig
188
 
189
+ # ---------------------------
190
+ # Analysis helpers
191
+ # ---------------------------
192
+ def company_trade_summary(company_name):
193
+ buyers = [amc for amc, comps in BUY_MAP.items() if company_name in comps]
194
+ sellers = [amc for amc, comps in SELL_MAP.items() if company_name in comps]
195
+ fresh = [amc for amc, comps in FRESH_BUY.items() if company_name in comps]
196
+ exits = [amc for amc, comps in COMPLETE_EXIT.items() if company_name in comps]
197
+ df = pd.DataFrame({
198
+ "Role": ["Buyer"]*len(buyers) + ["Seller"]*len(sellers) + ["Fresh buy"]*len(fresh) + ["Complete exit"]*len(exits),
199
+ "AMC": buyers + sellers + fresh + exits
200
+ })
201
+ if df.empty:
202
+ return None, pd.DataFrame([], columns=["Role","AMC"])
203
+ counts = df.groupby("Role").size().reset_index(name="Count")
204
+ fig = go.Figure(go.Bar(x=counts["Role"], y=counts["Count"], marker_color=["green","red","orange","black"][:len(counts)]))
205
+ fig.update_layout(title_text=f"Trade summary for {company_name}", height=300, width=600)
206
+ return fig, df
207
+
208
+ def amc_transfer_summary(amc_name):
209
+ sold = SELL_MAP.get(amc_name, [])
210
+ transfers = []
211
+ for s in sold:
212
+ buyers = [amc for amc, comps in BUY_MAP.items() if s in comps]
213
+ for b in buyers:
214
+ transfers.append({"security": s, "buyer_amc": b})
215
+ df = pd.DataFrame(transfers)
216
+ if df.empty:
217
+ return None, pd.DataFrame([], columns=["security","buyer_amc"])
218
+ counts = df['buyer_amc'].value_counts().reset_index()
219
+ counts.columns = ['Buyer AMC', 'Count']
220
+ fig = go.Figure(go.Bar(x=counts['Buyer AMC'], y=counts['Count'], marker_color='lightslategray'))
221
+ fig.update_layout(title_text=f"Inferred transfers from {amc_name}", height=300, width=600)
222
+ return fig, df
223
+
224
+ # Build an initial figure (no transfers by default)
225
+ initial_G = build_graph(include_transfers=True)
226
+ initial_fig = graph_to_plotly(initial_G)
227
+
228
+ # ---------------------------
229
+ # Gradio UI (Blocks)
230
+ # ---------------------------
231
+ with gr.Blocks() as demo:
232
+ gr.Markdown("## Mutual Fund Churn explorer — interactive network + transfer analysis")
233
  with gr.Row():
234
+ with gr.Column(scale=3):
235
+ gr.Markdown("### Network customization")
236
+ node_color_company = gr.ColorPicker(value="#FFCF9E", label="Company node color")
237
+ node_color_amc = gr.ColorPicker(value="#9EC5FF", label="AMC node color")
238
+ node_shape_company = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="Company node shape")
239
+ node_shape_amc = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="AMC node shape")
240
+ edge_color_buy = gr.ColorPicker(value="#2ca02c", label="BUY edge color")
241
+ edge_color_sell = gr.ColorPicker(value="#d62728", label="SELL edge color")
242
+ edge_color_transfer = gr.ColorPicker(value="#888888", label="Transfer edge color")
243
+ edge_thickness = gr.Slider(minimum=0.5, maximum=6.0, value=1.4, step=0.1, label="Edge thickness base")
244
+ include_transfers = gr.Checkbox(value=True, label="Infer AMC → AMC transfers (show direct loops)")
245
+ update_button = gr.Button("Update network")
246
+
247
+ gr.Markdown("### Inspect specific node")
248
+ select_company = gr.Dropdown(choices=COMPANIES, label="Select company (show buyers/sellers)")
249
+ select_amc = gr.Dropdown(choices=AMCS, label="Select AMC (show inferred transfers)")
250
+ with gr.Column(scale=7):
251
+ network_plot = gr.Plot(value=initial_fig, label="Network graph (drag to zoom)")
252
+
253
+ company_out_plot = gr.Plot(label="Company trade summary")
254
+ company_out_table = gr.DataFrame(label="Trades (company)")
255
+ amc_out_plot = gr.Plot(label="AMC inferred transfers")
256
+ amc_out_table = gr.DataFrame(label="Inferred transfers (AMC)")
257
+
258
+ def update_network(node_color_company_val, node_color_amc_val, node_shape_company_val, node_shape_amc_val,
259
+ edge_color_buy_val, edge_color_sell_val, edge_color_transfer_val, edge_thickness_val,
260
+ include_transfers_val):
261
+ G = build_graph(include_transfers=include_transfers_val)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  fig = graph_to_plotly(G,
263
+ node_color_amc=node_color_amc_val,
264
+ node_color_company=node_color_company_val,
265
+ node_shape_amc=node_shape_amc_val,
266
+ node_shape_company=node_shape_company_val,
267
+ edge_color_buy=edge_color_buy_val,
268
+ edge_color_sell=edge_color_sell_val,
269
+ edge_color_transfer=edge_color_transfer_val,
270
+ edge_thickness_base=edge_thickness_val,
271
+ show_labels=True)
272
  return fig
273
 
274
+ def on_company_select(cname):
275
+ fig, df = company_trade_summary(cname)
276
+ if fig is None:
277
+ return None, pd.DataFrame([], columns=["Role","AMC"])
278
+ return fig, df
279
+
280
+ def on_amc_select(aname):
281
+ fig, df = amc_transfer_summary(aname)
282
+ if fig is None:
283
+ return None, pd.DataFrame([], columns=["security","buyer_amc"])
284
+ return fig, df
285
+
286
+ update_button.click(fn=update_network,
287
+ inputs=[node_color_company, node_color_amc, node_shape_company, node_shape_amc,
288
+ edge_color_buy, edge_color_sell, edge_color_transfer, edge_thickness, include_transfers],
289
+ outputs=[network_plot])
290
+ select_company.change(fn=on_company_select, inputs=[select_company], outputs=[company_out_plot, company_out_table])
291
+ select_amc.change(fn=on_amc_select, inputs=[select_amc], outputs=[amc_out_plot, amc_out_table])
292
 
293
  if __name__ == "__main__":
294
+ demo.launch(server_name="0.0.0.0", server_port=7860)