gauravlochab commited on
Commit
5aa3a66
·
1 Parent(s): ef47052

feat: adding volumne graph

Browse files
Files changed (1) hide show
  1. app.py +550 -9
app.py CHANGED
@@ -42,6 +42,7 @@ logger.info(f"Running from directory: {os.getcwd()}")
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,7 +158,7 @@ def get_agent_name(agent_id: int, agents: List[Dict[str, Any]]) -> str:
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,7 +166,7 @@ def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
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):
@@ -177,13 +178,20 @@ def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
177
  apr = json_data.get("apr")
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
@@ -194,6 +202,7 @@ def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
194
  "apr": apr,
195
  "adjusted_apr": adjusted_apr,
196
  "roi": roi,
 
197
  "timestamp": timestamp_dt,
198
  "agent_id": agent_id,
199
  "is_dummy": False
@@ -203,7 +212,7 @@ def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
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
  """
@@ -688,6 +697,436 @@ def generate_apr_visualizations():
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
@@ -2510,7 +2949,7 @@ def dashboard():
2510
  with gr.Blocks() as demo:
2511
  gr.Markdown("# Average Modius Agent Performance")
2512
 
2513
- # Create tabs for APR and ROI metrics
2514
  with gr.Tabs():
2515
  # APR Metrics tab
2516
  with gr.Tab("APR Metrics"):
@@ -2558,16 +2997,38 @@ def dashboard():
2558
 
2559
  # Add a text area for status messages
2560
  roi_status_text = gr.Textbox(label="Status", value="Ready", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2561
 
2562
  # Add custom CSS for making the plots responsive
2563
  gr.HTML("""
2564
  <style>
2565
  /* Make plots responsive */
2566
- #responsive_apr_plot, #responsive_roi_plot {
2567
  width: 100% !important;
2568
  max-width: 100% !important;
2569
  }
2570
- #responsive_apr_plot > div, #responsive_roi_plot > div {
2571
  width: 100% !important;
2572
  height: auto !important;
2573
  min-height: 500px !important;
@@ -2586,13 +3047,17 @@ def dashboard():
2586
  accent-color: #3498db !important;
2587
  }
2588
 
 
 
 
 
2589
  /* Make the toggle section more compact */
2590
- #apr_toggle_title, #roi_toggle_title {
2591
  margin-bottom: 0;
2592
  margin-top: 10px;
2593
  }
2594
 
2595
- #apr_toggle_container, #roi_toggle_container {
2596
  margin-top: 5px;
2597
  }
2598
 
@@ -2626,6 +3091,12 @@ def dashboard():
2626
  color: #3498db;
2627
  margin-right: 5px;
2628
  }
 
 
 
 
 
 
2629
  </style>
