gauravlochab commited on
Commit
f8524e7
·
1 Parent(s): 398c34c

feat: add roi graph

Browse files
Files changed (1) hide show
  1. app.py +807 -191
app.py CHANGED
@@ -14,7 +14,7 @@ import matplotlib.pyplot as plt
14
  import matplotlib.dates as mdates
15
  import random
16
  import logging
17
- from typing import List, Dict, Any
18
  # Comment out the import for now and replace with dummy functions
19
  # from app_trans_new import create_transcation_visualizations,create_active_agents_visualizations
20
  # APR visualization functions integrated directly
@@ -39,8 +39,9 @@ logging.getLogger("matplotlib").setLevel(logging.WARNING)
39
  logger.info("============= APPLICATION STARTING =============")
40
  logger.info(f"Running from directory: {os.getcwd()}")
41
 
42
- # Global variable to store the data for reuse
43
  global_df = None
 
44
 
45
  # Configuration
46
  API_BASE_URL = "https://afmdb.autonolas.tech"
@@ -156,7 +157,7 @@ def get_agent_name(agent_id: int, agents: List[Dict[str, Any]]) -> str:
156
  return "Unknown"
157
 
158
  def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
159
- """Extract APR value, adjusted APR value, and timestamp from JSON value"""
160
  try:
161
  agent_id = attr.get("agent_id", "unknown")
162
  logger.debug(f"Extracting APR value for agent {agent_id}")
@@ -164,7 +165,7 @@ def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
164
  # The APR value is stored in the json_value field
165
  if attr["json_value"] is None:
166
  logger.debug(f"Agent {agent_id}: json_value is None")
167
- return {"apr": None, "adjusted_apr": None, "timestamp": None, "agent_id": agent_id, "is_dummy": False}
168
 
169
  # If json_value is a string, parse it
170
  if isinstance(attr["json_value"], str):
@@ -177,26 +178,39 @@ def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
177
  adjusted_apr = json_data.get("adjusted_apr") # Extract adjusted_apr if present
178
  timestamp = json_data.get("timestamp")
179
 
180
- logger.debug(f"Agent {agent_id}: Raw APR value: {apr}, adjusted APR value: {adjusted_apr}, timestamp: {timestamp}")
 
 
 
 
 
181
 
182
  # Convert timestamp to datetime if it exists
183
  timestamp_dt = None
184
  if timestamp:
185
  timestamp_dt = datetime.fromtimestamp(timestamp)
186
 
187
- result = {"apr": apr, "adjusted_apr": adjusted_apr, "timestamp": timestamp_dt, "agent_id": agent_id, "is_dummy": False}
 
 
 
 
 
 
 
188
  logger.debug(f"Agent {agent_id}: Extracted result: {result}")
189
  return result
190
  except (json.JSONDecodeError, KeyError, TypeError) as e:
191
  logger.error(f"Error parsing JSON value: {e} for agent_id: {attr.get('agent_id')}")
192
  logger.error(f"Problematic json_value: {attr.get('json_value')}")
193
- return {"apr": None, "adjusted_apr": None, "timestamp": None, "agent_id": attr.get('agent_id'), "is_dummy": False}
194
 
195
  def fetch_apr_data_from_db():
196
  """
197
  Fetch APR data from database using the API.
198
  """
199
  global global_df
 
200
 
201
  logger.info("==== Starting APR data fetch ====")
202
 
@@ -244,30 +258,54 @@ def fetch_apr_data_from_db():
244
 
245
  logger.info(f"Found {len(apr_attributes)} APR attributes total")
246
 
247
- # Step 5: Extract APR data
248
- logger.info("Extracting APR data from attributes")
249
  apr_data_list = []
 
 
250
  for attr in apr_attributes:
251
- apr_data = extract_apr_value(attr)
252
- if apr_data["apr"] is not None and apr_data["timestamp"] is not None:
253
  # Get agent name
254
  agent_name = get_agent_name(attr["agent_id"], modius_agents)
255
  # Add agent name to the data
256
- apr_data["agent_name"] = agent_name
257
  # Add is_dummy flag (all real data)
258
- apr_data["is_dummy"] = False
259
 
260
- # Include all APR values (including negative ones) EXCEPT zero and -100
261
- if apr_data["apr"] != 0 and apr_data["apr"] != -100:
262
- apr_data["metric_type"] = "APR"
263
- logger.debug(f"Agent {agent_name} ({attr['agent_id']}): APR value: {apr_data['apr']}")
264
- # Add to the data list
265
- apr_data_list.append(apr_data)
266
- else:
267
- # Log that we're skipping zero or -100 values
268
- logger.debug(f"Skipping value for agent {agent_name} ({attr['agent_id']}): {apr_data['apr']} (zero or -100)")
269
-
270
- logger.info(f"Extracted {len(apr_data_list)} valid APR data points")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  # Added debug for adjusted APR data after May 10th
273
  may_10_2025 = datetime(2025, 5, 10)
@@ -412,14 +450,20 @@ def fetch_apr_data_from_db():
412
  if keys_used:
413
  logger.info(f"Keys used for adjusted_apr after May 10th: {keys_used}")
414
 
415
- # Convert to DataFrame
416
  if not apr_data_list:
417
  logger.error("No valid APR data extracted")
418
  global_df = pd.DataFrame([])
419
- return global_df
420
-
421
- # Convert list of dictionaries to DataFrame
422
- global_df = pd.DataFrame(apr_data_list)
 
 
 
 
 
 
423
 
424
  # Log the resulting dataframe
425
  logger.info(f"Created DataFrame with {len(global_df)} rows")
@@ -448,21 +492,23 @@ def fetch_apr_data_from_db():
448
  for idx, row in global_df.iterrows():
449
  logger.debug(f"Row {idx}: {row.to_dict()}")
450
 
451
- # Add this at the end, right before returning the global_df
452
  logger.info("Analyzing adjusted_apr data availability...")
453
  log_adjusted_apr_availability(global_df)
454
 
455
- return global_df
456
 
457
  except requests.exceptions.RequestException as e:
458
  logger.error(f"API request error: {e}")
459
  global_df = pd.DataFrame([])
460
- return global_df
 
461
  except Exception as e:
462
  logger.error(f"Error fetching APR data: {e}")
463
  logger.exception("Exception traceback:")
464
  global_df = pd.DataFrame([])
465
- return global_df
 
466
 
467
  def log_adjusted_apr_availability(df):
