SHELLAPANDIANGANHUNGING commited on
Commit
787db49
·
verified ·
1 Parent(s): 49a5b23

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +298 -21
app.py CHANGED
@@ -2001,6 +2001,286 @@ else:
2001
  # st.markdown(
2002
  # '<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>',
2003
  # unsafe_allow_html=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2004
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
2005
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
2006
 
@@ -2130,12 +2410,11 @@ with col_insights:
2130
  st.info("Operator data not available for Operator Risk Profiling.")
2131
 
2132
  # =====================================================================
2133
- # 🔹 KOLOM KANAN — AI RECOMMENDATIONS (PER INSIGHT)
2134
  # =====================================================================
2135
  with col_recs:
2136
  st.subheader("Recommendations")
2137
 
2138
- # Reset list to collect recommendations per insight
2139
  ai_recommendations = []
2140
 
2141
  # 1. Critical Hour Insight → AI Rec
@@ -2193,23 +2472,24 @@ with col_recs:
2193
  "reasoning": "Shift distribution is balanced."
2194
  })
2195
 
2196
- # 4. Operator Risk Insight → AI Rec
2197
  if col_operator and col_operator in df.columns and not df.empty:
2198
- worst_operator = df[col_operator].value_counts().idxmax()
2199
- op_pct = (df[col_operator].value_counts()[worst_operator] / len(df)) * 100
 
2200
 
2201
- if op_pct > 5:
2202
- ai_recommendations.append({
2203
- "action": "Coaching or mandatory rest for the identified high-risk operator.",
2204
- "data_point": f"Operator {worst_operator}: {df[col_operator].value_counts()[worst_operator]} alerts ({op_pct:.1f}%)",
2205
- "reasoning": "Operator has highest fatigue alerts — requires individual intervention."
2206
- })
2207
- else:
2208
- ai_recommendations.append({
2209
- "action": "Continue general monitoring no single operator dominates risk.",
2210
- "data_point": f"Top Operator: {worst_operator} {df[col_operator].value_counts()[worst_operator]} alerts ({op_pct:.1f}%)",
2211
- "reasoning": "Risk is distributed across operators — no urgent individual action needed."
2212
- })
2213
 
2214
  # Render each recommendation as a card
2215
  for rec in ai_recommendations:
@@ -2276,7 +2556,4 @@ with col_recs:
2276
  # ================= FOOTER ===========================
2277
  st.markdown("---")
2278
  st.markdown(
2279
- '<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>',
2280
- unsafe_allow_html=True
2281
- )
2282
- # )
 
2001
  # st.markdown(
2002
  # '<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>',
2003
  # unsafe_allow_html=True
