Spaces:
Sleeping
Sleeping
Merge remote-tracking branch 'github/main'
Browse files- app.py +151 -40
- drift_detector.sqlite3 +0 -0
app.py
CHANGED
|
@@ -574,9 +574,9 @@ def calculate_drift(dropdown_value):
|
|
| 574 |
|
| 575 |
|
| 576 |
def create_drift_chart(drift_history):
|
| 577 |
-
"""Create drift chart from actual data"""
|
| 578 |
try:
|
| 579 |
-
if not drift_history:
|
| 580 |
# Empty chart if no data
|
| 581 |
fig = go.Figure()
|
| 582 |
fig.add_annotation(
|
|
@@ -589,49 +589,99 @@ def create_drift_chart(drift_history):
|
|
| 589 |
fig.update_layout(
|
| 590 |
title='Model Drift Over Time - No Data',
|
| 591 |
template='plotly_white',
|
| 592 |
-
height=400
|
|
|
|
|
|
|
| 593 |
)
|
| 594 |
return fig
|
| 595 |
|
|
|
|
|
|
|
| 596 |
# Extract dates and scores from actual data
|
| 597 |
dates = []
|
| 598 |
scores = []
|
| 599 |
|
| 600 |
-
for entry in drift_history:
|
|
|
|
|
|
|
| 601 |
# Handle different date formats
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
else:
|
| 615 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
|
| 617 |
-
# Handle drift score
|
| 618 |
-
score = entry.get("drift_score", 0)
|
| 619 |
if isinstance(score, str):
|
| 620 |
try:
|
| 621 |
score = float(score)
|
| 622 |
-
except:
|
|
|
|
| 623 |
score = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
scores.append(score)
|
|
|
|
|
|
|
|
|
|
| 625 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
fig = go.Figure()
|
|
|
|
|
|
|
| 627 |
fig.add_trace(go.Scatter(
|
| 628 |
x=dates,
|
| 629 |
y=scores,
|
| 630 |
mode='lines+markers',
|
| 631 |
name='Drift Score',
|
| 632 |
line=dict(color='#ff6b6b', width=3),
|
| 633 |
-
marker=dict(
|
| 634 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 635 |
))
|
| 636 |
|
| 637 |
# Add threshold line at 50%
|
|
@@ -639,55 +689,110 @@ def create_drift_chart(drift_history):
|
|
| 639 |
y=50,
|
| 640 |
line_dash="dash",
|
| 641 |
line_color="orange",
|
| 642 |
-
|
|
|
|
|
|
|
| 643 |
)
|
| 644 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
fig.update_layout(
|
| 646 |
-
title=f'Model Drift Over Time ({len(drift_history)}
|
| 647 |
xaxis_title='Date',
|
| 648 |
yaxis_title='Drift Score (%)',
|
| 649 |
template='plotly_white',
|
| 650 |
-
height=
|
| 651 |
showlegend=True,
|
| 652 |
-
yaxis=dict(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 653 |
)
|
| 654 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 655 |
return fig
|
| 656 |
|
| 657 |
except Exception as e:
|
| 658 |
print(f"β Error creating drift chart: {e}")
|
| 659 |
-
|
|
|
|
|
|
|
| 660 |
fig = go.Figure()
|
| 661 |
fig.add_annotation(
|
| 662 |
-
text=f"Error creating chart
|
| 663 |
xref="paper", yref="paper",
|
| 664 |
x=0.5, y=0.5,
|
| 665 |
showarrow=False,
|
| 666 |
-
font=dict(size=14, color="red")
|
|
|
|
| 667 |
)
|
| 668 |
fig.update_layout(
|
| 669 |
title='Error Creating Drift Chart',
|
| 670 |
template='plotly_white',
|
| 671 |
-
height=400
|
|
|
|
|
|
|
| 672 |
)
|
| 673 |
return fig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
def refresh_drift_history(dropdown_value):
|
| 675 |
-
"""Refresh drift history
|
| 676 |
if not dropdown_value:
|
| 677 |
return [], gr.update(value=None)
|
| 678 |
|
| 679 |
try:
|
| 680 |
model_name = extract_model_name_from_dropdown(dropdown_value, current_model_mapping)
|
|
|
|
| 681 |
|
| 682 |
if not DATABASE_AVAILABLE:
|
| 683 |
-
#
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 691 |
else:
|
| 692 |
# Get actual drift history from database
|
| 693 |
history_result = get_drift_history_handler({"model_name": model_name})
|
|
@@ -699,14 +804,20 @@ def refresh_drift_history(dropdown_value):
|
|
| 699 |
history = []
|
| 700 |
print(f"β οΈ No drift history found for {model_name}")
|
| 701 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
chart = create_drift_chart(history)
|
|
|
|
| 703 |
return history, chart
|
| 704 |
|
| 705 |
except Exception as e:
|
| 706 |
print(f"β Error refreshing drift history: {e}")
|
|
|
|
|
|
|
| 707 |
return [], gr.update(value=None)
|
| 708 |
|
| 709 |
-
|
| 710 |
def initialize_interface():
|
| 711 |
"""Initialize interface"""
|
| 712 |
global current_model_mapping
|
|
|
|
| 574 |
|
| 575 |
|
| 576 |
def create_drift_chart(drift_history):
|
| 577 |
+
"""Create drift chart from actual data with improved data handling"""
|
| 578 |
try:
|
| 579 |
+
if not drift_history or len(drift_history) == 0:
|
| 580 |
# Empty chart if no data
|
| 581 |
fig = go.Figure()
|
| 582 |
fig.add_annotation(
|
|
|
|
| 589 |
fig.update_layout(
|
| 590 |
title='Model Drift Over Time - No Data',
|
| 591 |
template='plotly_white',
|
| 592 |
+
height=400,
|
| 593 |
+
xaxis_title='Date',
|
| 594 |
+
yaxis_title='Drift Score (%)'
|
| 595 |
)
|
| 596 |
return fig
|
| 597 |
|
| 598 |
+
print(f"π DEBUG: Processing {len(drift_history)} drift records")
|
| 599 |
+
|
| 600 |
# Extract dates and scores from actual data
|
| 601 |
dates = []
|
| 602 |
scores = []
|
| 603 |
|
| 604 |
+
for i, entry in enumerate(drift_history):
|
| 605 |
+
print(f"π DEBUG: Processing entry {i}: {entry}")
|
| 606 |
+
|
| 607 |
# Handle different date formats
|
| 608 |
+
date_value = entry.get("date", entry.get("created_at", entry.get("timestamp", "")))
|
| 609 |
+
|
| 610 |
+
if date_value:
|
| 611 |
+
if isinstance(date_value, str):
|
| 612 |
+
try:
|
| 613 |
+
from datetime import datetime
|
| 614 |
+
# Try different date formats
|
| 615 |
+
if "T" in date_value:
|
| 616 |
+
# ISO format with time
|
| 617 |
+
date_obj = datetime.fromisoformat(date_value.replace("Z", "+00:00"))
|
| 618 |
+
formatted_date = date_obj.strftime("%Y-%m-%d")
|
| 619 |
+
elif "-" in date_value and len(date_value) >= 10:
|
| 620 |
+
# YYYY-MM-DD format
|
| 621 |
+
date_obj = datetime.strptime(date_value[:10], "%Y-%m-%d")
|
| 622 |
+
formatted_date = date_obj.strftime("%Y-%m-%d")
|
| 623 |
+
else:
|
| 624 |
+
# Use as-is if can't parse
|
| 625 |
+
formatted_date = str(date_value)[:10]
|
| 626 |
+
except Exception as date_error:
|
| 627 |
+
print(f"β οΈ Date parsing error for '{date_value}': {date_error}")
|
| 628 |
+
formatted_date = f"Entry {i + 1}"
|
| 629 |
+
else:
|
| 630 |
+
# Handle datetime objects
|
| 631 |
+
try:
|
| 632 |
+
formatted_date = date_value.strftime("%Y-%m-%d")
|
| 633 |
+
except:
|
| 634 |
+
formatted_date = str(date_value)
|
| 635 |
else:
|
| 636 |
+
formatted_date = f"Entry {i + 1}"
|
| 637 |
+
|
| 638 |
+
dates.append(formatted_date)
|
| 639 |
+
|
| 640 |
+
# Handle drift score - try multiple possible field names
|
| 641 |
+
score = entry.get("drift_score", entry.get("score", entry.get("drift", 0)))
|
| 642 |
|
|
|
|
|
|
|
| 643 |
if isinstance(score, str):
|
| 644 |
try:
|
| 645 |
score = float(score)
|
| 646 |
+
except ValueError:
|
| 647 |
+
print(f"β οΈ Could not convert score '{score}' to float, using 0")
|
| 648 |
score = 0
|
| 649 |
+
elif score is None:
|
| 650 |
+
score = 0
|
| 651 |
+
|
| 652 |
+
# Convert decimal to percentage if needed
|
| 653 |
+
if isinstance(score, (int, float)):
|
| 654 |
+
if 0 <= score <= 1:
|
| 655 |
+
score = score * 100 # Convert decimal to percentage
|
| 656 |
+
score = max(0, min(100, score)) # Clamp between 0-100
|
| 657 |
+
else:
|
| 658 |
+
score = 0
|
| 659 |
+
|
| 660 |
scores.append(score)
|
| 661 |
+
print(f"π DEBUG: Added point - Date: {formatted_date}, Score: {score}")
|
| 662 |
+
|
| 663 |
+
print(f"π DEBUG: Final data - Dates: {dates}, Scores: {scores}")
|
| 664 |
|
| 665 |
+
if len(dates) == 0 or len(scores) == 0:
|
| 666 |
+
raise ValueError("No valid data points found")
|
| 667 |
+
|
| 668 |
+
# Create the plot
|
| 669 |
fig = go.Figure()
|
| 670 |
+
|
| 671 |
+
# Add the main drift line
|
| 672 |
fig.add_trace(go.Scatter(
|
| 673 |
x=dates,
|
| 674 |
y=scores,
|
| 675 |
mode='lines+markers',
|
| 676 |
name='Drift Score',
|
| 677 |
line=dict(color='#ff6b6b', width=3),
|
| 678 |
+
marker=dict(
|
| 679 |
+
size=10,
|
| 680 |
+
color='#ff6b6b',
|
| 681 |
+
line=dict(width=2, color='white')
|
| 682 |
+
),
|
| 683 |
+
hovertemplate='<b>Date:</b> %{x}<br><b>Drift Score:</b> %{y:.1f}%<extra></extra>',
|
| 684 |
+
connectgaps=True # Connect points even if there are gaps
|
| 685 |
))
|
| 686 |
|
| 687 |
# Add threshold line at 50%
|
|
|
|
| 689 |
y=50,
|
| 690 |
line_dash="dash",
|
| 691 |
line_color="orange",
|
| 692 |
+
line_width=2,
|
| 693 |
+
annotation_text="Alert Threshold (50%)",
|
| 694 |
+
annotation_position="bottom right"
|
| 695 |
)
|
| 696 |
|
| 697 |
+
# Add another threshold at 75% for critical level
|
| 698 |
+
fig.add_hline(
|
| 699 |
+
y=75,
|
| 700 |
+
line_dash="dot",
|
| 701 |
+
line_color="red",
|
| 702 |
+
line_width=2,
|
| 703 |
+
annotation_text="Critical Threshold (75%)",
|
| 704 |
+
annotation_position="top right"
|
| 705 |
+
)
|
| 706 |
+
|
| 707 |
+
# Update layout with better formatting
|
| 708 |
fig.update_layout(
|
| 709 |
+
title=f'Model Drift Over Time ({len(drift_history)} data points)',
|
| 710 |
xaxis_title='Date',
|
| 711 |
yaxis_title='Drift Score (%)',
|
| 712 |
template='plotly_white',
|
| 713 |
+
height=450,
|
| 714 |
showlegend=True,
|
| 715 |
+
yaxis=dict(
|
| 716 |
+
range=[0, 100], # Set Y-axis range from 0 to 100%
|
| 717 |
+
ticksuffix='%'
|
| 718 |
+
),
|
| 719 |
+
xaxis=dict(
|
| 720 |
+
tickangle=45 if len(dates) > 5 else 0, # Angle labels for many dates
|
| 721 |
+
type='category' # Treat dates as categories for better spacing
|
| 722 |
+
),
|
| 723 |
+
hovermode='x unified', # Better hover experience
|
| 724 |
+
margin=dict(b=100) # More bottom margin for angled labels
|
| 725 |
)
|
| 726 |
|
| 727 |
+
# Add grid for better readability
|
| 728 |
+
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='lightgray')
|
| 729 |
+
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='lightgray')
|
| 730 |
+
|
| 731 |
return fig
|
| 732 |
|
| 733 |
except Exception as e:
|
| 734 |
print(f"β Error creating drift chart: {e}")
|
| 735 |
+
print(f"β Drift history data: {drift_history}")
|
| 736 |
+
|
| 737 |
+
# Return error chart
|
| 738 |
fig = go.Figure()
|
| 739 |
fig.add_annotation(
|
| 740 |
+
text=f"Error creating chart:\n{str(e)}\n\nCheck console for details",
|
| 741 |
xref="paper", yref="paper",
|
| 742 |
x=0.5, y=0.5,
|
| 743 |
showarrow=False,
|
| 744 |
+
font=dict(size=14, color="red"),
|
| 745 |
+
align="center"
|
| 746 |
)
|
| 747 |
fig.update_layout(
|
| 748 |
title='Error Creating Drift Chart',
|
| 749 |
template='plotly_white',
|
| 750 |
+
height=400,
|
| 751 |
+
xaxis_title='Date',
|
| 752 |
+
yaxis_title='Drift Score (%)'
|
| 753 |
)
|
| 754 |
return fig
|
| 755 |
+
|
| 756 |
+
|
| 757 |
+
def debug_drift_data(drift_history):
|
| 758 |
+
"""Helper function to debug drift history data structure"""
|
| 759 |
+
print("π DEBUG: Drift History Analysis")
|
| 760 |
+
print(f"Type: {type(drift_history)}")
|
| 761 |
+
print(f"Length: {len(drift_history) if drift_history else 0}")
|
| 762 |
+
|
| 763 |
+
if drift_history:
|
| 764 |
+
for i, entry in enumerate(drift_history[:3]): # Show first 3 entries
|
| 765 |
+
print(f"Entry {i}: {entry}")
|
| 766 |
+
print(f" Keys: {list(entry.keys()) if isinstance(entry, dict) else 'Not a dict'}")
|
| 767 |
+
|
| 768 |
+
return drift_history
|
| 769 |
+
|
| 770 |
+
|
| 771 |
def refresh_drift_history(dropdown_value):
|
| 772 |
+
"""Refresh drift history with improved debugging"""
|
| 773 |
if not dropdown_value:
|
| 774 |
return [], gr.update(value=None)
|
| 775 |
|
| 776 |
try:
|
| 777 |
model_name = extract_model_name_from_dropdown(dropdown_value, current_model_mapping)
|
| 778 |
+
print(f"π DEBUG: Getting drift history for model: {model_name}")
|
| 779 |
|
| 780 |
if not DATABASE_AVAILABLE:
|
| 781 |
+
# Enhanced mock data for demo mode
|
| 782 |
+
from datetime import datetime, timedelta
|
| 783 |
+
base_date = datetime.now() - timedelta(days=10)
|
| 784 |
+
|
| 785 |
+
history = []
|
| 786 |
+
for i in range(6): # Create 6 data points
|
| 787 |
+
date_obj = base_date + timedelta(days=i * 2)
|
| 788 |
+
score = 20 + (i * 15) + (i % 2 * 10) # Varied scores: 20, 45, 50, 75, 70, 95
|
| 789 |
+
history.append({
|
| 790 |
+
"date": date_obj.strftime("%Y-%m-%d"),
|
| 791 |
+
"drift_score": min(95, score), # Cap at 95
|
| 792 |
+
"model_name": model_name
|
| 793 |
+
})
|
| 794 |
+
|
| 795 |
+
print(f"π DEBUG: Generated {len(history)} mock drift records")
|
| 796 |
else:
|
| 797 |
# Get actual drift history from database
|
| 798 |
history_result = get_drift_history_handler({"model_name": model_name})
|
|
|
|
| 804 |
history = []
|
| 805 |
print(f"β οΈ No drift history found for {model_name}")
|
| 806 |
|
| 807 |
+
# Debug the data structure
|
| 808 |
+
history = debug_drift_data(history)
|
| 809 |
+
|
| 810 |
+
# Create chart
|
| 811 |
chart = create_drift_chart(history)
|
| 812 |
+
|
| 813 |
return history, chart
|
| 814 |
|
| 815 |
except Exception as e:
|
| 816 |
print(f"β Error refreshing drift history: {e}")
|
| 817 |
+
import traceback
|
| 818 |
+
traceback.print_exc()
|
| 819 |
return [], gr.update(value=None)
|
| 820 |
|
|
|
|
| 821 |
def initialize_interface():
|
| 822 |
"""Initialize interface"""
|
| 823 |
global current_model_mapping
|
drift_detector.sqlite3
CHANGED
|
Binary files a/drift_detector.sqlite3 and b/drift_detector.sqlite3 differ
|
|
|