SHELLAPANDIANGANHUNGING commited on
Commit
87bc929
·
verified ·
1 Parent(s): 9c0e3d9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +9 -408
app.py CHANGED
@@ -1718,6 +1718,13 @@ else:
1718
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1719
  st.exception(e)
1720
 
 
 
 
 
 
 
 
1721
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1722
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1723
 
@@ -1760,6 +1767,7 @@ with col_insights:
1760
 
1761
  # ===================== 2. High-Speed Fatigue Analysis =====================
1762
  if col_speed and col_speed in df.columns:
 
1763
  high_speed_threshold = 20
1764
  high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1765
  high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
@@ -1782,413 +1790,6 @@ with col_insights:
1782
  st.info(
1783
  f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
1784
  )
1785
- else:
1786
- st.info("Speed data not available for High-Speed Fatigue Analysis.")
1787
-
1788
- # ===================== 3. Weekend vs Weekday Risk =====================
1789
- df['is_weekend'] = df['start'].dt.weekday >= 5
1790
- weekend_alerts = len(df[df['is_weekend']])
1791
- weekday_alerts = len(df[~df['is_weekend']])
1792
- weekend_pct = (weekend_alerts / len(df)) * 100 if len(df) > 0 else 0
1793
-
1794
- st.markdown(f"**Weekend vs Weekday Risk Distribution**")
1795
- st.markdown(
1796
- f"""
1797
- <div style="display: flex; gap: 10px; justify-content: space-between;">
1798
- <div style="text-align: center; flex: 1;">
1799
- <div style="font-size: 18px; font-weight: bold;">{weekend_alerts}</div>
1800
- <div style="color: #555;">Weekend</div>
1801
- <div style="color: red; font-size: 12px;">{weekend_pct:.1f}%</div>
1802
- </div>
1803
- <div style="text-align: center; flex: 1;">
1804
- <div style="font-size: 18px; font-weight: bold;">{weekday_alerts}</div>
1805
- <div style="color: #555;">Weekday</div>
1806
- <div style="color: green; font-size: 12px;">{100-weekend_pct:.1f}%</div>
1807
- </div>
1808
- </div>
1809
- """,
1810
- unsafe_allow_html=True
1811
- )
1812
- if weekend_pct > 60:
1813
- st.warning(f"High weekend fatigue risk ({weekend_pct:.1f}%). Consider weekend rest protocols.")
1814
- else:
1815
- st.info(f"Weekend fatigue is {weekend_pct:.1f}% — balanced with weekday activity.")
1816
-
1817
- # ===================== 4. Shift-Based Risk Analysis =====================
1818
- if col_shift and col_shift in df.columns:
1819
- shift_counts = df[col_shift].value_counts()
1820
- st.markdown(f"**Shift-Based Risk Distribution**")
1821
- for shift, count in shift_counts.items():
1822
- shift_pct = (count / len(df)) * 100
1823
- st.markdown(f"- **Shift {shift}**: {count} alerts ({shift_pct:.1f}%)")
1824
- max_shift = shift_counts.idxmax() if not shift_counts.empty else "N/A"
1825
- if max_shift != "N/A":
1826
- st.warning(f"Shift {max_shift} has the highest fatigue alert count. Investigate scheduling or rest breaks.")
1827
- else:
1828
- st.info("Shift data not available for analysis.")
1829
-
1830
- # ===================== 5. Fleet Type Risk Distribution =====================
1831
- if col_fleet_type and col_fleet_type in df.columns:
1832
- fleet_counts = df[col_fleet_type].value_counts()
1833
- st.markdown(f"**Fleet Type Risk Distribution**")
1834
- for fleet, count in fleet_counts.items():
1835
- fleet_pct = (count / len(df)) * 100
1836
- st.markdown(f"- **{fleet}**: {count} alerts ({fleet_pct:.1f}%)")
1837
- max_fleet = fleet_counts.idxmax() if not fleet_counts.empty else "N/A"
1838
- if max_fleet != "N/A":
1839
- st.warning(f"Fleet type '{max_fleet}' has the highest fatigue alert count. Investigate vehicle-specific factors.")
1840
- else:
1841
- st.info("Fleet type data not available for analysis.")
1842
-
1843
- # ===================== 6. Top 5 Operators by Alert Count =====================
1844
- if col_operator and col_operator in df.columns:
1845
- top5_operators = df[col_operator].value_counts().head(5)
1846
- st.markdown(f"**Top 5 Operators by Fatigue Alerts**")
1847
- for op, count in top5_operators.items():
1848
- op_pct = (count / len(df)) * 100
1849
- st.markdown(f"- **{op}**: {count} alerts ({op_pct:.1f}%)")
1850
- if not top5_operators.empty:
1851
- top_op = top5_operators.index[0]
1852
- st.warning(f"Operator {top_op} has the highest alert count. Prioritize fatigue intervention.")
1853
- else:
1854
- st.info("Operator data not available for analysis.")
1855
-
1856
- # ===================== 7. Fatigue Alert Trend Over Time (Weekly) =====================
1857
- df_weekly = df.set_index('start').resample('W').size().reset_index(name='count')
1858
- if len(df_weekly) > 0:
1859
- trend_slope = np.polyfit(range(len(df_weekly)), df_weekly['count'], 1)[0]
1860
- st.markdown(f"**Fatigue Alert Trend (Weekly)**")
1861
- if trend_slope > 0.5:
1862
- st.warning(f"Trend is increasing (slope: {trend_slope:.2f}). Fatigue risk is rising.")
1863
- elif trend_slope < -0.5:
1864
- st.info(f"Trend is decreasing (slope: {trend_slope:.2f}). Fatigue risk is improving.")
1865
- else:
1866
- st.info(f"Trend is stable (slope: {trend_slope:.2f}).")
1867
- else:
1868
- st.info("Insufficient data for weekly trend analysis.")
1869
-
1870
- # ===================== 8. Geographic Risk Hotspots =====================
1871
- if 'location' in df.columns or ('lat' in df.columns and 'lon' in df.columns):
1872
- st.markdown(f"**Geographic Risk Hotspots**")
1873
- if 'location' in df.columns:
1874
- loc_counts = df['location'].value_counts().head(3)
1875
- for loc, count in loc_counts.items():
1876
- loc_pct = (count / len(df)) * 100
1877
- st.markdown(f"- **{loc}**: {count} alerts ({loc_pct:.1f}%)")
1878
- else:
1879
- st.info("Geographic data not available for hotspot analysis.")
1880
- else:
1881
- st.info("Geographic data not available for hotspot analysis.")
1882
-
1883
- # ===================== 9. Correlation: Speed vs Hour =====================
1884
- if col_speed and col_speed in df.columns and 'hour' in df.columns:
1885
- corr = df[col_speed].corr(df['hour'])
1886
- st.markdown(f"**Correlation: Speed vs Hour**")
1887
- if abs(corr) > 0.3:
1888
- st.warning(f"Moderate correlation detected (r = {corr:.2f}). Speed patterns may vary by hour.")
1889
- else:
1890
- st.info(f"No strong correlation (r = {corr:.2f}). Speed is consistent across hours.")
1891
- else:
1892
- st.info("Insufficient data for speed vs hour correlation.")
1893
-
1894
- # =====================================================================
1895
- # 🔹 KOLOM KANAN — AI RECOMMENDATIONS
1896
- # =====================================================================
1897
- with col_recs:
1898
- st.subheader("AI Recommendations")
1899
-
1900
- # Generate 9 recommendations based on the 9 insights above
1901
- recs = []
1902
-
1903
- # 1. Critical Hour
1904
- if critical_pct > 10:
1905
- recs.append("Implement mandatory 15-min break protocol during 3-6 AM shifts.")
1906
- else:
1907
- recs.append("Maintain current scheduling for early shifts — risk is low.")
1908
-
1909
- # 2. High-Speed Fatigue
1910
- if 'col_speed' in locals() and col_speed in df.columns:
1911
- if high_speed_pct > 20:
1912
- recs.append("Install speed monitoring systems to alert drivers during high-speed fatigue alerts.")
1913
- else:
1914
- recs.append("Continue monitoring speed-related fatigue — current levels are acceptable.")
1915
- else:
1916
- recs.append("Enable speed data collection to assess high-speed fatigue risk.")
1917
-
1918
- # 3. Weekend Risk
1919
- if weekend_pct > 60:
1920
- recs.append("Schedule mandatory rest periods after weekend shifts to reduce fatigue accumulation.")
1921
- else:
1922
- recs.append("Maintain current weekend scheduling protocols — risk is balanced.")
1923
-
1924
- # 4. Shift Risk
1925
- if col_shift and col_shift in df.columns:
1926
- max_shift = df[col_shift].value_counts().idxmax() if not df[col_shift].value_counts().empty else "N/A"
1927
- if max_shift != "N/A":
1928
- recs.append(f"Review shift {max_shift} scheduling and rest-break frequency for operators.")
1929
- else:
1930
- recs.append("No dominant shift risk — continue monitoring all shifts equally.")
1931
- else:
1932
- recs.append("Enable shift data collection to assess shift-based fatigue risk.")
1933
-
1934
- # 5. Fleet Risk
1935
- if col_fleet_type and col_fleet_type in df.columns:
1936
- max_fleet = df[col_fleet_type].value_counts().idxmax() if not df[col_fleet_type].value_counts().empty else "N/A"
1937
- if max_fleet != "N/A":
1938
- recs.append(f"Investigate fatigue factors specific to fleet type '{max_fleet}' (e.g., ergonomics, route, etc.).")
1939
- else:
1940
- recs.append("No dominant fleet risk — continue monitoring all fleet types equally.")
1941
- else:
1942
- recs.append("Enable fleet type data collection to assess vehicle-specific fatigue risk.")
1943
-
1944
- # 6. Top Operator Risk
1945
- if col_operator and col_operator in df.columns:
1946
- if not df[col_operator].value_counts().empty:
1947
- top_op = df[col_operator].value_counts().index[0]
1948
- recs.append(f"Scheduled one-on-one fatigue risk assessment for operator {top_op}.")
1949
- else:
1950
- recs.append("No top operator identified — continue general monitoring.")
1951
- else:
1952
- recs.append("Enable operator data collection to identify high-risk drivers.")
1953
-
1954
- # 7. Trend Risk
1955
- if len(df_weekly) > 0:
1956
- trend_slope = np.polyfit(range(len(df_weekly)), df_weekly['count'], 1)[0]
1957
- if trend_slope > 0.5:
1958
- recs.append("Initiate immediate fatigue risk review across all shifts and operators.")
1959
- elif trend_slope < -0.5:
1960
- recs.append("Recognize current safety protocols — continue to monitor trend reversal.")
1961
- else:
1962
- recs.append("Maintain current protocols — fatigue trend is stable.")
1963
- else:
1964
- recs.append("Insufficient data to assess fatigue trend.")
1965
-
1966
- # 8. Geographic Risk
1967
- if 'location' in df.columns or ('lat' in df.columns and 'lon' in df.columns):
1968
- if 'location' in df.columns:
1969
- top_loc = df['location'].value_counts().index[0] if not df['location'].value_counts().empty else "N/A"
1970
- if top_loc != "N/A":
1971
- recs.append(f"Review route planning and rest stops for high-risk location: {top_loc}.")
1972
- else:
1973
- recs.append("No geographic hotspots identified.")
1974
- else:
1975
- recs.append("Enable geographic data collection for hotspot analysis.")
1976
- else:
1977
- recs.append("Enable geographic data collection for hotspot analysis.")
1978
-
1979
- # 9. Correlation Risk
1980
- if col_speed and col_speed in df.columns and 'hour' in df.columns:
1981
- corr = df[col_speed].corr(df['hour'])
1982
- if abs(corr) > 0.3:
1983
- recs.append("Implement time-of-day speed limits to reduce hour-speed correlation risk.")
1984
- else:
1985
- recs.append("Speed patterns are consistent — no immediate action required.")
1986
- else:
1987
- recs.append("Enable speed and hour data for correlation analysis.")
1988
-
1989
- # Display all 9 recommendations in one box with 9 list items
1990
- st.markdown(f"""
1991
- <div class="ai-insight-box">
1992
- <div class="ai-insight-title">AI Action Plan</div>
1993
- <ul style="padding-left: 20px; margin: 8px 0; line-height: 1.5;">
1994
- """, unsafe_allow_html=True)
1995
-
1996
- for i, rec in enumerate(recs, 1):
1997
- st.markdown(f"<li>{rec}</li>", unsafe_allow_html=True)
1998
-
1999
- st.markdown("</ul></div>", unsafe_allow_html=True)
2000
- # ===================== 3. Shift Pattern Analysis =====================
2001
- if col_shift and col_shift in df.columns:
2002
-
2003
- shift_counts = df[col_shift].value_counts()
2004
- st.markdown(f"**Shift Pattern Risk**")
2005
-
2006
- for shift_val in shift_counts.index:
2007
- shift_pct = (shift_counts[shift_val] / len(df)) * 100
2008
-
2009
- st.markdown(
2010
- f"""
2011
- <div style="font-size: 24px; font-weight: bold;">{shift_counts[shift_val]}</div>
2012
- <div style="color: red; font-size: 14px; margin-top: -5px;">↑ {shift_pct:.1f}% of total alerts</div>
2013
- """,
2014
- unsafe_allow_html=True
2015
- )
2016
-
2017
- if shift_pct > 50:
2018
- st.warning(
2019
- f"Shift {shift_val} has disproportionately high alerts ({shift_pct:.1f}%). "
2020
- f"Review shift scheduling and workload."
2021
- )
2022
- else:
2023
- st.info(
2024
- f"Shift {shift_val} alert distribution is acceptable ({shift_pct:.1f}%)."
2025
- )
2026
-
2027
- else:
2028
- st.info("Shift data not available for Shift Pattern Analysis.")
2029
-
2030
- # ===================== 4. Operator Risk Profiling =====================
2031
- if col_operator and col_operator in df.columns:
2032
-
2033
- operator_alerts = df[col_operator].value_counts()
2034
- top_risk_operators = operator_alerts.head(5)
2035
-
2036
- st.markdown("**High-Risk Operator Identification**")
2037
- colors = ["#d32f2f", "#e57373", "#ef9a9a", "#ffcdd2", "#ffe1e4"]
2038
-
2039
- for idx, (op_name, count) in enumerate(top_risk_operators.items()):
2040
- op_pct = (count / len(df)) * 100
2041
- color = colors[idx] if idx < len(colors) else colors[-1]
2042
-
2043
- st.markdown(
2044
- f"**Operator:** {op_name} \n**Alerts:** {count}"
2045
- )
2046
- st.markdown(
2047
- f"<span style='font-weight:600'>Share:</span> "
2048
- f"<span style='color:{color}; font-weight:700'>{op_pct:.1f}% of total alerts</span>",
2049
- unsafe_allow_html=True
2050
- )
2051
-
2052
- if op_pct > 5:
2053
- st.warning(
2054
- f"Operator {op_name} has high fatigue risk ({op_pct:.1f}%). "
2055
- f"Consider coaching or rest plan."
2056
- )
2057
- else:
2058
- st.info(
2059
- f"Operator {op_name} fatigue risk is within acceptable range ({op_pct:.1f}%)."
2060
- )
2061
-
2062
- else:
2063
- st.info("Operator data not available for Operator Risk Profiling.")
2064
-
2065
-
2066
- # =====================================================================
2067
- # 🔹 KOLOM KANAN — AI RECOMMENDATIONS
2068
- # =====================================================================
2069
- with col_recs:
2070
-
2071
- st.subheader("Recommendations")
2072
- ai_recs = []
2073
- insights_found = []
2074
-
2075
- # Peak Hour
2076
- if "hour" in df.columns and not df.empty:
2077
- peak_hour = df["hour"].value_counts().idxmax()
2078
- critical_hours = [2, 3, 4, 5]
2079
-
2080
- if peak_hour in critical_hours:
2081
- insights_found.append(
2082
- f"Most fatigue risk occurs at **{peak_hour}:00** — during critical circadian low period (3-6 AM)."
2083
- )
2084
- else:
2085
- insights_found.append(
2086
- f"Most fatigue risk occurs at **{peak_hour}:00** — likely due to circadian drop."
2087
- )
2088
-
2089
- # Risk Shift
2090
- if col_shift and col_shift in df.columns and not df.empty:
2091
- worst_shift = df[col_shift].value_counts().idxmax()
2092
- insights_found.append(
2093
- f"Highest fatigue recorded in **Shift {worst_shift}** — review scheduling & workload."
2094
- )
2095
-
2096
- # Worst Operator
2097
- if col_operator and col_operator in df.columns and not df.empty:
2098
- worst_operator = df[col_operator].value_counts().idxmax()
2099
- insights_found.append(
2100
- f"Operator at highest risk: **{worst_operator}** — suggested coaching or rest plan."
2101
- )
2102
-
2103
- # Duration Risk
2104
- if "duration_sec" in df.columns and not df.empty:
2105
- avg_duration = df["duration_sec"].mean()
2106
- if avg_duration > 10:
2107
- insights_found.append(
2108
- "Long fatigue event duration suggests slow response — improve alerting training."
2109
- )
2110
-
2111
- # ===================== AI DECISION ENGINE =====================
2112
- if insights_found:
2113
-
2114
- if any("circadian" in i.lower() for i in insights_found):
2115
- ai_recs.append({
2116
- "recommendation": "Deploy enhanced fatigue monitoring systems during 3-6 AM.",
2117
- "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}%)",
2118
- "reason": "High percentage of alerts during circadian low period."
2119
- })
2120
-
2121
- if any("shift" in i.lower() for i in insights_found):
2122
- ai_recs.append({
2123
- "recommendation": "Review shift rotation schedules.",
2124
- "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts",
2125
- "reason": "This shift shows highest fatigue alerts."
2126
- })
2127
-
2128
- if any("operator" in i.lower() for i in insights_found):
2129
- ai_recs.append({
2130
- "recommendation": "Coaching or mandatory rest for the identified high-risk operator.",
2131
- "data_point": f"Operator {worst_operator}: {df[col_operator].value_counts()[worst_operator]} alerts",
2132
- "reason": "Operator has highest fatigue alerts."
2133
- })
2134
-
2135
- if any("duration" in i.lower() for i in insights_found):
2136
- ai_recs.append({
2137
- "recommendation": "Improve fatigue alert response training.",
2138
- "data_point": f"Avg Duration: {avg_duration:.1f} sec",
2139
- "reason": "Long fatigue event duration indicates slow response."
2140
- })
2141
-
2142
- # Render all recommendations
2143
- import re
2144
-
2145
- for rec in ai_recs:
2146
-
2147
- data_point_colored = re.sub(
2148
- r'(\d+\.?\d*%)',
2149
- r'<span style="color: red;">\1</span>',
2150
- rec['data_point']
2151
- )
2152
-
2153
- reason_colored = re.sub(
2154
- r'(\d+\.?\d*%)',
2155
- r'<span style="color: red;">\1</span>',
2156
- rec['reason']
2157
- )
2158
-
2159
- st.markdown(
2160
- f"""
2161
- <div style="
2162
- background: #f8f9fa;
2163
- border: 1px solid #dee2e6;
2164
- border-radius: 8px;
2165
- padding: 15px;
2166
- margin: 10px 0;
2167
- ">
2168
- <div style="font-weight: bold; background: #e9ecef; padding: 8px; border-radius: 5px;">
2169
- AI Recommendation
2170
- </div>
2171
- <div style="padding-top: 8px;"><strong>Action:</strong> {rec['recommendation']}</div>
2172
- <div style="padding: 8px; background: #e9ecef; border-radius: 5px;">
2173
- <strong>Data Point:</strong> {data_point_colored}
2174
- </div>
2175
- <div style="padding: 8px; background: #f1f1f1; border-radius: 5px;">
2176
- <strong>AI Reasoning:</strong> {reason_colored}
2177
- </div>
2178
- </div>
2179
- """,
2180
- unsafe_allow_html=True
2181
- )
2182
 
2183
  else:
2184
- st.info(
2185
- "No specific data points available for AI recommendations. "
2186
- "Ensure relevant columns are present (hour, shift, operator, duration, speed)."
2187
- )
2188
-
2189
- # ================= FOOTER ===========================
2190
- st.markdown("---")
2191
- st.markdown(
2192
- '<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>',
2193
- unsafe_allow_html=True
2194
- )
 
1718
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1719
  st.exception(e)
1720
 
1721
+ # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1722
+ else:
1723
+ st.info(f"No strong correlation (r = {corr:.2f}). Speed is consistent across hours.")
1724
+ else:
1725
+ st.info("Insufficient data for speed vs hour correlation.")
1726
+
1727
+ # =====================================================================
1728
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1729
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1730
 
 
1767
 
1768
  # ===================== 2. High-Speed Fatigue Analysis =====================
1769
  if col_speed and col_speed in df.columns:
1770
+
1771
  high_speed_threshold = 20
1772
  high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1773
  high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
 
1790
  st.info(
1791
  f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
1792
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1793
 
1794
  else:
1795
+ st.info("Speed data not available for High-Speed Fatigue Analysis.")