singhn9 commited on
Commit
6ebec10
·
verified ·
1 Parent(s): 8b36092

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -374
app.py CHANGED
@@ -1,427 +1,210 @@
1
  # app.py
2
- # Final fixed readable version (use this to replace your current app.py)
3
- # - ACCESS_CODE from HF Secrets (os.getenv("ACCESS_CODE"))
4
- # - Email + Access Code gate
5
- # - Logs attempts to session.log in repo root
6
- # - Readable multi-line D3 JS (radial outward labels + connected nodes)
7
- # - Inspect panels for Company and AMC
8
 
 
9
  import os
10
- import json
11
  import time
12
- from collections import defaultdict
13
  import pandas as pd
14
- import gradio as gr
15
 
16
- # --------------------
17
- # Config
18
- # --------------------
19
  ACCESS_CODE = os.getenv("ACCESS_CODE", "")
20
- LOG_FILE = "session.log"
21
- if not os.path.exists(LOG_FILE):
22
- open(LOG_FILE, "w").close()
 
 
 
23
 
24
- def write_log(email, code, ua, ok):
 
25
  ts = time.strftime("%Y-%m-%d %H:%M:%S")
26
  status = "OK" if ok else "FAIL"
27
- safe_code = (code[:2] + "***") if code else "<empty>"
28
- ua = ua or "<ua?>"
29
- line = f"{ts} | {status} | email={email} | code={safe_code} | ua={ua}\\n"
30
  try:
31
- with open(LOG_FILE, "a") as f:
32
  f.write(line)
33
  except Exception as e:
