File size: 13,599 Bytes
3e2ffcb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#!/usr/bin/env python3
"""
OnCall.ai System - Latency Chart Generator
==========================================

Generates comprehensive latency analysis charts from saved statistics.
Reads JSON files produced by latency_evaluator.py and creates visualizations.

No LLM calls - pure data visualization.

Author: YanBo Chen  
Date: 2025-08-04
"""

import json
import os
import sys
from typing import Dict, List, Any
from datetime import datetime
from pathlib import Path
import glob

# Visualization imports
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np


class LatencyChartGenerator:
    """Generate charts from latency evaluation statistics - no LLM dependency"""
    
    def __init__(self):
        """Initialize chart generator"""
        print("πŸ“ˆ Initializing Latency Chart Generator...")
        
        # Set up professional chart style
        plt.style.use('default')
        sns.set_palette("husl")
        
        print("βœ… Chart Generator ready")
    
    def load_latest_statistics(self, results_dir: str = None) -> Dict[str, Any]:
        """
        Load the most recent latency statistics file
        
        Args:
            results_dir: Directory containing statistics files
        """
        if results_dir is None:
            results_dir = Path(__file__).parent / "results"
        
        # Find latest statistics file
        pattern = str(results_dir / "latency_statistics_*.json")
        stat_files = glob.glob(pattern)
        
        if not stat_files:
            raise FileNotFoundError(f"No latency statistics files found in {results_dir}")
        
        # Get the most recent file
        latest_file = max(stat_files, key=os.path.getmtime)
        
        print(f"πŸ“Š Loading statistics from: {latest_file}")
        
        with open(latest_file, 'r', encoding='utf-8') as f:
            stats = json.load(f)
        
        return stats
    
    def generate_comprehensive_charts(self, stats: Dict[str, Any]) -> str:
        """
        Generate comprehensive 4-category latency analysis charts
        
        Creates professional charts showing:
        1. Category comparison bar chart
        2. Individual query scatter plot  
        3. Statistical summary table
        4. Performance distribution box plot
        """
        try:
            # Create figure with subplots
            fig, axes = plt.subplots(2, 2, figsize=(16, 12))
            fig.suptitle('OnCall.ai Latency Analysis - Category Comparison', 
                        fontsize=16, fontweight='bold')
            
            category_results = stats['category_results']
            overall_results = stats['overall_results']
            
            # Chart 1: Category Comparison Bar Chart
            ax1 = axes[0, 0]
            categories = []
            avg_latencies = []
            std_devs = []
            
            # Collect category data
            for category, cat_stats in category_results.items():
                if cat_stats['query_count'] > 0:
                    categories.append(category.replace('_', ' ').title())
                    avg_latencies.append(cat_stats['average_latency'])
                    std_devs.append(cat_stats['std_deviation'])
            
            # Add overall
            categories.append('Overall')
            avg_latencies.append(overall_results['average_latency'])
            std_devs.append(overall_results['std_deviation'])
            
            # Create bar chart with error bars
            bars = ax1.bar(categories, avg_latencies, capsize=5, alpha=0.8, 
                          color=['#1f77b4', '#ff7f0e', '#d62728', '#2ca02c'])
            ax1.errorbar(categories, avg_latencies, yerr=std_devs, fmt='none', 
                        color='black', capsize=3, capthick=1)
            
            ax1.set_title('Average Latency by Category', fontweight='bold')
            ax1.set_ylabel('Latency (seconds)')
            ax1.set_xlabel('Query Category')
            ax1.grid(True, alpha=0.3)
            
            # Add value labels on bars
            for bar, avg, std in zip(bars, avg_latencies, std_devs):
                height = bar.get_height()
                ax1.text(bar.get_x() + bar.get_width()/2., height + std*0.1,
                        f'{avg:.1f}s', ha='center', va='bottom', fontweight='bold')
            
            # Add target line
            ax1.axhline(y=30.0, color='red', linestyle='--', alpha=0.7, label='30s Target')
            ax1.legend()
            
            # Chart 2: Individual Query Performance
            ax2 = axes[0, 1]
            
            query_indices = []
            latencies = []
            colors = []
            
            color_map = {'diagnosis': '#1f77b4', 'treatment': '#ff7f0e', 'mixed': '#d62728'}
            query_idx = 0
            
            for category, cat_stats in category_results.items():
                for latency in cat_stats['individual_latencies']:
                    query_indices.append(query_idx)
                    latencies.append(latency)
                    colors.append(color_map.get(category, 'gray'))
                    query_idx += 1
            
            if latencies:
                ax2.scatter(query_indices, latencies, c=colors, alpha=0.7, s=100)
                ax2.set_title('Individual Query Performance', fontweight='bold')
                ax2.set_ylabel('Latency (seconds)')
                ax2.set_xlabel('Query Index')
                ax2.grid(True, alpha=0.3)
                
                # Add target line
                ax2.axhline(y=30.0, color='red', linestyle='--', alpha=0.7, label='30s Target')
                
                # Add category legend
                from matplotlib.patches import Patch
                legend_elements = [Patch(facecolor=color_map[cat], label=cat.title()) 
                                 for cat in color_map.keys() if cat in category_results.keys()]
                ax2.legend(handles=legend_elements)
            else:
                ax2.text(0.5, 0.5, 'No latency data available', 
                        ha='center', va='center', transform=ax2.transAxes)
                ax2.set_title('Individual Query Performance', fontweight='bold')
            
            # Chart 3: Statistical Summary Table
            ax3 = axes[1, 0]
            ax3.axis('tight')
            ax3.axis('off')
            
            # Create summary table
            table_data = []
            headers = ['Category', 'Avg (s)', 'Std (s)', 'Min (s)', 'Max (s)', 'Count']
            
            for category, cat_stats in category_results.items():
                if cat_stats['query_count'] > 0:
                    table_data.append([
                        category.replace('_', ' ').title(),
                        f"{cat_stats['average_latency']:.2f}",
                        f"{cat_stats['std_deviation']:.2f}",
                        f"{cat_stats['min_latency']:.2f}",
                        f"{cat_stats['max_latency']:.2f}",
                        str(cat_stats['query_count'])
                    ])
            
            # Add overall row
            table_data.append([
                'Overall',
                f"{overall_results['average_latency']:.2f}",
                f"{overall_results['std_deviation']:.2f}",
                f"{overall_results['min_latency']:.2f}",
                f"{overall_results['max_latency']:.2f}",
                str(overall_results['successful_queries'])
            ])
            
            if table_data:
                table = ax3.table(cellText=table_data, colLabels=headers,
                                cellLoc='center', loc='center',
                                colWidths=[0.2, 0.15, 0.15, 0.15, 0.15, 0.1])
                table.auto_set_font_size(False)
                table.set_fontsize(10)
                table.scale(1, 2)
                
                # Style the table header
                for i in range(len(headers)):
                    table[(0, i)].set_text_props(weight='bold', color='white')
                    table[(0, i)].set_facecolor('#2E7D32')
            
            ax3.set_title('Statistical Summary', fontweight='bold', pad=20)
            
            # Chart 4: Performance Distribution
            ax4 = axes[1, 1]
            
            # Create box plot if we have multiple data points
            box_data = []
            box_labels = []
            
            for category, cat_stats in category_results.items():
                if cat_stats['individual_latencies'] and len(cat_stats['individual_latencies']) > 0:
                    box_data.append(cat_stats['individual_latencies'])
                    box_labels.append(category.replace('_', ' ').title())
            
            if box_data and len(box_data) > 0:
                box_plot = ax4.boxplot(box_data, labels=box_labels, patch_artist=True)
                
                # Color the boxes
                colors = ['#1f77b4', '#ff7f0e', '#d62728']
                for patch, color in zip(box_plot['boxes'], colors[:len(box_plot['boxes'])]):
                    patch.set_facecolor(color)
                    patch.set_alpha(0.7)
                
                ax4.set_title('Latency Distribution by Category', fontweight='bold')
                ax4.set_ylabel('Latency (seconds)')
                ax4.grid(True, alpha=0.3)
                
                # Add target line
                ax4.axhline(y=30.0, color='red', linestyle='--', alpha=0.7, label='30s Target')
                ax4.legend()
            else:
                # For single data points, show a simple bar chart
                single_categories = []
                single_latencies = []
                
                for category, cat_stats in category_results.items():
                    if cat_stats['query_count'] > 0:
                        single_categories.append(category.replace('_', ' ').title())
                        single_latencies.append(cat_stats['average_latency'])
                
                if single_categories:
                    ax4.bar(single_categories, single_latencies, alpha=0.7, 
                           color=['#1f77b4', '#ff7f0e', '#d62728'][:len(single_categories)])
                    ax4.set_title('Category Latency (Single Query Each)', fontweight='bold')
                    ax4.set_ylabel('Latency (seconds)')
                    ax4.grid(True, alpha=0.3)
                    ax4.axhline(y=30.0, color='red', linestyle='--', alpha=0.7, label='30s Target')
                    ax4.legend()
                else:
                    ax4.text(0.5, 0.5, 'No data available for distribution plot', 
                            ha='center', va='center', transform=ax4.transAxes)
                    ax4.set_title('Latency Distribution', fontweight='bold')
            
            # Adjust layout and save
            plt.tight_layout()
            
            # Save chart
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            chart_filename = f"latency_analysis_charts_{timestamp}.png"
            
            # Ensure results directory exists
            results_dir = Path(__file__).parent / "results"
            results_dir.mkdir(exist_ok=True)
            chart_path = results_dir / chart_filename
            
            plt.savefig(chart_path, dpi=300, bbox_inches='tight', 
                       facecolor='white', edgecolor='none')
            plt.close()
            
            print(f"πŸ“ˆ Charts saved to: {chart_path}")
            return str(chart_path)
            
        except Exception as e:
            print(f"❌ Chart generation failed: {e}")
            return ""
    
    def print_statistics_summary(self, stats: Dict[str, Any]):
        """Print formatted statistics summary to console"""
        category_results = stats['category_results']
        overall_results = stats['overall_results']
        
        print(f"\nπŸ“Š === LATENCY ANALYSIS CHART SUMMARY ===")
        print(f"Overall Performance:")
        print(f"   Average Latency: {overall_results['average_latency']:.2f}s (Β±{overall_results['std_deviation']:.2f})")
        print(f"   Success Rate: {overall_results['successful_queries']}/{overall_results['total_queries']}")
        print(f"   30s Target Compliance: {overall_results['target_compliance']:.1%}")
        
        print(f"\nCategory Breakdown:")
        for category, cat_stats in category_results.items():
            if cat_stats['query_count'] > 0:
                print(f"   {category.capitalize()}: {cat_stats['average_latency']:.2f}s (Β±{cat_stats['std_deviation']:.2f}) [{cat_stats['query_count']} queries]")


# Independent execution interface
if __name__ == "__main__":
    """Independent chart generation interface"""
    
    print("πŸ“ˆ OnCall.ai Latency Chart Generator")
    
    # Initialize chart generator
    chart_gen = LatencyChartGenerator()
    
    try:
        # Load latest statistics
        stats = chart_gen.load_latest_statistics()
        
        # Generate charts
        chart_path = chart_gen.generate_comprehensive_charts(stats)
        
        # Print summary
        chart_gen.print_statistics_summary(stats)
        
        print(f"\nβœ… Chart generation complete!")
        print(f"πŸ“ˆ Charts saved to: {chart_path}")
        
    except FileNotFoundError as e:
        print(f"❌ {e}")
        print("πŸ’‘ Please run latency_evaluator.py first to generate statistics data")
    except Exception as e:
        print(f"❌ Chart generation failed: {e}")