2004
+ # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
2005
+ # st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
2006
+
2007
+ # # Membagi tampilan menjadi dua kolom
2008
+ # col_insights, col_recs = st.columns(2)
2009
+
2010
+ # # =====================================================================
2011
+ # # 🔹 KOLOM KIRI — INSIGHTS BY ADVANCED ANALYTICS
2012
+ # # =====================================================================
2013
+ # with col_insights:
2014
+ # st.subheader("Insights by Advanced Analytics")
2015
+
2016
+ # # ===================== 1. Critical Hour Analysis =====================
2017
+ # critical_hours = [2, 3, 4, 5]
2018
+ # critical_alerts = df[df['hour'].isin(critical_hours)]
2019
+ # critical_pct = (len(critical_alerts) / len(df)) * 100 if len(df) > 0 else 0
2020
+
2021
+ # st.markdown(f"**Critical Hour Risk (3-6 AM)**")
2022
+ # bg_color = (
2023
+ # "#ffcccc" if critical_pct > 50 else
2024
+ # "#ffebcc" if critical_pct > 25 else
2025
+ # "#ffffcc" if critical_pct > 10 else
2026
+ # "#e6ffe6"
2027
+ # )
2028
+ # st.markdown(
2029
+ # f'<div style="background-color: {bg_color}; padding: 10px; border-radius: 5px;">'
2030
+ # f'Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}% of total alerts)</div>',
2031
+ # unsafe_allow_html=True
2032
+ # )
2033
+
2034
+ # if critical_pct > 10:
2035
+ # st.warning(
2036
+ # f"High risk: {critical_pct:.1f}% of fatigue alerts occur during critical hours (3-6 AM). "
2037
+ # f"This is a known circadian dip period."
2038
+ # )
2039
+ # else:
2040
+ # st.info(
2041
+ # f"{critical_pct:.1f}% of alerts occur during critical hours. This is within acceptable range."
2042
+ # )
2043
+
2044
+ # # ===================== 2. High-Speed Fatigue Analysis =====================
2045
+ # if col_speed and col_speed in df.columns:
2046
+ # high_speed_threshold = 20
2047
+ # high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
2048
+ # high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
2049
+
2050
+ # st.markdown(f"**High-Speed Fatigue Risk (Speed > {high_speed_threshold} km/h)**")
2051
+ # st.markdown(
2052
+ # f"""
2053
+ # <div style="font-size: 24px; font-weight: bold;">{len(high_speed_fatigue)}</div>
2054
+ # <div style="color: red; font-size: 14px; margin-top: -5px;">↑ {high_speed_pct:.1f}% of total alerts</div>
2055
+ # """,
2056
+ # unsafe_allow_html=True
2057
+ # )
2058
+
2059
+ # if high_speed_pct > 20:
2060
+ # st.warning(
2061
+ # f"High risk: {high_speed_pct:.1f}% of fatigue alerts occur at high speeds. "
2062
+ # f"This increases accident severity potential."
2063
+ # )
2064
+ # else:
2065
+ # st.info(
2066
+ # f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
2067
+ # )
2068
+ # else:
2069
+ # st.info("Speed data not available for High-Speed Fatigue Analysis.")
2070
+
2071
+ # # ===================== 3. Shift Pattern Analysis =====================
2072
+ # if col_shift and col_shift in df.columns:
2073
+ # shift_counts = df[col_shift].value_counts()
2074
+ # st.markdown(f"**Shift Pattern Risk**")
2075
+
2076
+ # for shift_val in shift_counts.index:
2077
+ # shift_pct = (shift_counts[shift_val] / len(df)) * 100
2078
+
2079
+ # st.markdown(
2080
+ # f"""
2081
+ # <div style="font-size: 24px; font-weight: bold;">{shift_counts[shift_val]}</div>
2082
+ # <div style="color: red; font-size: 14px; margin-top: -5px;">↑ {shift_pct:.1f}% of total alerts</div>
2083
+ # """,
2084
+ # unsafe_allow_html=True
2085
+ # )
2086
+
2087
+ # if shift_pct > 50:
2088
+ # st.warning(
2089
+ # f"Shift {shift_val} has disproportionately high alerts ({shift_pct:.1f}%). "
2090
+ # f"Review shift scheduling and workload."
2091
+ # )
2092
+ # else:
2093
+ # st.info(
2094
+ # f"Shift {shift_val} alert distribution is acceptable ({shift_pct:.1f}%)."
2095
+ # )
2096
+ # else:
2097
+ # st.info("Shift data not available for Shift Pattern Analysis.")
2098
+
2099
+ # # ===================== 4. Operator Risk Profiling =====================
2100
+ # if col_operator and col_operator in df.columns:
2101
+ # operator_alerts = df[col_operator].value_counts()
2102
+ # top_risk_operators = operator_alerts.head(5)
2103
+
2104
+ # st.markdown("**High-Risk Operator Identification**")
2105
+ # colors = ["#d32f2f", "#e57373", "#ef9a9a", "#ffcdd2", "#ffe1e4"]
2106
+
2107
+ # for idx, (op_name, count) in enumerate(top_risk_operators.items()):
2108
+ # op_pct = (count / len(df)) * 100
2109
+ # color = colors[idx] if idx < len(colors) else colors[-1]
2110
+
2111
+ # st.markdown(
2112
+ # f"**Operator:** {op_name} \n**Alerts:** {count}"
2113
+ # )
2114
+ # st.markdown(
2115
+ # f"<span style='font-weight:600'>Share:</span> "
2116
+ # f"<span style='color:{color}; font-weight:700'>{op_pct:.1f}% of total alerts</span>",
2117
+ # unsafe_allow_html=True
2118
+ # )
2119
+
2120
+ # if op_pct > 5:
2121
+ # st.warning(
2122
+ # f"Operator {op_name} has high fatigue risk ({op_pct:.1f}%). "
2123
+ # f"Consider coaching or rest plan."
2124
+ # )
2125
+ # else:
2126
+ # st.info(
2127
+ # f"Operator {op_name} fatigue risk is within acceptable range ({op_pct:.1f}%)."
2128
+ # )
2129
+ # else:
2130
+ # st.info("Operator data not available for Operator Risk Profiling.")
2131
+
2132
+ # # =====================================================================
2133
+ # # 🔹 KOLOM KANAN — AI RECOMMENDATIONS (PER INSIGHT)
2134
+ # # =====================================================================
2135
+ # with col_recs:
2136
+ # st.subheader("Recommendations")
2137
+
2138
+ # # Reset list to collect recommendations per insight
2139
+ # ai_recommendations = []
2140
+
2141
+ # # 1. Critical Hour Insight → AI Rec
2142
+ # if "hour" in df.columns and not df.empty:
2143
+ # peak_hour = df["hour"].value_counts().idxmax()
2144
+ # critical_hours = [2, 3, 4, 5]
2145
+
2146
+ # if peak_hour in critical_hours:
2147
+ # ai_recommendations.append({
2148
+ # "action": "Deploy enhanced fatigue monitoring systems during 3-6 AM.",
2149
+ # "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}%)",
2150
+ # "reasoning": "High percentage of alerts during circadian low period."
2151
+ # })
2152
+ # else:
2153
+ # ai_recommendations.append({
2154
+ # "action": "Monitor fatigue patterns around peak hour (Hour {peak_hour}).",
2155
+ # "data_point": f"Peak Hour: {peak_hour}:00 — {df['hour'].value_counts()[peak_hour]} alerts",
2156
+ # "reasoning": "This hour shows highest fatigue occurrence."
2157
+ # })
2158
+
2159
+ # # 2. High-Speed Insight → AI Rec
2160
+ # if col_speed and col_speed in df.columns and not df.empty:
2161
+ # high_speed_threshold = 20
2162
+ # high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
2163
+ # high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
2164
+
2165
+ # if high_speed_pct > 20:
2166
+ # ai_recommendations.append({
2167
+ # "action": "Implement speed-reduction protocols during fatigue-prone hours.",
2168
+ # "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
2169
+ # "reasoning": "High-speed alerts increase accident severity potential."
2170
+ # })
2171
+ # else:
2172
+ # ai_recommendations.append({
2173
+ # "action": "Maintain current speed monitoring — risk level is acceptable.",
2174
+ # "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
2175
+ # "reasoning": "Current high-speed fatigue rate is within acceptable range."
2176
+ # })
2177
+
2178
+ # # 3. Shift Pattern Insight → AI Rec
2179
+ # if col_shift and col_shift in df.columns and not df.empty:
2180
+ # worst_shift = df[col_shift].value_counts().idxmax()
2181
+ # shift_pct = (df[col_shift].value_counts()[worst_shift] / len(df)) * 100
2182
+
2183
+ # if shift_pct > 50:
2184
+ # ai_recommendations.append({
2185
+ # "action": "Review shift rotation schedules for Shift {worst_shift}.",
2186
+ # "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
2187
+ # "reasoning": "Disproportionately high fatigue alerts indicate scheduling imbalance."
2188
+ # })
2189
+ # else:
2190
+ # ai_recommendations.append({
2191
+ # "action": "Continue monitoring all shifts — no dominant risk identified.",
2192
+ # "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
2193
+ # "reasoning": "Shift distribution is balanced."
2194
+ # })
2195
+
2196
+ # # 4. Operator Risk Insight → AI Rec
2197
+ # if col_operator and col_operator in df.columns and not df.empty:
2198
+ # worst_operator = df[col_operator].value_counts().idxmax()
2199
+ # op_pct = (df[col_operator].value_counts()[worst_operator] / len(df)) * 100
2200
+
2201
+ # if op_pct > 5:
2202
+ # ai_recommendations.append({
2203
+ # "action": "Coaching or mandatory rest for the identified high-risk operator.",
2204
+ # "data_point": f"Operator {worst_operator}: {df[col_operator].value_counts()[worst_operator]} alerts ({op_pct:.1f}%)",
2205
+ # "reasoning": "Operator has highest fatigue alerts — requires individual intervention."
2206
+ # })
2207
+ # else:
2208
+ # ai_recommendations.append({
2209
+ # "action": "Continue general monitoring — no single operator dominates risk.",
2210
+ # "data_point": f"Top Operator: {worst_operator} — {df[col_operator].value_counts()[worst_operator]} alerts ({op_pct:.1f}%)",
2211
+ # "reasoning": "Risk is distributed across operators — no urgent individual action needed."
2212
+ # })
2213
+
2214
+ # # Render each recommendation as a card
2215
+ # for rec in ai_recommendations:
2216
+ # # Highlight percentages in red
2217
+ # data_point_colored = rec['data_point'].replace(
2218
+ # f"({rec['data_point'].split('(')[-1]}",
2219
+ # f"(<span style='color: red;'>{rec['data_point'].split('(')[-1]}"
2220
+ # ).replace(")", "</span>)")
2221
+
2222
+ # reasoning_colored = rec['reasoning'].replace(
2223
+ # f"({rec['reasoning'].split('(')[-1]}",
2224
+ # f"(<span style='color: red;'>{rec['reasoning'].split('(')[-1]}"
2225
+ # ).replace(")", "</span>)")
2226
+
2227
+ # st.markdown(
2228
+ # f"""
2229
+ # <div style="
2230
+ # background: #f8f9fa;
2231
+ # border: 1px solid #dee2e6;
2232
+ # border-radius: 8px;
2233
+ # padding: 15px;
2234
+ # margin: 10px 0;
2235
+ # box-shadow: 0 2px 8px rgba(0,0,0,0.05);
2236
+ # ">
2237
+ # <div style="
2238
+ # font-weight: bold;
2239
+ # background: #e9ecef;
2240
+ # padding: 8px;
2241
+ # border-radius: 5px;
2242
+ # margin-bottom: 8px;
2243
+ # border-left: 4px solid #495057;
2244
+ # ">
2245
+ # AI Recommendation
2246
+ # </div>
2247
+ # <div style="padding: 8px 0;">
2248
+ # <strong>Action:</strong> {rec['action']}
2249
+ # </div>
2250
+ # <div style="
2251
+ # padding: 8px;
2252
+ # background: #f1f1f1;
2253
+ # border-radius: 5px;
2254
+ # margin: 8px 0;
2255
+ # ">
2256
+ # <strong>Data Point:</strong> {data_point_colored}
2257
+ # </div>
2258
+ # <div style="
2259
+ # padding: 8px;
2260
+ # background: #f1f1f1;
2261
+ # border-radius: 5px;
2262
+ # ">
2263
+ # <strong>AI Reasoning:</strong> {reasoning_colored}
2264
+ # </div>
2265
+ # </div>
2266
+ # """,
2267
+ # unsafe_allow_html=True
2268
+ # )
2269
+
2270
+ # if not ai_recommendations:
2271
+ # st.info(
2272
+ # "No specific data points available for AI recommendations. "
2273
+ # "Ensure relevant columns are present (hour, shift, operator, duration, speed)."
2274
+ # )
2275
+
2276
+ # # ================= FOOTER ===========================
2277
+ # st.markdown("---")
2278
+ # st.markdown(
2279
+ # '<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>',
2280
+ # unsafe_allow_html=True
2281
+ # )
2282
+ # # )
2283
+
2284
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
2285
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
2286
 
 
2410
  st.info("Operator data not available for Operator Risk Profiling.")