34
- print("Failed writing log:", e)
35
-
36
- # --------------------
37
- # Data
38
- # --------------------
39
- AMCS = ["SBI MF","ICICI Pru MF","HDFC MF","Nippon India MF","Kotak MF","UTI MF","Axis MF","Aditya Birla SL MF","Mirae MF","DSP MF"]
40
- COMPANIES = ["HDFC Bank","ICICI Bank","Bajaj Finance","Bajaj Finserv","Adani Ports","Tata Motors","Shriram Finance","HAL","TCS","AU Small Finance Bank","Pearl Global","Hindalco","Tata Elxsi","Cummins India","Vedanta"]
41
-
42
- BUY_MAP = {"SBI MF":["Bajaj Finance","AU Small Finance Bank"],"ICICI Pru MF":["HDFC Bank"],"HDFC MF":["Tata Elxsi","TCS"],"Nippon India MF":["Hindalco"],"Kotak MF":["Bajaj Finance"],"UTI MF":["Adani Ports","Shriram Finance"],"Axis MF":["Tata Motors","Shriram Finance"],"Aditya Birla SL MF":["AU Small Finance Bank"],"Mirae MF":["Bajaj Finance","HAL"],"DSP MF":["Tata Motors","Bajaj Finserv"]}
43
- SELL_MAP = {"SBI MF":["Tata Motors"],"ICICI Pru MF":["Bajaj Finance","Adani Ports"],"HDFC MF":["HDFC Bank"],"Nippon India MF":["Hindalco"],"Kotak MF":["AU Small Finance Bank"],"UTI MF":["Hindalco","TCS"],"Axis MF":["TCS"],"Aditya Birla SL MF":["Adani Ports"],"Mirae MF":["TCS"],"DSP MF":["HAL","Shriram Finance"]}
44
- COMPLETE_EXIT = {"DSP MF":["Shriram Finance"]}
45
- FRESH_BUY = {"HDFC MF":["Tata Elxsi"],"UTI MF":["Adani Ports"],"Mirae MF":["HAL"]}
46
-
47
- def sanitize_map(m):
48
- out = {}
49
- for k, vals in m.items():
50
- out[k] = [v for v in vals if v in COMPANIES]
51
- return out
52
-
53
- BUY_MAP = sanitize_map(BUY_MAP)
54
- SELL_MAP = sanitize_map(SELL_MAP)
55
- COMPLETE_EXIT = sanitize_map(COMPLETE_EXIT)
56
- FRESH_BUY = sanitize_map(FRESH_BUY)
57
-
58
- SHORT_LABELS = {"SBI MF":"SBI","ICICI Pru MF":"ICICI","HDFC MF":"HDFC","Nippon India MF":"NIPP","Kotak MF":"KOT","UTI MF":"UTI","Axis MF":"AXIS","Aditya Birla SL MF":"ABSL","Mirae MF":"MIR","DSP MF":"DSP","HDFC Bank":"HDFCB","ICICI Bank":"ICICB","Bajaj Finance":"BajFin","Bajaj Finserv":"BajFsv","Adani Ports":"AdPorts","Tata Motors":"TataM","Shriram Finance":"ShrFin","HAL":"HAL","TCS":"TCS","AU Small Finance Bank":"AUSFB","Pearl Global":"PearlG","Hindalco":"Hind","Tata Elxsi":"Elxsi","Cummins India":"Cumm","Vedanta":"Ved"}
59
- FULL_LABELS = {k: k for k in SHORT_LABELS}
60
-
61
- # --------------------
62
- # Transfers and ordering
63
- # --------------------
64
- def infer_transfers(buy_map, sell_map):
65
- c2s = defaultdict(list)
66
- c2b = defaultdict(list)
67
- transfers = defaultdict(int)
68
- for amc, comps in sell_map.items():
69
- for c in comps:
70
- c2s[c].append(amc)
71
- for amc, comps in buy_map.items():
72
- for c in comps:
73
- c2b[c].append(amc)
74
- for c in set(c2s) | set(c2b):
75
- for s in c2s[c]:
76
- for b in c2b[c]:
77
- transfers[(s, b)] += 1
78
- return transfers
79
-
80
- TRANSFER_COUNTS = infer_transfers(BUY_MAP, SELL_MAP)
81
-
82
- def mixed_order(amcs, companies):
83
- out = []
84
- m = max(len(amcs), len(companies))
85
- for i in range(m):
86
- if i < len(amcs): out.append(amcs[i])
87
- if i < len(companies): out.append(companies[i])
88
- return out
89
-
90
- NODES = mixed_order(AMCS, COMPANIES)
91
- NODE_TYPE = {n: ("amc" if n in AMCS else "company") for n in NODES}
92
-
93
- def build_flows():
94
- buys = []
95
- sells = []
96
- transfers = []
97
- loops = set()
98
- for amc, comps in BUY_MAP.items():
99
- for c in comps:
100
- w = 3 if c in FRESH_BUY.get(amc, []) else 1
101
- buys.append((amc, c, w))
102
- for amc, comps in SELL_MAP.items():
103
- for c in comps:
104
- w = 3 if c in COMPLETE_EXIT.get(amc, []) else 1
105
- sells.append((c, amc, w))
106
- for (s, b), w in TRANSFER_COUNTS.items():
107
- transfers.append((s, b, w))
108
- for a, c, _ in buys:
109
- for c2, b, _ in sells:
110
- if c == c2:
111
- loops.add((a, c, b))
112
- return buys, sells, transfers, list(loops)
113
-
114
- BUYS, SELLS, TRANSFER_LIST, LOOPS = build_flows()
115
-
116
- # --------------------
117
- # Inspect callbacks
118
- # --------------------
119
  def company_trade_summary(company):
120
  buyers = [a for a, cs in BUY_MAP.items() if company in cs]
121
  sellers = [a for a, cs in SELL_MAP.items() if company in cs]
122
- fresh = [a for a, cs in FRESH_BUY.items() if company in cs]
123
- exits = [a for a, cs in COMPLETE_EXIT.items() if company in cs]
124
  df = pd.DataFrame({
125
- "Role": (["Buyer"] * len(buyers)) + (["Seller"] * len(sellers)) + (["Fresh buy"] * len(fresh)) + (["Complete exit"] * len(exits)),
126
- "AMC": buyers + sellers + fresh + exits
127
  })
 
128
  if df.empty:
129
  return None, df
 
130
  counts = df.groupby("Role").size().reset_index(name="Count")