2630
  """)
2631
 
@@ -2681,6 +3152,31 @@ def dashboard():
2681
  )
2682
  return error_fig
2683
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2684
  # Initialize the APR graph on load with a placeholder
2685
  apr_placeholder_fig = go.Figure()
2686
  apr_placeholder_fig.add_annotation(
@@ -2701,6 +3197,16 @@ def dashboard():
2701
  )
2702
  combined_roi_graph.value = roi_placeholder_fig
2703
 
 
 
 
 
 
 
 
 
 
 
2704
  # Function to update the APR graph based on toggle states
2705
  def update_apr_graph_with_toggles(apr_visible, adjusted_apr_visible):
2706
  return update_apr_graph(apr_visible, adjusted_apr_visible)
@@ -2793,6 +3299,41 @@ def dashboard():
2793
  inputs=[roi_toggle],
2794
  outputs=[combined_roi_graph]
2795
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2796
 
2797
  return demo
2798
 
 
42
  # Global variables to store the data for reuse
43
  global_df = None
44
  global_roi_df = None
45
+ global_volume_df = None
46
 
47
  # Configuration
48
  API_BASE_URL = "https://afmdb.autonolas.tech"
 
158
  return "Unknown"
159
 
160
  def extract_apr_value(attr: Dict[str, Any]) -> Dict[str, Any]:
161
+ """Extract APR value, adjusted APR value, ROI value, volume, and timestamp from JSON value"""
162
  try:
163
  agent_id = attr.get("agent_id", "unknown")
164
  logger.debug(f"Extracting APR value for agent {agent_id}")
 
166
  # The APR value is stored in the json_value field
167
  if attr["json_value"] is None:
168
  logger.debug(f"Agent {agent_id}: json_value is None")
169
+ return {"apr": None, "adjusted_apr": None, "roi": None, "volume": None, "timestamp": None, "agent_id": agent_id, "is_dummy": False}
170
 
171
  # If json_value is a string, parse it
172
  if isinstance(attr["json_value"], str):
 
178
  apr = json_data.get("apr")
179
  adjusted_apr = json_data.get("adjusted_apr") # Extract adjusted_apr if present
180
  timestamp = json_data.get("timestamp")
181
+ volume = json_data.get("volume") # Extract volume if present
182
 
183
  # Extract ROI (f_i_ratio) from calculation_metrics if it exists
184
  roi = None
185
  if "calculation_metrics" in json_data and json_data["calculation_metrics"] is not None:
186
  roi = json_data["calculation_metrics"].get("f_i_ratio")
187
 
188
+ # Try to extract volume from portfolio_snapshot if it's not directly in json_data
189
+ if volume is None and "portfolio_snapshot" in json_data and json_data["portfolio_snapshot"] is not None:
190
+ portfolio = json_data["portfolio_snapshot"].get("portfolio")
191
+ if portfolio and isinstance(portfolio, dict):
192
+ volume = portfolio.get("volume")
193
+
194
+ logger.debug(f"Agent {agent_id}: Raw APR value: {apr}, adjusted APR value: {adjusted_apr}, ROI value: {roi}, volume: {volume}, timestamp: {timestamp}")
195
 
196
  # Convert timestamp to datetime if it exists
197
  timestamp_dt = None
 
202
  "apr": apr,
203
  "adjusted_apr": adjusted_apr,
204
  "roi": roi,
205
+ "volume": volume,
206
  "timestamp": timestamp_dt,
207
  "agent_id": agent_id,
208
  "is_dummy": False
 
212
  except (json.JSONDecodeError, KeyError, TypeError) as e:
213
  logger.error(f"Error parsing JSON value: {e} for agent_id: {attr.get('agent_id')}")
214
  logger.error(f"Problematic json_value: {attr.get('json_value')}")
215
+ return {"apr": None, "adjusted_apr": None, "roi": None, "volume": None, "timestamp": None, "agent_id": attr.get('agent_id'), "is_dummy": False}
216
 
217
  def fetch_apr_data_from_db():
218
  """
 
697
 
698
  return combined_fig, csv_file
699
 