2411
 
2412
  # =====================================================================
2413
+ # 🔹 KOLOM KANAN — AI RECOMMENDATIONS (PER INSIGHT + PER OPERATOR)
2414
  # =====================================================================
2415
  with col_recs:
2416
  st.subheader("Recommendations")
2417
 
 
2418
  ai_recommendations = []
2419
 
2420
  # 1. Critical Hour Insight → AI Rec
 
2472
  "reasoning": "Shift distribution is balanced."
2473
  })
2474
 
2475
+ # 4. Operator Risk Profiling → AI Rec for EACH of Top 5 Operators
2476
  if col_operator and col_operator in df.columns and not df.empty:
2477
+ top_operators = df[col_operator].value_counts().head(5)
2478
+ for op_name, count in top_operators.items():
2479
+ op_pct = (count / len(df)) * 100
2480
 
2481
+ if op_pct > 5:
2482
+ ai_recommendations.append({
2483
+ "action": f"Coaching or mandatory rest for Operator {op_name}.",
2484
+ "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)",
2485
+ "reasoning": f"Operator has high fatigue alerts — requires individual intervention."
2486
+ })
2487
+ else:
2488
+ ai_recommendations.append({
2489
+ "action": f"Continue general monitoring for Operator {op_name}.",
2490
+ "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)",
2491
+ "reasoning": f"Risk is within acceptable range — no urgent action needed."
2492
+ })
2493
 
2494
  # Render each recommendation as a card
2495
  for rec in ai_recommendations:
 
2556
  # ================= FOOTER ===========================
2557
  st.markdown("---")
2558
  st.markdown(
2559
+ '<div class="footer">FatigueAnalyzer - Transform