File size: 5,814 Bytes
174e0f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bcd921
174e0f0
 
9bcd921
 
174e0f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""
ROI chart implementations.
"""
import plotly.graph_objects as go
import pandas as pd
import logging
from datetime import datetime
from typing import Dict, Any, Optional, Tuple

from ..config.constants import DATE_RANGES, Y_AXIS_RANGES, FILE_PATHS
from .base_chart import BaseChart

logger = logging.getLogger(__name__)


class ROIChart(BaseChart):
    """Chart for ROI visualizations."""
    
    def create_chart(self, df: pd.DataFrame, **kwargs) -> go.Figure:
        """Create ROI time series chart."""
        if df.empty:
            return self._create_empty_chart("No ROI data available")
        
        # Filter for ROI data only
        roi_data = df[df['metric_type'] == 'ROI'].copy()
        if roi_data.empty:
            return self._create_empty_chart("No ROI data available")
        
        # ROI data is already in percentage format from corrected CSV
        # No conversion needed
        
        # Apply daily median aggregation to reduce outliers
        roi_data = self.data_processor.aggregate_daily_medians(roi_data, ['roi'])
        if roi_data.empty:
            return self._create_empty_chart("No ROI data available after aggregation")
        
        # Apply high APR filtering to ROI data (need to get APR data first to determine what to filter)
        # Fetch APR data to determine which agent-timestamp combinations to filter
        apr_df, _ = self.data_processor.fetch_apr_data_from_db()
        if not apr_df.empty:
            # Apply same processing to APR data to get the filtering reference
            apr_processed = apr_df[apr_df['metric_type'] == 'APR'].copy()
            apr_processed = self.data_processor.aggregate_daily_medians(apr_processed, ['apr', 'adjusted_apr'])
            
            # Apply high APR filtering to both datasets
            _, roi_data = self.data_processor.filter_high_apr_values(apr_processed, roi_data)
        
        if roi_data.empty:
            return self._create_empty_chart("No ROI data available after high APR filtering")
        
        # Save processed ROI data for verification (after all processing steps)
        processed_csv_path = self.data_processor.save_to_csv(roi_data, FILE_PATHS['roi_processed_csv'])
        if processed_csv_path:
            logger.info(f"Saved processed ROI data to {processed_csv_path} for verification")
            logger.info(f"Processed ROI data contains {len(roi_data)} rows after agent exclusion, daily median aggregation, and high APR filtering")
        
        # Filter outliers (disabled but keeping for compatibility)
        roi_data = self._filter_outliers(roi_data, 'roi')
        
        # Get time range
        min_time = roi_data['timestamp'].min()
        max_time = roi_data['timestamp'].max()
        x_start_date = min_time  # Use actual start date for ROI
        
        # Calculate average runtime for title
        fixed_start_date = DATE_RANGES['feb_start']
        agent_runtimes = {}
        for agent_id in roi_data['agent_id'].unique():
            agent_data = roi_data[roi_data['agent_id'] == agent_id]
            agent_name = agent_data['agent_name'].iloc[0]
            last_report = agent_data['timestamp'].max()
            runtime_days = (last_report - fixed_start_date).total_seconds() / (24 * 3600)
            agent_runtimes[agent_id] = {
                'agent_name': agent_name,
                'last_report': last_report,
                'runtime_days': runtime_days
            }
        
        avg_runtime = sum(data['runtime_days'] for data in agent_runtimes.values()) / len(agent_runtimes) if agent_runtimes else 0
        
        # Create figure
        fig = self._create_base_figure()
        
        # Add background shapes
        y_range = Y_AXIS_RANGES['roi']
        self._add_background_shapes(fig, min_time, max_time, y_range['min'], y_range['max'])
        self._add_zero_line(fig, min_time, max_time)
        
        # Calculate moving averages
        avg_roi_data = self._calculate_moving_average(roi_data, 'roi')
        
        # Add individual agent data points
        unique_agents = roi_data['agent_name'].unique()
        color_map = self._get_color_map(unique_agents)
        self._add_agent_data_points(fig, roi_data, 'roi', color_map)
        
        # Add ROI moving average line
        self._add_moving_average_line(
            fig, avg_roi_data, 'roi', 
            'Average ROI (3d window)', 
            self.colors['roi'], 
            width=2
        )
        
        # Update layout and axes
        title = f"Modius Agents ROI (over avg. {avg_runtime:.1f} days runtime)"
        self._update_layout(
            fig, 
            title=title,
            y_axis_title="ROI [%]",
            y_range=[y_range['min'], y_range['max']]
        )
        
        # Use auto-range for both x-axis and y-axis to show all available data
        self._update_axes(
            fig,
            x_range=None,  # Let plotly auto-determine the best x-axis range
            y_auto=True    # Let plotly auto-determine the best y-axis range
        )
        
        # Save chart
        self._save_chart(
            fig, 
            FILE_PATHS['roi_graph_html'], 
            FILE_PATHS['roi_graph_png']
        )
        
        return fig


def generate_roi_visualizations(data_processor=None) -> Tuple[go.Figure, Optional[str]]:
    """Generate ROI visualizations."""
    from ..data.data_processor import DataProcessor
    
    if data_processor is None:
        data_processor = DataProcessor()
    
    # Fetch data
    _, roi_df = data_processor.fetch_apr_data_from_db()
    
    # Create chart
    roi_chart = ROIChart(data_processor)
    fig, csv_path = roi_chart.generate_visualization(
        roi_df, 
        csv_filename=FILE_PATHS['roi_csv']
    )
    
    return fig, csv_path