700
+ def generate_volume_visualizations():
701
+ """Generate volume visualizations with real data only (no dummy data)"""
702
+ global global_df
703
+ global global_volume_df
704
+
705
+ # Use the existing APR data which already contains volume
706
+ if global_df is None or global_df.empty:
707
+ df, _ = fetch_apr_data_from_db()
708
+ else:
709
+ df = global_df
710
+
711
+ # Filter for records with volume data
712
+ volume_df = df[df['volume'].notna()].copy()
713
+
714
+ # Set global_volume_df for access by other functions
715
+ global_volume_df = volume_df
716
+
717
+ # If we got no data at all, return placeholder figures
718
+ if volume_df.empty:
719
+ logger.info("No volume data available. Using fallback visualization.")
720
+ # Create empty visualizations with a message using Plotly
721
+ fig = go.Figure()
722
+ fig.add_annotation(
723
+ x=0.5, y=0.5,
724
+ text="No volume data available",
725
+ font=dict(size=20),
726
+ showarrow=False
727
+ )
728
+ fig.update_layout(
729
+ xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
730
+ yaxis=dict(showgrid=False, zeroline=False, showticklabels=False)
731
+ )
732
+
733
+ # Save as static file for reference
734
+ fig.write_html("modius_volume_graph.html")
735
+ fig.write_image("modius_volume_graph.png")
736
+
737
+ csv_file = None
738
+ return fig, csv_file
739
+
740
+ # Save to CSV before creating visualizations
741
+ csv_file = save_volume_to_csv(volume_df)
742
+
743
+ # Create combined time series graph for volume
744
+ combined_fig = create_combined_volume_time_series_graph(volume_df)
745
+
746
+ return combined_fig, csv_file
747
+
748
+ def save_volume_to_csv(df):
749
+ """Save the volume data DataFrame to a CSV file and return the file path"""
750
+ if df.empty:
751
+ logger.error("No volume data to save to CSV")
752
+ return None
753
+
754
+ # Define the CSV file path
755
+ csv_file = "modius_volume_values.csv"
756
+
757
+ # Save to CSV
758
+ df.to_csv(csv_file, index=False)
759
+ logger.info(f"Volume data saved to {csv_file}")
760
+
761
+ return csv_file
762
+
763
+ def create_combined_volume_time_series_graph(df):
764
+ """Create a time series graph showing volume values across all agents"""
765
+ if len(df) == 0:
766
+ logger.error("No data to plot combined volume graph")
767
+ fig = go.Figure()
768
+ fig.add_annotation(
769
+ text="No volume data available",
770
+ x=0.5, y=0.5,
771
+ showarrow=False, font=dict(size=20)
772
+ )
773
+ return fig
774
+
775
+ # IMPORTANT: Force data types to ensure consistency
776
+ df['volume'] = df['volume'].astype(float) # Ensure volume is float
777
+
778
+ # Get min and max time for shapes
779
+ min_time = df['timestamp'].min()
780
+ max_time = df['timestamp'].max()
781
+
782
+ # Use the actual start date from the data
783
+ x_start_date = min_time
784
+
785
+ # CRITICAL: Log the exact dataframe we're using for plotting to help debug
786
+ logger.info(f"Volume Graph data - shape: {df.shape}, columns: {df.columns}")
787
+ logger.info(f"Volume Graph data - unique agents: {df['agent_name'].unique().tolist()}")
788
+ logger.info(f"Volume Graph data - min volume: {df['volume'].min()}, max volume: {df['volume'].max()}")
789
+
790
+ # Export full dataframe to CSV for debugging
791
+ debug_csv = "debug_volume_data.csv"
792
+ df.to_csv(debug_csv)
793
+ logger.info(f"Exported volume graph data to {debug_csv} for debugging")
794
+
795
+ # Create Plotly figure in a clean state
796
+ fig = go.Figure()
797
+
798
+ # Add background shape for volume region
799
+ fig.add_shape(
800
+ type="rect",
801
+ fillcolor="rgba(230, 243, 255, 0.3)",
802
+ line=dict(width=0),
803
+ y0=0, y1=df['volume'].max() * 1.1, # Use a reasonable upper limit for volume
804
+ x0=min_time, x1=max_time,
805
+ layer="below"
806
+ )
807
+
808
+ # Add zero line
809
+ fig.add_shape(
810
+ type="line",
811
+ line=dict(dash="solid", width=1.5, color="black"),
812
+ y0=0, y1=0,
813
+ x0=min_time, x1=max_time
814
+ )
815
+
816
+ # Group by timestamp and calculate mean volume
817
+ avg_volume_data = df.groupby('timestamp')['volume'].mean().reset_index()
818
+
819
+ # Sort by timestamp
820
+ avg_volume_data = avg_volume_data.sort_values('timestamp')
821
+
822
+ # Log the average volume data
823
+ logger.info(f"Calculated average volume data with {len(avg_volume_data)} points")
824
+ for idx, row in avg_volume_data.iterrows():
825
+ logger.info(f" Average point {idx}: timestamp={row['timestamp']}, avg_volume={row['volume']}")
826
+
827
+ # Calculate moving average based on a time window (3 days)
828
+ # Sort data by timestamp
829
+ df_sorted = df.sort_values('timestamp')
830
+
831
+ # Create a new dataframe for the moving average
832
+ avg_volume_data_with_ma = avg_volume_data.copy()
833
+ avg_volume_data_with_ma['moving_avg'] = None # Initialize the moving average column
834
+
835
+ # Define the time window for the moving average (3 days)
836
+ time_window = pd.Timedelta(days=3)
837
+ logger.info(f"Calculating moving average with time window of {time_window}")
838
+
839
+ # Calculate the moving averages for each timestamp
840
+ for i, row in avg_volume_data_with_ma.iterrows():
841
+ current_time = row['timestamp']
842
+ window_start = current_time - time_window
843
+
844
+ # Get all data points within the 3-day time window
845
+ window_data = df_sorted[
846
+ (df_sorted['timestamp'] >= window_start) &
847
+ (df_sorted['timestamp'] <= current_time)
848
+ ]
849
+
850
+ # Calculate the average volume for the 3-day time window
851
+ if not window_data.empty:
852
+ avg_volume_data_with_ma.at[i, 'moving_avg'] = window_data['volume'].mean()
853
+ logger.debug(f"Volume time window {window_start} to {current_time}: {len(window_data)} points, avg={window_data['volume'].mean()}")
854
+ else:
855
+ # If no data points in the window, use the current value
856
+ avg_volume_data_with_ma.at[i, 'moving_avg'] = row['volume']
857
+ logger.debug(f"No data points in time window for {current_time}, using current value {row['volume']}")
858
+
859
+ logger.info(f"Calculated time-based moving averages with {len(avg_volume_data_with_ma)} points")
860
+
861
+ # Find the last date where we have valid moving average data
862
+ last_valid_ma_date = avg_volume_data_with_ma[avg_volume_data_with_ma['moving_avg'].notna()]['timestamp'].max() if not avg_volume_data_with_ma['moving_avg'].dropna().empty else None
863
+
864
+ # If we don't have any valid moving average data, use the max time from the original data
865
+ last_valid_date = last_valid_ma_date if last_valid_ma_date is not None else df['timestamp'].max()
866
+
867
+ logger.info(f"Last valid moving average date: {last_valid_ma_date}")
868
+ logger.info(f"Using last valid date for graph: {last_valid_date}")
869
+
870
+ # Plot individual agent data points with agent names in hover, but limit display for scalability
871
+ if not df.empty:
872
+ # Group by agent to use different colors for each agent
873
+ unique_agents = df['agent_name'].unique()
874
+ colors = px.colors.qualitative.Plotly[:len(unique_agents)]
875
+
876
+ # Create a color map for agents
877
+ color_map = {agent: colors[i % len(colors)] for i, agent in enumerate(unique_agents)}
878
+
879
+ # Calculate the total number of data points per agent to determine which are most active
880
+ agent_counts = df['agent_name'].value_counts()
881
+
882
+ # Determine how many agents to show individually (limit to top 5 most active)
883
+ MAX_VISIBLE_AGENTS = 5
884
+ top_agents = agent_counts.nlargest(min(MAX_VISIBLE_AGENTS, len(agent_counts))).index.tolist()
885
+
886
+ logger.info(f"Showing {len(top_agents)} agents by default out of {len(unique_agents)} total agents")
887
+
888
+ # Add data points for each agent, but only make top agents visible by default
889
+ for agent_name in unique_agents:
890
+ agent_data = df[df['agent_name'] == agent_name]
891
+
892
+ # Explicitly convert to Python lists
893
+ x_values = agent_data['timestamp'].tolist()
894
+ y_values = agent_data['volume'].tolist()
895
+
896
+ # Change default visibility to False to hide all agent data points
897
+ is_visible = False
898
+
899
+ # Add data points as markers for volume
900
+ fig.add_trace(
901
+ go.Scatter(
902
+ x=x_values,
903
+ y=y_values,
904
+ mode='markers', # Only markers for original data
905
+ marker=dict(
906
+ color=color_map[agent_name],
907
+ symbol='circle',
908
+ size=10,
909
+ line=dict(width=1, color='black')
910
+ ),
911
+ name=f'Agent: {agent_name} (Volume)',
912
+ hovertemplate='Time: %{x}<br>Volume: %{y:.2f}<br>Agent: ' + agent_name + '<extra></extra>',
913
+ visible=is_visible # All agents hidden by default
914
+ )
915
+ )
916
+ logger.info(f"Added volume data points for agent {agent_name} with {len(x_values)} points (visible: {is_visible})")
917
+
918
+ # Add volume moving average as a smooth line
919
+ x_values_ma = avg_volume_data_with_ma['timestamp'].tolist()
920
+ y_values_ma = avg_volume_data_with_ma['moving_avg'].tolist()
921
+
922
+ # Create hover template for the volume moving average line
923
+ hover_data_volume = []
924
+ for idx, row in avg_volume_data_with_ma.iterrows():
925
+ timestamp = row['timestamp']
926
+ # Format timestamp to show only up to seconds (not milliseconds)
927
+ formatted_timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S')
928
+
929
+ # Calculate number of active agents in the last 24 hours
930
+ time_24h_ago = timestamp - pd.Timedelta(hours=24)
931
+ active_agents = len(df[(df['timestamp'] >= time_24h_ago) &
932
+ (df['timestamp'] <= timestamp)]['agent_id'].unique())
933
+
934
+ hover_data_volume.append(
935
+ f"Time: {formatted_timestamp}<br>Avg Volume (3d window): {row['moving_avg']:.2f}<br>Active agents (24h): {active_agents}"
936
+ )
937
+
938
+ fig.add_trace(
939
+ go.Scatter(
940
+ x=x_values_ma,
941
+ y=y_values_ma,
942
+ mode='lines', # Only lines for moving average
943
+ line=dict(color='purple', width=2), # Purple line for volume
944
+ name='Average Volume (3d window)',
945
+ hovertext=hover_data_volume,
946
+ hoverinfo='text',
947
+ visible=True # Visible by default
948
+ )
949
+ )
950
+ logger.info(f"Added 3-day moving average volume trace with {len(x_values_ma)} points")
951
+
952
+ # Update layout
953
+ fig.update_layout(
954
+ title=dict(
955
+ text="Modius Agents Volume",
956
+ font=dict(
957
+ family="Arial, sans-serif",
958
+ size=22,
959
+ color="black",
960
+ weight="bold"
961
+ )
962
+ ),
963
+ xaxis_title=None, # Remove x-axis title to use annotation instead
964
+ yaxis_title=None, # Remove the y-axis title as we'll use annotations instead
965
+ template="plotly_white",
966
+ height=600, # Reduced height for better fit on smaller screens
967
+ autosize=True, # Enable auto-sizing for responsiveness
968
+ legend=dict(
969
+ orientation="h",
970
+ yanchor="bottom",
971
+ y=1.02,
972
+ xanchor="right",
973
+ x=1,
974
+ groupclick="toggleitem"
975
+ ),
976
+ margin=dict(r=30, l=120, t=40, b=50), # Increased bottom margin for x-axis title
977
+ hovermode="closest"
978
+ )
979
+
980
+ # Add single annotation for y-axis
981
+ fig.add_annotation(
982
+ x=-0.08, # Position further from the y-axis to avoid overlapping with tick labels
983
+ y=df['volume'].max() / 2, # Center of the y-axis
984
+ xref="paper",
985
+ yref="y",
986
+ text="Volume",
987
+ showarrow=False,
988
+ font=dict(size=16, family="Arial, sans-serif", color="black", weight="bold"), # Adjusted font size
989
+ textangle=-90, # Rotate text to be vertical
990
+ align="center"
991
+ )
992
+
993
+ # Update layout for legend
994
+ fig.update_layout(
995
+ legend=dict(
996
+ orientation="h",
997
+ yanchor="bottom",
998
+ y=1.02,
999
+ xanchor="right",
1000
+ x=1,
1001
+ groupclick="toggleitem",
1002
+ font=dict(
1003
+ family="Arial, sans-serif",
1004
+ size=14, # Adjusted font size
1005
+ color="black",
1006
+ weight="bold"
1007
+ )
1008
+ )
1009
+ )
1010
+
1011
+ # Update y-axis with autoscaling for volume
1012
+ fig.update_yaxes(
1013
+ showgrid=True,
1014
+ gridwidth=1,
1015
+ gridcolor='rgba(0,0,0,0.1)',
1016
+ autorange=True, # Enable autoscaling for volume
1017
+ tickformat=".2f", # Format tick labels with 2 decimal places
1018
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold"), # Adjusted font size
1019
+ title=None # Remove the built-in axis title since we're using annotations
1020
+ )
1021
+
1022
+ # Update x-axis with better formatting and fixed range
1023
+ fig.update_xaxes(
1024
+ showgrid=True,
1025
+ gridwidth=1,
1026
+ gridcolor='rgba(0,0,0,0.1)',
1027
+ # Set fixed range with start date and ending at the last valid date
1028
+ autorange=False, # Disable autoscaling
1029
+ range=[x_start_date, last_valid_date], # Set fixed range from start date to last valid date
1030
+ tickformat="%b %d", # Simplified date format without time
1031
+ tickangle=-30, # Angle the labels for better readability
1032
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold"), # Adjusted font size
1033
+ title=None # Remove built-in title to use annotation instead
1034
+ )
1035
+
1036
+ try:
1037
+ # Save the figure
1038
+ graph_file = "modius_volume_graph.html"
1039
+ fig.write_html(graph_file, include_plotlyjs='cdn', full_html=False)
1040
+
1041
+ # Also save as image for compatibility
1042
+ img_file = "modius_volume_graph.png"
1043
+ try:
1044
+ fig.write_image(img_file)
1045
+ logger.info(f"Volume graph saved to {graph_file} and {img_file}")
1046
+ except Exception as e:
1047
+ logger.error(f"Error saving volume image: {e}")
1048
+ logger.info(f"Volume graph saved to {graph_file} only")
1049
+
1050
+ # Return the figure object for direct use in Gradio
1051
+ return fig
1052
+ except Exception as e:
1053
+ # If the complex graph approach fails, create a simpler one
1054
+ logger.error(f"Error creating advanced volume graph: {e}")
1055
+ logger.info("Falling back to simpler volume graph")
1056
+
1057
+ # Create a simpler graph as fallback
1058
+ simple_fig = go.Figure()
1059
+
1060
+ # Add zero line
1061
+ simple_fig.add_shape(
1062
+ type="line",
1063
+ line=dict(dash="solid", width=1.5, color="black"),
1064
+ y0=0, y1=0,
1065
+ x0=min_time, x1=max_time
1066
+ )
1067
+
1068
+ # Simply plot the average volume data with moving average
1069
+ if not avg_volume_data.empty:
1070
+ # Add moving average as a line
1071
+ simple_fig.add_trace(
1072
+ go.Scatter(
1073
+ x=avg_volume_data_with_ma['timestamp'],
1074
+ y=avg_volume_data_with_ma['moving_avg'],
1075
+ mode='lines',
1076
+ name='Average Volume (3d window)',
1077
+ line=dict(width=2, color='purple') # Purple line for volume
1078
+ )
1079
+ )
1080
+
1081
+ # Simplified layout with adjusted y-axis range
1082
+ simple_fig.update_layout(
1083
+ title=dict(
1084
+ text="Modius Agents Volume",
1085
+ font=dict(
1086
+ family="Arial, sans-serif",
1087
+ size=22,
1088
+ color="black",
1089
+ weight="bold"
1090
+ )
1091
+ ),
1092
+ xaxis_title=None,
1093
+ yaxis_title=None,
1094
+ template="plotly_white",
1095
+ height=600,
1096
+ autosize=True,
1097
+ margin=dict(r=30, l=120, t=40, b=50)
1098
+ )
1099
+
1100
+ # Update y-axis with autoscaling for volume
1101
+ simple_fig.update_yaxes(
1102
+ showgrid=True,
1103
+ gridwidth=1,
1104
+ gridcolor='rgba(0,0,0,0.1)',
1105
+ autorange=True, # Enable autoscaling for volume
1106
+ tickformat=".2f",
1107
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold"),
1108
+ title=None # Remove the built-in axis title since we're using annotations
1109
+ )
1110
+
1111
+ # Update x-axis with better formatting and fixed range
1112
+ simple_fig.update_xaxes(
1113
+ showgrid=True,
1114
+ gridwidth=1,
1115
+ gridcolor='rgba(0,0,0,0.1)',
1116
+ autorange=False,
1117
+ range=[x_start_date, max_time],
1118
+ tickformat="%b %d",
1119
+ tickangle=-30,
1120
+ tickfont=dict(size=14, family="Arial, sans-serif", color="black", weight="bold")
1121
+ )
1122
+
1123
+ # Save the figure
1124
+ graph_file = "modius_volume_graph.html"
1125
+ simple_fig.write_html(graph_file, include_plotlyjs='cdn', full_html=False)
1126
+
1127
+ # Return the simple figure
1128
+ return simple_fig
1129
+
1130
  def generate_roi_visualizations():
