import streamlit as st import pandas as pd import plotly.graph_objects as go import plotly.express as px from inference import ( predict_tx, plot_3d_tx_ego, neighbor_risk_distribution, overall_risk_distribution, ego_risk_distribution, ) # ---------------------------------------------------------- # PAGE SETUP # ---------------------------------------------------------- st.set_page_config(page_title="Bitcoin Transaction Risk Explorer", layout="wide") # ---------------------------------------------------------- # BEAUTIFUL UI CSS (animations + modern visuals) # ---------------------------------------------------------- st.markdown(""" """, unsafe_allow_html=True) # ---------------------------------------------------------- # HEADER # ---------------------------------------------------------- st.markdown("

Bitcoin Transaction Risk Explorer

", unsafe_allow_html=True) st.markdown("""
AI-powered forensic intelligence. Predict illicit risk, analyze network behavior, and visualize suspicious transaction activity in 3D.
""", unsafe_allow_html=True) # ---------------------------------------------------------- # Sidebar helper: attractive bar chart renderer # ---------------------------------------------------------- def render_sidebar_bar_chart(counts: dict): """Render an attractive Plotly bar chart in the sidebar with labels outside (avoid clipping).""" if not counts: st.sidebar.write("No risk data available.") return df = pd.DataFrame({ "Risk": list(counts.keys()), "Count": list(counts.values()) }) # keep consistent order: Safe, High, Low, Medium (if present) desired_order = ["Safe", "High", "Low", "Medium"] df['Risk'] = pd.Categorical(df['Risk'], categories=[r for r in desired_order if r in df['Risk'].unique()], ordered=True) df = df.sort_values('Risk') fig = px.bar( df, x="Risk", y="Count", text="Count", color="Risk", color_discrete_map={ "Safe": "#4da3ff", "High": "#ff2e63", "Low": "#00e0c7", "Medium": "#ffa41b" } ) fig.update_traces( texttemplate="%{text:,}", # format with commas textposition="outside", # move label outside bar to prevent clipping cliponaxis=False, marker=dict(line=dict(color="rgba(255,255,255,0.55)", width=1.5)) ) fig.update_layout( height=340, margin=dict(l=10, r=10, t=8, b=8), yaxis_title="Count", xaxis_title="Risk", plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)", font=dict(color="white"), ) st.sidebar.plotly_chart(fig, use_container_width=True) # ---------------------------------------------------------- # SIDEBAR — Global Risk Distribution # ---------------------------------------------------------- st.sidebar.header("📊 Global Risk Overview") overall_counts = overall_risk_distribution() # metric cards if overall_counts: total = sum(overall_counts.values()) for k, v in overall_counts.items(): st.sidebar.markdown( f"""
{k}
{v:,} ({v/total*100:.1f}%)
""", unsafe_allow_html=True, ) # Render improved Plotly bar chart (fixes large-label clipping) render_sidebar_bar_chart(overall_counts) else: st.sidebar.write("No risk data available.") st.sidebar.markdown("---") # Dataset Link st.sidebar.markdown(""" 🔗 **Dataset Source:** [Open Bitcoin Fraud Dataset →](hhttps://huggingface.co/datasets/vansh0003/Bitcoin_Dataset/tree/main) """) # ---------------------------------------------------------- # INPUTS # ---------------------------------------------------------- st.subheader("🔍 Analyze a Transaction") tx_input = st.text_input("Enter transaction ID (txId)", value="78144215") col_h1, col_h2 = st.columns(2) with col_h1: hops = st.slider("Ego-graph hops", 1, 4, 2) with col_h2: max_nodes = st.slider("Max nodes in ego graph", 50, 800, 400, step=50) # ---------------------------------------------------------- # ANALYSIS LOGIC # ---------------------------------------------------------- if st.button("Analyze"): try: try: txid = int(tx_input) except: txid = tx_input info = predict_tx(txid) st.markdown("### Prediction Summary") c1, c2, c3, c4, c5 = st.columns(5) # Metric function def card(col, title, value): col.markdown( f"""
{title}
{value}
""", unsafe_allow_html=True ) card(c1, "Transaction ID", info["txId"]) card(c2, "Illicit Probability", f"{info['proba_illicit']:.6f}") card(c3, "Risk Bucket", info["risk_bucket"]) card(c4, "Degree", info["degree"]) card(c5, "Original Class", info["class"]) st.markdown("---") # ---------------------------------------------------- # Neon Donut Chart (Ego Risk) # ---------------------------------------------------- st.markdown("### 🥧 Ego-Network Risk Composition") ego_counts = ego_risk_distribution(txid, hops=hops, max_nodes=max_nodes) if ego_counts: labels = list(ego_counts.keys()) values = list(ego_counts.values()) color_map = { "High": "#ff2e63", "Medium": "#ffa41b", "Low": "#00e0c7", "Safe": "#5596ff" } colors = [color_map.get(r, "#9fbcd6") for r in labels] pie_fig = go.Figure( data=[go.Pie( labels=labels, values=values, hole=0.55, hoverinfo="label+percent+value", textinfo="percent", textfont=dict(size=14, color="white"), marker=dict( colors=colors, line=dict(color="rgba(255,255,255,0.18)", width=2) ), sort=False )] ) pie_fig.update_layout( title="Risk Distribution (Ego Network)", title_font=dict(color="white"), paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", annotations=[ dict( text="EGO", x=0.5, y=0.5, font=dict(size=22, color="white"), showarrow=False ) ], margin=dict(l=10, r=10, t=60, b=10), showlegend=True, legend=dict( orientation="h", yanchor="bottom", y=-0.15, xanchor="center", x=0.5, font=dict(color="#dff8ff") ) ) st.plotly_chart(pie_fig, use_container_width=True) else: st.info("No ego-network risk data available.") st.markdown("---") # ------------------------------------------------------ # NEIGHBOR RISK # ------------------------------------------------------ st.markdown("### 📌 Neighbor Risk Distribution") nb_counts = neighbor_risk_distribution(txid) if nb_counts: st.bar_chart(pd.Series(nb_counts)) else: st.info("No neighbors found for this transaction.") st.markdown("---") # ------------------------------------------------------ # 3D NETWORK GRAPH # ------------------------------------------------------ st.markdown("### 🌐 3D Ego Graph") fig = plot_3d_tx_ego(txid, hops=hops, max_nodes=max_nodes) st.plotly_chart(fig, use_container_width=True) except Exception as e: st.error(f"Unexpected error: {e}")