# app.py # Mutual Fund Churn – Static Weighted Arc Diagram # Short labels inside nodes, full labels on click # Fully corrected version (NO syntax errors) import gradio as gr import pandas as pd import json import numpy as np from collections import defaultdict # --------------------------- # Data # --------------------------- 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" ] 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" ] 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"] } 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"] } COMPLETE_EXIT = {"DSP MF": ["Shriram Finance"]} FRESH_BUY = {"HDFC MF": ["Tata Elxsi"], "UTI MF": ["Adani Ports"], "Mirae MF": ["HAL"]} def sanitize_map(m): out = {} for k, vals in m.items(): out[k] = [v for v in vals if v in COMPANIES] return out BUY_MAP = sanitize_map(BUY_MAP) SELL_MAP = sanitize_map(SELL_MAP) COMPLETE_EXIT = sanitize_map(COMPLETE_EXIT) FRESH_BUY = sanitize_map(FRESH_BUY) # --------------------------- # Short + Full Label Maps # --------------------------- 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" } FULL_LABEL = {k: k for k in SHORT_LABELS} # --------------------------- # Infer AMC→AMC transfers # --------------------------- def infer_amc_transfers(buy_map, sell_map): transfers = defaultdict(int) c2s = defaultdict(list) c2b = defaultdict(list) for amc, comps in sell_map.items(): for c in comps: c2s[c].append(amc) for amc, comps in buy_map.items(): for c in comps: c2b[c].append(amc) for c in set(c2s) | set(c2b): for s in c2s[c]: for b in c2b[c]: transfers[(s, b)] += 1 return transfers TRANSFER_COUNTS = infer_amc_transfers(BUY_MAP, SELL_MAP) # --------------------------- # Mixed ordering for layout # --------------------------- def build_mixed_ordering(amcs, companies): out = [] N = max(len(amcs), len(companies)) for i in range(N): if i < len(amcs): out.append(amcs[i]) if i < len(companies): out.append(companies[i]) return out NODES = build_mixed_ordering(AMCS, COMPANIES) NODE_TYPE = {n: ("amc" if n in AMCS else "company") for n in NODES} # --------------------------- # Build flows # --------------------------- def build_flows(): buys, sells, transfers, loops = [], [], [], [] for amc, comps in BUY_MAP.items(): for c in comps: w = 3 if c in FRESH_BUY.get(amc, []) else 1 buys.append((amc, c, w)) for amc, comps in SELL_MAP.items(): for c in comps: w = 3 if c in COMPLETE_EXIT.get(amc, []) else 1 sells.append((c, amc, w)) for (s, b), w in TRANSFER_COUNTS.items(): transfers.append((s, b, w)) # loops (AMC → Company → AMC) seen = set() for a, c, _ in buys: for c2, b, _ in sells: if c == c2: seen.add((a, c, b)) loops = list(seen) return buys, sells, transfers, loops BUYS, SELLS, TRANSFERS, LOOPS = build_flows() # --------------------------- # Inspect panels # --------------------------- def company_trade_summary(company): buyers = [a for a, cs in BUY_MAP.items() if company in cs] sellers = [a for a, cs in SELL_MAP.items() if company in cs] fresh = [a for a, cs in FRESH_BUY.items() if company in cs] exits = [a for a, cs in COMPLETE_EXIT.items() if company in cs] df = pd.DataFrame({ "Role": ["Buyer"] * len(buyers) + ["Seller"] * len(sellers) + ["Fresh buy"] * len(fresh) + ["Complete exit"] * len(exits), "AMC": buyers + sellers + fresh + exits }) if df.empty: return None, df counts = df.groupby("Role").size().reset_index(name="Count") fig = { "data": [{"type": "bar", "x": counts["Role"].tolist(), "y": counts["Count"].tolist()}], "layout": {"title": f"Trades for {company}"} } return fig, df def amc_transfer_summary(amc): sold = SELL_MAP.get(amc, []) transfers = [] for s in sold: buyers = [a for a, cs in BUY_MAP.items() if s in cs] for b in buyers: transfers.append({"security": s, "buyer_amc": b}) df = pd.DataFrame(transfers) if df.empty: return None, df counts = df["buyer_amc"].value_counts().reset_index() counts.columns = ["Buyer AMC", "Count"] fig = { "data": [{"type": "bar", "x": counts["Buyer AMC"].tolist(), "y": counts["Count"].tolist()}], "layout": {"title": f"Inferred transfers from {amc}"} } return fig, df # --------------------------- # JavaScript Template (raw, safe) # --------------------------- JS_TEMPLATE = r"""
""" # --------------------------- # Build HTML with replacements # --------------------------- def make_arc_html(): html = JS_TEMPLATE html = html.replace("__NODES__", json.dumps(NODES)) html = html.replace("__NODE_TYPE__", json.dumps(NODE_TYPE)) html = html.replace("__BUYS__", json.dumps(BUYS)) html = html.replace("__SELLS__", json.dumps(SELLS)) html = html.replace("__TRANSFERS__", json.dumps(TRANSFERS)) html = html.replace("__LOOPS__", json.dumps(LOOPS)) html = html.replace("__SHORT_LABEL__", json.dumps(SHORT_LABELS)) html = html.replace("__FULL_LABEL__", json.dumps(FULL_LABEL)) return html initial_html = make_arc_html() # --------------------------- # Gradio UI # --------------------------- with gr.Blocks(title="MF Churn – Arc Diagram") as demo: gr.Markdown("## Mutual Fund Churn — Weighted Arc Diagram (short labels → full label on click)") gr.HTML(initial_html) gr.Markdown("### Inspect Company / AMC") select_company = gr.Dropdown(COMPANIES, label="Select company") company_plot = gr.Plot() company_table = gr.DataFrame() select_amc = gr.Dropdown(AMCS, label="Select AMC") amc_plot = gr.Plot() amc_table = gr.DataFrame() select_company.change(company_trade_summary, select_company, [company_plot, company_table]) select_amc.change(amc_transfer_summary, select_amc, [amc_plot, amc_table]) if __name__ == "__main__": demo.launch()