import plotly.graph_objects as go import plotly.express as px import pandas as pd def generate_budget_utilization_gauge_chart(total_budget, total_expense): """Generate a gauge chart comparing budget vs. total expense.""" gauge_steps = [ {'range': [0, total_budget * 0.7], 'color': '#d4f1dd'}, {'range': [total_budget * 0.7, total_budget * 0.9], 'color': '#fff2cc'}, {'range': [total_budget * 0.9, total_budget * 1.2], 'color': '#f8d7da'} ] gauge_threshold = { 'line': {'color': 'red', 'width': 4}, 'thickness': 0.75, 'value': total_budget } gauge = go.Indicator( mode="gauge+number+delta", value=total_expense, title={'text': "ค่าใช้จ่ายทั้งหมด", 'font': {'size': 24}}, delta={ 'reference': total_budget, 'increasing': {'color': 'red', 'symbol': "\u25B2"}, 'decreasing': {'color': 'green', 'symbol': "\u25BC"} }, gauge={ 'axis': {'range': [None, total_budget * 1.2], 'tickwidth': 1}, 'bar': {'color': '#1f77b4'}, 'bgcolor': 'white', 'borderwidth': 2, 'bordercolor': 'gray', 'steps': gauge_steps, 'threshold': gauge_threshold } ) fig = go.Figure(gauge) fig.update_layout( paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(t=50, r=25, l=25, b=25), height=300 ) return fig def generate_deliverable_budget_vs_expense_bar_chart(budget_df, expense_df): """Generate a grouped bar chart comparing budget vs. actual expense per deliverable.""" # Calculate total budget per deliverable budget_df['total_budget'] = budget_df[['wage', 'materials', 'tools_equipment', 'services', 'misc']].sum(axis=1) budget_df['title'] = budget_df.get('title', pd.Series(["Unnamed Deliverable"] * len(budget_df))) # Prepare budget summary budget_summary = budget_df[['title', 'total_budget']].rename(columns={'title': 'deliverable_title'}) # Summarize expenses per deliverable expense_summary = ( expense_df.groupby('associated_deliverable_id')['total_payment_amount'] .sum() .reset_index() ) # Map deliverable titles to expenses deliverable_titles = budget_df[['deliverable_id', 'title']].drop_duplicates() expense_summary = ( pd.merge( expense_summary, deliverable_titles, left_on='associated_deliverable_id', right_on='deliverable_id', how='left' ) .rename(columns={ 'title': 'deliverable_title', 'total_payment_amount': 'spending' }) ) # Merge budget and expense summaries merged_df = pd.merge( budget_summary, expense_summary[['deliverable_title', 'spending']], on='deliverable_title', how='left' ).fillna({'spending': 0}) # Create bar chart fig = go.Figure() fig.add_trace(go.Bar( x=merged_df['deliverable_title'], y=merged_df['total_budget'], name='งบประมาณ', marker_color='green' )) fig.add_trace(go.Bar( x=merged_df['deliverable_title'], y=merged_df['spending'], name='ค่าใช้จ่าย', marker_color='red' )) fig.update_layout( title='แผนภูมิเปรียบเทียบงบประมาณและค่าใช้จ่าย', barmode='group', xaxis_title='การส่งมอบ', yaxis_title='จำนวนเงิน (บาท)', legend=dict(orientation='h', y=-0.2), height=400, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(t=50, r=25, l=25, b=25) ) return fig def generate_spending_distribution_pie_chart(expense_df, budget_df): """Generate a pie chart showing the distribution of spending by deliverables.""" # Summarize total spending per deliverable spending_summary = ( expense_df.groupby('associated_deliverable_id')['total_payment_amount'] .sum() .reset_index() ) # Map deliverable titles deliverable_titles = budget_df[['deliverable_id', 'title']].drop_duplicates() spending_summary = ( pd.merge( spending_summary, deliverable_titles, left_on='associated_deliverable_id', right_on='deliverable_id', how='left' ) .rename(columns={ 'title': 'deliverable_title', 'total_payment_amount': 'amount' }) ) # Handle missing titles spending_summary['deliverable_title'] = spending_summary['deliverable_title'].fillna("Unnamed Deliverable") # Create pie chart fig = px.pie( spending_summary, values='amount', names='deliverable_title', title='ยอดค่าใช้จ่ายของการส่งมอบ', color_discrete_sequence=px.colors.qualitative.Set2 ) fig.update_traces( textinfo='label+value', hoverinfo='label+percent', textposition='inside', insidetextorientation='radial' ) fig.update_layout( paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(t=50, r=25, l=25, b=25), height=400 ) return fig def generate_daily_spending_bar_chart(expense_df): """Generate a bar chart showing daily spending amounts.""" # Prepare date and group by date expense_df['transaction_date'] = pd.to_datetime(expense_df['transaction_date']).dt.date daily_summary = ( expense_df.groupby('transaction_date')['total_payment_amount'] .sum() .reset_index() .rename(columns={ 'transaction_date': 'date', 'total_payment_amount': 'amount' }) .sort_values('date') ) # Create bar chart fig = go.Figure() fig.add_trace(go.Bar( x=daily_summary['date'], y=daily_summary['amount'], name='ค่าใช้จ่ายในแต่ละวัน', marker_color='#1f77b4' )) fig.update_layout( title='ค่าใช้จ่ายในแต่ละวัน', xaxis_title='วันที่', yaxis_title='จำนวนเงิน (บาท)', paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(t=50, r=25, l=25, b=25), height=400 ) return fig def generate_cumulative_spending_line_chart(expense_df): """Generate a line chart showing cumulative spending over time.""" # Convert to date and summarize spending per day expense_df['transaction_date'] = pd.to_datetime(expense_df['transaction_date']).dt.date daily_summary = ( expense_df.groupby('transaction_date')['total_payment_amount'] .sum() .reset_index() .rename(columns={'transaction_date': 'date', 'total_payment_amount': 'amount'}) .sort_values('date') ) # Calculate cumulative spending daily_summary['cumulative_amount'] = daily_summary['amount'].cumsum() # Create line chart fig = go.Figure() fig.add_trace(go.Scatter( x=daily_summary['date'], y=daily_summary['cumulative_amount'], mode='lines+markers', name='Cumulative Spending', marker_color='#ff7f0e' )) fig.update_layout( title='ยอดค่าใช้จ่ายสะสมตามช่วงเวลา', xaxis_title='วันที่', yaxis_title='ยอดค่าใช้จ่ายสะสม (บาท)', paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(t=50, r=25, l=25, b=25), height=400 ) return fig def generate_risk_level_distribution_pie_chart(deliverable_df): """Generate a pie chart showing the distribution of deliverables by risk level.""" risk_summary = ( deliverable_df['risk_level'] .value_counts() .reset_index(name='count') .rename(columns={'index': 'risk_level'}) ) risk_color_map = { 'green': '#28a745', 'yellow': '#ffc107', 'red': '#dc3545', 'unknown': '#6c757d' } fig = px.pie( risk_summary, values='count', names='risk_level', title='แผนภูมิระดับความเสี่ยง', color='risk_level', color_discrete_map=risk_color_map ) fig.update_traces( textinfo='label+percent', hoverinfo='label+value+percent', textposition='inside' ) fig.update_layout( paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(t=50, r=25, l=25, b=25), height=400 ) return fig def generate_deliverable_timeline_gantt_chart(deliverable_df): """Generate a Gantt chart showing deliverables over time, colored by risk level.""" df = deliverable_df.copy() # Ensure proper date format df['start_date'] = pd.to_datetime(df['start_date']) df['end_date'] = pd.to_datetime(df['end_date']) # Fill missing values df['title'] = df['title'].fillna("Unnamed Deliverable") df['risk_level'] = df['risk_level'].fillna("unknown") # lowercase to match color map # Rename columns for display df = df.rename(columns={ 'title': 'หัวข้อ', 'risk_level': 'ระดับความเสี่ยง' }) risk_color_map = { 'green': '#28a745', 'yellow': '#ffc107', 'red': '#dc3545', 'unknown': '#6c757d' } # Create Gantt chart fig = px.timeline( df, x_start='start_date', x_end='end_date', y='หัวข้อ', color='ระดับความเสี่ยง', title='ไทม์ไลน์การส่งมอบ (Gantt Chart)', hover_data=['status', 'risk_level_rationale'], color_discrete_map=risk_color_map ) # Reverse Y-axis to have earliest on top fig.update_yaxes(autorange='reversed') # Layout customization fig.update_layout( paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin=dict(t=50, r=25, l=25, b=25), height=500 ) return fig def render_deliverable_summary_cards(deliverable_df): """Render HTML cards for deliverables with expandable risk rationale using
/ (JS-free, Gradio-safe).""" def format_date(date_str): if pd.isna(date_str): return "-" return pd.to_datetime(date_str).strftime("%d %b %Y") risk_level_colors = { "green": "#28a745", "yellow": "#ffc107", "red": "#dc3545", "unknown": "#6c757d" } status_colors = { "ongoing": "#17a2b8", "done": "#007bff", } cards_html = """
""" for _, row in deliverable_df.iterrows(): title = row.get('title', 'Untitled Deliverable') deliverable_id = row.get('deliverable_id', '-') status = str(row.get('status', 'N/A')).lower() risk_level = str(row.get('risk_level', 'unknown')).lower() rationale = row.get('risk_level_rationale', 'No rationale provided') status_color = status_colors.get(status, "#6c757d") risk_color = risk_level_colors.get(risk_level, "#6c757d") status_map = { 'done': 'เสร็จสิ้น', 'ongoing': 'กำลังดำเนินการ' } thai_status = status_map.get(status, 'ไม่ทราบสถานะ') start_date = format_date(row.get('start_date')) end_date = format_date(row.get('end_date')) cards_html += f"""
{title}
ID: {deliverable_id}
{thai_status} ระดับความเสี่ยง: {risk_level.capitalize()}
เริ่มต้น: {start_date}
สิ้นสุด: {end_date}
แสดงเหตุผลของความเสี่ยง
เหตุผลของความเสี่ยง:
{rationale}
""" cards_html += "
" return cards_html