468
  """
@@ -605,7 +651,7 @@ def generate_apr_visualizations():
605
  global global_df
606
 
607
  # Fetch data from database
608
- df = fetch_apr_data_from_db()
609
 
610
  # If we got no data at all, return placeholder figures
611
  if df.empty:
@@ -642,6 +688,464 @@ def generate_apr_visualizations():
642
 
643
  return combined_fig, csv_file
644
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  def create_time_series_graph_per_agent(df):
646
  """Create a time series graph for each agent using Plotly"""
647
  # Get unique agents
@@ -802,8 +1306,12 @@ def create_combined_time_series_graph(df):
802
  df['apr'] = df['apr'].astype(float) # Ensure APR is float
803
  df['metric_type'] = df['metric_type'].astype(str) # Ensure metric_type is string
804
 
805
- # Set x-axis start date to April 17, 2025 as requested by user
806
- x_start_date = datetime(2025, 4, 17, 0, 0, 0)
 
 
 
 
807
 
808
  # CRITICAL: Log the exact dataframe we're using for plotting to help debug
809
  logger.info(f"Graph data - shape: {df.shape}, columns: {df.columns}")
@@ -1908,182 +2416,290 @@ def dashboard():
1908
  with gr.Blocks() as demo:
1909
  gr.Markdown("# Average Modius Agent Performance")
1910
 
1911
- # APR Metrics tab - the only tab
1912
- with gr.Tab("APR Metrics"):
1913
- with gr.Column():
1914
- refresh_btn = gr.Button("Refresh APR Data")
1915
-
1916
- # Create container for plotly figure with responsive sizing
1917
  with gr.Column():
1918
- combined_graph = gr.Plot(label="APR for All Agents", elem_id="responsive_plot")
1919
-
1920
- # Create compact toggle controls at the bottom of the graph
1921
- with gr.Row(visible=True):
1922
- gr.Markdown("##### Toggle Graph Lines", elem_id="toggle_title")
1923
-
1924
- with gr.Row():
1925
- with gr.Column():
1926
- with gr.Row(elem_id="toggle_container"):
1927
- with gr.Column(scale=1, min_width=150):
1928
- apr_toggle = gr.Checkbox(label="APR Average", value=True, elem_id="apr_toggle")
1929
-
1930
- with gr.Column(scale=1, min_width=150):
1931
- adjusted_apr_toggle = gr.Checkbox(label="ETH Adjusted APR Average", value=True, elem_id="adjusted_apr_toggle")
1932
-
1933
- # Add custom CSS for making the plot responsive
1934
- gr.HTML("""
1935
- <style>
1936
- /* Make plot responsive */
1937
- #responsive_plot {
1938
- width: 100% !important;
1939
- max-width: 100% !important;
1940
- }
1941
- #responsive_plot > div {
1942
- width: 100% !important;
1943
- height: auto !important;
1944
- min-height: 500px !important;
1945
- }
1946
 
1947
- /* Existing toggle checkbox styling */
1948
- #apr_toggle .gr-checkbox {
1949
- accent-color: #e74c3c !important;
1950
- }
1951
 
1952
- #adjusted_apr_toggle .gr-checkbox {
1953
- accent-color: #2ecc71 !important;
1954
- }
1955
 
1956
- /* Make the toggle section more compact */
1957
- #toggle_title {
1958
- margin-bottom: 0;
1959
- margin-top: 10px;
1960
- }
 
 
 
1961
 
1962
- #toggle_container {
1963
- margin-top: 5px;
1964
- }
 
 
 
 
1965
 
1966
- /* Style the checkbox labels */
1967
- .gr-form.gr-box {
1968
- border: none !important;
1969
- background: transparent !important;
1970
- }
1971
 
1972
- /* Make checkboxes and labels appear on the same line */
1973
- .gr-checkbox-container {
1974
- display: flex !important;
1975
- align-items: center !important;
1976
- }
1977
 
1978
- /* Add colored indicators */
1979
- #apr_toggle .gr-checkbox-label::before {
1980
- content: "";
1981
- color: #e74c3c;
1982
- margin-right: 5px;
1983
- }
1984
 
1985
- #adjusted_apr_toggle .gr-checkbox-label::before {
1986
- content: "";
1987
- color: #2ecc71;
1988
- margin-right: 5px;
1989
- }
1990
- </style>
1991
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1992
 
1993
- # Function to update the graph
1994
- def update_apr_graph(show_apr_ma=True, show_adjusted_apr_ma=True):
1995
- # Generate visualization and get figure object directly
1996
- try:
1997
- combined_fig, _ = generate_apr_visualizations()
1998
-
1999
- # Update visibility of traces based on toggle values
2000
- for i, trace in enumerate(combined_fig.data):
2001
- # Check if this is a moving average trace
2002
- if trace.name == 'Average APR (3d window)':
2003
- trace.visible = show_apr_ma
2004
- elif trace.name == 'Average ETH Adjusted APR (3d window)':
2005
- trace.visible = show_adjusted_apr_ma
2006
-
2007
- return combined_fig
2008
- except Exception as e:
2009
- logger.exception("Error generating APR visualization")
2010
- # Create error figure
2011
- error_fig = go.Figure()
2012
- error_fig.add_annotation(
2013
- text=f"Error: {str(e)}",
2014
- x=0.5, y=0.5,
2015
- showarrow=False,
2016
- font=dict(size=15, color="red")
2017
- )
2018
- return error_fig
2019
 
2020
- # Initialize the graph on load with a placeholder
2021
- placeholder_fig = go.Figure()
2022
- placeholder_fig.add_annotation(
2023
- text="Click 'Refresh APR Data' to load APR graph",
 
 
 
2024
  x=0.5, y=0.5,
2025
  showarrow=False,
2026
- font=dict(size=15)
2027
  )
2028
- combined_graph.value = placeholder_fig
 
 
 
 
 
 
2029
 
2030
- # Function to update the graph based on toggle states
2031
- def update_graph_with_toggles(apr_visible, adjusted_apr_visible):
2032
- return update_apr_graph(apr_visible, adjusted_apr_visible)
 
 
2033
 
2034
- # Function to update the graph without parameters (for refresh button)
2035
- def refresh_graph():
2036
- """Refresh APR data from the database and update the visualization"""
2037
- try:
2038
- # Fetch new APR data
2039
- logger.info("Manually refreshing APR data...")
2040
- fetch_apr_data_from_db()
2041
-
2042
- # Verify data was fetched successfully
2043
- if global_df is None or len(global_df) == 0:
2044
- logger.error("Failed to fetch APR data")
2045
- return combined_graph.value, "Error: Failed to fetch APR data. Check the logs for details."
2046
-
2047
- # Log info about fetched data with focus on adjusted_apr
2048
- may_10_2025 = datetime(2025, 5, 10)
2049
- if 'timestamp' in global_df and 'adjusted_apr' in global_df:
2050
- after_may_10 = global_df[global_df['timestamp'] >= may_10_2025]
2051
- with_adjusted_after_may_10 = after_may_10[after_may_10['adjusted_apr'].notna()]
2052
-
2053
- logger.info(f"Data points after May 10th, 2025: {len(after_may_10)}")
2054
- logger.info(f"Data points with adjusted_apr after May 10th, 2025: {len(with_adjusted_after_may_10)}")
2055
-
2056
- # Generate new visualization
2057
- logger.info("Generating new APR visualization...")
2058
- new_graph = update_apr_graph(apr_toggle.value, adjusted_apr_toggle.value)
2059
- return new_graph, "APR data refreshed successfully"
2060
- except Exception as e:
2061
- logger.error(f"Error refreshing APR data: {e}")
2062
- return combined_graph.value, f"Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2063
 