131
- fig = {"data": [{"type":"bar", "x": counts["Role"].tolist(), "y": counts["Count"].tolist()}], "layout": {"title": f"Trades for {company}"}}
 
 
 
 
 
 
 
 
 
132
  return fig, df
133
 
 
134
  def amc_transfer_summary(amc):
135
  sold = SELL_MAP.get(amc, [])
136
  transfers = []
 
137
  for s in sold:
138
  buyers = [a for a, cs in BUY_MAP.items() if s in cs]
139
  for b in buyers:
140
  transfers.append({"security": s, "buyer_amc": b})
 
141
  df = pd.DataFrame(transfers)
 
142
  if df.empty:
143
  return None, df
 
144
  counts = df["buyer_amc"].value_counts().reset_index()
145
  counts.columns = ["Buyer AMC", "Count"]
146
- fig = {"data": [{"type":"bar", "x": counts["Buyer AMC"].tolist(), "y": counts["Count"].tolist()}], "layout": {"title": f"Inferred transfers from {amc}"}}
 
 
 
 
 
 
 
 
 
 
147
  return fig, df
148
 
149
- # --------------------
150
- # Readable JS template (multi-line)
151
- # --------------------
152
- JS_TEMPLATE = r"""
153
- <div id="arc-container" style="width:100%; height:720px;"></div>
154
- <div style="margin-top:8px;">
155
- <button id="arc-reset" style="padding:8px 12px; border-radius:6px;">Reset</button>
156
- </div>
157
- <div style="margin-top:12px; font-family:sans-serif; font-size:13px;">
158
- <b>Legend</b><br/>
159
- <span style="color:#1a9850; font-weight:600;">BUY</span>: green solid<br/>
160
- <span style="color:#d73027; font-weight:600;">SELL</span>: red dotted<br/>
161
- <span style="color:#636363; font-weight:600;">TRANSFER</span>: grey (inferred)<br/>
162
- <span style="color:#1f9e89; font-weight:600;">LOOP</span>: teal (external arc)<br/>
163
- <div style="margin-top:6px;color:#666;font-size:12px;">Click a node to expand full label and show connected nodes' full labels (radial outward).</div>
164
- </div>
165
- <script src="https://d3js.org/d3.v7.min.js"></script>
166
- <script>
167
- const NODES = __NODES__;
168
- const NODE_TYPE = __NODE_TYPE__;
169
- const BUYS = __BUYS__;
170
- const SELLS = __SELLS__;
171
- const TRANSFERS = __TRANSFERS__;
172
- const LOOPS = __LOOPS__;
173
- const SHORT_LABEL = __SHORT_LABEL__;
174
- const FULL_LABEL = __FULL_LABEL__;
175
-
176
- function drawArc(){
177
- const container = document.getElementById("arc-container");
178
- container.innerHTML = "";
179
- const w = Math.min(1000, container.clientWidth || 900);
180
- const h = Math.max(520, Math.floor(w * 0.68));
181
- const svg = d3.select(container).append("svg")
182
- .attr("width","100%")
183
- .attr("height", h)
184
- .attr("viewBox", [-w/2, -h/2, w, h].join(" "));
185
-
186
- const radius = Math.min(w,h) * 0.36;
187
- const n = NODES.length;
188
- function angle(i){ return (i/n) * 2 * Math.PI; }
189
-
190
- const pos = NODES.map((name,i)=>{
191
- const ang = angle(i) - Math.PI/2;
192
- return { name, angle: ang, x: Math.cos(ang)*radius, y: Math.sin(ang)*radius };
193
- });
194
-
195
- const index = {};
196
- NODES.forEach((nm,i)=> index[nm]=i);
197
-
198
- const nodeG = svg.append("g").selectAll("g")
199
- .data(pos).enter().append("g")
200
- .attr("transform", d => `translate(${d.x},${d.y})`);
201
-
202
- nodeG.append("circle")
203
- .attr("r",18)
204
- .style("fill", d => NODE_TYPE[d.name] === "amc" ? "#003f5c" : "#f59e0b")
205
- .style("stroke","#111")
206
- .style("stroke-width",1)
207
- .style("cursor","pointer");
208
-
209
- nodeG.append("text")
210
- .attr("dy","0.35em")
211
- .style("font-size","10px")
212
- .style("fill","#fff")
213
- .style("text-anchor","middle")
214
- .style("pointer-events","none")
215
- .text(d => SHORT_LABEL[d.name] || d.name);
216
-
217
- function arcPath(x0,y0,x1,y1,above){
218
- const mx=(x0+x1)/2, my=(y0+y1)/2;
219
- const len=Math.sqrt(mx*mx+my*my) || 1;
220
- const ux=mx/len, uy=my/len;
221
- const offset = (above ? -1 : 1) * Math.max(36, radius*0.9);
222
- const cx = mx + ux*offset, cy = my + uy*offset;
223
- return `M ${x0} ${y0} Q ${cx} ${cy} ${x1} ${y1}`;
224
- }
225
-
226
- const allW = [].concat(BUYS.map(d=>d[2]), SELLS.map(d=>d[2]), TRANSFERS.map(d=>d[2]));
227
- const sw = d3.scaleLinear().domain([1, Math.max(...allW,1)]).range([1.2,6]);
228
-
229
- const buyG = svg.append("g");
230
- BUYS.forEach(([a,c,w])=>{
231
- if(!(a in index) || !(c in index)) return;
232
- const s = pos[index[a]], t = pos[index[c]];
233
- buyG.append("path")
234
- .attr("d", arcPath(s.x,s.y,t.x,t.y,true))
235
- .attr("stroke","#1a9850")
236
- .attr("fill","none")
237
- .attr("stroke-width", sw(w))
238
- .attr("data-src", a)
239
- .attr("data-tgt", c)
240
- .attr("opacity", 0.92);
241
- });
242
-
243
- const sellG = svg.append("g");
244
- SELLS.forEach(([c,a,w])=>{
245
- if(!(c in index) || !(a in index)) return;
246
- const s = pos[index[c]], t = pos[index[a]];
247
- sellG.append("path")
248
- .attr("d", arcPath(s.x,s.y,t.x,t.y,false))
249
- .attr("stroke","#d73027")
250
- .attr("fill","none")
251
- .attr("stroke-dasharray","4,3")
252
- .attr("stroke-width", sw(w))
253
- .attr("data-src", c)
254
- .attr("data-tgt", a)
255
- .attr("opacity", 0.86);
256
- });
257
-
258
- const trG = svg.append("g");
259
- TRANSFERS.forEach(([s,b,w])=>{
260
- if(!(s in index) || !(b in index)) return;
261
- const sp = pos[index[s]], tp = pos[index[b]];
262
- const mx = (sp.x + tp.x)/2, my = (sp.y + tp.y)/2;
263
- trG.append("path")
264
- .attr("d", `M ${sp.x} ${sp.y} Q ${mx*0.3} ${my*0.3} ${tp.x} ${tp.y}`)
265
- .attr("stroke","#636363")
266
- .attr("fill","none")
267
- .attr("stroke-width", sw(w))
268
- .attr("opacity", 0.7)
269
- .attr("data-src", s)
270
- .attr("data-tgt", b);
271
- });
272
-
273
- const loopG = svg.append("g");
274
- LOOPS.forEach(([a,c,b])=>{
275
- if(!(a in index) || !(b in index)) return;
276
- const sa = pos[index[a]], sb = pos[index[b]];
277
- const mx = (sa.x + sb.x)/2, my = (sa.y + sb.y)/2;
278
- const len = Math.sqrt((sa.x - sb.x)**2 + (sa.y - sb.y)**2);
279
- const outward = Math.max(40, radius*0.28 + len*0.12);
280
- const dx = mx, dy = my;
281
- const nlen = Math.sqrt(dx*dx + dy*dy) || 1;
282
- const ux = dx / nlen, uy = dy / nlen;
283
- const cx = mx + ux*outward, cy = my + uy*outward;
284
- const d = `M ${sa.x} ${sa.y} Q ${cx} ${cy} ${sb.x} ${sb.y}`;
285
- loopG.append("path")
286
- .attr("d", d)
287
- .attr("stroke","#1f9e89")
288
- .attr("fill","none")
289
- .attr("stroke-width", 2.8)
290
- .attr("opacity", 0.95);
291
- });
292
-
293
- const outsideLayer = svg.append("g").attr("class","outside-labels");
294
-
295
- function placeOutsideLabels(names){
296
- outsideLayer.selectAll("*").remove();
297
- names.forEach(nm=>{
298
- const p = pos[index[nm]];
299
- const offset = radius + 30;
300
- const x = p.x + Math.cos(p.angle)*offset;
301
- const y = p.y + Math.sin(p.angle)*offset;
302
- const deg = (p.angle * 180 / Math.PI);
303
- const anchor = (deg > -90 && deg < 90) ? "start" : "end";
304
- outsideLayer.append("text")
305
- .attr("x", x)
306
- .attr("y", y)
307
- .attr("dy","0.35em")
308
- .text(FULL_LABEL[nm] || nm)
309
- .style("font-family","sans-serif")
310
- .style("font-size","12px")
311
- .style("fill","#0b1220")
312
- .style("text-anchor", anchor);
313
- });
314
- }
315
-
316
- function highlight(nodeName){
317
- nodeG.selectAll("circle").style("opacity", d => d.name===nodeName ? 1.0 : 0.18);
318
- nodeG.selectAll("text").style("opacity", d => d.name===nodeName ? 0.0 : 0.28);
319
-
320
- const connected = new Set([nodeName]);
321
- buyG.selectAll("path").each(function(){ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt"); if(s===nodeName||t===nodeName){ connected.add(s); connected.add(t); }});
322
- sellG.selectAll("path").each(function(){ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt"); if(s===nodeName||t===nodeName){ connected.add(s); connected.add(t); }});
323
- trG.selectAll("path").each(function(){ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt"); if(s===nodeName||t===nodeName){ connected.add(s); connected.add(t); }});
324
-
325
- placeOutsideLabels(Array.from(connected));
326
-
327
- buyG.selectAll("path").style("opacity", function(){ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt"); return (s===nodeName||t===nodeName) ? 0.98 : 0.06; });
328
- sellG.selectAll("path").style("opacity", function(){ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt"); return (s===nodeName||t===nodeName) ? 0.98 : 0.06; });
329
- trG.selectAll("path").style("opacity", function(){ const s=this.getAttribute("data-src"), t=this.getAttribute("data-tgt"); return (s===nodeName||t===nodeName) ? 0.98 : 0.06; });
330
- }
331
-
332
- function resetView(){
333
- nodeG.selectAll("circle").style("opacity",1.0);
334
- nodeG.selectAll("text").style("opacity",1.0).text(d => SHORT_LABEL[d.name] || d.name);
335
- buyG.selectAll("path").style("opacity",0.92);
336
- sellG.selectAll("path").style("opacity",0.86);
337
- trG.selectAll("path").style("opacity",0.7);
338
- loopG.selectAll("path").style("opacity",0.95);
339
- outsideLayer.selectAll("*").remove();
340
- }
341
-
342
- nodeG.selectAll("circle").on("click", function(event,d){ highlight(d.name); event.stopPropagation(); });
343
- nodeG.selectAll("text").on("click", function(event,d){ highlight(d.name); event.stopPropagation(); });
344
-
345
- document.getElementById("arc-reset").onclick = resetView;
346
- svg.on("click", ()=> resetView());
347
- }
348
 
349
- drawArc();
350
- window.addEventListener("resize", drawArc);
351
- </script>
352
- """
353
-
354
- # Build final HTML
355
- def make_arc_html():
356
- html = JS_TEMPLATE
357
- html = html.replace("__NODES__", json.dumps(NODES))
358
- html = html.replace("__NODE_TYPE__", json.dumps(NODE_TYPE))
359
- html = html.replace("__BUYS__", json.dumps(BUYS))
360
- html = html.replace("__SELLS__", json.dumps(SELLS))
361
- html = html.replace("__TRANSFERS__", json.dumps(TRANSFER_LIST))
362
- html = html.replace("__LOOPS__", json.dumps(LOOPS))
363
- html = html.replace("__SHORT_LABEL__", json.dumps(SHORT_LABELS))
364
- html = html.replace("__FULL_LABEL__", json.dumps(FULL_LABELS))
365
- return html
366
-
367
- ARC_HTML = make_arc_html()
368
-
369
- # Gate function
370
- def gate_fn(email, code, ua):
371
  if not email or not code:
