import plotly.graph_objects as go import pandas as pd def create_verdict_donut(df: pd.DataFrame) -> go.Figure: """ Creates a High-Fidelity Donut Chart. """ if df.empty: return _create_empty_figure("No Data") color_map = { 'resolved': '#10B981', # Emerald 500 'possibly_resolved': '#34D399', # Emerald 400 'duplicate': '#F59E0B', # Amber 500 'unresolved': '#64748B', # Slate 500 'error': '#EF4444', # Red 500 'unknown': '#CBD5E1' # Slate 300 } colors = [color_map.get(v, '#999') for v in df['verdict']] total = df['count'].sum() fig = go.Figure(data=[go.Pie( labels=df['verdict'], values=df['count'], hole=0.7, marker=dict(colors=colors, line=dict(color='#1F2937', width=0)), textinfo='value', hoverinfo='label+percent', textfont=dict(size=14, color='white') )]) fig.update_layout( title=dict( text="Analysis Distribution", font=dict(size=14, color="#6B7280"), x=0.5, xanchor='center' ), showlegend=True, legend=dict( orientation="h", yanchor="top", y=-0.1, xanchor="center", x=0.5, font=dict(color="#9CA3AF", size=11) ), margin=dict(t=40, b=60, l=20, r=20), height=250, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', annotations=[dict(text=str(total), x=0.5, y=0.5, font_size=32, showarrow=False, font_color="#E5E7EB")], modebar_remove=['zoom', 'pan', 'select', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d', 'toImage'] ) return fig def create_timeline_chart(full_df: pd.DataFrame) -> go.Figure: """ Creates a Timeline showing activity over time. """ if full_df.empty: return _create_empty_figure("No Timeline Data") df = full_df.copy() df['date'] = pd.to_datetime(df['updated_at'], errors='coerce').dt.date daily_counts = df.groupby('date').size().reset_index(name='count') fig = go.Figure() fig.add_trace(go.Scatter( x=daily_counts['date'], y=daily_counts['count'], mode='lines+markers', name='Activity', line=dict(color='#3B82F6', width=2, shape='spline'), marker=dict(size=4, color='#60A5FA', line=dict(width=0)), fill='tozeroy', fillcolor='rgba(59, 130, 246, 0.1)' )) fig.update_layout( title=dict( text="Activity Timeline (Last Updates)", font=dict(size=14, color="#6B7280"), x=0.5, xanchor='center' ), xaxis=dict( showgrid=False, color="#9CA3AF", gridcolor="rgba(255,255,255,0.1)", tickformat="%b %d" ), yaxis=dict( showgrid=True, gridcolor="rgba(255,255,255,0.05)", color="#9CA3AF", zeroline=False ), margin=dict(t=40, b=20, l=30, r=10), height=250, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', showlegend=False, modebar_remove=['zoom', 'pan', 'select', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d', 'toImage'] ) return fig def _create_empty_figure(text): fig = go.Figure() fig.add_annotation(text=text, showarrow=False, font=dict(color="gray")) fig.update_layout( height=250, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', xaxis=dict(visible=False), yaxis=dict(visible=False), modebar_remove=['zoom', 'pan', 'select', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d', 'toImage'] ) return fig def create_efficiency_funnel(stats_df: pd.DataFrame, total_open_issues: int) -> go.Figure: """ Creates a Funnel Chart showing how the AI filters the noise. """ if stats_df.empty: return _create_empty_figure("No Data") total_backlog = total_open_issues total_analyzed = stats_df['count'].sum() ai_solved = stats_df[stats_df['verdict'].isin(['resolved', 'duplicate', 'possibly_resolved'])]['count'].sum() values = [total_backlog, total_analyzed, ai_solved] stages = ["Total Backlog", "Analyzed by AI", "Actionable Findings"] fig = go.Figure(go.Funnel( y=stages, x=values, textinfo="value+percent initial", textfont=dict(color="#e5e5e5", size=12), marker=dict(color=["#64748B", "#4486F0", "#10B981"]), connector=dict(line=dict(color="rgba(128,128,128,0.5)", width=1)) )) fig.update_layout( title=dict( text="AI Efficiency Funnel", font=dict(size=14, color="#6B7280"), x=0.5, xanchor='center' ), xaxis=dict( showgrid=False, color="#9CA3AF", gridcolor="rgba(255,255,255,0.1)", tickformat="%b %d" ), yaxis=dict( showgrid=True, gridcolor="rgba(255,255,255,0.1)", color="#9CA3AF", zeroline=False ), margin=dict(t=40, b=20, l=100, r=10), height=250, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', showlegend=False, modebar_remove=['pan', 'select', 'lasso2d', 'autoScale2d', 'resetScale2d'] ) return fig