1131
  """Generate ROI visualizations with real data only (no dummy data)"""
1132
  global global_roi_df
 
2949
  with gr.Blocks() as demo:
2950
  gr.Markdown("# Average Modius Agent Performance")
2951
 
2952
+ # Create tabs for APR, ROI, and Volume metrics
2953
  with gr.Tabs():
2954
  # APR Metrics tab
2955
  with gr.Tab("APR Metrics"):
 
2997
 
2998
  # Add a text area for status messages
2999
  roi_status_text = gr.Textbox(label="Status", value="Ready", interactive=False)
3000
+
3001
+ # Volume Metrics tab
3002
+ with gr.Tab("Volume Metrics"):
3003
+ with gr.Column():
3004
+ refresh_volume_btn = gr.Button("Refresh Volume Data")
3005
+
3006
+ # Create container for plotly figure with responsive sizing
3007
+ with gr.Column():
3008
+ combined_volume_graph = gr.Plot(label="Volume for All Agents", elem_id="responsive_volume_plot")
3009
+
3010
+ # Create compact toggle controls at the bottom of the graph
3011
+ with gr.Row(visible=True):
3012
+ gr.Markdown("##### Toggle Graph Lines", elem_id="volume_toggle_title")
3013
+
3014
+ with gr.Row():
3015
+ with gr.Column():
3016
+ with gr.Row(elem_id="volume_toggle_container"):
3017
+ with gr.Column(scale=1, min_width=150):
3018
+ volume_toggle = gr.Checkbox(label="Volume Average", value=True, elem_id="volume_toggle")
3019
+
3020
+ # Add a text area for status messages
3021
+ volume_status_text = gr.Textbox(label="Status", value="Ready", interactive=False)
3022
 