372
- write_log(email or "<empty>", code or "<empty>", ua or "<ua?>", False)
373
- return gr.update(visible=True), gr.update(visible=False), "Enter both email and access code."
374
- if ACCESS_CODE and code.strip() == ACCESS_CODE:
375
- write_log(email, code, ua or "<ua?>", True)
376
- return gr.update(visible=False), gr.update(visible(True), visible=True), f"Access granted for {email}."
 
 
 
 
 
 
 
 
 
 
377
  else:
378
- write_log(email, code, ua or "<ua?>", False)
379
- return gr.update(visible=True), gr.update(visible=False), "Invalid access code."
 
 
 
 
380
 
381
- # NOTE: The two occurrences above using gr.update(visible(True), visible=True) are intentional mistakes to avoid.
382
- # The correct code below replaces gate_fn usage in the UI wiring.
383
 
384
- # Build UI
385
- with gr.Blocks(title="MF Churn — Secure Arc Diagram") as demo:
 
 
 
386
  gr.Markdown("## 🔒 Mutual Fund Churn — Secure Access")
387
 
 
388
  with gr.Column(visible=True) as gate_col:
389
- email_box = gr.Textbox(label="Email")
390
- code_box = gr.Textbox(label="Access Code", type="password")
391
- ua_box = gr.Textbox(visible=False, elem_id="ua_box")
392
- submit_btn = gr.Button("Unlock")
393
- gate_msg = gr.Markdown("")
394
- gr.HTML("<script>document.getElementById('ua_box').value = navigator.userAgent || '';</script>")
395
-
 
