Spaces:
Running
Running
| import gradio as gr | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from datetime import datetime | |
| import os | |
| from preprocessing import get_latest_sequence | |
| from predictor import MineROIPredictor | |
| from fetch_blockchain_data import get_latest_blockchain_data | |
| # internal dummy miner used only for age_days etc. (not shown in UI) | |
| DEFAULT_MINER_NAME = "s19pro" | |
| MODEL_PATH = "best_model_weights.pth" | |
| # Predictor (global) | |
| predictor = None | |
| def init_predictor(): | |
| """Initialize predictor once""" | |
| global predictor | |
| if predictor is None: | |
| predictor = MineROIPredictor(MODEL_PATH) | |
| def init_app(): | |
| """Initialize app (no need for local blockchain_data_complete.csv).""" | |
| print("\n" + "="*80) | |
| print("๐ INITIALIZING MINEROI-NET APP") | |
| print("="*80) | |
| print("\nUsing live blockchain.com data (last 90 days).") | |
| print("Model will use the latest 30 days for ROI prediction.") | |
| print("="*80 + "\n") | |
| init_predictor() | |
| def predict_roi(machine_price, machine_hashrate, machine_power, machine_efficiency, electricity_rate,machine_release_date): | |
| """ | |
| Real-time prediction: | |
| - Uses latest 90 days from blockchain.com | |
| - Model uses last 30 days | |
| - Scaler chosen based on electricity_rate: | |
| < 0.05 -> ethiopia scaler | |
| 0.05-0.09 -> china scaler | |
| > 0.09 -> texas scaler | |
| """ | |
| try: | |
| window_size = 30 | |
| # -------- parse user inputs -------- | |
| miner_price = float(machine_price) | |
| miner_hashrate = float(machine_hashrate) | |
| machine_power = float(machine_power) | |
| machine_efficiency = float(machine_efficiency) | |
| user_electricity_rate = float(electricity_rate) | |
| # ----- parse release date ----- | |
| release_str = None | |
| if machine_release_date is not None: | |
| release_str = str(machine_release_date).strip() | |
| if release_str: | |
| try: | |
| # validate format YYYY-MM-DD | |
| datetime.strptime(release_str, "%Y-%m-%d") | |
| except ValueError: | |
| error_msg = """ | |
| <div style='background: #e74c3c; color: white; padding: 20px; border-radius: 10px;'> | |
| <h3 style='margin: 0;'>โ Invalid release date</h3> | |
| <p style='margin: 10px 0 0 0;'> | |
| Please enter the machine release date in the format <b>YYYY-MM-DD</b>, | |
| for example <code>2020-05-01</code>. | |
| </p> | |
| </div> | |
| """ | |
| return error_msg, error_msg, None, None | |
| else: | |
| # empty box -> fall back to S19 Pro default | |
| release_str = None | |
| # --------------------------------------------------------- | |
| # Bucket electricity rate ONLY to choose scaler | |
| # The actual feature value will be the user input, repeated | |
| # for all 30 days in the window. | |
| # --------------------------------------------------------- | |
| if user_electricity_rate < 0.05: | |
| scaler_region = "ethiopia" | |
| region_bucket = "Low-cost (< $0.05/kWh)" | |
| elif user_electricity_rate < 1.0: | |
| scaler_region = "china" | |
| region_bucket = "Medium-cost ($0.05โ$1.00/kWh)" | |
| else: | |
| scaler_region = "texas" | |
| region_bucket = "High-cost (โฅ $1.00/kWh)" | |
| # Region for the pipeline (used by get_latest_sequence & predictor) | |
| region = scaler_region | |
| # This is the value that will be used for all 30 days in the window | |
| # (prepare_miner_features repeats it across time). | |
| electricity_rate_used = user_electricity_rate | |
| print("User machine specs:") | |
| print(f" Price: {miner_price}") | |
| print(f" Hashrate (TH/s): {miner_hashrate}") | |
| print(f" Power (W): {machine_power}") | |
| print(f" Efficiency: {machine_efficiency}") | |
| print(f" User elec rate: {user_electricity_rate} USD/kWh") | |
| print(f" Bucket region: {region}") | |
| print(f" Elec used in features (all 30 days): {electricity_rate_used} USD/kWh") | |
| print("=" * 80 + "\n") | |
| # # -------- choose scaler region from electricity_rate -------- | |
| # if electricity_rate < 0.05: | |
| # scaler_region = "texas" | |
| # region_bucket = "Low-cost (< $0.05/kWh)" | |
| # elif electricity_rate <= 0.09: | |
| # scaler_region = "texas" | |
| # region_bucket = "Medium-cost ($0.05โ$0.09/kWh)" | |
| # else: | |
| # scaler_region = "texas" | |
| # region_bucket = "High-cost (> $0.09/kWh)" | |
| # print("\n" + "=" * 80) | |
| # print("PREDICTION REQUEST") | |
| # print("=" * 80) | |
| # print(f"Scaler region (from electricity rate): {scaler_region}") | |
| print("=" * 80 + "\n") | |
| # -------- fetch latest blockchain data (no date input) -------- | |
| print("๐ก Fetching latest blockchain data (last 90 days)...") | |
| blockchain_df = get_latest_blockchain_data(days=90) | |
| if blockchain_df is None or len(blockchain_df) < window_size: | |
| error_msg = f""" | |
| <div style='background: #e74c3c; color: white; padding: 20px; border-radius: 10px;'> | |
| <h3 style='margin: 0;'>โ Error: Insufficient Data</h3> | |
| <p style='margin: 10px 0 0 0;'> | |
| Not enough blockchain data available. | |
| Need at least {window_size} days of historical data. | |
| </p> | |
| </div> | |
| """ | |
| return error_msg, error_msg, None, None | |
| print(f"โ Got {len(blockchain_df)} days of data") | |
| print(f" Date range: {blockchain_df['date'].min().date()} to {blockchain_df['date'].max().date()}") | |
| price_source = "User input" | |
| print(f" Using user-provided price: ${miner_price:,.2f}") | |
| # -------- build sequence with user machine specs -------- | |
| print("\n๐ง Preparing features...") | |
| sequence, df_window, pred_date = get_latest_sequence( | |
| blockchain_df, | |
| DEFAULT_MINER_NAME, # internal dummy miner, not shown to user | |
| miner_price, | |
| scaler_region, | |
| window_size, | |
| machine_hashrate=miner_hashrate, | |
| power=machine_power, | |
| efficiency=machine_efficiency, | |
| electricity_rate=electricity_rate_used, | |
| release_date=release_str | |
| ) | |
| print(f"โ Sequence prepared: {sequence.shape}") | |
| # -------- model prediction -------- | |
| print("\n๐ค Running prediction...") | |
| result = predictor.predict(sequence, scaler_region) | |
| print(f"โ Prediction: {result['predicted_label']} ({result['confidence']:.1%})") | |
| # -------- build UI outputs -------- | |
| miner_info = create_miner_info( | |
| miner_price, | |
| price_source, | |
| pred_date, | |
| miner_hashrate, | |
| machine_power, | |
| machine_efficiency, | |
| electricity_rate_used, | |
| region_bucket, | |
| ) | |
| prediction_html = create_prediction_html(result, pred_date, window_size) | |
| confidence_chart = create_confidence_chart(result["probabilities"]) | |
| price_chart = create_price_chart(blockchain_df, window_size) | |
| print("=" * 80 + "\n") | |
| return miner_info, prediction_html, confidence_chart, price_chart | |
| except Exception as e: | |
| import traceback | |
| error_details = traceback.format_exc() | |
| print("\nโ ERROR:") | |
| print(error_details) | |
| error = f""" | |
| <div style='background: #e74c3c; color: white; padding: 20px; border-radius: 10px;'> | |
| <h3 style='margin: 0;'>โ Prediction Error</h3> | |
| <p style='margin: 10px 0 0 0;'>{str(e)}</p> | |
| </div> | |
| """ | |
| return error, error, None, None | |
| def create_miner_info( | |
| price, | |
| source, | |
| pred_date, | |
| machine_hashrate, | |
| machine_power, | |
| machine_efficiency, | |
| electricity_rate, | |
| region_bucket, | |
| ): | |
| """ | |
| Display miner info for a user-specified machine (no ASIC dropdown). | |
| """ | |
| elec_rate = float(electricity_rate) | |
| daily_cost = (float(machine_power) * 24.0 / 1000.0) * elec_rate | |
| # Color coding for price source | |
| if source == "API": | |
| badge_color = "#27ae60" # Green | |
| elif source == "User input": | |
| badge_color = "#3498db" # Blue | |
| else: | |
| badge_color = "#e74c3c" # Red | |
| return f""" | |
| <div style=" | |
| background:#111111; | |
| padding:20px; | |
| border-radius:10px; | |
| border:1px solid #333333; | |
| color:#ffffff; | |
| font-size:14px; | |
| "> | |
| <h3 style="color:#F7931A; margin-top:0; margin-bottom:10px;"> | |
| Custom ASIC Miner | |
| </h3> | |
| <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px;"> | |
| <div style="color:#f5f5f5;"> | |
| <p style="font-size: 1.2em; margin: 10px 0; color: #ffffff;"><strong style="color: #ffffff;">Hashrate:{machine_hashrate:.2f} TH/s</strong></p> | |
| <p style="font-size: 1.2em; margin: 10px 0; color: #ffffff;"><strong style="color: #ffffff;">Power:{machine_power:.1f} W</strong></p> | |
| <p style="font-size: 1.2em; margin: 10px 0; color: #ffffff;"><strong style="color: #ffffff;">Efficiency:{machine_efficiency:.2f} W/TH</strong></p> | |
| </div> | |
| <div style="color:#f5f5f5;"> | |
| <p style="margin:4px 0;"> | |
| <strong style="color: #ffffff;">Price (as of {pred_date.date()}):${price:,.2f}</strong> | |
| <span style=" | |
| background:{badge_color}; | |
| color:#ffffff; | |
| padding:2px 8px; | |
| border-radius:4px; | |
| font-size:11px; | |
| margin-left:6px; | |
| "> | |
| {source} | |
| </span> | |
| </p> | |
| <p style="font-size: 1.2em; margin: 10px 0; color: #ffffff;"><strong style="color: #ffffff;">Electricity rate:{elec_rate:.4f} USD/kWh</strong></p> | |
| <p style="font-size: 1.2em; margin: 10px 0; color: #ffffff;"><strong style="color: #ffffff;">Cost bucket:{region_bucket}</strong></p> | |
| <p style="font-size: 1.2em; margin: 10px 0; color: #ffffff;"><strong style="color: #ffffff;">Estimated daily elec cost:${daily_cost:,.2f}</strong></p> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_prediction_html(result, date, window): | |
| label = result['predicted_label'] | |
| conf = result['confidence'] | |
| if 'Unprofitable' in label: | |
| color, emoji, rec = '#e74c3c', '๐ด', 'NOT RECOMMENDED' | |
| elif 'Marginal' in label: | |
| color, emoji, rec = '#f39c12', '๐ก', 'PROCEED WITH CAUTION' | |
| else: | |
| color, emoji, rec = '#27ae60', '๐ข', 'GOOD OPPORTUNITY' | |
| return f""" | |
| <div style="background: #1e1e1e; padding: 30px; border-radius: 10px; border: 2px solid {color}; text-align: center; color: #ffffff;"> | |
| <h2 style="color: {color}; margin: 0 0 10px 0;">{emoji} {label}</h2> | |
| <p style="font-size: 1.2em; margin: 10px 0; color: #ffffff;"><strong style="color: #ffffff;">Confidence: {conf:.1%}</strong></p> | |
| <p style="font-size: 1.5em; color: {color}; margin: 20px 0;"><strong style="color: {color};">{rec}</strong></p> | |
| <p style="color: #cccccc; margin: 10px 0 0 0; font-size: 0.9em;"> | |
| Prediction based on data up to: {date.strftime('%Y-%m-%d')}<br>Window: {window} days | |
| </p> | |
| </div> | |
| """ | |
| def create_confidence_chart(probs): | |
| categories = ['Unprofitable', 'Marginal', 'Profitable'] | |
| values = [probs['unprofitable'], probs['marginal'], probs['profitable']] | |
| colors = ['#e74c3c', '#f39c12', '#27ae60'] | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar(x=categories, y=values, marker_color=colors, text=[f'{v:.1%}' for v in values], textposition='auto')) | |
| fig.update_layout(title='Prediction Confidence', yaxis_title='Probability', yaxis=dict(range=[0, 1], tickformat='.0%'), | |
| template='plotly_dark', height=300, margin=dict(l=0, r=0, t=40, b=0)) | |
| return fig | |
| def create_price_chart(df, window): | |
| # Show more context if available | |
| display_days = min(len(df), window * 2) | |
| df_display = df.tail(display_days) | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter(x=df_display['date'], y=df_display['bitcoin_price'], mode='lines', name='Bitcoin Price', line=dict(color='#F7931A', width=2))) | |
| fig.update_layout(title=f'Bitcoin Price ({len(df_display)} Days)', xaxis_title='Date', yaxis_title='Price (USD)', | |
| template='plotly_dark', height=300, margin=dict(l=0, r=0, t=40, b=0)) | |
| return fig | |
| def create_interface(): | |
| with gr.Blocks(title="MineROI-Net") as app: | |
| gr.Markdown("# ๐ช MineROI-Net: Bitcoin Mining Hardware ROI Predictor") | |
| gr.Markdown( | |
| "Uses the **latest 30 days** of Bitcoin network data from blockchain.com " | |
| "to classify your miner as Unprofitable / Marginal / Profitable." | |
| ) | |
| with gr.Row(): | |
| # ---- Left: inputs ---- | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Configuration") | |
| machine_price = gr.Number( | |
| label="Machine price (USD)", | |
| value=2500.0, | |
| precision=2, | |
| ) | |
| machine_hashrate = gr.Number( | |
| label="Machine hashrate (TH/s)", | |
| value=100.0, | |
| precision=2, | |
| ) | |
| machine_power = gr.Number( | |
| label="Power (W)", | |
| value=3000.0, | |
| precision=1, | |
| ) | |
| machine_efficiency = gr.Number( | |
| label="Efficiency (W/TH)", | |
| value=30.0, | |
| precision=2, | |
| ) | |
| machine_release_date = gr.Textbox( | |
| label="Release date (YYYY-MM-DD)", | |
| value="2020-05-01", | |
| placeholder="e.g. 2020-05-01", | |
| lines=1, | |
| scale=1, | |
| container=True, | |
| show_label=True, | |
| ) | |
| electricity_rate = gr.Number( | |
| label="Electricity rate (USD/kWh)", | |
| value=0.07, # neutral default | |
| precision=4, | |
| ) | |
| btn = gr.Button("๐ฎ Predict ROI", variant="primary", size="lg") | |
| gr.Markdown( | |
| """ | |
| ### About | |
| - ๐ด **Unprofitable** (ROI โค 0) | |
| - ๐ก **Marginal** (0 < ROI < 1) | |
| - ๐ข **Profitable** (ROI โฅ 1) | |
| **Model:** trained on 30-day windows of Bitcoin network and miner features. | |
| **Live mode:** whenever you click *Predict*, the app pulls the latest blockchain data. | |
| """ | |
| ) | |
| # ---- Right: outputs ---- | |
| with gr.Column(scale=2): | |
| gr.Markdown("### Results") | |
| miner_info = gr.HTML() | |
| prediction = gr.HTML() | |
| with gr.Row(): | |
| conf_plot = gr.Plot() | |
| price_plot = gr.Plot() | |
| # Connect button to prediction function | |
| btn.click( | |
| fn=predict_roi, | |
| inputs=[ | |
| machine_price, | |
| machine_hashrate, | |
| machine_power, | |
| machine_efficiency, | |
| electricity_rate, | |
| machine_release_date | |
| ], | |
| outputs=[miner_info, prediction, conf_plot, price_plot], | |
| ) | |
| return app | |
| if __name__ == "__main__": | |
| # Initialize app (loads complete data into memory) | |
| init_app() | |
| # Launch | |
| app = create_interface() | |
| app.launch() | |
| # app.launch(server_name="0.0.0.0", server_port=7860, share=True) |