| | """ |
| | Hierarchy-Based Production Flow Visualization |
| | Shows how kits flow through production based on dependency hierarchy |
| | """ |
| |
|
| | import streamlit as st |
| | import pandas as pd |
| | import plotly.express as px |
| | import plotly.graph_objects as go |
| | from plotly.subplots import make_subplots |
| | try: |
| | import networkx as nx |
| | NETWORKX_AVAILABLE = True |
| | except ImportError: |
| | NETWORKX_AVAILABLE = False |
| | nx = None |
| |
|
| | import numpy as np |
| | import sys |
| |
|
| | from src.config.optimization_config import ( |
| | KIT_LEVELS, KIT_DEPENDENCIES, TEAM_REQ_PER_PRODUCT, |
| | shift_code_to_name, line_code_to_name |
| | ) |
| | from src.config.constants import ShiftType, LineType, KitLevel |
| |
|
| | |
| | try: |
| | from src.visualization.kit_relationships import display_kit_relationships_dashboard |
| | except ImportError: |
| | display_kit_relationships_dashboard = None |
| |
|
| | def display_hierarchy_operations_dashboard(results): |
| | """Enhanced operations dashboard showing hierarchy-based production flow""" |
| | st.header("π Hierarchy-Based Operations Dashboard") |
| | st.markdown("---") |
| | |
| | |
| | tab1, tab2, tab3 = st.tabs([ |
| | "π Production Flow", |
| | "π Hierarchy Analytics", |
| | "π Kit Relationships" |
| | ]) |
| | |
| | with tab1: |
| | display_production_flow_visualization(results) |
| | |
| | with tab2: |
| | display_hierarchy_analytics(results) |
| | |
| | with tab3: |
| | |
| | if display_kit_relationships_dashboard: |
| | display_kit_relationships_dashboard(results) |
| | else: |
| | st.error("Kit relationships dashboard not available. Please check installation.") |
| |
|
| | def display_production_flow_visualization(results): |
| | """Show how products flow through production lines by hierarchy""" |
| | st.subheader("π Kit Production Flow by Hierarchy") |
| | |
| | |
| | flow_data = prepare_hierarchy_flow_data(results) |
| | |
| | if not flow_data: |
| | st.warning("No production data available for flow visualization") |
| | return |
| | |
| | |
| | |
| | |
| |
|
| | |
| | st.subheader("π¦ Production by Level") |
| | level_summary = get_hierarchy_level_summary(flow_data) |
| | |
| | |
| | level_names = ['prepack', 'subkit', 'master'] |
| | available_levels = [level for level in level_names if level in level_summary] |
| | |
| | if available_levels: |
| | cols = st.columns(len(available_levels)) |
| | |
| | for i, level_name in enumerate(available_levels): |
| | data = level_summary[level_name] |
| | with cols[i]: |
| | |
| | st.markdown(f""" |
| | <div style=" |
| | background: linear-gradient(135deg, #f0f8ff, #e6f3ff); |
| | padding: 1rem; |
| | border-radius: 0.5rem; |
| | text-align: center; |
| | border-left: 4px solid {'#90EE90' if level_name == 'prepack' else '#FFD700' if level_name == 'subkit' else '#FF6347'}; |
| | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | "> |
| | <div style="font-size: 0.8rem; color: #666; text-transform: uppercase; letter-spacing: 1px;"> |
| | {level_name.title()} Kits |
| | </div> |
| | <div style="font-size: 1.5rem; font-weight: bold; color: #333; margin: 0.2rem 0;"> |
| | {data['count']} products |
| | </div> |
| | <div style="font-size: 1rem; color: #555;"> |
| | {data['total_units']:,.0f} units |
| | </div> |
| | </div> |
| | """, unsafe_allow_html=True) |
| | |
| | |
| | st.subheader("π
Hierarchy Production Timeline") |
| | try: |
| | fig_timeline = create_hierarchy_timeline(flow_data) |
| | st.plotly_chart(fig_timeline, use_container_width=True) |
| | except Exception as e: |
| | st.warning(f"Timeline chart temporarily unavailable. Showing alternative visualization.") |
| | |
| | if flow_data: |
| | df_simple = pd.DataFrame([{ |
| | 'Day': f"Day {row['day']}", |
| | 'Level': row['level_name'].title(), |
| | 'Units': row['units'], |
| | 'Product': row['product'] |
| | } for row in flow_data]) |
| | |
| | fig_simple = px.bar(df_simple, x='Day', y='Units', color='Level', |
| | title='Production Volume by Day and Hierarchy Level', |
| | color_discrete_map={ |
| | 'Prepack': '#90EE90', |
| | 'Subkit': '#FFD700', |
| | 'Master': '#FF6347' |
| | }) |
| | st.plotly_chart(fig_simple, use_container_width=True) |
| |
|
| | def display_hierarchy_analytics(results): |
| | """Deep dive analytics on hierarchy production performance""" |
| | st.subheader("π Hierarchy Performance Analytics") |
| | |
| | |
| | analytics_data = prepare_hierarchy_analytics_data(results) |
| | |
| | if not analytics_data: |
| | st.warning("No hierarchy data available for analytics") |
| | return |
| | |
| | |
| | col1, col2, col3, col4 = st.columns(4) |
| | |
| | with col1: |
| | prepack_efficiency = analytics_data.get('prepack_efficiency', 0) |
| | st.metric("Prepack Efficiency", f"{prepack_efficiency:.1f}%", |
| | delta=f"{prepack_efficiency-95:.1f}%" if prepack_efficiency != 95 else None) |
| | |
| | with col2: |
| | dependency_violations = analytics_data.get('dependency_violations', 0) |
| | st.metric("Dependency Violations", f"{dependency_violations}", |
| | delta=f"-{dependency_violations}" if dependency_violations > 0 else None) |
| | |
| | with col3: |
| | avg_lead_time = analytics_data.get('avg_lead_time', 0) |
| | st.metric("Avg Lead Time", f"{avg_lead_time:.1f} days") |
| | |
| | with col4: |
| | hierarchy_cost_efficiency = analytics_data.get('cost_efficiency', 0) |
| | st.metric("Cost Efficiency", f"β¬{hierarchy_cost_efficiency:.2f}/unit") |
| | |
| | |
| | st.subheader("π Dependency Network Analysis") |
| | fig_network = create_dependency_network_chart(analytics_data) |
| | st.plotly_chart(fig_network, use_container_width=True) |
| | |
| | |
| | st.subheader("π₯ Hierarchy Production Heatmap") |
| | heatmap_fig = create_hierarchy_heatmap(results) |
| | st.plotly_chart(heatmap_fig, use_container_width=True) |
| | |
| |
|
| |
|
| | |
| |
|
| | def display_production_sequence_analysis(results): |
| | """Analyze production sequence and timing""" |
| | st.subheader("π― Production Sequence Analysis") |
| | |
| | |
| | if not sequence_data: |
| | st.warning("No sequence data available") |
| | return |
| | |
| | |
| | col1, col2, col3 = st.columns(3) |
| | |
| | with col1: |
| | sequence_score = sequence_data.get('sequence_adherence_score', 0) |
| | st.metric("Sequence Adherence", f"{sequence_score:.1f}%", |
| | help="How well production follows optimal hierarchy sequence") |
| | |
| | with col2: |
| | early_productions = sequence_data.get('early_productions', 0) |
| | st.metric("Early Productions", f"{early_productions}", |
| | help="Products produced before their dependencies") |
| | |
| | with col3: |
| | optimal_sequences = sequence_data.get('optimal_sequences', 0) |
| | st.metric("Optimal Sequences", f"{optimal_sequences}%", |
| | help="Percentage of products following optimal sequence") |
| | |
| | |
| | if sequence_data.get('violations'): |
| | st.subheader("β οΈ Sequence Violations") |
| | violations_df = pd.DataFrame(sequence_data['violations']) |
| | |
| | fig = px.scatter(violations_df, |
| | x='production_day', y='dependency_day', |
| | color='severity', size='impact', |
| | hover_data=['product', 'dependency'], |
| | title='Production vs Dependency Timing (Violations in Red)', |
| | labels={'production_day': 'When Product Was Made', |
| | 'dependency_day': 'When Dependency Was Made'}) |
| | |
| | |
| | max_day = max(violations_df['production_day'].max(), violations_df['dependency_day'].max()) |
| | fig.add_shape(type="line", x0=0, y0=0, x1=max_day, y1=max_day, |
| | line=dict(dash="dash", color="gray"), |
| | name="Ideal Sequence Line") |
| | |
| | st.plotly_chart(fig, use_container_width=True) |
| | |
| | |
| | st.subheader("π‘ Optimization Suggestions") |
| | suggestions = generate_sequence_suggestions(sequence_data) |
| | for suggestion in suggestions: |
| | st.info(f"π‘ {suggestion}") |
| |
|
| | |
| |
|
| | def prepare_hierarchy_flow_data(results): |
| | """Prepare data for hierarchy flow visualization""" |
| | flow_data = [] |
| | |
| | for row in results['run_schedule']: |
| | product = row['product'] |
| | level = KIT_LEVELS.get(product, KitLevel.MASTER) |
| | level_name = KitLevel.get_name(level) |
| | |
| | flow_data.append({ |
| | 'product': product, |
| | 'level': level, |
| | 'level_name': level_name, |
| | 'day': row['day'], |
| | 'shift': row['shift'], |
| | 'line_type': row['line_type_id'], |
| | 'line_idx': row['line_idx'], |
| | 'hours': row['run_hours'], |
| | 'units': row['units'], |
| | 'dependencies': KIT_DEPENDENCIES.get(product, []) |
| | }) |
| | |
| | return flow_data |
| |
|
| | def create_hierarchy_timeline(flow_data): |
| | """Create timeline showing hierarchy production sequence""" |
| | if not flow_data: |
| | return go.Figure() |
| | |
| | |
| | timeline_data = [] |
| | |
| | from datetime import datetime, timedelta |
| | base_date = datetime(2025, 1, 1) |
| | |
| | for row in flow_data: |
| | shift_name = ShiftType.get_name(row['shift']) |
| | line_name = LineType.get_name(row['line_type']) |
| | |
| | |
| | start_date = base_date + timedelta(days=row['day']-1) |
| | end_date = start_date + timedelta(hours=row['hours']) |
| | |
| | timeline_data.append({ |
| | 'Product': row['product'], |
| | 'Level': row['level_name'].title(), |
| | 'Start': start_date, |
| | 'End': end_date, |
| | 'Day': f"Day {row['day']}", |
| | 'Shift': shift_name, |
| | 'Line': f"{line_name} {row['line_idx']}", |
| | 'Units': row['units'], |
| | 'Hours': row['hours'], |
| | 'Priority': row['level'] |
| | }) |
| | |
| | df = pd.DataFrame(timeline_data) |
| | |
| | if df.empty: |
| | return go.Figure() |
| | |
| | |
| | fig = px.timeline(df, |
| | x_start='Start', x_end='End', |
| | y='Line', |
| | color='Level', |
| | hover_data=['Product', 'Units', 'Hours', 'Shift', 'Day'], |
| | title='Production Timeline by Hierarchy Level', |
| | color_discrete_map={ |
| | 'Prepack': '#90EE90', |
| | 'Subkit': '#FFD700', |
| | 'Master': '#FF6347' |
| | }) |
| | |
| | fig.update_layout( |
| | height=500, |
| | xaxis_title='Production Timeline', |
| | yaxis_title='Production Line' |
| | ) |
| | |
| | return fig |
| |
|
| | def prepare_hierarchy_analytics_data(results): |
| | """Prepare analytics data for hierarchy performance""" |
| | analytics = { |
| | 'prepack_efficiency': 0, |
| | 'dependency_violations': 0, |
| | 'avg_lead_time': 0, |
| | 'cost_efficiency': 0, |
| | 'violations': [], |
| | 'dependencies': KIT_DEPENDENCIES |
| | } |
| | |
| | |
| | total_cost = results.get('objective', 0) |
| | total_units = sum(results.get('weekly_production', {}).values()) |
| | |
| | if total_units > 0: |
| | analytics['cost_efficiency'] = total_cost / total_units |
| | |
| | |
| | production_times = {} |
| | for row in results['run_schedule']: |
| | product = row['product'] |
| | day = row['day'] |
| | if product not in production_times or day < production_times[product]: |
| | production_times[product] = day |
| | |
| | violations = 0 |
| | violation_details = [] |
| | |
| | for product, prod_day in production_times.items(): |
| | dependencies = KIT_DEPENDENCIES.get(product, []) |
| | for dep in dependencies: |
| | if dep in production_times: |
| | dep_day = production_times[dep] |
| | if dep_day > prod_day: |
| | violations += 1 |
| | violation_details.append({ |
| | 'product': product, |
| | 'dependency': dep, |
| | 'production_day': prod_day, |
| | 'dependency_day': dep_day, |
| | 'severity': 'high' if dep_day - prod_day > 1 else 'medium', |
| | 'impact': abs(dep_day - prod_day) |
| | }) |
| | |
| | analytics['dependency_violations'] = violations |
| | analytics['violations'] = violation_details |
| | |
| | return analytics |
| |
|
| | |
| | |
| |
|
| | def create_hierarchy_heatmap(results): |
| | """Create heatmap showing hierarchy production by line and day""" |
| | |
| | heatmap_data = [] |
| | |
| | for row in results['run_schedule']: |
| | product = row['product'] |
| | level_name = KitLevel.get_name(KIT_LEVELS.get(product, KitLevel.MASTER)) |
| | line_name = f"{LineType.get_name(row['line_type_id'])} {row['line_idx']}" |
| | |
| | heatmap_data.append({ |
| | 'Line': line_name, |
| | 'Day': f"Day {row['day']}", |
| | 'Level': level_name, |
| | 'Units': row['units'], |
| | 'Hours': row['run_hours'] |
| | }) |
| | |
| | if not heatmap_data: |
| | return go.Figure() |
| | |
| | df = pd.DataFrame(heatmap_data) |
| | |
| | |
| | pivot_df = df.pivot_table( |
| | values='Units', |
| | index='Line', |
| | columns='Day', |
| | aggfunc='sum', |
| | fill_value=0 |
| | ) |
| | |
| | fig = px.imshow(pivot_df.values, |
| | x=pivot_df.columns, |
| | y=pivot_df.index, |
| | color_continuous_scale='Blues', |
| | title='Production Volume Heatmap (Units per Day)', |
| | labels=dict(x="Day", y="Production Line", color="Units")) |
| | |
| | return fig |
| |
|
| | def create_dependency_network_chart(analytics_data): |
| | """Create network chart showing dependency relationships""" |
| | dependencies = analytics_data.get('dependencies', {}) |
| | |
| | if not dependencies or not NETWORKX_AVAILABLE: |
| | return go.Figure().add_annotation( |
| | text="Dependency network visualization requires 'networkx' package. Install with: pip install networkx" if not NETWORKX_AVAILABLE else "No dependency relationships to display", |
| | xref="paper", yref="paper", |
| | x=0.5, y=0.5, showarrow=False |
| | ) |
| | |
| | |
| | G = nx.DiGraph() |
| | |
| | |
| | for product, deps in dependencies.items(): |
| | if product and deps: |
| | G.add_node(product) |
| | for dep in deps: |
| | if dep: |
| | G.add_node(dep) |
| | G.add_edge(dep, product) |
| | |
| | if len(G.nodes()) == 0: |
| | return go.Figure().add_annotation( |
| | text="No dependency relationships to display", |
| | xref="paper", yref="paper", |
| | x=0.5, y=0.5, showarrow=False |
| | ) |
| | |
| | |
| | pos = nx.spring_layout(G, k=3, iterations=50) |
| | |
| | |
| | edge_x = [] |
| | edge_y = [] |
| | for edge in G.edges(): |
| | x0, y0 = pos[edge[0]] |
| | x1, y1 = pos[edge[1]] |
| | edge_x.extend([x0, x1, None]) |
| | edge_y.extend([y0, y1, None]) |
| | |
| | edge_trace = go.Scatter(x=edge_x, y=edge_y, |
| | line=dict(width=0.5, color='#888'), |
| | hoverinfo='none', |
| | mode='lines') |
| | |
| | |
| | node_x = [] |
| | node_y = [] |
| | node_text = [] |
| | node_color = [] |
| | |
| | for node in G.nodes(): |
| | x, y = pos[node] |
| | node_x.append(x) |
| | node_y.append(y) |
| | node_text.append(node) |
| | |
| | |
| | level = KIT_LEVELS.get(node, KitLevel.MASTER) |
| | if level == KitLevel.PREPACK: |
| | node_color.append('#90EE90') |
| | elif level == KitLevel.SUBKIT: |
| | node_color.append('#FFD700') |
| | else: |
| | node_color.append('#FF6347') |
| | |
| | node_trace = go.Scatter(x=node_x, y=node_y, |
| | mode='markers+text', |
| | text=node_text, |
| | textposition='middle center', |
| | marker=dict(size=20, color=node_color, line=dict(width=2, color='black')), |
| | hoverinfo='text', |
| | hovertext=node_text) |
| | |
| | fig = go.Figure(data=[edge_trace, node_trace], |
| | layout=go.Layout( |
| | title='Kit Dependency Network', |
| | titlefont_size=16, |
| | showlegend=False, |
| | hovermode='closest', |
| | margin=dict(b=20,l=5,r=5,t=40), |
| | annotations=[ dict( |
| | text="Green=Prepack, Gold=Subkit, Red=Master", |
| | showarrow=False, |
| | xref="paper", yref="paper", |
| | x=0.005, y=-0.002, |
| | xanchor='left', yanchor='bottom', |
| | font=dict(size=12) |
| | )], |
| | xaxis=dict(showgrid=False, zeroline=False, showticklabels=False), |
| | yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))) |
| | |
| | return fig |
| |
|
| |
|
| |
|
| |
|
| |
|
| | def generate_sequence_suggestions(sequence_data): |
| | """Generate optimization suggestions based on sequence analysis""" |
| | suggestions = [] |
| | |
| | adherence = sequence_data.get('sequence_adherence_score', 0) |
| | violations = sequence_data.get('early_productions', 0) |
| | |
| | if adherence < 80: |
| | suggestions.append( |
| | "Consider adjusting production sequence to better follow hierarchy dependencies. " |
| | "Current adherence is below optimal (80%)." |
| | ) |
| | |
| | if violations > 0: |
| | suggestions.append( |
| | f"Found {violations} dependency violations. Review production scheduling to ensure " |
| | "prepacks are produced before subkits, and subkits before masters." |
| | ) |
| | |
| | if adherence >= 95: |
| | suggestions.append( |
| | "Excellent sequence adherence! Production is following optimal hierarchy flow." |
| | ) |
| | |
| | if not suggestions: |
| | suggestions.append("Production sequence analysis complete. No major issues detected.") |
| | |
| | return suggestions |
| |
|
| | def get_hierarchy_level_summary(flow_data): |
| | """Get summary statistics for each hierarchy level""" |
| | summary = {} |
| | |
| | for level_name in ['prepack', 'subkit', 'master']: |
| | level_products = [row for row in flow_data if row['level_name'] == level_name] |
| | |
| | summary[level_name] = { |
| | 'count': len(set(row['product'] for row in level_products)), |
| | 'total_units': sum(row['units'] for row in level_products), |
| | 'total_hours': sum(row['hours'] for row in level_products) |
| | } |
| | |
| | return summary |