Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1064,85 +1064,139 @@ with col_3d:
|
|
| 1064 |
insight_text = "<div class='ai-insight'>Insufficient data for insight.</div>"
|
| 1065 |
st.markdown(insight_text, unsafe_allow_html=True)
|
| 1066 |
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
# =================== OBJECTIVE 4 - What Unsafe Issues Appear Most Often? ===================
|
| 1070 |
-
st.markdown("<h3 class='section-title'>OBJECTIVE 4 — What Unsafe Issues Appear Most Often?</h3>", unsafe_allow_html=True)
|
| 1071 |
-
|
| 1072 |
-
# Function for consistent title
|
| 1073 |
-
def create_consistent_title(title_text):
|
| 1074 |
-
return f"<div style='font-family: Arial; font-size: 16px; font-weight: bold; color: #2c3e50; text-align: center; margin-bottom: 8px;'>{title_text}</div>"
|
| 1075 |
-
|
| 1076 |
-
# Try to import wordcloud
|
| 1077 |
try:
|
| 1078 |
from wordcloud import WordCloud
|
| 1079 |
import matplotlib.pyplot as plt
|
| 1080 |
import plotly.express as px
|
| 1081 |
-
import plotly.graph_objects as go
|
| 1082 |
WORDCLOUD_AVAILABLE = True
|
| 1083 |
except ImportError:
|
| 1084 |
WORDCLOUD_AVAILABLE = False
|
| 1085 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1086 |
if WORDCLOUD_AVAILABLE:
|
| 1087 |
-
# Prepare data
|
| 1088 |
-
df_all = df_local.copy()
|
| 1089 |
-
|
| 1090 |
-
# 2-column layout
|
| 1091 |
-
col_chart, col_cloud = st.columns([1.2, 1])
|
| 1092 |
|
| 1093 |
-
#
|
| 1094 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1095 |
st.markdown(create_consistent_title("Distribution of All Issue Categories"), unsafe_allow_html=True)
|
| 1096 |
-
|
| 1097 |
-
if 'temuan_kategori' not in df_all.columns:
|
| 1098 |
-
st.warning("Column 'temuan_kategori' not found.")
|
| 1099 |
-
else:
|
| 1100 |
-
# Count categories
|
| 1101 |
-
cat_counts = df_all['temuan_kategori'].value_counts()
|
| 1102 |
-
total = cat_counts.sum()
|
| 1103 |
-
percentages = (cat_counts / total * 100).round(1)
|
| 1104 |
-
|
| 1105 |
-
# Define colors (sesuai preferensi Anda)
|
| 1106 |
-
color_map = {
|
| 1107 |
-
'Positive': '#2E7D32', # Hijau
|
| 1108 |
-
'Unsafe Condition': '#EF5350', # Merah tua (pastel red)
|
| 1109 |
-
'Unsafe Action': '#F48FB1', # Pink pastel
|
| 1110 |
-
'Near Miss': '#BDBDBD', # Abu-abu
|
| 1111 |
-
}
|
| 1112 |
-
# Default color for unknown
|
| 1113 |
-
default_color = '#9E9E9E'
|
| 1114 |
-
|
| 1115 |
-
# Assign colors
|
| 1116 |
-
colors = [color_map.get(cat, default_color) for cat in cat_counts.index]
|
| 1117 |
-
|
| 1118 |
-
# Create base pie chart — tanpa legend
|
| 1119 |
-
fig = go.Figure(data=[go.Pie(
|
| 1120 |
-
labels=cat_counts.index,
|
| 1121 |
-
values=cat_counts.values,
|
| 1122 |
-
marker=dict(colors=colors),
|
| 1123 |
-
textinfo='percent+label',
|
| 1124 |
-
textposition='inside',
|
| 1125 |
-
hoverinfo='label+percent',
|
| 1126 |
-
sort=False # pertahankan urutan asli
|
| 1127 |
-
)])
|
| 1128 |
-
fig.update_layout(
|
| 1129 |
-
height=450,
|
| 1130 |
-
showlegend=False, # ❌ Matikan legend default Plotly
|
| 1131 |
-
margin=dict(t=20, b=20, l=20, r=20)
|
| 1132 |
-
)
|
| 1133 |
-
st.plotly_chart(fig, use_container_width=True)
|
| 1134 |
|
| 1135 |
-
|
| 1136 |
-
|
| 1137 |
|
| 1138 |
-
|
| 1139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1140 |
|
| 1141 |
-
|
| 1142 |
-
|
| 1143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1144 |
|
| 1145 |
-
target
|
| 1146 |
# =================== 5. Matrix (Tetap Dipertahankan) ===================
|
| 1147 |
st.markdown("<h3 class='section-title'>OBJECTIVE 5 - Findings vs Lead Time: Which Divisions Move Slow?</h3>", unsafe_allow_html=True)
|
| 1148 |
|
|
|
|
| 1064 |
insight_text = "<div class='ai-insight'>Insufficient data for insight.</div>"
|
| 1065 |
st.markdown(insight_text, unsafe_allow_html=True)
|
| 1066 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1067 |
try:
|
| 1068 |
from wordcloud import WordCloud
|
| 1069 |
import matplotlib.pyplot as plt
|
| 1070 |
import plotly.express as px
|
|
|
|
| 1071 |
WORDCLOUD_AVAILABLE = True
|
| 1072 |
except ImportError:
|
| 1073 |
WORDCLOUD_AVAILABLE = False
|
| 1074 |
|
| 1075 |
+
st.markdown("<h3 class='section-title'>OBJECTIVE 4 - What Unsafe Issues Appear Most Often?</h3>",
|
| 1076 |
+
unsafe_allow_html=True)
|
| 1077 |
+
|
| 1078 |
+
# 🔹 Fungsi untuk membuat judul seragam
|
| 1079 |
+
def create_consistent_title(title_text):
|
| 1080 |
+
return f"<div style='font-family: Arial; font-size: 16px; font-weight: bold; color: #2c3e50; text-align: center; margin-bottom: 8px;'>{title_text}</div>"
|
| 1081 |
+
|
| 1082 |
if WORDCLOUD_AVAILABLE:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1083 |
|
| 1084 |
+
# 🔥 Data untuk pie chart: semua kategori
|
| 1085 |
+
df_all_kategori = df_local.copy()
|
| 1086 |
+
|
| 1087 |
+
# 🔥 Data untuk wordcloud: hanya Non-Positive
|
| 1088 |
+
df_filtered_kategori = df_local[df_local['temuan_kategori'] != 'Positive'] \
|
| 1089 |
+
if 'temuan_kategori' in df_local.columns else df_local
|
| 1090 |
+
|
| 1091 |
+
# 2 Kolom
|
| 1092 |
+
col1, col2 = st.columns(2)
|
| 1093 |
+
|
| 1094 |
+
# === PIE CHART: Semua temuan_kategori (Tanpa Legend Default, Custom Legend Khusus) ===
|
| 1095 |
+
with col1:
|
| 1096 |
st.markdown(create_consistent_title("Distribution of All Issue Categories"), unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1097 |
|
| 1098 |
+
if 'temuan_kategori' in df_all_kategori.columns:
|
| 1099 |
+
category_counts = df_all_kategori['temuan_kategori'].value_counts()
|
| 1100 |
|
| 1101 |
+
if not category_counts.empty:
|
| 1102 |
+
# Mapping warna
|
| 1103 |
+
color_map = {
|
| 1104 |
+
'Positive': '#2E7D32', # Hijau
|
| 1105 |
+
'Unsafe Condition': '#EF5350', # Merah Muda
|
| 1106 |
+
'Unsafe Action': '#F48FB1', # Pink
|
| 1107 |
+
'Near Miss': '#BDBDBD' # Abu-abu
|
| 1108 |
+
}
|
| 1109 |
+
colors = [color_map.get(cat, '#9E9E9E') for cat in category_counts.index]
|
| 1110 |
|
| 1111 |
+
# Buat pie chart tanpa legend
|
| 1112 |
+
fig_pie = px.pie(
|
| 1113 |
+
names=category_counts.index,
|
| 1114 |
+
values=category_counts.values,
|
| 1115 |
+
color_discrete_sequence=colors
|
| 1116 |
+
)
|
| 1117 |
+
fig_pie.update_traces(
|
| 1118 |
+
textposition='inside',
|
| 1119 |
+
textinfo='percent+label',
|
| 1120 |
+
marker=dict(line=dict(color='#FFFFFF', width=1))
|
| 1121 |
+
)
|
| 1122 |
+
fig_pie.update_layout(
|
| 1123 |
+
showlegend=False, # ❌ matikan legend default
|
| 1124 |
+
height=450,
|
| 1125 |
+
margin=dict(t=20, b=20, l=40, r=40)
|
| 1126 |
+
)
|
| 1127 |
+
st.plotly_chart(fig_pie, use_container_width=True)
|
| 1128 |
+
|
| 1129 |
+
# ✅ CUSTOM LEGEND: Gabungan lingkaran + garis
|
| 1130 |
+
st.markdown("<div style='text-align: center; font-size: 14px; margin-top: -10px;'>", unsafe_allow_html=True)
|
| 1131 |
+
legend_items = []
|
| 1132 |
+
|
| 1133 |
+
for cat in category_counts.index:
|
| 1134 |
+
color = color_map.get(cat, '#9E9E9E')
|
| 1135 |
+
if cat in ['Unsafe Action', 'Near Miss']:
|
| 1136 |
+
# Gunakan garis horizontal kecil
|
| 1137 |
+
item = (
|
| 1138 |
+
f"<span style='display: inline-block; width: 16px; height: 4px; background-color: {color}; "
|
| 1139 |
+
f"margin: 0 8px; border-radius: 1px;'></span>"
|
| 1140 |
+
f"{cat}"
|
| 1141 |
+
)
|
| 1142 |
+
else:
|
| 1143 |
+
# Gunakan lingkaran kecil (default)
|
| 1144 |
+
item = (
|
| 1145 |
+
f"<span style='display: inline-block; width: 12px; height: 12px; background-color: {color}; "
|
| 1146 |
+
f"border-radius: 50%; margin: 0 8px;'></span>"
|
| 1147 |
+
f"{cat}"
|
| 1148 |
+
)
|
| 1149 |
+
legend_items.append(item)
|
| 1150 |
+
|
| 1151 |
+
# Gabungkan semua item
|
| 1152 |
+
legend_html = " | ".join(legend_items)
|
| 1153 |
+
st.markdown(f"<div>{legend_html}</div>", unsafe_allow_html=True)
|
| 1154 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
| 1155 |
+
|
| 1156 |
+
else:
|
| 1157 |
+
st.warning("No data available for pie chart.")
|
| 1158 |
+
else:
|
| 1159 |
+
st.warning("Column 'temuan_kategori' not available.")
|
| 1160 |
+
|
| 1161 |
+
# === WORDCLOUD: Hanya Non-Positive (dari keyword_kategori) ===
|
| 1162 |
+
with col2:
|
| 1163 |
+
st.markdown(create_consistent_title("Unsafe Issues"), unsafe_allow_html=True)
|
| 1164 |
+
|
| 1165 |
+
if df_filtered_kategori.empty:
|
| 1166 |
+
st.warning("No data available after filtering out 'Positive' category.")
|
| 1167 |
+
else:
|
| 1168 |
+
if 'keyword_kategori' in df_filtered_kategori.columns:
|
| 1169 |
+
# Gabungkan text
|
| 1170 |
+
import re
|
| 1171 |
+
text = ' '.join(df_filtered_kategori['keyword_kategori'].dropna().astype(str))
|
| 1172 |
+
text = re.sub(r'[^a-zA-Z\s]', ' ', text)
|
| 1173 |
+
|
| 1174 |
+
if text.strip():
|
| 1175 |
+
# Buat WordCloud
|
| 1176 |
+
wordcloud = WordCloud(
|
| 1177 |
+
width=1600, # resolusi besar agar HD
|
| 1178 |
+
height=800,
|
| 1179 |
+
background_color='white',
|
| 1180 |
+
colormap='viridis',
|
| 1181 |
+
max_words=1000,
|
| 1182 |
+
random_state=42
|
| 1183 |
+
).generate(text)
|
| 1184 |
+
|
| 1185 |
+
# Output kecil tapi tajam
|
| 1186 |
+
fig, ax = plt.subplots(figsize=(3, 2), dpi=200)
|
| 1187 |
+
ax.imshow(wordcloud, interpolation='bilinear')
|
| 1188 |
+
ax.axis('off')
|
| 1189 |
+
plt.tight_layout()
|
| 1190 |
+
|
| 1191 |
+
st.pyplot(fig, use_container_width=True)
|
| 1192 |
+
else:
|
| 1193 |
+
st.warning("No valid text remaining after cleaning.")
|
| 1194 |
+
else:
|
| 1195 |
+
st.warning("Column 'keyword_kategori' not available.")
|
| 1196 |
+
|
| 1197 |
+
else:
|
| 1198 |
+
st.info("WordCloud library not installed. Install `wordcloud` and `matplotlib` to enable this feature.")
|
| 1199 |
|
|
|
|
| 1200 |
# =================== 5. Matrix (Tetap Dipertahankan) ===================
|
| 1201 |
st.markdown("<h3 class='section-title'>OBJECTIVE 5 - Findings vs Lead Time: Which Divisions Move Slow?</h3>", unsafe_allow_html=True)
|
| 1202 |
|