396
  with gr.Column(visible=False) as app_col:
397
- gr.Markdown("### Arc Diagram (click node to expand labels)")
398
- gr.HTML(ARC_HTML)
 
 
 
 
 
 
 
399
  gr.Markdown("---")
400
- gr.Markdown("### Inspect Company")
401
- select_company = gr.Dropdown(choices=COMPANIES, label="Select company")
402
- company_plot = gr.Plot()
403
- company_table = gr.DataFrame()
404
- gr.Markdown("### Inspect AMC")
405
- select_amc = gr.Dropdown(choices=AMCS, label="Select AMC")
 
406
  amc_plot = gr.Plot()
407
  amc_table = gr.DataFrame()
408
 
409
- # Correct gate wrapper that performs the visibility updates
410
- def gate_wrapper(email, code, ua):
411
- if not email or not code:
412
- write_log(email or "<empty>", code or "<empty>", ua or "<ua?>", False)
413
- return gr.update(visible=True), gr.update(visible=False), "Enter both email and access code."
414
- if ACCESS_CODE and code.strip() == ACCESS_CODE:
415
- write_log(email, code, ua or "<ua?>", True)
416
- return gr.update(visible=False), gr.update(visible=True), f"Access granted for {email}."
417
- else:
418
- write_log(email, code, ua or "<ua?>", False)
419
- return gr.update(visible=True), gr.update(visible=False), "Invalid access code."
420
-
421
- submit_btn.click(fn=gate_wrapper, inputs=[email_box, code_box, ua_box], outputs=[gate_col, app_col, gate_msg])
422
-
423
- select_company.change(company_trade_summary, select_company, [company_plot, company_table])
424
- select_amc.change(amc_transfer_summary, select_amc, [amc_plot, amc_table])
425
 
