CoolFace
Apppublic

yeshwanth23/mlops

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py836 linesDownload Raw Back to root
1#!/usr/bin/env python3
2"""
3Plotly Dash Dashboard for Bitcoin Transaction Anomaly Detection
4This script creates an interactive dashboard for visualizing Bitcoin transaction data
5and anomaly detection results.
6"""
7
8import os
9import json
10import pandas as pd
11import numpy as np
12import requests
13from datetime import datetime, timedelta
14
15import dash
16from dash import dcc, html, Input, Output, State, callback
17import dash_bootstrap_components as dbc
18import plotly.express as px
19import plotly.graph_objects as go
20
21# API endpoint
22API_ENDPOINT = "http://localhost:5000/predict"
23
24# Create Dash app
25app = dash.Dash(
26    __name__,
27    external_stylesheets=[dbc.themes.BOOTSTRAP],
28    meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
29)
30
31app.title = "Bitcoin Transaction Anomaly Detection"
32
33# Load sample data (in a real scenario, this would come from Delta Lake)
34def load_sample_data():
35    """
36    Load sample Bitcoin transaction data
37    In a real implementation, this would load data from Delta Lake
38    """
39    # Generate synthetic data for demonstration
40    np.random.seed(42)
41    n_samples = 1000
42    
43    # Generate timestamps for the last 24 hours
44    now = datetime.now()
45    timestamps = [now - timedelta(minutes=np.random.randint(0, 24*60)) for _ in range(n_samples)]
46    timestamps.sort()
47    
48    # Generate transaction data
49    data = {
50        'hash': [f"tx_{i}" for i in range(n_samples)],
51        'transaction_time': timestamps,
52        'size': np.random.lognormal(7, 1, n_samples).astype(int),
53        'weight': np.random.lognormal(8, 1, n_samples).astype(int),
54        'fee': np.random.lognormal(9, 1.5, n_samples).astype(int),
55        'inputs_count': np.random.randint(1, 10, n_samples),
56        'outputs_count': np.random.randint(1, 5, n_samples),
57        'input_value': np.random.lognormal(16, 2, n_samples).astype(int),
58        'output_value': np.random.lognormal(16, 2, n_samples).astype(int),
59    }
60    
61    # Calculate derived metrics
62    df = pd.DataFrame(data)
63    df['fee_rate'] = df['fee'] / df['size']
64    df['fee_per_weight'] = df['fee'] / df['weight']
65    
66    # Generate anomaly scores (mostly normal with a few anomalies)
67    anomaly_scores = np.random.normal(0, 0.5, n_samples)
68    # Make a few transactions anomalous
69    anomaly_indices = np.random.choice(n_samples, size=int(n_samples * 0.02), replace=False)
70    for idx in anomaly_indices:
71        anomaly_scores[idx] = np.random.uniform(1.5, 3)
72    
73    df['anomaly_score'] = anomaly_scores
74    df['is_anomaly'] = df['anomaly_score'] > 1.0
75    
76    return df
77
78# App layout
79app.layout = dbc.Container(
80    [
81        # Header
82        dbc.Row(
83            [
84                dbc.Col(
85                    [
86                        html.H1("Bitcoin Transaction Anomaly Detection", className="display-4"),
87                        html.P(
88                            "Interactive dashboard for monitoring Bitcoin transactions and detecting anomalies",
89                            className="lead",
90                        ),
91                    ],
92                    width={"size": 10, "offset": 1},
93                )
94            ],
95            className="mb-4 mt-4",
96        ),
97        
98        # Filters and Controls
99        dbc.Row(
100            [
101                dbc.Col(
102                    [
103                        dbc.Card(
104                            [
105                                dbc.CardHeader("Filters"),
106                                dbc.CardBody(
107                                    [
108                                        dbc.Row(
109                                            [
110                                                dbc.Col(
111                                                    [
112                                                        html.Label("Time Range"),
113                                                        dcc.Dropdown(
114                                                            id="time-range-dropdown",
115                                                            options=[
116                                                                {"label": "Last Hour", "value": "1h"},
117                                                                {"label": "Last 6 Hours", "value": "6h"},
118                                                                {"label": "Last 12 Hours", "value": "12h"},
119                                                                {"label": "Last 24 Hours", "value": "24h"},
120                                                                {"label": "All Time", "value": "all"},
121                                                            ],
122                                                            value="24h",
123                                                        ),
124                                                    ],
125                                                    width=6,
126                                                ),
127                                                dbc.Col(
128                                                    [
129                                                        html.Label("Anomaly Threshold"),
130                                                        dcc.Slider(
131                                                            id="anomaly-threshold-slider",
132                                                            min=0,
133                                                            max=2,
134                                                            step=0.1,
135                                                            value=1.0,
136                                                            marks={i: str(i) for i in range(0, 3)},
137                                                        ),
138                                                    ],
139                                                    width=6,
140                                                ),
141                                            ]
142                                        ),
143                                        html.Br(),
144                                        dbc.Row(
145                                            [
146                                                dbc.Col(
147                                                    [
148                                                        html.Label("Transaction Size Range (bytes)"),
149                                                        dcc.RangeSlider(
150                                                            id="size-range-slider",
151                                                            min=0,
152                                                            max=10000,
153                                                            step=100,
154                                                            value=[0, 10000],
155                                                            marks={i: str(i) for i in range(0, 10001, 2000)},
156                                                        ),
157                                                    ]
158                                                )
159                                            ]
160                                        ),
161                                        html.Br(),
162                                        dbc.Row(
163                                            [
164                                                dbc.Col(
165                                                    [
166                                                        html.Label("Show Anomalies Only"),
167                                                        dbc.Switch(id="anomalies-only-switch", value=False),
168                                                    ],
169                                                    width=6,
170                                                ),
171                                                dbc.Col(
172                                                    [
173                                                        html.Label("Auto Refresh"),
174                                                        dbc.Switch(id="auto-refresh-switch", value=True),
175                                                    ],
176                                                    width=6,
177                                                ),
178                                            ]
179                                        ),
180                                    ]
181                                ),
182                            ],
183                            className="mb-4",
184                        ),
185                    ],
186                    width={"size": 10, "offset": 1},
187                )
188            ]
189        ),
190        
191        # KPI Cards
192        dbc.Row(
193            [
194                dbc.Col(
195                    [
196                        dbc.Card(
197                            [
198                                dbc.CardBody(
199                                    [
200                                        html.H4("Total Transactions", className="card-title"),
201                                        html.H2(id="total-transactions", className="card-value"),
202                                    ]
203                                )
204                            ],
205                            className="mb-4 text-center",
206                        ),
207                    ],
208                    width=3,
209                ),
210                dbc.Col(
211                    [
212                        dbc.Card(
213                            [
214                                dbc.CardBody(
215                                    [
216                                        html.H4("Anomalies Detected", className="card-title"),
217                                        html.H2(id="total-anomalies", className="card-value text-danger"),
218                                    ]
219                                )
220                            ],
221                            className="mb-4 text-center",
222                        ),
223                    ],
224                    width=3,
225                ),
226                dbc.Col(
227                    [
228                        dbc.Card(
229                            [
230                                dbc.CardBody(
231                                    [
232                                        html.H4("Avg. Fee Rate (sat/byte)", className="card-title"),
233                                        html.H2(id="avg-fee-rate", className="card-value"),
234                                    ]
235                                )
236                            ],
237                            className="mb-4 text-center",
238                        ),
239                    ],
240                    width=3,
241                ),
242                dbc.Col(
243                    [
244                        dbc.Card(
245                            [
246                                dbc.CardBody(
247                                    [
248                                        html.H4("Avg. Transaction Size", className="card-title"),
249                                        html.H2(id="avg-tx-size", className="card-value"),
250                                    ]
251                                )
252                            ],
253                            className="mb-4 text-center",
254                        ),
255                    ],
256                    width=3,
257                ),
258            ],
259            className="mb-4",
260        ),
261        
262        # Charts
263        dbc.Row(
264            [
265                dbc.Col(
266                    [
267                        dbc.Card(
268                            [
269                                dbc.CardHeader("Transactions Over Time"),
270                                dbc.CardBody(
271                                    [
272                                        dcc.Graph(id="transactions-time-chart"),
273                                    ]
274                                ),
275                            ],
276                            className="mb-4",
277                        ),
278                    ],
279                    width=6,
280                ),
281                dbc.Col(
282                    [
283                        dbc.Card(
284                            [
285                                dbc.CardHeader("Anomaly Score Distribution"),
286                                dbc.CardBody(
287                                    [
288                                        dcc.Graph(id="anomaly-score-histogram"),
289                                    ]
290                                ),
291                            ],
292                            className="mb-4",
293                        ),
294                    ],
295                    width=6,
296                ),
297            ]
298        ),
299        
300        dbc.Row(
301            [
302                dbc.Col(
303                    [
304                        dbc.Card(
305                            [
306                                dbc.CardHeader("Fee Rate vs. Transaction Size"),
307                                dbc.CardBody(
308                                    [
309                                        dcc.Graph(id="fee-size-scatter"),
310                                    ]
311                                ),
312                            ],
313                            className="mb-4",
314                        ),
315                    ],
316                    width=12,
317                ),
318            ]
319        ),
320        
321        # Transaction Table
322        dbc.Row(
323            [
324                dbc.Col(
325                    [
326                        dbc.Card(
327                            [
328                                dbc.CardHeader("Recent Transactions"),
329                                dbc.CardBody(
330                                    [
331                                        html.Div(id="transactions-table"),
332                                    ]
333                                ),
334                            ],
335                            className="mb-4",
336                        ),
337                    ],
338                    width=12,
339                ),
340            ]
341        ),
342        
343        # Anomaly Prediction Form
344        dbc.Row(
345            [
346                dbc.Col(
347                    [
348                        dbc.Card(
349                            [
350                                dbc.CardHeader("Test Transaction Anomaly Detection"),
351                                dbc.CardBody(
352                                    [
353                                        dbc.Row(
354                                            [
355                                                dbc.Col(
356                                                    [
357                                                        html.Label("Transaction Hash"),
358                                                        dbc.Input(id="tx-hash-input", placeholder="Enter transaction hash", type="text"),
359                                                    ],
360                                                    width=6,
361                                                ),
362                                                dbc.Col(
363                                                    [
364                                                        html.Label("Transaction Size (bytes)"),
365                                                        dbc.Input(id="tx-size-input", placeholder="Enter size", type="number", value=250),
366                                                    ],
367                                                    width=3,
368                                                ),
369                                                dbc.Col(
370                                                    [
371                                                        html.Label("Transaction Weight"),
372                                                        dbc.Input(id="tx-weight-input", placeholder="Enter weight", type="number", value=1000),
373                                                    ],
374                                                    width=3,
375                                                ),
376                                            ]
377                                        ),
378                                        html.Br(),
379                                        dbc.Row(
380                                            [
381                                                dbc.Col(
382                                                    [
383                                                        html.Label("Fee (satoshis)"),
384                                                        dbc.Input(id="tx-fee-input", placeholder="Enter fee", type="number", value=5000),
385                                                    ],
386                                                    width=3,
387                                                ),
388                                                dbc.Col(
389                                                    [
390                                                        html.Label("Inputs Count"),
391                                                        dbc.Input(id="tx-inputs-input", placeholder="Enter inputs count", type="number", value=2),
392                                                    ],
393                                                    width=3,
394                                                ),
395                                                dbc.Col(
396                                                    [
397                                                        html.Label("Outputs Count"),
398                                                        dbc.Input(id="tx-outputs-input", placeholder="Enter outputs count", type="number", value=2),
399                                                    ],
400                                                    width=3,
401                                                ),
402                                                dbc.Col(
403                                                    [
404                                                        html.Label("Input Value (satoshis)"),
405                                                        dbc.Input(id="tx-input-value-input", placeholder="Enter input value", type="number", value=1000000),
406                                                    ],
407                                                    width=3,
408                                                ),
409                                            ]
410                                        ),
411                                        html.Br(),
412                                        dbc.Row(
413                                            [
414                                                dbc.Col(
415                                                    [
416                                                        html.Label("Output Value (satoshis)"),
417                                                        dbc.Input(id="tx-output-value-input", placeholder="Enter output value", type="number", value=995000),
418                                                    ],
419                                                    width=3,
420                                                ),
421                                                dbc.Col(
422                                                    [
423                                                        dbc.Button("Check Transaction", id="check-tx-button", color="primary", className="mt-4"),
424                                                    ],
425                                                    width=3,
426                                                ),
427                                                dbc.Col(
428                                                    [
429                                                        html.Div(id="prediction-result", className="mt-4"),
430                                                    ],
431                                                    width=6,
432                                                ),
433                                            ]
434                                        ),
435                                    ]
436                                ),
437                            ],
438                            className="mb-4",
439                        ),
440                    ],
441                    width={"size": 10, "offset": 1},
442                )
443            ]
444        ),
445        
446        # Footer
447        dbc.Row(
448            [
449                dbc.Col(
450                    [
451                        html.Hr(),
452                        html.P(
453                            "Bitcoin Transaction Anomaly Detection Dashboard - Powered by MLOps Pipeline",
454                            className="text-center text-muted",
455                        ),
456                    ],
457                    width=12,
458                )
459            ]
460        ),
461        
462        # Store for data
463        dcc.Store(id="transaction-data-store"),
464        
465        # Interval for auto refresh
466        dcc.Interval(
467            id="auto-refresh-interval",
468            interval=30 * 1000,  # 30 seconds
469            n_intervals=0,
470            disabled=False,
471        ),
472    ],
473    fluid=True,
474)
475
476# Callbacks
477@app.callback(
478    Output("transaction-data-store", "data"),
479    [
480        Input("auto-refresh-interval", "n_intervals"),
481        Input("anomaly-threshold-slider", "value"),
482        Input("size-range-slider", "value"),
483        Input("time-range-dropdown", "value"),
484    ],
485)
486def update_data(n_intervals, anomaly_threshold, size_range, time_range):
487    """
488    Update the transaction data based on filters
489    """
490    # Load sample data
491    df = load_sample_data()
492    
493    # Apply time range filter
494    now = datetime.now()
495    if time_range != "all":
496        hours = int(time_range.replace("h", ""))
497        df = df[df["transaction_time"] >= now - timedelta(hours=hours)]
498    
499    # Apply size range filter
500    df = df[(df["size"] >= size_range[0]) & (df["size"] <= size_range[1])]
501    
502    # Update anomaly flag based on threshold
503    df["is_anomaly"] = df["anomaly_score"] > anomaly_threshold
504    
505    # Convert to JSON for storage
506    return df.to_json(date_format="iso", orient="split")
507
508@app.callback(
509    [
510        Output("total-transactions", "children"),
511        Output("total-anomalies", "children"),
512        Output("avg-fee-rate", "children"),
513        Output("avg-tx-size", "children"),
514    ],
515    [
516        Input("transaction-data-store", "data"),
517        Input("anomalies-only-switch", "value"),
518    ],
519)
520def update_kpi_cards(json_data, anomalies_only):
521    """
522    Update KPI cards based on filtered data
523    """
524    df = pd.read_json(json_data, orient="split")
525    
526    if anomalies_only:
527        df = df[df["is_anomaly"]]
528    
529    total_tx = len(df)
530    total_anomalies = df["is_anomaly"].sum()
531    avg_fee_rate = df["fee_rate"].mean()
532    avg_tx_size = df["size"].mean()
533    
534    return (
535        f"{total_tx:,}",
536        f"{total_anomalies:,}",
537        f"{avg_fee_rate:.2f}",
538        f"{avg_tx_size:,.0f}",
539    )
540
541@app.callback(
542    Output("transactions-time-chart", "figure"),
543    [
544        Input("transaction-data-store", "data"),
545        Input("anomalies-only-switch", "value"),
546        Input("anomaly-threshold-slider", "value"),
547    ],
548)
549def update_time_chart(json_data, anomalies_only, anomaly_threshold):
550    """
551    Update transactions over time chart
552    """
553    df = pd.read_json(json_data, orient="split")
554    
555    if anomalies_only:
556        df = df[df["is_anomaly"]]
557    
558    # Group by hour
559    df["hour"] = df["transaction_time"].dt.floor("H")
560    hourly_counts = df.groupby(["hour", "is_anomaly"]).size().reset_index(name="count")
561    
562    # Create figure
563    fig = go.Figure()
564    
565    # Add normal transactions
566    normal_data = hourly_counts[~hourly_counts["is_anomaly"]]
567    if not normal_data.empty:
568        fig.add_trace(
569            go.Scatter(
570                x=normal_data["hour"],
571                y=normal_data["count"],
572                mode="lines",
573                name="Normal Transactions",
574                line=dict(color="green", width=2),
575                stackgroup="one",
576            )
577        )
578    
579    # Add anomalous transactions
580    anomaly_data = hourly_counts[hourly_counts["is_anomaly"]]
581    if not anomaly_data.empty:
582        fig.add_trace(
583            go.Scatter(
584                x=anomaly_data["hour"],
585                y=anomaly_data["count"],
586                mode="lines",
587                name="Anomalous Transactions",
588                line=dict(color="red", width=2),
589                stackgroup="one",
590            )
591        )
592    
593    fig.update_layout(
594        title="Transaction Volume Over Time",
595        xaxis_title="Time",
596        yaxis_title="Number of Transactions",
597        legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
598        margin=dict(l=40, r=40, t=40, b=40),
599    )
600    
601    return fig
602
603@app.callback(
604    Output("anomaly-score-histogram", "figure"),
605    [
606        Input("transaction-data-store", "data"),
607        Input("anomaly-threshold-slider", "value"),
608    ],
609)
610def update_anomaly_histogram(json_data, anomaly_threshold):
611    """
612    Update anomaly score histogram
613    """
614    df = pd.read_json(json_data, orient="split")
615    
616    fig = go.Figure()
617    
618    # Add histogram
619    fig.add_trace(
620        go.Histogram(
621            x=df["anomaly_score"],
622            nbinsx=30,
623            marker_color="lightblue",
624        )
625    )
626    
627    # Add threshold line
628    fig.add_shape(
629        type="line",
630        x0=anomaly_threshold,
631        x1=anomaly_threshold,
632        y0=0,
633        y1=1,
634        yref="paper",
635        line=dict(color="red", width=2, dash="dash"),
636    )
637    
638    fig.add_annotation(
639        x=anomaly_threshold,
640        y=0.95,
641        yref="paper",
642        text="Threshold",
643        showarrow=True,
644        arrowhead=1,
645        ax=40,
646        ay=0,
647    )
648    
649    fig.update_layout(
650        title="Distribution of Anomaly Scores",
651        xaxis_title="Anomaly Score",
652        yaxis_title="Count",
653        margin=dict(l=40, r=40, t=40, b=40),
654    )
655    
656    return fig
657
658@app.callback(
659    Output("fee-size-scatter", "figure"),
660    [
661        Input("transaction-data-store", "data"),
662        Input("anomalies-only-switch", "value"),
663    ],
664)
665def update_fee_size_scatter(json_data, anomalies_only):
666    """
667    Update fee rate vs. transaction size scatter plot
668    """
669    df = pd.read_json(json_data, orient="split")
670    
671    if anomalies_only:
672        df = df[df["is_anomaly"]]
673    
674    fig = px.scatter(
675        df,
676        x="size",
677        y="fee_rate",
678        color="is_anomaly",
679        color_discrete_map={True: "red", False: "blue"},
680        hover_data=["hash", "fee", "inputs_count", "outputs_count"],
681        opacity=0.7,
682        title="Fee Rate vs. Transaction Size",
683    )
684    
685    fig.update_layout(
686        xaxis_title="Transaction Size (bytes)",
687        yaxis_title="Fee Rate (satoshis/byte)",
688        legend_title="Is Anomaly",
689        margin=dict(l=40, r=40, t=40, b=40),
690    )
691    
692    return fig
693
694@app.callback(
695    Output("transactions-table", "children"),
696    [
697        Input("transaction-data-store", "data"),
698        Input("anomalies-only-switch", "value"),
699    ],
700)
701def update_transactions_table(json_data, anomalies_only):
702    """
703    Update transactions table
704    """
705    df = pd.read_json(json_data, orient="split")
706    
707    if anomalies_only:
708        df = df[df["is_anomaly"]]
709    
710    # Sort by time (most recent first) and take the most recent 10
711    df = df.sort_values("transaction_time", ascending=False).head(10)
712    
713    # Format the table
714    table_header = [
715        html.Thead(
716            html.Tr(
717                [
718                    html.Th("Time"),
719                    html.Th("Hash"),
720                    html.Th("Size"),
721                    html.Th("Fee"),
722                    html.Th("Fee Rate"),
723                    html.Th("Anomaly Score"),
724                    html.Th("Status"),
725                ]
726            )
727        )
728    ]
729    
730    rows = []
731    for _, row in df.iterrows():
732        status_badge = dbc.Badge("Anomaly", color="danger") if row["is_anomaly"] else dbc.Badge("Normal", color="success")
733        
734        rows.append(
735            html.Tr(
736                [
737                    html.Td(row["transaction_time"].strftime("%Y-%m-%d %H:%M:%S")),
738                    html.Td(row["hash"][:10] + "..."),
739                    html.Td(f"{row['size']:,}"),
740                    html.Td(f"{row['fee']:,}"),
741                    html.Td(f"{row['fee_rate']:.2f}"),
742                    html.Td(f"{row['anomaly_score']:.2f}"),
743                    html.Td(status_badge),
744                ]
745            )
746        )
747    
748    table_body = [html.Tbody(rows)]
749    
750    return dbc.Table(table_header + table_body, bordered=True, hover=True, responsive=True, striped=True)
751
752@app.callback(
753    Output("auto-refresh-interval", "disabled"),
754    [Input("auto-refresh-switch", "value")],
755)
756def toggle_auto_refresh(auto_refresh):
757    """
758    Toggle auto refresh
759    """
760    return not auto_refresh
761
762@app.callback(
763    Output("prediction-result", "children"),
764    [Input("check-tx-button", "n_clicks")],
765    [
766        State("tx-hash-input", "value"),
767        State("tx-size-input", "value"),
768        State("tx-weight-input", "value"),
769        State("tx-fee-input", "value"),
770        State("tx-inputs-input", "value"),
771        State("tx-outputs-input", "value"),
772        State("tx-input-value-input", "value"),
773        State("tx-output-value-input", "value"),
774    ],
775)
776def check_transaction(n_clicks, tx_hash, size, weight, fee, inputs, outputs, input_value, output_value):
777    """
778    Check a transaction for anomalies
779    """
780    if n_clicks is None:
781        return ""
782    
783    # In a real implementation, this would call the API
784    # For demonstration, we'll simulate a response
785    
786    # Create transaction data
787    tx_data = {
788        "hash": tx_hash or "test_transaction",
789        "size": size or 250,
790        "weight": weight or 1000,
791        "fee": fee or 5000,
792        "inputs_count": inputs or 2,
793        "outputs_count": outputs or 2,
794        "input_value": input_value or 1000000,
795        "output_value": output_value or 995000,
796    }
797    
798    try:
799        # In a real implementation, this would be an API call
800        # response = requests.post(API_ENDPOINT, json=tx_data)
801        # result = response.json()
802        
803        # For demonstration, simulate a response
804        fee_rate = tx_data["fee"] / tx_data["size"]
805        is_anomaly = False
806        explanation = "Transaction appears normal based on its characteristics."
807        
808        # Simple anomaly detection logic for demonstration
809        if fee_rate > 100 or fee_rate < 1 or tx_data["size"] > 10000:
810            is_anomaly = True
811            explanation = f"Potential anomaly detected: Unusual fee rate ({fee_rate:.2f} satoshis/byte)"
812        
813        result = {
814            "transaction_hash": tx_data["hash"],
815            "anomaly_score": 1.5 if is_anomaly else 0.2,
816            "is_anomaly": is_anomaly,
817            "explanation": explanation,
818        }
819        
820        # Create result display
821        status_badge = dbc.Badge("ANOMALY DETECTED", color="danger") if result["is_anomaly"] else dbc.Badge("NORMAL", color="success")
822        
823        return html.Div(
824            [
825                html.Div([status_badge], className="mb-2"),
826                html.Div(f"Anomaly Score: {result['anomaly_score']:.2f}", className="mb-2"),
827                html.Div(result["explanation"]),
828            ]
829        )
830    
831    except Exception as e:
832        return html.Div(f"Error: {str(e)}", className="text-danger")
833
834if __name__ == "__main__":
835    app.run_server(debug=True, host="0.0.0.0", port=8050)
836