3023
  # Add custom CSS for making the plots responsive
3024
  gr.HTML("""
3025
  <style>
3026
  /* Make plots responsive */
3027
+ #responsive_apr_plot, #responsive_roi_plot, #responsive_volume_plot {
3028
  width: 100% !important;
3029
  max-width: 100% !important;
3030
  }
3031
+ #responsive_apr_plot > div, #responsive_roi_plot > div, #responsive_volume_plot > div {
3032
  width: 100% !important;
3033
  height: auto !important;
3034
  min-height: 500px !important;
 
3047
  accent-color: #3498db !important;
3048
  }
3049
 
3050
+ #volume_toggle .gr-checkbox {
3051
+ accent-color: #9b59b6 !important;
3052
+ }
3053
+
3054
  /* Make the toggle section more compact */
3055
+ #apr_toggle_title, #roi_toggle_title, #volume_toggle_title {
3056
  margin-bottom: 0;
3057
  margin-top: 10px;
3058
  }
3059
 
3060
+ #apr_toggle_container, #roi_toggle_container, #volume_toggle_container {
3061
  margin-top: 5px;
3062
  }
3063
 
 
3091
  color: #3498db;
3092
  margin-right: 5px;
3093
  }
3094
+
3095
+ #volume_toggle .gr-checkbox-label::before {
3096
+ content: "●";
3097
+ color: #9b59b6;
3098
+ margin-right: 5px;
3099
+ }
3100
  </style>
3101
  """)