426
  if __name__ == "__main__":
427
  demo.launch()
 
1
  # app.py
2
+ # FINAL VERSION Uses iframe to load D3 graph; login gate; session.log logging
 
 
 
 
 
3
 
4
+ import gradio as gr
5
  import os
 
6
  import time
 
7
  import pandas as pd
 
8
 
9
+ # ===============================
10
+ # CONFIG
11
+ # ===============================
12
  ACCESS_CODE = os.getenv("ACCESS_CODE", "")
13
+ LOGFILE = "session.log"
14
+
15
+ # Create log file if missing
16
+ if not os.path.exists(LOGFILE):
17
+ open(LOGFILE, "w").close()
18
+
19
 
20
+ def log_attempt(email, code, ua, ok):
21
+ """Append login attempts to session.log."""
22
  ts = time.strftime("%Y-%m-%d %H:%M:%S")
23
  status = "OK" if ok else "FAIL"
24
+ safe_code = code[:2] + "***" if code else "<none>"
25
+ ua = ua or "<no-ua>"
26
+ line = f"{ts} | {status} | email={email} | code={safe_code} | ua={ua}\n"
27
  try:
28
+ with open(LOGFILE, "a") as f:
29
  f.write(line)
30
  except Exception as e:
31
+ print("Log error:", e)
32
+
33
+
34
+ # ===============================
35
+ # INSPECT CALLBACKS
36
+ # ===============================
37
+ AMCS = [
38
+ "SBI MF", "ICICI Pru MF", "HDFC MF", "Nippon India MF", "Kotak MF",
39
+ "UTI MF", "Axis MF", "Aditya Birla SL MF", "Mirae MF", "DSP MF"
40
+ ]
41
+
42
+ COMPANIES = [
43
+ "HDFC Bank", "ICICI Bank", "Bajaj Finance", "Bajaj Finserv", "Adani Ports",
44
+ "Tata Motors", "Shriram Finance", "HAL", "TCS", "AU Small Finance Bank",
45
+ "Pearl Global", "Hindalco", "Tata Elxsi", "Cummins India", "Vedanta"
46
+ ]
47
+
48
+ BUY_MAP = {
49
+ "SBI MF": ["Bajaj Finance", "AU Small Finance Bank"],
50
+ "ICICI Pru MF": ["HDFC Bank"],
51
+ "HDFC MF": ["Tata Elxsi", "TCS"],
52
+ "Nippon India MF": ["Hindalco"],
53
+ "Kotak MF": ["Bajaj Finance"],
54
+ "UTI MF": ["Adani Ports", "Shriram Finance"],
55
+ "Axis MF": ["Tata Motors", "Shriram Finance"],
56
+ "Aditya Birla SL MF": ["AU Small Finance Bank"],
57
+ "Mirae MF": ["Bajaj Finance", "HAL"],
58
+ "DSP MF": ["Tata Motors", "Bajaj Finserv"]
59
+ }
60
+
61
+ SELL_MAP = {
62
+ "SBI MF": ["Tata Motors"],
63
+ "ICICI Pru MF": ["Bajaj Finance", "Adani Ports"],
64
+ "HDFC MF": ["HDFC Bank"],
65
+ "Nippon India MF": ["Hindalco"],
66
+ "Kotak MF": ["AU Small Finance Bank"],
67
+ "UTI MF": ["Hindalco", "TCS"],
68
+ "Axis MF": ["TCS"],
69
+ "Aditya Birla SL MF": ["Adani Ports"],
70
+ "Mirae MF": ["TCS"],
71
+ "DSP MF": ["HAL", "Shriram Finance"]
72
+ }
73
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  def company_trade_summary(company):
75
  buyers = [a for a, cs in BUY_MAP.items() if company in cs]
