Halimhailey's picture
Upload folder using huggingface_hub
ffa1f50 verified
"""
Kit Relationship Visualization
Shows the actual dependency relationships between kits in production
based on kit_hierarchy.json data
"""
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
import json
import sys
from src.config.constants import ShiftType, LineType, KitLevel
# Optional networkx for advanced network layouts
try:
import networkx as nx
NETWORKX_AVAILABLE = True
except ImportError:
NETWORKX_AVAILABLE = False
nx = None
def load_kit_hierarchy():
"""Load kit hierarchy data from JSON file"""
try:
with open('data/hierarchy_exports/kit_hierarchy.json', 'r') as f:
return json.load(f)
except FileNotFoundError:
st.error("Kit hierarchy file not found. Please ensure kit_hierarchy.json exists in data/hierarchy_exports/")
return {}
except json.JSONDecodeError:
st.error("Invalid kit hierarchy JSON format")
return {}
def display_kit_relationships_dashboard(results):
"""Main dashboard showing kit relationships in production"""
st.header("πŸ”— Kit Relationship Dashboard")
st.markdown("Visualizing dependencies between kits being produced")
st.markdown("---")
# Load hierarchy data
hierarchy_data = load_kit_hierarchy()
if not hierarchy_data:
st.warning("No kit hierarchy data available")
return
# Get produced kits from results
produced_kits = set()
if 'weekly_production' in results:
produced_kits = set(results['weekly_production'].keys())
elif 'run_schedule' in results:
produced_kits = set(row['product'] for row in results['run_schedule'])
if not produced_kits:
st.warning("No production data available")
return
# Create tabs for different relationship views
tab1, tab2, tab3, tab4 = st.tabs([
"🌐 Dependency Network",
"πŸ“Š Relationship Matrix",
"🎯 Production Flow",
"⚠️ Dependency Analysis"
])
with tab1:
display_dependency_network(hierarchy_data, produced_kits, results)
with tab2:
display_relationship_matrix(hierarchy_data, produced_kits, results)
with tab3:
display_production_flow_relationships(hierarchy_data, produced_kits, results)
with tab4:
display_dependency_analysis(hierarchy_data, produced_kits, results)
def display_dependency_network(hierarchy_data, produced_kits, results):
"""Show interactive network graph of kit dependencies"""
st.subheader("🌐 Kit Dependency Network")
st.markdown("Interactive graph showing which kits depend on other kits")
# Build relationship data for produced kits only
relationships = build_relationship_data(hierarchy_data, produced_kits)
if not relationships:
st.info("No dependency relationships found between produced kits")
return
# Get production timing data
production_timing = get_production_timing(results)
# Create network visualization
col1, col2 = st.columns([3, 1])
with col1:
if NETWORKX_AVAILABLE:
fig = create_interactive_network_graph(relationships, production_timing)
st.plotly_chart(fig, use_container_width=True)
else:
fig = create_simple_dependency_chart(relationships, production_timing)
st.plotly_chart(fig, use_container_width=True)
st.info("πŸ’‘ Install networkx for advanced network layouts: `pip install networkx`")
with col2:
# Network statistics
st.subheader("πŸ“ˆ Network Stats")
all_kits = set()
for rel in relationships:
all_kits.add(rel['source'])
all_kits.add(rel['target'])
st.metric("Total Kits", len(all_kits))
st.metric("Dependencies", len(relationships))
# Dependency depth analysis
max_depth = calculate_dependency_depth(relationships)
st.metric("Max Dependency Depth", max_depth)
# Most dependent kits
dependent_kits = get_most_dependent_kits(relationships)
st.subheader("πŸ”— Most Dependencies")
for kit, count in dependent_kits[:5]:
st.write(f"**{kit}**: {count} dependencies")
def display_relationship_matrix(hierarchy_data, produced_kits, results):
"""Show dependency matrix heatmap"""
st.subheader("πŸ“Š Kit Dependency Matrix")
st.markdown("Heatmap showing which kits (rows) depend on which other kits (columns)")
# Build dependency matrix
matrix_data = build_dependency_matrix(hierarchy_data, produced_kits)
if matrix_data.empty:
st.info("No dependency relationships to visualize in matrix form")
return
# Create heatmap
fig = px.imshow(matrix_data.values,
x=matrix_data.columns,
y=matrix_data.index,
color_continuous_scale='Blues',
title='Kit Dependency Matrix (1 = depends on, 0 = no dependency)',
labels=dict(x="Dependency (what is needed)",
y="Kit (what depends on others)",
color="Dependency"))
fig.update_layout(height=600)
st.plotly_chart(fig, use_container_width=True)
# Show matrix as table
with st.expander("πŸ“‹ View Dependency Matrix as Table"):
st.dataframe(matrix_data, use_container_width=True)
def display_production_flow_relationships(hierarchy_data, produced_kits, results):
"""Show how relationships affect production timing"""
st.subheader("🎯 Production Flow with Relationships")
st.markdown("Timeline showing when dependent kits are produced")
# Get production timing and relationships
production_timing = get_production_timing(results)
relationships = build_relationship_data(hierarchy_data, produced_kits)
if not production_timing or not relationships:
st.info("Insufficient data for production flow analysis")
return
# Create timeline with dependency arrows
fig = create_production_timeline_with_dependencies(production_timing, relationships)
st.plotly_chart(fig, use_container_width=True)
# Timing analysis table
st.subheader("⏰ Dependency Timing Analysis")
timing_analysis = analyze_dependency_timing(production_timing, relationships)
if timing_analysis:
df = pd.DataFrame(timing_analysis)
st.dataframe(df, use_container_width=True)
def display_dependency_analysis(hierarchy_data, produced_kits, results):
"""Analyze dependency fulfillment and violations"""
st.subheader("⚠️ Dependency Analysis & Violations")
production_timing = get_production_timing(results)
relationships = build_relationship_data(hierarchy_data, produced_kits)
# Analyze violations
violations = find_dependency_violations(production_timing, relationships)
# Summary metrics
col1, col2, col3, col4 = st.columns(4)
with col1:
total_deps = len(relationships)
st.metric("Total Dependencies", total_deps)
with col2:
violated_deps = len(violations)
st.metric("Violations", violated_deps,
delta=f"-{violated_deps}" if violated_deps > 0 else None)
with col3:
if total_deps > 0:
success_rate = ((total_deps - violated_deps) / total_deps) * 100
st.metric("Success Rate", f"{success_rate:.1f}%")
else:
st.metric("Success Rate", "N/A")
with col4:
if violations:
avg_violation = sum(v['days_early'] for v in violations) / len(violations)
st.metric("Avg Days Early", f"{avg_violation:.1f}")
else:
st.metric("Avg Days Early", "0")
# Violation details
if violations:
st.subheader("🚨 Dependency Violations")
st.markdown("Cases where kits were produced before their dependencies")
violation_df = pd.DataFrame(violations)
# Violation severity chart
fig = px.scatter(violation_df,
x='dependency_day', y='kit_day',
size='days_early', color='severity',
hover_data=['kit', 'dependency'],
title='Dependency Violations (Below diagonal = violation)',
labels={'dependency_day': 'When Dependency Was Made',
'kit_day': 'When Kit Was Made'})
# Add diagonal line showing ideal timing
max_day = max(violation_df['dependency_day'].max(), violation_df['kit_day'].max())
fig.add_shape(type="line", x0=0, y0=0, x1=max_day, y1=max_day,
line=dict(dash="dash", color="green"),
name="Ideal Timeline")
st.plotly_chart(fig, use_container_width=True)
# Detailed violation table
st.dataframe(violation_df[['kit', 'dependency', 'kit_day', 'dependency_day',
'days_early', 'severity']], use_container_width=True)
else:
st.success("πŸŽ‰ No dependency violations found! All kits produced in correct order.")
# Recommendations
st.subheader("πŸ’‘ Recommendations")
recommendations = generate_dependency_recommendations(violations, relationships, production_timing)
for rec in recommendations:
st.info(f"πŸ’‘ {rec}")
# Helper Functions
def build_relationship_data(hierarchy_data, produced_kits):
"""Build relationship data for visualization"""
relationships = []
for kit_id, kit_info in hierarchy_data.items():
if kit_id not in produced_kits:
continue
# Add direct dependencies
dependencies = kit_info.get('dependencies', [])
for dep in dependencies:
if dep in produced_kits: # Only show relationships between produced kits
relationships.append({
'source': dep, # Dependency (what's needed)
'target': kit_id, # Kit that depends on it
'type': 'direct',
'source_type': hierarchy_data.get(dep, {}).get('type', 'unknown'),
'target_type': kit_info.get('type', 'unknown')
})
return relationships
def build_dependency_matrix(hierarchy_data, produced_kits):
"""Build dependency matrix for heatmap"""
produced_list = sorted(list(produced_kits))
if len(produced_list) == 0:
return pd.DataFrame()
# Initialize matrix
matrix = pd.DataFrame(0, index=produced_list, columns=produced_list)
# Fill matrix with dependencies
for kit_id in produced_list:
kit_info = hierarchy_data.get(kit_id, {})
dependencies = kit_info.get('dependencies', [])
for dep in dependencies:
if dep in produced_list:
matrix.loc[kit_id, dep] = 1 # kit_id depends on dep
return matrix
def get_production_timing(results):
"""Extract production timing for each kit"""
timing = {}
if 'run_schedule' in results:
for run in results['run_schedule']:
kit = run['product']
day = run['day']
# Use earliest day if kit is produced multiple times
if kit not in timing or day < timing[kit]:
timing[kit] = day
return timing
def create_interactive_network_graph(relationships, production_timing):
"""Create interactive network graph using NetworkX layout"""
if not NETWORKX_AVAILABLE:
return create_simple_dependency_chart(relationships, production_timing)
# Create NetworkX graph
G = nx.DiGraph()
# Add edges (relationships)
for rel in relationships:
G.add_edge(rel['source'], rel['target'], type=rel['type'])
if len(G.nodes()) == 0:
return go.Figure().add_annotation(
text="No relationships to display",
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
)
# Calculate layout
pos = nx.spring_layout(G, k=3, iterations=50)
# Create edge traces
edge_x, edge_y = [], []
edge_info = []
for edge in G.edges():
source, target = edge
x0, y0 = pos[source]
x1, y1 = pos[target]
edge_x.extend([x0, x1, None])
edge_y.extend([y0, y1, None])
# Add arrow annotation
edge_info.append({
'x': (x0 + x1) / 2,
'y': (y0 + y1) / 2,
'text': 'β†’',
'source': source,
'target': target
})
edge_trace = go.Scatter(x=edge_x, y=edge_y,
line=dict(width=2, color='#888'),
hoverinfo='none',
mode='lines')
# Create node traces
node_x, node_y, node_text, node_color, node_size = [], [], [], [], []
node_info = []
for node in G.nodes():
x, y = pos[node]
node_x.append(x)
node_y.append(y)
# Node size based on number of connections
in_degree = G.in_degree(node)
out_degree = G.out_degree(node)
total_degree = in_degree + out_degree
node_size.append(20 + total_degree * 5)
# Color by production timing
prod_day = production_timing.get(node, 0)
if prod_day == 1:
node_color.append('#90EE90') # Light green for early
elif prod_day <= 3:
node_color.append('#FFD700') # Gold for middle
else:
node_color.append('#FF6347') # Tomato for late
# Node text and info
short_name = node[:12] + "..." if len(node) > 12 else node
node_text.append(short_name)
node_info.append(f"{node}<br>Day: {prod_day}<br>In: {in_degree}, Out: {out_degree}")
node_trace = go.Scatter(x=node_x, y=node_y,
mode='markers+text',
text=node_text,
textposition='middle center',
hovertext=node_info,
hoverinfo='text',
marker=dict(size=node_size,
color=node_color,
line=dict(width=2, color='black')))
# Create figure
fig = go.Figure(data=[edge_trace, node_trace],
layout=go.Layout(
title='Kit Dependency Network (Size=Connections, Color=Production Day)',
showlegend=False,
hovermode='closest',
margin=dict(b=20,l=5,r=5,t=40),
annotations=[
dict(text="Green=Early, Gold=Middle, Red=Late production",
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 create_simple_dependency_chart(relationships, production_timing):
"""Create simple dependency chart without NetworkX"""
if not relationships:
return go.Figure().add_annotation(
text="No dependencies to display",
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False
)
# Create a simple directed graph visualization
# Group kits by their role (sources, targets)
sources = set(rel['source'] for rel in relationships)
targets = set(rel['target'] for rel in relationships)
# Create positions
all_kits = list(sources | targets)
positions = {kit: (i, production_timing.get(kit, 0)) for i, kit in enumerate(all_kits)}
# Create traces
edge_x, edge_y = [], []
for rel in relationships:
source_pos = positions[rel['source']]
target_pos = positions[rel['target']]
edge_x.extend([source_pos[0], target_pos[0], None])
edge_y.extend([source_pos[1], target_pos[1], None])
# Edge trace
edge_trace = go.Scatter(x=edge_x, y=edge_y,
line=dict(width=2, color='#888'),
hoverinfo='none',
mode='lines')
# Node trace
node_x = [positions[kit][0] for kit in all_kits]
node_y = [positions[kit][1] for kit in all_kits]
node_text = [kit[:10] + "..." if len(kit) > 10 else kit for kit in all_kits]
node_trace = go.Scatter(x=node_x, y=node_y,
mode='markers+text',
text=node_text,
textposition='top center',
marker=dict(size=15, color='lightblue',
line=dict(width=2, color='black')),
hovertext=all_kits,
hoverinfo='text')
fig = go.Figure(data=[edge_trace, node_trace],
layout=go.Layout(
title='Kit Dependencies (Y-axis = Production Day)',
showlegend=False,
xaxis=dict(title='Kits'),
yaxis=dict(title='Production Day')))
return fig
def create_production_timeline_with_dependencies(production_timing, relationships):
"""Create timeline showing production order with dependency arrows"""
if not production_timing:
return go.Figure()
# Prepare data
timeline_data = []
for kit, day in production_timing.items():
timeline_data.append({
'Kit': kit,
'Day': day,
'Short_Name': kit[:15] + "..." if len(kit) > 15 else kit
})
df = pd.DataFrame(timeline_data)
# Create scatter plot
fig = px.scatter(df, x='Day', y='Kit',
hover_data=['Kit'],
title='Production Timeline with Dependencies')
# Add dependency arrows
for rel in relationships:
source_day = production_timing.get(rel['source'], 0)
target_day = production_timing.get(rel['target'], 0)
# Add arrow if both kits are in timeline
if source_day > 0 and target_day > 0:
fig.add_annotation(
x=target_day, y=rel['target'],
ax=source_day, ay=rel['source'],
arrowhead=2, arrowsize=1, arrowwidth=2,
arrowcolor="red" if source_day > target_day else "green"
)
fig.update_layout(height=max(400, len(df) * 20))
return fig
def calculate_dependency_depth(relationships):
"""Calculate maximum dependency depth"""
if not NETWORKX_AVAILABLE or not relationships:
return 0
G = nx.DiGraph()
for rel in relationships:
G.add_edge(rel['source'], rel['target'])
try:
return nx.dag_longest_path_length(G)
except:
return 0
def get_most_dependent_kits(relationships):
"""Get kits with most dependencies"""
dependency_counts = {}
for rel in relationships:
target = rel['target']
dependency_counts[target] = dependency_counts.get(target, 0) + 1
return sorted(dependency_counts.items(), key=lambda x: x[1], reverse=True)
def find_dependency_violations(production_timing, relationships):
"""Find cases where kits were produced before their dependencies"""
violations = []
for rel in relationships:
source = rel['source'] # dependency
target = rel['target'] # kit that depends on it
source_day = production_timing.get(source, 0)
target_day = production_timing.get(target, 0)
if source_day > 0 and target_day > 0 and source_day > target_day:
days_early = source_day - target_day
severity = 'high' if days_early > 2 else 'medium' if days_early > 1 else 'low'
violations.append({
'kit': target,
'dependency': source,
'kit_day': target_day,
'dependency_day': source_day,
'days_early': days_early,
'severity': severity
})
return violations
def analyze_dependency_timing(production_timing, relationships):
"""Analyze timing of all dependency relationships"""
timing_analysis = []
for rel in relationships:
source = rel['source']
target = rel['target']
source_day = production_timing.get(source, 0)
target_day = production_timing.get(target, 0)
if source_day > 0 and target_day > 0:
timing_diff = target_day - source_day
status = "βœ… Correct" if timing_diff >= 0 else "❌ Violation"
timing_analysis.append({
'Kit': target[:20] + "..." if len(target) > 20 else target,
'Dependency': source[:20] + "..." if len(source) > 20 else source,
'Kit Day': target_day,
'Dep Day': source_day,
'Gap (Days)': timing_diff,
'Status': status
})
return sorted(timing_analysis, key=lambda x: x['Gap (Days)'])
def generate_dependency_recommendations(violations, relationships, production_timing):
"""Generate recommendations based on dependency analysis"""
recommendations = []
if not violations:
recommendations.append("Excellent! All dependencies are being fulfilled in the correct order.")
return recommendations
# Group violations by severity
high_severity = [v for v in violations if v['severity'] == 'high']
medium_severity = [v for v in violations if v['severity'] == 'medium']
if high_severity:
recommendations.append(
f"🚨 High Priority: {len(high_severity)} critical dependency violations found. "
"Consider rescheduling production to ensure dependencies are produced first."
)
if medium_severity:
recommendations.append(
f"⚠️ Medium Priority: {len(medium_severity)} moderate dependency timing issues. "
"Review production sequence for optimization opportunities."
)
# Most problematic kits
problem_kits = {}
for v in violations:
kit = v['kit']
problem_kits[kit] = problem_kits.get(kit, 0) + 1
if problem_kits:
worst_kit = max(problem_kits.items(), key=lambda x: x[1])
recommendations.append(
f"🎯 Focus Area: Kit {worst_kit[0]} has {worst_kit[1]} dependency issues. "
"Consider moving its production later in the schedule."
)
return recommendations