3102
 
 
3152
  )
3153
  return error_fig
3154
 
3155
+ # Function to update the Volume graph
3156
+ def update_volume_graph(show_volume_ma=True):
3157
+ # Generate visualization and get figure object directly
3158
+ try:
3159
+ combined_fig, _ = generate_volume_visualizations()
3160
+
3161
+ # Update visibility of traces based on toggle values
3162
+ for i, trace in enumerate(combined_fig.data):
3163
+ # Check if this is a moving average trace
3164
+ if trace.name == 'Average Volume (3d window)':
3165
+ trace.visible = show_volume_ma
3166
+
3167
+ return combined_fig
3168
+ except Exception as e:
3169
+ logger.exception("Error generating Volume visualization")
3170
+ # Create error figure
3171
+ error_fig = go.Figure()
3172
+ error_fig.add_annotation(
3173
+ text=f"Error: {str(e)}",
3174
+ x=0.5, y=0.5,
3175
+ showarrow=False,
3176
+ font=dict(size=15, color="red")
3177
+ )
3178
+ return error_fig
3179
+
3180
  # Initialize the APR graph on load with a placeholder
3181
  apr_placeholder_fig = go.Figure()
3182
  apr_placeholder_fig.add_annotation(
 
3197
  )
3198
  combined_roi_graph.value = roi_placeholder_fig