2064
- # Add a text area for status messages
2065
- status_text = gr.Textbox(label="Status", value="Ready", interactive=False)
 
 
2066
 
2067
- # Set up the button click event for refresh
2068
- refresh_btn.click(
2069
- fn=refresh_graph,
2070
- inputs=[],
2071
- outputs=[combined_graph, status_text]
2072
- )
 
 
2073
 
2074
- # Set up the toggle switch events
2075
- apr_toggle.change(
2076
- fn=update_graph_with_toggles,
2077
- inputs=[apr_toggle, adjusted_apr_toggle],
2078
- outputs=[combined_graph]
2079
- )
 
 
 
 
 
 
 
 
 
2080
 
2081
- adjusted_apr_toggle.change(
2082
- fn=update_graph_with_toggles,
2083
- inputs=[apr_toggle, adjusted_apr_toggle],
2084
- outputs=[combined_graph]
2085
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2086
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2087
  return demo
2088
 
2089
  # Launch the dashboard
 
14
  import matplotlib.dates as mdates
15
  import random
16
  import logging
17
+ from typing import List, Dict, Any, Optional
18
  # Comment out the import for now and replace with dummy functions
19
  # from app_trans_new import create_transcation_visualizations,create_active_agents_visualizations
20
  # APR visualization functions integrated directly
 
39
  logger.info("============= APPLICATION STARTING =============")
40
  logger.info(f"Running from directory: {os.getcwd()}")
41
 
42
+ # Global variables to store the data for reuse
43
  global_df = None
44
+ global_roi_df = None
45
 
46
  # Configuration
47
  API_BASE_URL = "https://afmdb.autonolas.tech"
 
157
  return "Unknown"
158
 
159
  def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
160
+ """Extract APR value, adjusted APR value, ROI value, and timestamp from JSON value"""
161
  try:
162
  agent_id = attr.get("agent_id", "unknown")
163
  logger.debug(f"Extracting APR value for agent {agent_id}")
 
165
  # The APR value is stored in the json_value field
166
  if attr["json_value"] is None:
167
  logger.debug(f"Agent {agent_id}: json_value is None")
168
+ return {"apr": None, "adjusted_apr": None, "roi": None, "timestamp": None, "agent_id": agent_id, "is_dummy": False}
169
 
170
  # If json_value is a string, parse it
171
  if isinstance(attr["json_value"], str):
 
178
  adjusted_apr = json_data.get("adjusted_apr") # Extract adjusted_apr if present
179
  timestamp = json_data.get("timestamp")
180
 
181
+ # Extract ROI (f_i_ratio) from calculation_metrics if it exists
182
+ roi = None
183
+ if "calculation_metrics" in json_data and json_data["calculation_metrics"] is not None:
184
+ roi = json_data["calculation_metrics"].get("f_i_ratio")
185
+
186
+ logger.debug(f"Agent {agent_id}: Raw APR value: {apr}, adjusted APR value: {adjusted_apr}, ROI value: {roi}, timestamp: {timestamp}")
187
 
188
  # Convert timestamp to datetime if it exists
189
  timestamp_dt = None
190
  if timestamp:
191
  timestamp_dt = datetime.fromtimestamp(timestamp)
192
 
193
+ result = {
194
+ "apr": apr,
195
+ "adjusted_apr": adjusted_apr,
196
+ "roi": roi,
197
+ "timestamp": timestamp_dt,
198
+ "agent_id": agent_id,
199
+ "is_dummy": False
200
+ }
201
  logger.debug(f"Agent {agent_id}: Extracted result: {result}")
202
  return result
203
  except (json.JSONDecodeError, KeyError, TypeError) as e:
204
  logger.error(f"Error parsing JSON value: {e} for agent_id: {attr.get('agent_id')}")
205
  logger.error(f"Problematic json_value: {attr.get('json_value')}")
206
+ return {"apr": None, "adjusted_apr": None, "roi": None, "timestamp": None, "agent_id": attr.get('agent_id'), "is_dummy": False}
207
 
208
  def fetch_apr_data_from_db():
209
  """
210
  Fetch APR data from database using the API.
211
  """
212
  global global_df
213
+ global global_roi_df
214
 
215
  logger.info("==== Starting APR data fetch ====")
216
 
 
258
 
259
  logger.info(f"Found {len(apr_attributes)} APR attributes total")
260
 
261
+ # Step 5: Extract APR and ROI data
262
+ logger.info("Extracting APR and ROI data from attributes")
263
  apr_data_list = []
264
+ roi_data_list = []
265
+
266
  for attr in apr_attributes:
267
+ data = extract_apr_value(attr)
268
+ if data["timestamp"] is not None:
269
  # Get agent name
270
  agent_name = get_agent_name(attr["agent_id"], modius_agents)
271
  # Add agent name to the data
272
+ data["agent_name"] = agent_name
273
  # Add is_dummy flag (all real data)
274
+ data["is_dummy"] = False
275
 
276
+ # Process APR data
277
+ if data["apr"] is not None:
278
+ # Include all APR values (including negative ones) EXCEPT zero and -100
279
+ if data["apr"] != 0 and data["apr"] != -100:
280
+ apr_entry = data.copy()
281
+ apr_entry["metric_type"] = "APR"
282
+ logger.debug(f"Agent {agent_name} ({attr['agent_id']}): APR value: {data['apr']}")
283
+ # Add to the APR data list
284
+ apr_data_list.append(apr_entry)
285
+ else:
286
+ # Log that we're skipping zero or -100 values
287
+ logger.debug(f"Skipping APR value for agent {agent_name} ({attr['agent_id']}): {data['apr']} (zero or -100)")
288
+
289
+ # Process ROI data
290
+ if data["roi"] is not None:
291
+ # Include all ROI values except extreme outliers
292
+ if data["roi"] > -10 and data["roi"] < 10: # Filter extreme outliers
293
+ roi_entry = {
294
+ "roi": data["roi"],
295
+ "timestamp": data["timestamp"],
296
+ "agent_id": data["agent_id"],
297
+ "agent_name": agent_name,
298
+ "is_dummy": False,
299
+ "metric_type": "ROI"
300
+ }
301
+ logger.debug(f"Agent {agent_name} ({attr['agent_id']}): ROI value: {data['roi']}")
302
+ # Add to the ROI data list
303
+ roi_data_list.append(roi_entry)
304
+ else:
305
+ # Log that we're skipping extreme outlier values
306
+ logger.debug(f"Skipping ROI value for agent {agent_name} ({attr['agent_id']}): {data['roi']} (extreme outlier)")
307
+
308
+ logger.info(f"Extracted {len(apr_data_list)} valid APR data points and {len(roi_data_list)} valid ROI data points")
309
 
310
  # Added debug for adjusted APR data after May 10th
311
  may_10_2025 = datetime(2025, 5, 10)
 
450
  if keys_used:
451
  logger.info(f"Keys used for adjusted_apr after May 10th: {keys_used}")
452
 
453
+ # Convert to DataFrames
454
  if not apr_data_list:
455
  logger.error("No valid APR data extracted")
456
  global_df = pd.DataFrame([])
457
+ else:
458
+ # Convert list of dictionaries to DataFrame for APR
459
+ global_df = pd.DataFrame(apr_data_list)
460
+
461
+ if not roi_data_list:
462
+ logger.error("No valid ROI data extracted")
463
+ global_roi_df = pd.DataFrame([])
464
+ else:
465
+ # Convert list of dictionaries to DataFrame for ROI
466
+ global_roi_df = pd.DataFrame(roi_data_list)
467
 
468
  # Log the resulting dataframe
469
  logger.info(f"Created DataFrame with {len(global_df)} rows")
 
492
  for idx, row in global_df.iterrows():
493
  logger.debug(f"Row {idx}: {row.to_dict()}")
494
 
495
+ # Add this at the end, right before returning
496
  logger.info("Analyzing adjusted_apr data availability...")
497
  log_adjusted_apr_availability(global_df)
498
 
499
+ return global_df, global_roi_df
500
 
501
  except requests.exceptions.RequestException as e:
502
  logger.error(f"API request error: {e}")
503
  global_df = pd.DataFrame([])
504
+ global_roi_df = pd.DataFrame([])
505
+ return global_df, global_roi_df
506
  except Exception as e:
507
  logger.error(f"Error fetching APR data: {e}")
508
  logger.exception("Exception traceback:")
509
  global_df = pd.DataFrame([])
510
+ global_roi_df = pd.DataFrame([])
511
+ return global_df, global_roi_df
512
 
513
  def log_adjusted_apr_availability(df):
514
  """
 
651
  global global_df
652
 
653
  # Fetch data from database
654
+ df, _ = fetch_apr_data_from_db()
655
 
656
  # If we got no data at all, return placeholder figures
657
  if df.empty:
 
688
 
689
  return combined_fig, csv_file
690
 
691
+ def generate_roi_visualizations():
692
+ """Generate ROI visualizations with real data only (no dummy data)"""
693
+ global global_roi_df
694
+
695
+ # Fetch data from database if not already fetched
696
+ if global_roi_df is None or global_roi_df.empty:
697
+ _, df_roi = fetch_apr_data_from_db()
698
+ else:
699
+ df_roi = global_roi_df
700
+
701
+ # If we got no data at all, return placeholder figures
702
+ if df_roi.empty:
703
+ logger.info("No ROI data available. Using fallback visualization.")
704
+ # Create empty visualizations with a message using Plotly
705
+ fig = go.Figure()
706
+ fig.add_annotation(
707
+ x=0.5, y=0.5,
708
+ text="No ROI data available",
709
+ font=dict(size=20),
710
+ showarrow=False
711
+ )
712
+ fig.update_layout(
713
+ xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
714
+ yaxis=dict(showgrid=False, zeroline=False, showticklabels=False)
715
+ )
716
+
717
+ # Save as static file for reference
718
+ fig.write_html("modius_roi_graph.html")
719
+ fig.write_image("modius_roi_graph.png")
720
+
721
+ csv_file = None
722
+ return fig, csv_file
723
+
724
+ # Set global_roi_df for access by other functions
725
+ global_roi_df = df_roi
726
+
727
+ # Save to CSV before creating visualizations
728
+ csv_file = save_roi_to_csv(df_roi)
729
+
730
+ # Create combined time series graph for ROI
731
+ combined_fig = create_combined_roi_time_series_graph(df_roi)
732
+
733
+ return combined_fig, csv_file
734
+
735
+ def create_combined_roi_time_series_graph(df):
736
+ """Create a time series graph showing average ROI values across all agents"""
737
+ if len(df) == 0:
738
+ logger.error("No data to plot combined ROI graph")
739
+ fig = go.Figure()
740
+ fig.add_annotation(
741
+ text="No ROI data available",
742
+ x=0.5, y=0.5,
743
+ showarrow=False, font=dict(size=20)
744
+ )
745
+ return fig
746
+
747
+ # IMPORTANT: Force data types to ensure consistency
748
+ df['roi'] = df['roi'].astype(float) # Ensure ROI is float
749
+ df['metric_type'] = df['metric_type'].astype(str) # Ensure metric_type is string
750
+
751
+ # Get min and max time for shapes
752
+ min_time = df['timestamp'].min()
753
+ max_time = df['timestamp'].max()
754
+
755
+ # Use the actual start date from the data instead of a fixed date
756
+ x_start_date = min_time
757
+
758
+ # CRITICAL: Log the exact dataframe we're using for plotting to help debug
759
+ logger.info(f"ROI Graph data - shape: {df.shape}, columns: {df.columns}")
760
+ logger.info(f"ROI Graph data - unique agents: {df['agent_name'].unique().tolist()}")
761
+ logger.info(f"ROI Graph data - min ROI: {df['roi'].min()}, max ROI: {df['roi'].max()}")
762
+
763
+ # Export full dataframe to CSV for debugging
764
+ debug_csv = "debug_roi_data.csv"
765
+ df.to_csv(debug_csv)
766
+ logger.info(f"Exported ROI graph data to {debug_csv} for debugging")
767
+
768
+ # Create Plotly figure in a clean state
769
+ fig = go.Figure()
770
+
771
+ # Get min and max time for shapes
772
+ min_time = df['timestamp'].min()
773
+ max_time = df['timestamp'].max()
774
+
775
+ # Add background shapes for positive and negative regions
776
+ # Add shape for positive ROI region (above zero)
777
+ fig.add_shape(
778
+ type="rect",
779
+ fillcolor="rgba(230, 243, 255, 0.3)",
780
+ line=dict(width=0),
781
+ y0=0, y1=1, # Use a fixed positive value
782
+ x0=min_time, x1=max_time,
783
+ layer="below"
784
+ )
785
+
786
+ # Add shape for negative ROI region (below zero)
787
+ fig.add_shape(
788
+ type="rect",
789
+ fillcolor="rgba(255, 230, 230, 0.3)",
790
+ line=dict(width=0),
791
+ y0=-1, y1=0, # Use a fixed negative value
792
+ x0=min_time, x1=max_time,
793
+ layer="below"
794
+ )
795
+
796
+ # Add zero line
797
+ fig.add_shape(
798
+ type="line",
799
+ line=dict(dash="solid", width=1.5, color="black"),
800
+ y0=0, y1=0,
801
+ x0=min_time, x1=max_time
802
+ )
803
+
804
+ # Filter out outliers (ROI values above 2 or below -2)
805
+ outlier_data = df[(df['roi'] > 2) | (df['roi'] < -2)].copy()
806
+ df_filtered = df[(df['roi'] <= 2) & (df['roi'] >= -2)].copy()
807
+
808
+ # Log the outliers for better debugging
809
+ if len(outlier_data) > 0:
810
+ excluded_count = len(outlier_data)
811
+ logger.info(f"Excluded {excluded_count} data points with outlier ROI values (>2 or <-2)")
812
+
813
+ # Group outliers by agent for detailed logging
814
+ outlier_agents = outlier_data.groupby('agent_name')
815
+ for agent_name, agent_outliers in outlier_agents:
816
+ logger.info(f"Agent '{agent_name}' has {len(agent_outliers)} outlier values:")
817
+ for idx, row in agent_outliers.iterrows():
818
+ logger.info(f" - ROI: {row['roi']}, timestamp: {row['timestamp']}")
819
+
820
+ # Use the filtered data for all subsequent operations
821
+ df = df_filtered
822
+
823
+ # Group by timestamp and calculate mean ROI
824
+ avg_roi_data = df.groupby('timestamp')['roi'].mean().reset_index()
825
+
826
+ # Sort by timestamp
827
+ avg_roi_data = avg_roi_data.sort_values('timestamp')
828
+
829
+ # Log the average ROI data
830
+ logger.info(f"Calculated average ROI data with {len(avg_roi_data)} points")
831
+ for idx, row in avg_roi_data.iterrows():
832
+ logger.info(f" Average point {idx}: timestamp={row['timestamp']}, avg_roi={row['roi']}")
833
+
834
+ # Calculate moving average based on a time window (3 days)
835
+ # Sort data by timestamp
836
+ df_sorted = df.sort_values('timestamp')
837
+
838
+ # Create a new dataframe for the moving average
839
+ avg_roi_data_with_ma = avg_roi_data.copy()
840
+ avg_roi_data_with_ma['moving_avg'] = None # Initialize the moving average column
841
+
842
+ # Define the time window for the moving average (3 days)
843
+ time_window = pd.Timedelta(days=3)
844
+ logger.info(f"Calculating moving average with time window of {time_window}")
845
+
846
+ # Calculate the moving averages for each timestamp
847
+ for i, row in avg_roi_data_with_ma.iterrows():
848
+ current_time = row['timestamp']
849
+ window_start = current_time - time_window
850
+
851
+ # Get all data points within the 3-day time window
852
+ window_data = df_sorted[
853
+ (df_sorted['timestamp'] >= window_start) &
854
+ (df_sorted['timestamp'] <= current_time)
855
+ ]
856
+
857
+ # Calculate the average ROI for the 3-day time window
858
+ if not window_data.empty:
859
+ avg_roi_data_with_ma.at[i, 'moving_avg'] = window_data['roi'].mean()
860
+ logger.debug(f"ROI time window {window_start} to {current_time}: {len(window_data)} points, avg={window_data['roi'].mean()}")
861
+ else:
862
+ # If no data points in the window, use the current value
863
+ avg_roi_data_with_ma.at[i, 'moving_avg'] = row['roi']
864
+ logger.debug(f"No data points in time window for {current_time}, using current value {row['roi']}")
865
+
866
+ logger.info(f"Calculated time-based moving averages with {len(avg_roi_data_with_ma)} points")
867
+
868
+ # Plot individual agent data points with agent names in hover, but limit display for scalability
869
+ if not df.empty:
870
+ # Group by agent to use different colors for each agent
871
+ unique_agents = df['agent_name'].unique()
872
+ colors = px.colors.qualitative.Plotly[:len(unique_agents)]
873
+
874
+ # Create a color map for agents
875
+ color_map = {agent: colors[i % len(colors)] for i, agent in enumerate(unique_agents)}
876
+
877
+ # Calculate the total number of data points per agent to determine which are most active
878
+ agent_counts = df['agent_name'].value_counts()
879
+
880
+ # Determine how many agents to show individually (limit to top 5 most active)
881
+ MAX_VISIBLE_AGENTS = 5
882
+ top_agents = agent_counts.nlargest(min(MAX_VISIBLE_AGENTS, len(agent_counts))).index.tolist()
883
+
884
+ logger.info(f"Showing {len(top_agents)} agents by default out of {len(unique_agents)} total agents")
885
+
886
+ # Add data points for each agent, but only make top agents visible by default
887
+ for agent_name in unique_agents:
888
+ agent_data = df[df['agent_name'] == agent_name]
889
+
890
+ # Explicitly convert to Python lists
891
+ x_values = agent_data['timestamp'].tolist()
892
+ y_values = agent_data['roi'].tolist()
893
+
894
+ # Change default visibility to False to hide all agent data points
895
+ is_visible = False
896
+
897
+ # Add data points as markers for ROI
898
+ fig.add_trace(
899
+ go.Scatter(
900
+ x=x_values,
901
+ y=y_values,
902
+ mode='markers', # Only markers for original data
903
+ marker=dict(
904
+ color=color_map[agent_name],
905
+ symbol='circle',
906
+ size=10,
907
+ line=dict(width=1, color='black')
908
+ ),
909
+ name=f'Agent: {agent_name} (ROI)',
910
+ hovertemplate='Time: %{x}<br>ROI: %{y:.2f}<br>Agent: ' + agent_name + '<extra></extra>',
911
+ visible=is_visible # All agents hidden by default
912
+ )
913
+ )
914
+ logger.info(f"Added ROI data points for agent {agent_name} with {len(x_values)} points (visible: {is_visible})")
915
+
916
+ # Add ROI moving average as a smooth line
917
+ x_values_ma = avg_roi_data_with_ma['timestamp'].tolist()
918
+ y_values_ma = avg_roi_data_with_ma['moving_avg'].tolist()
919
+
920
+ # Create hover template for the ROI moving average line
921
+ hover_data_roi = []
922
+ for idx, row in avg_roi_data_with_ma.iterrows():
923
+ timestamp = row['timestamp']
924
+ hover_data_roi.append(
925
+ f"Time: {timestamp}<br>Avg ROI (3d window): {row['moving_avg']:.2f}"
926
+ )
927
+
928
+ fig.add_trace(
929
+ go.Scatter(
930
+ x=x_values_ma,
931
+ y=y_values_ma,
932
+ mode='lines', # Only lines for moving average
933
+ line=dict(color='blue', width=2), # Thinner line
934
+ name='Average ROI (3d window)',
935
+ hovertext=hover_data_roi,
936
+ hoverinfo='text',
937
+ visible=True # Visible by default
938
+ )
939
+ )
940
+ logger.info(f"Added 3-day moving average ROI trace with {len(x_values_ma)} points")
941
+
942
+ # Update layout
943
+ fig.update_layout(
944
+ title=dict(
945
+ text="Modius Agents ROI",
946
+ font=dict(
947
+ family="Arial, sans-serif",
948
+ size=22,
949
+ color="black",
950
+ weight="bold"
951
+ )
952
+ ),
953
+ xaxis_title=None, # Remove x-axis title to use annotation instead
954
+ yaxis_title=None, # Remove the y-axis title as we'll use annotations instead
955
+ template="plotly_white",
956
+ height=600, # Reduced height for better fit on smaller screens
957
+ autosize=True, # Enable auto-sizing for responsiveness
958
+ legend=dict(
959
+ orientation="h",
960
+ yanchor="bottom",
961
+ y=1.02,
962
+ xanchor="right",
963
+ x=1,
964
+ groupclick="toggleitem"
965
+ ),
966
+ margin=dict(r=30, l=120, t=40, b=50), # Increased bottom margin for x-axis title
967
+ hovermode="closest"
968
+ )
969
+
970
+ # Add annotations for y-axis regions
971
+ fig.add_annotation(
972
+ x=-0.08, # Position further from the y-axis to avoid overlapping with tick labels
973
+ y=-0.5, # Middle of the negative region
974
+ xref="paper",
975
+ yref="y",
976
+ text="Negative ROI [ratio]",
977
+ showarrow=False,
978
+ font=dict(size=16, family="Arial, sans-serif", color="black", weight="bold"), # Adjusted font size
979
+ textangle=-90, # Rotate text to be vertical
980
+ align="center"
981
+ )
982
+
983
+ fig.add_annotation(
984
+ x=-0.08, # Position further from the y-axis to avoid overlapping with tick labels
985
+ y=0.5, # Middle of the positive region
986
+ xref="paper",
987
+ yref="y",
988
+ text="Positive ROI [ratio]",
989
+ showarrow=False,
990
+ font=dict(size=16, family="Arial, sans-serif", color="black", weight="bold"), # Adjusted font size
991
+ textangle=-90, # Rotate text to be vertical
992
+ align="center"
993
+ )
994
+
995
+ # Update layout for legend
996
+ fig.update_layout(
997
+ legend=dict(
998
+ orientation="h",
999
+ yanchor="bottom",
1000
+ y=1.02,
1001
+ xanchor="right",
1002
+ x=1,
1003
+ groupclick="toggleitem",
1004
+ font=dict(
1005
+ family="Arial, sans-serif",
1006
+ size=14, # Adjusted font size
1007
+ color="black",
1008
+ weight="bold"
1009
+ )
1010
+ )
1011
+ )
1012
+
1013
+ # Update y-axis with fixed range of -1 to +1 for ROI
1014
+ fig.update_yaxes(
1015
+ showgrid=True,
1016
+ gridwidth=1,
1017
+ gridcolor='rgba(0,0,0,0.1)',
1018
+ # Use fixed range instead of autoscaling
1019
+ autorange=False, # Disable autoscaling
1020
+ range=[-1, 1], # Set fixed range from -1 to +1
1021
+ tickformat=".2f", # Format tick labels with 2 decimal places
1022
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold"), # Adjusted font size
1023
+ title=None # Remove the built-in axis title since we're using annotations
1024
+ )
1025
+
1026
+ # Update x-axis with better formatting and fixed range
1027
+ fig.update_xaxes(
1028
+ showgrid=True,
1029
+ gridwidth=1,
1030
+ gridcolor='rgba(0,0,0,0.1)',
1031
+ # Set fixed range with April 17 as start date
1032
+ autorange=False, # Disable autoscaling
1033
+ range=[x_start_date, max_time], # Set fixed range from April 17 to max date
1034
+ tickformat="%b %d", # Simplified date format without time
1035
+ tickangle=-30, # Angle the labels for better readability
1036
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold"), # Adjusted font size
1037
+ title=None # Remove built-in title to use annotation instead
1038
+ )
1039
+
1040
+ try:
1041
+ # Save the figure
1042
+ graph_file = "modius_roi_graph.html"
1043
+ fig.write_html(graph_file, include_plotlyjs='cdn', full_html=False)
1044
+
1045
+ # Also save as image for compatibility
1046
+ img_file = "modius_roi_graph.png"
1047
+ try:
1048
+ fig.write_image(img_file)
1049
+ logger.info(f"ROI graph saved to {graph_file} and {img_file}")
1050
+ except Exception as e:
1051
+ logger.error(f"Error saving ROI image: {e}")
1052
+ logger.info(f"ROI graph saved to {graph_file} only")
1053
+
1054
+ # Return the figure object for direct use in Gradio
1055
+ return fig
1056
+ except Exception as e:
1057
+ # If the complex graph approach fails, create a simpler one
1058
+ logger.error(f"Error creating advanced ROI graph: {e}")
1059
+ logger.info("Falling back to Simpler ROI graph")
1060
+
1061
+ # Create a simpler graph as fallback
1062
+ simple_fig = go.Figure()
1063
+
1064
+ # Add zero line
1065
+ simple_fig.add_shape(
1066
+ type="line",
1067
+ line=dict(dash="solid", width=1.5, color="black"),
1068
+ y0=0, y1=0,
1069
+ x0=min_time, x1=max_time
1070
+ )
1071
+
1072
+ # Simply plot the average ROI data with moving average
1073
+ if not avg_roi_data.empty:
1074
+ # Add moving average as a line
1075
+ simple_fig.add_trace(
1076
+ go.Scatter(
1077
+ x=avg_roi_data_with_ma['timestamp'],
1078
+ y=avg_roi_data_with_ma['moving_avg'],
1079
+ mode='lines',
1080
+ name='Average ROI (3d window)',
1081
+ line=dict(width=2, color='blue') # Thinner line
1082
+ )
1083
+ )
1084
+
1085
+ # Simplified layout with adjusted y-axis range
1086
+ simple_fig.update_layout(
1087
+ title=dict(
1088
+ text="Modius Agents ROI",
1089
+ font=dict(
1090
+ family="Arial, sans-serif",
1091
+ size=22,
1092
+ color="black",
1093
+ weight="bold"
1094
+ )
1095
+ ),
1096
+ xaxis_title=None,
1097
+ yaxis_title=None,
1098
+ template="plotly_white",
1099
+ height=600,
1100
+ autosize=True,
1101
+ margin=dict(r=30, l=120, t=40, b=50)
1102
+ )
1103
+
1104
+ # Update y-axis with fixed range of -1 to +1 for ROI
1105
+ simple_fig.update_yaxes(
1106
+ showgrid=True,
1107
+ gridwidth=1,
1108
+ gridcolor='rgba(0,0,0,0.1)',
1109
+ autorange=False,
1110
+ range=[-1, 1],
1111
+ tickformat=".2f",
1112
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold")
1113
+ )
1114
+
1115
+ # Update x-axis with better formatting and fixed range
1116
+ simple_fig.update_xaxes(
1117
+ showgrid=True,
1118
+ gridwidth=1,
1119
+ gridcolor='rgba(0,0,0,0.1)',
1120
+ autorange=False,
1121
+ range=[x_start_date, max_time],
1122
+ tickformat="%b %d",
1123
+ tickangle=-30,
1124
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold")
1125
+ )
1126
+
1127
+ # Save the figure
1128
+ graph_file = "modius_roi_graph.html"
1129
+ simple_fig.write_html(graph_file, include_plotlyjs='cdn', full_html=False)
1130
+
1131
+ # Return the simple figure
1132
+ return simple_fig
1133
+
1134
+ def save_roi_to_csv(df):
1135
+ """Save the ROI data DataFrame to a CSV file and return the file path"""
1136
+ if df.empty:
1137
+ logger.error("No ROI data to save to CSV")
1138
+ return None
1139
+
1140
+ # Define the CSV file path
1141
+ csv_file = "modius_roi_values.csv"
1142
+
1143
+ # Save to CSV
1144
+ df.to_csv(csv_file, index=False)
1145
+ logger.info(f"ROI data saved to {csv_file}")
1146
+
1147
+ return csv_file
1148
+
1149
  def create_time_series_graph_per_agent(df):
1150
  """Create a time series graph for each agent using Plotly"""
1151
  # Get unique agents
 
1306
  df['apr'] = df['apr'].astype(float) # Ensure APR is float
1307
  df['metric_type'] = df['metric_type'].astype(str) # Ensure metric_type is string
1308
 
1309
+ # Get min and max time for shapes
1310
+ min_time = df['timestamp'].min()
1311
+ max_time = df['timestamp'].max()
1312
+
1313
+ # Use April 17th, 2025 as the fixed start date for APR graph
1314
+ x_start_date = datetime(2025, 4, 17)
1315
 
1316
  # CRITICAL: Log the exact dataframe we're using for plotting to help debug
1317
  logger.info(f"Graph data - shape: {df.shape}, columns: {df.columns}")
 
2416
  with gr.Blocks() as demo:
2417
  gr.Markdown("# Average Modius Agent Performance")
2418
 
2419
+ # Create tabs for APR and ROI metrics
2420
+ with gr.Tabs():
2421
+ # APR Metrics tab
2422
+ with gr.Tab("APR Metrics"):
 
 
2423
  with gr.Column():
2424
+ refresh_apr_btn = gr.Button("Refresh APR Data")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2425
 
2426
+ # Create container for plotly figure with responsive sizing
2427
+ with gr.Column():
2428
+ combined_apr_graph = gr.Plot(label="APR for All Agents", elem_id="responsive_apr_plot")
 
2429
 
2430
+ # Create compact toggle controls at the bottom of the graph
2431
+ with gr.Row(visible=True):
2432
+ gr.Markdown("##### Toggle Graph Lines", elem_id="apr_toggle_title")
2433
 
2434
+ with gr.Row():
2435
+ with gr.Column():
2436
+ with gr.Row(elem_id="apr_toggle_container"):
2437
+ with gr.Column(scale=1, min_width=150):
2438
+ apr_toggle = gr.Checkbox(label="APR Average", value=True, elem_id="apr_toggle")
2439
+
2440
+ with gr.Column(scale=1, min_width=150):
2441
+ adjusted_apr_toggle = gr.Checkbox(label="ETH Adjusted APR Average", value=True, elem_id="adjusted_apr_toggle")
2442
 
2443
+ # Add a text area for status messages
2444
+ apr_status_text = gr.Textbox(label="Status", value="Ready", interactive=False)
2445
+
2446
+ # ROI Metrics tab
2447
+ with gr.Tab("ROI Metrics"):
2448
+ with gr.Column():
2449
+ refresh_roi_btn = gr.Button("Refresh ROI Data")
2450
 
2451
+ # Create container for plotly figure with responsive sizing
2452
+ with gr.Column():
2453
+ combined_roi_graph = gr.Plot(label="ROI for All Agents", elem_id="responsive_roi_plot")
 
 
2454
 
2455
+ # Create compact toggle controls at the bottom of the graph
2456
+ with gr.Row(visible=True):
2457
+ gr.Markdown("##### Toggle Graph Lines", elem_id="roi_toggle_title")
 
 
2458
 
2459
+ with gr.Row():
2460
+ with gr.Column():
2461
+ with gr.Row(elem_id="roi_toggle_container"):
2462
+ with gr.Column(scale=1, min_width=150):
2463
+ roi_toggle = gr.Checkbox(label="ROI Average", value=True, elem_id="roi_toggle")
 
2464
 
2465
+ # Add a text area for status messages
2466
+ roi_status_text = gr.Textbox(label="Status", value="Ready", interactive=False)
2467
+
2468
+ # Add custom CSS for making the plots responsive
2469
+ gr.HTML("""
2470
+ <style>
2471
+ /* Make plots responsive */
2472
+ #responsive_apr_plot, #responsive_roi_plot {
2473
+ width: 100% !important;
2474
+ max-width: 100% !important;
2475
+ }
2476
+ #responsive_apr_plot > div, #responsive_roi_plot > div {
2477
+ width: 100% !important;
2478
+ height: auto !important;
2479
+ min-height: 500px !important;
2480
+ }
2481
+
2482
+ /* Toggle checkbox styling */
2483
+ #apr_toggle .gr-checkbox {
2484
+ accent-color: #e74c3c !important;
2485
+ }
2486
+
2487
+ #adjusted_apr_toggle .gr-checkbox {
2488
+ accent-color: #2ecc71 !important;
2489
+ }
2490
+
2491
+ #roi_toggle .gr-checkbox {
2492
+ accent-color: #3498db !important;
2493
+ }
2494
+
2495
+ /* Make the toggle section more compact */
2496
+ #apr_toggle_title, #roi_toggle_title {
2497
+ margin-bottom: 0;
2498
+ margin-top: 10px;
2499
+ }
2500
+
2501
+ #apr_toggle_container, #roi_toggle_container {
2502
+ margin-top: 5px;
2503
+ }
2504
+
2505
+ /* Style the checkbox labels */
2506
+ .gr-form.gr-box {
2507
+ border: none !important;
2508
+ background: transparent !important;
2509
+ }
2510
+
2511
+ /* Make checkboxes and labels appear on the same line */
2512
+ .gr-checkbox-container {
2513
+ display: flex !important;
2514
+ align-items: center !important;
2515
+ }
2516
+
2517
+ /* Add colored indicators */
2518
+ #apr_toggle .gr-checkbox-label::before {
2519
+ content: "●";
2520
+ color: #e74c3c;
2521
+ margin-right: 5px;
2522
+ }
2523
+
2524
+ #adjusted_apr_toggle .gr-checkbox-label::before {
2525
+ content: "●";
2526
+ color: #2ecc71;
2527
+ margin-right: 5px;
2528
+ }
2529
+
2530
+ #roi_toggle .gr-checkbox-label::before {
2531
+ content: "●";
2532
+ color: #3498db;
2533
+ margin-right: 5px;
2534
+ }
2535
+ </style>
2536
+ """)
2537
+
2538
+ # Function to update the APR graph
2539
+ def update_apr_graph(show_apr_ma=True, show_adjusted_apr_ma=True):
2540
+ # Generate visualization and get figure object directly
2541
+ try:
2542
+ combined_fig, _ = generate_apr_visualizations()
2543
 
2544
+ # Update visibility of traces based on toggle values
2545
+ for i, trace in enumerate(combined_fig.data):
2546
+ # Check if this is a moving average trace
2547
+ if trace.name == 'Average APR (3d window)':
2548
+ trace.visible = show_apr_ma
2549
+ elif trace.name == 'Average ETH Adjusted APR (3d window)':
2550
+ trace.visible = show_adjusted_apr_ma
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2551
 
2552
+ return combined_fig
2553
+ except Exception as e:
2554
+ logger.exception("Error generating APR visualization")
2555
+ # Create error figure
2556
+ error_fig = go.Figure()
2557
+ error_fig.add_annotation(
2558
+ text=f"Error: {str(e)}",
2559
  x=0.5, y=0.5,
2560
  showarrow=False,
2561
+ font=dict(size=15, color="red")
2562
  )
2563
+ return error_fig
2564
+
2565
+ # Function to update the ROI graph
2566
+ def update_roi_graph(show_roi_ma=True):
2567
+ # Generate visualization and get figure object directly
2568
+ try:
2569
+ combined_fig, _ = generate_roi_visualizations()
2570
 
2571
+ # Update visibility of traces based on toggle values
2572
+ for i, trace in enumerate(combined_fig.data):
2573
+ # Check if this is a moving average trace
2574
+ if trace.name == 'Average ROI (3d window)':
2575
+ trace.visible = show_roi_ma
2576
 
2577
+ return combined_fig
2578
+ except Exception as e:
2579
+ logger.exception("Error generating ROI visualization")
2580
+ # Create error figure
2581
+ error_fig = go.Figure()
2582
+ error_fig.add_annotation(
2583
+ text=f"Error: {str(e)}",
2584
+ x=0.5, y=0.5,
2585
+ showarrow=False,
2586
+ font=dict(size=15, color="red")
2587
+ )
2588
+ return error_fig
2589
+
2590
+ # Initialize the APR graph on load with a placeholder
2591
+ apr_placeholder_fig = go.Figure()
2592
+ apr_placeholder_fig.add_annotation(
2593
+ text="Click 'Refresh APR Data' to load APR graph",
2594
+ x=0.5, y=0.5,
2595
+ showarrow=False,
2596
+ font=dict(size=15)
2597
+ )
2598
+ combined_apr_graph.value = apr_placeholder_fig
2599
+
2600
+ # Initialize the ROI graph on load with a placeholder
2601
+ roi_placeholder_fig = go.Figure()
2602
+ roi_placeholder_fig.add_annotation(
2603
+ text="Click 'Refresh ROI Data' to load ROI graph",
2604
+ x=0.5, y=0.5,
2605
+ showarrow=False,
2606
+ font=dict(size=15)
2607
+ )
2608
+ combined_roi_graph.value = roi_placeholder_fig
2609
+
2610
+ # Function to update the APR graph based on toggle states
2611
+ def update_apr_graph_with_toggles(apr_visible, adjusted_apr_visible):
2612
+ return update_apr_graph(apr_visible, adjusted_apr_visible)
2613
+
2614
+ # Function to update the ROI graph based on toggle states
2615
+ def update_roi_graph_with_toggles(roi_visible):
2616
+ return update_roi_graph(roi_visible)
2617
+
2618
+ # Function to refresh APR data
2619
+ def refresh_apr_data():
2620
+ """Refresh APR data from the database and update the visualization"""
2621
+ try:
2622
+ # Fetch new APR data
2623
+ logger.info("Manually refreshing APR data...")
2624
+ fetch_apr_data_from_db()
2625
 
2626
+ # Verify data was fetched successfully
2627
+ if global_df is None or len(global_df) == 0:
2628
+ logger.error("Failed to fetch APR data")
2629
+ return combined_apr_graph.value, "Error: Failed to fetch APR data. Check the logs for details."
2630
 
2631
+ # Log info about fetched data with focus on adjusted_apr
2632
+ may_10_2025 = datetime(2025, 5, 10)
2633
+ if 'timestamp' in global_df and 'adjusted_apr' in global_df:
2634
+ after_may_10 = global_df[global_df['timestamp'] >= may_10_2025]
2635
+ with_adjusted_after_may_10 = after_may_10[after_may_10['adjusted_apr'].notna()]
2636
+
2637
+ logger.info(f"Data points after May 10th, 2025: {len(after_may_10)}")
2638
+ logger.info(f"Data points with adjusted_apr after May 10th, 2025: {len(with_adjusted_after_may_10)}")
2639
 
2640
+ # Generate new visualization
2641
+ logger.info("Generating new APR visualization...")
2642
+ new_graph = update_apr_graph(apr_toggle.value, adjusted_apr_toggle.value)
2643
+ return new_graph, "APR data refreshed successfully"
2644
+ except Exception as e:
2645
+ logger.error(f"Error refreshing APR data: {e}")
2646
+ return combined_apr_graph.value, f"Error: {str(e)}"
2647
+
2648
+ # Function to refresh ROI data
2649
+ def refresh_roi_data():
2650
+ """Refresh ROI data from the database and update the visualization"""
2651
+ try:
2652
+ # Fetch new ROI data
2653
+ logger.info("Manually refreshing ROI data...")
2654
+ fetch_apr_data_from_db() # This also fetches ROI data
2655
 
2656
+ # Verify data was fetched successfully
2657
+ if global_roi_df is None or len(global_roi_df) == 0:
2658
+ logger.error("Failed to fetch ROI data")
2659
+ return combined_roi_graph.value, "Error: Failed to fetch ROI data. Check the logs for details."
2660
+
2661
+ # Generate new visualization
2662
+ logger.info("Generating new ROI visualization...")
2663
+ new_graph = update_roi_graph(roi_toggle.value)
2664
+ return new_graph, "ROI data refreshed successfully"
2665
+ except Exception as e:
2666
+ logger.error(f"Error refreshing ROI data: {e}")
2667
+ return combined_roi_graph.value, f"Error: {str(e)}"
2668
+
2669
+ # Set up the button click event for APR refresh
2670
+ refresh_apr_btn.click(
2671
+ fn=refresh_apr_data,
2672
+ inputs=[],
2673
+ outputs=[combined_apr_graph, apr_status_text]
2674
+ )
2675
 
2676
+ # Set up the button click event for ROI refresh
2677
+ refresh_roi_btn.click(
2678
+ fn=refresh_roi_data,
2679
+ inputs=[],
2680
+ outputs=[combined_roi_graph, roi_status_text]
2681
+ )
2682
+
2683
+ # Set up the toggle switch events for APR
2684
+ apr_toggle.change(
2685
+ fn=update_apr_graph_with_toggles,
2686
+ inputs=[apr_toggle, adjusted_apr_toggle],
2687
+ outputs=[combined_apr_graph]
2688
+ )
2689
+
2690
+ adjusted_apr_toggle.change(
2691
+ fn=update_apr_graph_with_toggles,
2692
+ inputs=[apr_toggle, adjusted_apr_toggle],
2693
+ outputs=[combined_apr_graph]
2694
+ )
2695
+
2696
+ # Set up the toggle switch events for ROI
2697
+ roi_toggle.change(
2698
+ fn=update_roi_graph_with_toggles,
2699
+ inputs=[roi_toggle],
2700
+ outputs=[combined_roi_graph]
2701
+ )
2702
+
2703
  return demo
2704
 
2705
  # Launch the dashboard