76
  sellers = [a for a, cs in SELL_MAP.items() if company in cs]
77
+
 
78
  df = pd.DataFrame({
79
+ "Role": (["Buyer"] * len(buyers)) + (["Seller"] * len(sellers)),
80
+ "AMC": buyers + sellers
81
  })
82
+
83
  if df.empty:
84
  return None, df
85
+
86
  counts = df.groupby("Role").size().reset_index(name="Count")
87
+ fig = {
88
+ "data": [{
89
+ "type": "bar",
90
+ "x": counts["Role"].tolist(),
91
+ "y": counts["Count"].tolist(),
92
+ "marker": {"color": ["green", "red"][: len(counts)]},
93
+ }],
94
+ "layout": {"title": f"Trades for {company}"}
95
+ }
96
+
97
  return fig, df
98
 
99
+
100
  def amc_transfer_summary(amc):
101
  sold = SELL_MAP.get(amc, [])
102
  transfers = []
103
+
104
  for s in sold:
105
  buyers = [a for a, cs in BUY_MAP.items() if s in cs]
106
  for b in buyers:
107
  transfers.append({"security": s, "buyer_amc": b})
108
+
109
  df = pd.DataFrame(transfers)
110
+
111
  if df.empty:
112
  return None, df
113
+
114
  counts = df["buyer_amc"].value_counts().reset_index()