3199
 
3200
+ # Initialize the Volume graph on load with a placeholder
3201
+ volume_placeholder_fig = go.Figure()
3202
+ volume_placeholder_fig.add_annotation(
3203
+ text="Click 'Refresh Volume Data' to load Volume graph",
3204
+ x=0.5, y=0.5,
3205
+ showarrow=False,
3206
+ font=dict(size=15)
3207
+ )
3208
+ combined_volume_graph.value = volume_placeholder_fig
3209
+
3210
  # Function to update the APR graph based on toggle states
3211
  def update_apr_graph_with_toggles(apr_visible, adjusted_apr_visible):
3212
  return update_apr_graph(apr_visible, adjusted_apr_visible)
 
3299
  inputs=[roi_toggle],
3300
  outputs=[combined_roi_graph]
3301
  )
3302
+
3303
+ # Function to refresh volume data
3304
+ def refresh_volume_data():
3305
+ """Refresh volume data from the database and update the visualization"""
3306
+ try:
3307
+ # Fetch new volume data
3308
+ logger.info("Manually refreshing volume data...")
3309
+ fetch_apr_data_from_db() # This also fetches volume data
3310
+
3311
+ # Verify data was fetched successfully
3312
+ if global_df is None or len(global_df) == 0:
3313
+ logger.error("Failed to fetch volume data")
3314
+ return combined_volume_graph.value, "Error: Failed to fetch volume data. Check the logs for details."
3315
+
3316
+ # Generate new visualization
3317
+ logger.info("Generating new volume visualization...")
3318
+ new_graph = update_volume_graph(volume_toggle.value)
3319
+ return new_graph, "Volume data refreshed successfully"
3320
+ except Exception as e:
3321
+ logger.error(f"Error refreshing volume data: {e}")
3322
+ return combined_volume_graph.value, f"Error: {str(e)}"
3323
+
3324
+ # Set up the button click event for volume refresh
3325
+ refresh_volume_btn.click(
3326
+ fn=refresh_volume_data,
3327
+ inputs=[],
3328
+ outputs=[combined_volume_graph, volume_status_text]
3329
+ )
3330
+
3331
+ # Set up the toggle switch events for volume
3332
+ volume_toggle.change(
3333
+ fn=update_volume_graph,
3334
+ inputs=[volume_toggle],
3335
+ outputs=[combined_volume_graph]
3336
+ )
3337
 
3338
  return demo
3339