SHELLAPANDIANGANHUNGING commited on
Commit
9c0e3d9
·
verified ·
1 Parent(s): 8e92fda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +225 -7
app.py CHANGED
@@ -1156,7 +1156,7 @@ import streamlit as st
1156
  import pandas as pd
1157
  import numpy as np
1158
  import plotly.graph_objects as go
1159
- st.subheader("OBJECTIVE 5: See your team’s fatigue Fatigue Hazard Profile!")
1160
 
1161
  # Custom CSS — tetap seperti sebelumnya (sudah sesuai preferensi)
1162
  st.markdown("""
@@ -1384,7 +1384,7 @@ else:
1384
  # ===============================================================
1385
  # LEGEND — UPDATED: Stable → One Time Event, Gray → Yellow
1386
  # ===============================================================
1387
- st.subheader("Hazard Gradient Legend")
1388
 
1389
  st.markdown("""
1390
  <div class="legend-container">
@@ -1519,15 +1519,23 @@ else:
1519
  )
1520
  return fig
1521
 
 
 
 
1522
  # ===============================================================
1523
  # CHARTS
1524
  # ===============================================================
1525
  col1, col2 = st.columns(2)
1526
  with col1:
1527
- st.plotly_chart(plot_chart(top_ob, "OB HAULER Operators (Hazard Gradient)"), use_container_width=True)
 
 
 
1528
  with col2:
1529
- st.plotly_chart(plot_chart(top_coal, "HAULING COAL Operators (Hazard Gradient)"), use_container_width=True)
1530
-
 
 
1531
  # ===============================================================
1532
  # AI INSIGHTS — DIPERBAIKI: Risk Summary jadi 1 box + 3 list
1533
  # ===============================================================
@@ -1752,7 +1760,6 @@ with col_insights:
1752
 
1753
  # ===================== 2. High-Speed Fatigue Analysis =====================
1754
  if col_speed and col_speed in df.columns:
1755
-
1756
  high_speed_threshold = 20
1757
  high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1758
  high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
@@ -1775,10 +1782,221 @@ with col_insights:
1775
  st.info(
1776
  f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
1777
  )
1778
-
1779
  else:
1780
  st.info("Speed data not available for High-Speed Fatigue Analysis.")
1781
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1782
  # ===================== 3. Shift Pattern Analysis =====================
1783
  if col_shift and col_shift in df.columns:
1784
 
 
1156
  import pandas as pd
1157
  import numpy as np
1158
  import plotly.graph_objects as go
1159
+ st.subheader("OBJECTIVE 5: See your team’s Fatigue Hazard Profile!")
1160
 
1161
  # Custom CSS — tetap seperti sebelumnya (sudah sesuai preferensi)
1162
  st.markdown("""
 
1384
  # ===============================================================
1385
  # LEGEND — UPDATED: Stable → One Time Event, Gray → Yellow
1386
  # ===============================================================
1387
+ st.subheader("Legend of Frequency Trends")
1388
 
1389
  st.markdown("""
1390
  <div class="legend-container">
 
1519
  )
1520
  return fig
1521
 
1522
+ # ===============================================================
1523
+ # CHARTS
1524
+ # ===============================================================
1525
  # ===============================================================
1526
  # CHARTS
1527
  # ===============================================================
1528
  col1, col2 = st.columns(2)
1529
  with col1:
1530
+ # Center-align chart
1531
+ st.markdown("<div style='display: flex; justify-content: center;'>", unsafe_allow_html=True)
1532
+ st.plotly_chart(plot_chart(top_ob, "OB HAULER Operator Hazard Profile"), use_container_width=False, config={'displayModeBar': False})
1533
+ st.markdown("</div>", unsafe_allow_html=True)
1534
  with col2:
1535
+ # Center-align chart
1536
+ st.markdown("<div style='display: flex; justify-content: center;'>", unsafe_allow_html=True)
1537
+ st.plotly_chart(plot_chart(top_coal, "COAL HAULING Operator Hazard Profile"), use_container_width=False, config={'displayModeBar': False})
1538
+ st.markdown("</div>", unsafe_allow_html=True)
1539
  # ===============================================================
1540
  # AI INSIGHTS — DIPERBAIKI: Risk Summary jadi 1 box + 3 list
1541
  # ===============================================================
 
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
  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