115
  counts.columns = ["Buyer AMC", "Count"]
116
+
117
+ fig = {
118
+ "data": [{
119
+ "type": "bar",
120
+ "x": counts["Buyer AMC"].tolist(),
121
+ "y": counts["Count"].tolist(),
122
+ "marker": {"color": "gray"}
123
+ }],
124
+ "layout": {"title": f"Inferred transfers from {amc}"}
125
+ }
126
+
127
  return fig, df
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ # ===============================
131
+ # GATE CHECK
132
+ # ===============================
133
+ def gate(email, code, ua):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  if not email or not code:
135
+ log_attempt(email, code, ua, False)
136
+ return (
137
+ gr.update(visible=True),
138
+ gr.update(visible=False),
139
+ "❌ Enter both email and code."
140
+ )
141
+
142
+ if code.strip() == ACCESS_CODE:
143
+ log_attempt(email, code, ua, True)
144
+ return (
145
+ gr.update(visible=False),
146
+ gr.update(visible=True),
147
+ f"✅ Access granted for {email}"
148
+ )
149
+
150
  else:
151
+ log_attempt(email, code, ua, False)
152
+ return (
153
+ gr.update(visible=True),
154
+ gr.update(visible=False),
155
+ "❌ Invalid code."
156
+ )
157
 
 
 
158
 
159
+ # ===============================
160
+ # UI
161
+ # ===============================
162
+ with gr.Blocks(css="body {background:#fafafa;}") as demo:
163
+
164
  gr.Markdown("## 🔒 Mutual Fund Churn — Secure Access")
165
 
166
+ # Gate login
167
  with gr.Column(visible=True) as gate_col:
168
+ email = gr.Textbox(label="Email")
169
+ code = gr.Textbox(label="Access Code", type="password")
170
+ ua = gr.Textbox(visible=False, elem_id="ua_box")
171
+ gr.HTML("<script>document.getElementById('ua_box').value = navigator.userAgent;</script>")
172
+ btn = gr.Button("Unlock")
173
+ msg = gr.Markdown("")
174
+
175
+ # Real app
176
  with gr.Column(visible=False) as app_col:
177
+ gr.Markdown("### 📊 Arc Diagram")
178
+
179
+ # Load the D3 visual from iframe
180
+ gr.HTML("""
181
+ <iframe src="graph.html"
182
+ style="width:100%; height:820px; border:none; background:white;">
183
+ </iframe>
184
+ """)
185
+
186
  gr.Markdown("---")
187
+ gr.Markdown("### Company-level view")
188
+ sel_company = gr.Dropdown(choices=COMPANIES, label="Select company")
189
+ comp_plot = gr.Plot()
190
+ comp_table = gr.DataFrame()
191
+
192
+ gr.Markdown("### AMC-level view")
193
+ sel_amc = gr.Dropdown(choices=AMCS, label="Select AMC")
194
  amc_plot = gr.Plot()
195
  amc_table = gr.DataFrame()
196
 
197
+ # Login callback
198
+ btn.click(
199
+ gate,
200
+ inputs=[email, code, ua],
201
+ outputs=[gate_col, app_col, msg]
202
+ )
203
+
204
+ # Inspect callbacks
205
+ sel_company.change(company_trade_summary, sel_company, [comp_plot, comp_table])
206
+ sel_amc.change(amc_transfer_summary, sel_amc, [amc_plot, amc_table])
207
+
 
 
 
 
 
208
 
209
  if __name__ == "__main__":
210
  demo.launch()