Ahmedik95316 commited on
Commit
8cf2942
·
1 Parent(s): 9d6a9cd

Update app/streamlit_app.py

Browse files

Cross Validation Implementation

Files changed (1) hide show
  1. app/streamlit_app.py +574 -25
app/streamlit_app.py CHANGED
@@ -117,6 +117,48 @@ class StreamlitAppManager:
117
  if 'auto_refresh' not in st.session_state:
118
  st.session_state.auto_refresh = False
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  # Initialize app manager
122
  app_manager = StreamlitAppManager()
@@ -244,7 +286,372 @@ def show_logs_section():
244
  else:
245
  st.warning(f"Log file not found: {log_path}")
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  def save_prediction_to_history(text: str, prediction: str, confidence: float):
249
  """Save prediction to session history"""
250
  prediction_entry = {
@@ -357,6 +764,81 @@ def create_prediction_history_chart():
357
  fig.update_layout(height=400)
358
  return fig
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
  def render_environment_info():
362
  """Render environment information"""
@@ -628,62 +1110,129 @@ def main():
628
  # Tab 3: Analytics
629
  with tab3:
630
  st.header("System Analytics")
631
-
632
- # Prediction history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
  if st.session_state.prediction_history:
634
  st.subheader("Recent Predictions")
635
-
636
  # History chart
637
  fig_history = create_prediction_history_chart()
638
  if fig_history:
639
  st.plotly_chart(fig_history, use_container_width=True)
640
-
641
  # History table
642
  history_df = pd.DataFrame(st.session_state.prediction_history)
643
  st.dataframe(history_df.tail(20), use_container_width=True)
644
-
645
  else:
646
  st.info(
647
  "No prediction history available. Make some predictions to see analytics.")
648
-
649
- # System metrics
650
  st.subheader("System Metrics")
651
-
652
  # Load various log files for analytics
653
  try:
654
- # API health check
655
  if app_manager.api_available:
656
  response = app_manager.session.get(
657
  f"{app_manager.config['api_url']}/metrics")
658
  if response.status_code == 200:
659
  metrics = response.json()
660
-
 
 
 
 
 
661
  col1, col2, col3, col4 = st.columns(4)
662
-
663
  with col1:
664
  st.metric("Total API Requests",
665
- metrics.get('total_requests', 0))
666
-
667
  with col2:
668
- st.metric("Unique Clients", metrics.get(
669
- 'unique_clients', 0))
670
-
671
  with col3:
672
- st.metric("Model Version", metrics.get(
673
- 'model_version', 'Unknown'))
674
-
675
  with col4:
676
- status = metrics.get('model_health', 'unknown')
677
  st.metric("Model Status", status)
678
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
  # Environment details
680
  st.subheader("Environment Details")
681
- env_data = metrics.get('environment', path_manager.environment)
 
682
  st.info(f"Running in: {env_data}")
683
 
684
  # Available files
685
- datasets = metrics.get('available_datasets', {})
686
- models = metrics.get('available_models', {})
687
 
688
  col1, col2 = st.columns(2)
689
  with col1:
@@ -697,7 +1246,7 @@ def main():
697
  for name, exists in models.items():
698
  status = "✅" if exists else "❌"
699
  st.write(f"{status} {name}")
700
-
701
  except Exception as e:
702
  st.warning(f"Could not load API metrics: {e}")
703
 
 
117
  if 'auto_refresh' not in st.session_state:
118
  st.session_state.auto_refresh = False
119
 
120
+ def get_cv_results_from_api(self):
121
+ """Get cross-validation results from API"""
122
+ try:
123
+ if not self.api_available:
124
+ return None
125
+
126
+ response = self.session.get(
127
+ f"{self.config['api_url']}/cv/results",
128
+ timeout=10
129
+ )
130
+
131
+ if response.status_code == 200:
132
+ return response.json()
133
+ elif response.status_code == 404:
134
+ return {'error': 'No CV results available'}
135
+ else:
136
+ return None
137
+ except Exception as e:
138
+ logger.warning(f"Could not fetch CV results: {e}")
139
+ return None
140
+
141
+ def get_model_comparison_from_api(self):
142
+ """Get model comparison results from API"""
143
+ try:
144
+ if not self.api_available:
145
+ return None
146
+
147
+ response = self.session.get(
148
+ f"{self.config['api_url']}/cv/comparison",
149
+ timeout=10
150
+ )
151
+
152
+ if response.status_code == 200:
153
+ return response.json()
154
+ elif response.status_code == 404:
155
+ return {'error': 'No comparison results available'}
156
+ else:
157
+ return None
158
+ except Exception as e:
159
+ logger.warning(f"Could not fetch model comparison: {e}")
160
+ return None
161
+
162
 
163
  # Initialize app manager
164
  app_manager = StreamlitAppManager()
 
286
  else:
287
  st.warning(f"Log file not found: {log_path}")
288
 
289
+ # ADD STANDALONE FS HERE
290
+ def render_cv_results_section(self):
291
+ """Render cross-validation results section"""
292
+ st.subheader("🎯 Cross-Validation Results")
293
+
294
+ cv_results = self.get_cv_results_from_api()
295
+
296
+ if cv_results is None:
297
+ st.warning("API not available - showing local CV results if available")
298
+
299
+ # Try to load local metadata
300
+ try:
301
+ from path_config import path_manager
302
+ metadata_path = path_manager.get_metadata_path()
303
+
304
+ if metadata_path.exists():
305
+ with open(metadata_path, 'r') as f:
306
+ metadata = json.load(f)
307
+ cv_results = {'cross_validation': metadata.get('cross_validation', {})}
308
+ else:
309
+ st.info("No local CV results found")
310
+ return
311
+ except Exception as e:
312
+ st.error(f"Could not load local CV results: {e}")
313
+ return
314
+
315
+ if cv_results and 'error' not in cv_results:
316
+ # Display model information
317
+ if 'model_version' in cv_results:
318
+ st.info(f"**Model Version:** {cv_results.get('model_version', 'Unknown')} | "
319
+ f"**Type:** {cv_results.get('model_type', 'Unknown')} | "
320
+ f"**Trained:** {cv_results.get('training_timestamp', 'Unknown')}")
321
+
322
+ cv_data = cv_results.get('cross_validation', {})
323
+
324
+ if cv_data:
325
+ # CV Methodology
326
+ methodology = cv_data.get('methodology', {})
327
+ col1, col2, col3 = st.columns(3)
328
+
329
+ with col1:
330
+ st.metric("CV Folds", methodology.get('n_splits', 'Unknown'))
331
+ with col2:
332
+ st.metric("CV Type", methodology.get('cv_type', 'StratifiedKFold'))
333
+ with col3:
334
+ st.metric("Random State", methodology.get('random_state', 42))
335
+
336
+ # Performance Metrics Summary
337
+ st.subheader("📊 Performance Summary")
338
+
339
+ test_scores = cv_data.get('test_scores', {})
340
+ if test_scores:
341
+
342
+ metrics_cols = st.columns(len(test_scores))
343
+ for idx, (metric, scores) in enumerate(test_scores.items()):
344
+ with metrics_cols[idx]:
345
+ if isinstance(scores, dict):
346
+ mean_val = scores.get('mean', 0)
347
+ std_val = scores.get('std', 0)
348
+ st.metric(
349
+ f"{metric.upper()}",
350
+ f"{mean_val:.4f}",
351
+ delta=f"±{std_val:.4f}"
352
+ )
353
+
354
+ # Detailed CV Scores Visualization
355
+ st.subheader("📈 Cross-Validation Scores by Metric")
356
+
357
+ # Create a comprehensive chart
358
+ chart_data = []
359
+ fold_results = cv_data.get('individual_fold_results', [])
360
+
361
+ if fold_results:
362
+ for fold_result in fold_results:
363
+ fold_num = fold_result.get('fold', 0)
364
+ test_scores_fold = fold_result.get('test_scores', {})
365
+
366
+ for metric, score in test_scores_fold.items():
367
+ chart_data.append({
368
+ 'Fold': f"Fold {fold_num}",
369
+ 'Metric': metric.upper(),
370
+ 'Score': score,
371
+ 'Type': 'Test'
372
+ })
373
+
374
+ # Add train scores if available
375
+ train_scores_fold = fold_result.get('train_scores', {})
376
+ for metric, score in train_scores_fold.items():
377
+ chart_data.append({
378
+ 'Fold': f"Fold {fold_num}",
379
+ 'Metric': metric.upper(),
380
+ 'Score': score,
381
+ 'Type': 'Train'
382
+ })
383
+
384
+ if chart_data:
385
+ df_cv = pd.DataFrame(chart_data)
386
+
387
+ # Create separate charts for each metric
388
+ for metric in df_cv['Metric'].unique():
389
+ metric_data = df_cv[df_cv['Metric'] == metric]
390
+
391
+ fig = px.bar(
392
+ metric_data,
393
+ x='Fold',
394
+ y='Score',
395
+ color='Type',
396
+ title=f'{metric} Scores Across CV Folds',
397
+ barmode='group'
398
+ )
399
+
400
+ fig.update_layout(height=400)
401
+ st.plotly_chart(fig, use_container_width=True)
402
+
403
+ # Performance Indicators
404
+ st.subheader("🔍 Model Quality Indicators")
405
+
406
+ performance_indicators = cv_data.get('performance_indicators', {})
407
+ col1, col2 = st.columns(2)
408
+
409
+ with col1:
410
+ overfitting_score = performance_indicators.get('overfitting_score', 'Unknown')
411
+ if isinstance(overfitting_score, (int, float)):
412
+ if overfitting_score < 0.05:
413
+ st.success(f"**Overfitting Score:** {overfitting_score:.4f} (Low)")
414
+ elif overfitting_score < 0.15:
415
+ st.warning(f"**Overfitting Score:** {overfitting_score:.4f} (Moderate)")
416
+ else:
417
+ st.error(f"**Overfitting Score:** {overfitting_score:.4f} (High)")
418
+ else:
419
+ st.info(f"**Overfitting Score:** {overfitting_score}")
420
+
421
+ with col2:
422
+ stability_score = performance_indicators.get('stability_score', 'Unknown')
423
+ if isinstance(stability_score, (int, float)):
424
+ if stability_score > 0.9:
425
+ st.success(f"**Stability Score:** {stability_score:.4f} (High)")
426
+ elif stability_score > 0.7:
427
+ st.warning(f"**Stability Score:** {stability_score:.4f} (Moderate)")
428
+ else:
429
+ st.error(f"**Stability Score:** {stability_score:.4f} (Low)")
430
+ else:
431
+ st.info(f"**Stability Score:** {stability_score}")
432
+
433
+ # Statistical Validation Results
434
+ if 'statistical_validation' in cv_results:
435
+ st.subheader("📈 Statistical Validation")
436
+
437
+ stat_validation = cv_results['statistical_validation']
438
+
439
+ for metric, validation_data in stat_validation.items():
440
+ if isinstance(validation_data, dict):
441
+ with st.expander(f"Statistical Tests - {metric.upper()}"):
442
+
443
+ col1, col2 = st.columns(2)
444
+
445
+ with col1:
446
+ st.write(f"**Improvement:** {validation_data.get('improvement', 0):.4f}")
447
+ st.write(f"**Effect Size:** {validation_data.get('effect_size', 0):.4f}")
448
+
449
+ with col2:
450
+ sig_improvement = validation_data.get('significant_improvement', False)
451
+ if sig_improvement:
452
+ st.success("**Significant Improvement:** Yes")
453
+ else:
454
+ st.info("**Significant Improvement:** No")
455
+
456
+ # Display test results
457
+ tests = validation_data.get('tests', {})
458
+ if tests:
459
+ st.write("**Statistical Test Results:**")
460
+ for test_name, test_result in tests.items():
461
+ if isinstance(test_result, dict):
462
+ p_value = test_result.get('p_value', 1.0)
463
+ significant = test_result.get('significant', False)
464
+
465
+ status = "✅ Significant" if significant else "❌ Not Significant"
466
+ st.write(f"- {test_name}: p-value = {p_value:.4f} ({status})")
467
+
468
+ # Promotion Validation
469
+ if 'promotion_validation' in cv_results:
470
+ st.subheader("🚀 Model Promotion Validation")
471
+
472
+ promotion_val = cv_results['promotion_validation']
473
+
474
+ col1, col2, col3 = st.columns(3)
475
+
476
+ with col1:
477
+ confidence = promotion_val.get('decision_confidence', 'Unknown')
478
+ if isinstance(confidence, (int, float)):
479
+ st.metric("Decision Confidence", f"{confidence:.2%}")
480
+ else:
481
+ st.metric("Decision Confidence", str(confidence))
482
+
483
+ with col2:
484
+ st.write(f"**Promotion Reason:**")
485
+ st.write(promotion_val.get('promotion_reason', 'Unknown'))
486
+
487
+ with col3:
488
+ st.write(f"**Comparison Method:**")
489
+ st.write(promotion_val.get('comparison_method', 'Unknown'))
490
+
491
+ # Raw CV Data (expandable)
492
+ with st.expander("🔍 Detailed CV Data"):
493
+ st.json(cv_data)
494
+
495
+ else:
496
+ st.info("No detailed CV test scores available")
497
+ else:
498
+ st.info("No cross-validation data available")
499
+ else:
500
+ error_msg = cv_results.get('error', 'Unknown error') if cv_results else 'No CV results available'
501
+ st.warning(f"Cross-validation results not available: {error_msg}")
502
+
503
+ def render_model_comparison_section(self):
504
+ """Render model comparison results section"""
505
+ st.subheader("⚖️ Model Comparison Results")
506
+
507
+ comparison_results = self.get_model_comparison_from_api()
508
+
509
+ if comparison_results is None:
510
+ st.warning("API not available - comparison results not accessible")
511
+ return
512
+
513
+ if comparison_results and 'error' not in comparison_results:
514
+
515
+ # Comparison Summary
516
+ summary = comparison_results.get('summary', {})
517
+ models_compared = comparison_results.get('models_compared', {})
518
+
519
+ st.info(f"**Comparison:** {models_compared.get('model1_name', 'Model 1')} vs "
520
+ f"{models_compared.get('model2_name', 'Model 2')} | "
521
+ f"**Timestamp:** {comparison_results.get('comparison_timestamp', 'Unknown')}")
522
+
523
+ # Decision Summary
524
+ col1, col2, col3 = st.columns(3)
525
+
526
+ with col1:
527
+ decision = summary.get('decision', False)
528
+ if decision:
529
+ st.success("**Decision:** Promote New Model")
530
+ else:
531
+ st.info("**Decision:** Keep Current Model")
532
+
533
+ with col2:
534
+ confidence = summary.get('confidence', 0)
535
+ st.metric("Decision Confidence", f"{confidence:.2%}")
536
+
537
+ with col3:
538
+ st.write("**Reason:**")
539
+ st.write(summary.get('reason', 'Unknown'))
540
+
541
+ # Performance Comparison
542
+ st.subheader("📊 Performance Comparison")
543
+
544
+ prod_performance = comparison_results.get('model_performance', {}).get('production_model', {})
545
+ cand_performance = comparison_results.get('model_performance', {}).get('candidate_model', {})
546
+
547
+ # Create comparison chart
548
+ if prod_performance.get('test_scores') and cand_performance.get('test_scores'):
549
+
550
+ comparison_data = []
551
+
552
+ prod_scores = prod_performance['test_scores']
553
+ cand_scores = cand_performance['test_scores']
554
+
555
+ for metric in set(prod_scores.keys()) & set(cand_scores.keys()):
556
+ prod_mean = prod_scores[metric].get('mean', 0)
557
+ cand_mean = cand_scores[metric].get('mean', 0)
558
+
559
+ comparison_data.extend([
560
+ {'Model': 'Production', 'Metric': metric.upper(), 'Score': prod_mean},
561
+ {'Model': 'Candidate', 'Metric': metric.upper(), 'Score': cand_mean}
562
+ ])
563
 
564
+ if comparison_data:
565
+ df_comparison = pd.DataFrame(comparison_data)
566
+
567
+ fig = px.bar(
568
+ df_comparison,
569
+ x='Metric',
570
+ y='Score',
571
+ color='Model',
572
+ title='Model Performance Comparison',
573
+ barmode='group'
574
+ )
575
+
576
+ fig.update_layout(height=400)
577
+ st.plotly_chart(fig, use_container_width=True)
578
+
579
+ # Detailed Metric Comparisons
580
+ st.subheader("🔍 Detailed Metric Analysis")
581
+
582
+ metric_comparisons = comparison_results.get('metric_comparisons', {})
583
+
584
+ if metric_comparisons:
585
+ for metric, comparison_data in metric_comparisons.items():
586
+ if isinstance(comparison_data, dict):
587
+
588
+ with st.expander(f"{metric.upper()} Analysis"):
589
+
590
+ col1, col2, col3 = st.columns(3)
591
+
592
+ with col1:
593
+ improvement = comparison_data.get('improvement', 0)
594
+ rel_improvement = comparison_data.get('relative_improvement', 0)
595
+
596
+ if improvement > 0:
597
+ st.success(f"**Improvement:** +{improvement:.4f}")
598
+ st.success(f"**Relative:** +{rel_improvement:.2f}%")
599
+ else:
600
+ st.info(f"**Improvement:** {improvement:.4f}")
601
+ st.info(f"**Relative:** {rel_improvement:.2f}%")
602
+
603
+ with col2:
604
+ effect_size = comparison_data.get('effect_size', 0)
605
+
606
+ if abs(effect_size) > 0.8:
607
+ st.success(f"**Effect Size:** {effect_size:.4f} (Large)")
608
+ elif abs(effect_size) > 0.5:
609
+ st.warning(f"**Effect Size:** {effect_size:.4f} (Medium)")
610
+ else:
611
+ st.info(f"**Effect Size:** {effect_size:.4f} (Small)")
612
+
613
+ with col3:
614
+ sig_improvement = comparison_data.get('significant_improvement', False)
615
+ practical_sig = comparison_data.get('practical_significance', False)
616
+
617
+ if sig_improvement:
618
+ st.success("**Statistical Significance:** Yes")
619
+ else:
620
+ st.info("**Statistical Significance:** No")
621
+
622
+ if practical_sig:
623
+ st.success("**Practical Significance:** Yes")
624
+ else:
625
+ st.info("**Practical Significance:** No")
626
+
627
+ # Statistical test results
628
+ tests = comparison_data.get('tests', {})
629
+ if tests:
630
+ st.write("**Statistical Tests:**")
631
+ for test_name, test_result in tests.items():
632
+ if isinstance(test_result, dict):
633
+ p_value = test_result.get('p_value', 1.0)
634
+ significant = test_result.get('significant', False)
635
+
636
+ status = "✅" if significant else "❌"
637
+ st.write(f"- {test_name}: p = {p_value:.4f} {status}")
638
+
639
+ # CV Methodology
640
+ cv_methodology = comparison_results.get('cv_methodology', {})
641
+ if cv_methodology:
642
+ st.subheader("🎯 Cross-Validation Methodology")
643
+ st.info(f"**CV Folds:** {cv_methodology.get('cv_folds', 'Unknown')} | "
644
+ f"**Session ID:** {comparison_results.get('session_id', 'Unknown')}")
645
+
646
+ # Raw comparison data (expandable)
647
+ with st.expander("🔍 Raw Comparison Data"):
648
+ st.json(comparison_results)
649
+
650
+ else:
651
+ error_msg = comparison_results.get('error', 'Unknown error') if comparison_results else 'No comparison results available'
652
+ st.warning(f"Model comparison results not available: {error_msg}")
653
+
654
+
655
  def save_prediction_to_history(text: str, prediction: str, confidence: float):
656
  """Save prediction to session history"""
657
  prediction_entry = {
 
764
  fig.update_layout(height=400)
765
  return fig
766
 
767
+ def create_cv_performance_chart(cv_results: dict) -> Optional[Any]:
768
+ """Create a comprehensive CV performance visualization"""
769
+ try:
770
+ if not cv_results or 'cross_validation' not in cv_results:
771
+ return None
772
+
773
+ cv_data = cv_results['cross_validation']
774
+ fold_results = cv_data.get('individual_fold_results', [])
775
+
776
+ if not fold_results:
777
+ return None
778
+
779
+ # Prepare data for visualization
780
+ chart_data = []
781
+
782
+ for fold_result in fold_results:
783
+ fold_num = fold_result.get('fold', 0)
784
+ test_scores = fold_result.get('test_scores', {})
785
+ train_scores = fold_result.get('train_scores', {})
786
+
787
+ for metric, score in test_scores.items():
788
+ chart_data.append({
789
+ 'Fold': fold_num,
790
+ 'Metric': metric.upper(),
791
+ 'Score': score,
792
+ 'Type': 'Test',
793
+ 'Fold_Label': f"Fold {fold_num}"
794
+ })
795
+
796
+ for metric, score in train_scores.items():
797
+ chart_data.append({
798
+ 'Fold': fold_num,
799
+ 'Metric': metric.upper(),
800
+ 'Score': score,
801
+ 'Type': 'Train',
802
+ 'Fold_Label': f"Fold {fold_num}"
803
+ })
804
+
805
+ if not chart_data:
806
+ return None
807
+
808
+ df_cv = pd.DataFrame(chart_data)
809
+
810
+ # Create faceted chart showing all metrics
811
+ fig = px.box(
812
+ df_cv[df_cv['Type'] == 'Test'], # Focus on test scores
813
+ x='Metric',
814
+ y='Score',
815
+ title='Cross-Validation Performance Distribution',
816
+ points='all'
817
+ )
818
+
819
+ # Add mean lines
820
+ for metric in df_cv['Metric'].unique():
821
+ metric_data = df_cv[(df_cv['Metric'] == metric) & (df_cv['Type'] == 'Test')]
822
+ mean_score = metric_data['Score'].mean()
823
+
824
+ fig.add_hline(
825
+ y=mean_score,
826
+ line_dash="dash",
827
+ line_color="red",
828
+ annotation_text=f"Mean: {mean_score:.3f}"
829
+ )
830
+
831
+ fig.update_layout(
832
+ height=500,
833
+ showlegend=True
834
+ )
835
+
836
+ return fig
837
+
838
+ except Exception as e:
839
+ logger.error(f"Failed to create CV chart: {e}")
840
+ return None
841
+
842
 
843
  def render_environment_info():
844
  """Render environment information"""
 
1110
  # Tab 3: Analytics
1111
  with tab3:
1112
  st.header("System Analytics")
1113
+
1114
+ # Add CV and Model Comparison sections
1115
+ col1, col2 = st.columns([1, 1])
1116
+
1117
+ with col1:
1118
+ if st.button("🔄 Refresh CV Results", use_container_width=True):
1119
+ st.rerun()
1120
+
1121
+ with col2:
1122
+ show_detailed_cv = st.checkbox("Show Detailed CV Analysis", value=True)
1123
+
1124
+ if show_detailed_cv:
1125
+ # Render cross-validation results
1126
+ app_manager.render_cv_results_section()
1127
+
1128
+ # Add separator
1129
+ st.divider()
1130
+
1131
+ # Render model comparison results
1132
+ app_manager.render_model_comparison_section()
1133
+
1134
+ # Add separator
1135
+ st.divider()
1136
+
1137
+ # Prediction history (existing content)
1138
  if st.session_state.prediction_history:
1139
  st.subheader("Recent Predictions")
1140
+
1141
  # History chart
1142
  fig_history = create_prediction_history_chart()
1143
  if fig_history:
1144
  st.plotly_chart(fig_history, use_container_width=True)
1145
+
1146
  # History table
1147
  history_df = pd.DataFrame(st.session_state.prediction_history)
1148
  st.dataframe(history_df.tail(20), use_container_width=True)
1149
+
1150
  else:
1151
  st.info(
1152
  "No prediction history available. Make some predictions to see analytics.")
1153
+
1154
+ # System metrics (existing content with CV enhancement)
1155
  st.subheader("System Metrics")
1156
+
1157
  # Load various log files for analytics
1158
  try:
1159
+ # API health check with CV information
1160
  if app_manager.api_available:
1161
  response = app_manager.session.get(
1162
  f"{app_manager.config['api_url']}/metrics")
1163
  if response.status_code == 200:
1164
  metrics = response.json()
1165
+
1166
+ # Basic metrics
1167
+ api_metrics = metrics.get('api_metrics', {})
1168
+ model_info = metrics.get('model_info', {})
1169
+ cv_summary = metrics.get('cross_validation_summary', {})
1170
+
1171
  col1, col2, col3, col4 = st.columns(4)
1172
+
1173
  with col1:
1174
  st.metric("Total API Requests",
1175
+ api_metrics.get('total_requests', 0))
1176
+
1177
  with col2:
1178
+ st.metric("Unique Clients",
1179
+ api_metrics.get('unique_clients', 0))
1180
+
1181
  with col3:
1182
+ st.metric("Model Version",
1183
+ model_info.get('model_version', 'Unknown'))
1184
+
1185
  with col4:
1186
+ status = model_info.get('model_health', 'unknown')
1187
  st.metric("Model Status", status)
1188
+
1189
+ # Cross-validation summary metrics
1190
+ if cv_summary.get('cv_available', False):
1191
+ st.subheader("Cross-Validation Summary")
1192
+
1193
+ cv_col1, cv_col2, cv_col3, cv_col4 = st.columns(4)
1194
+
1195
+ with cv_col1:
1196
+ cv_folds = cv_summary.get('cv_folds', 'Unknown')
1197
+ st.metric("CV Folds", cv_folds)
1198
+
1199
+ with cv_col2:
1200
+ cv_f1 = cv_summary.get('cv_f1_mean')
1201
+ cv_f1_std = cv_summary.get('cv_f1_std')
1202
+ if cv_f1 is not None and cv_f1_std is not None:
1203
+ st.metric("CV F1 Score", f"{cv_f1:.4f}", f"±{cv_f1_std:.4f}")
1204
+ else:
1205
+ st.metric("CV F1 Score", "N/A")
1206
+
1207
+ with cv_col3:
1208
+ cv_acc = cv_summary.get('cv_accuracy_mean')
1209
+ cv_acc_std = cv_summary.get('cv_accuracy_std')
1210
+ if cv_acc is not None and cv_acc_std is not None:
1211
+ st.metric("CV Accuracy", f"{cv_acc:.4f}", f"±{cv_acc_std:.4f}")
1212
+ else:
1213
+ st.metric("CV Accuracy", "N/A")
1214
+
1215
+ with cv_col4:
1216
+ overfitting = cv_summary.get('overfitting_score')
1217
+ if overfitting is not None:
1218
+ if overfitting < 0.05:
1219
+ st.metric("Overfitting", f"{overfitting:.4f}", "Low", delta_color="normal")
1220
+ elif overfitting < 0.15:
1221
+ st.metric("Overfitting", f"{overfitting:.4f}", "Moderate", delta_color="off")
1222
+ else:
1223
+ st.metric("Overfitting", f"{overfitting:.4f}", "High", delta_color="inverse")
1224
+ else:
1225
+ st.metric("Overfitting", "N/A")
1226
+
1227
  # Environment details
1228
  st.subheader("Environment Details")
1229
+ env_info = metrics.get('environment_info', {})
1230
+ env_data = env_info.get('environment', 'Unknown')
1231
  st.info(f"Running in: {env_data}")
1232
 
1233
  # Available files
1234
+ datasets = env_info.get('available_datasets', {})
1235
+ models = env_info.get('available_models', {})
1236
 
1237
  col1, col2 = st.columns(2)
1238
  with col1:
 
1246
  for name, exists in models.items():
1247
  status = "✅" if exists else "❌"
1248
  st.write(f"{status} {name}")
1249
+
1250
  except Exception as e:
1251
  st.warning(f"Could not load API metrics: {e}")
1252