SHELLAPANDIANGANHUNGING commited on
Commit
88d651a
·
verified ·
1 Parent(s): 67295b8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +119 -99
app.py CHANGED
@@ -503,6 +503,12 @@ if apply_filters:
503
  # Filter Group Model
504
  if filter_dict.get('group_model') is not None:
505
  df = df[df['group_model'] == filter_dict['group_model']]
 
 
 
 
 
 
506
 
507
  # Filter Shift
508
  if filter_dict.get('shift') is not None:
@@ -1760,7 +1766,6 @@ with col_insights:
1760
 
1761
  # ===================== 2. High-Speed Fatigue Analysis =====================
1762
  if col_speed and col_speed in df.columns:
1763
-
1764
  high_speed_threshold = 20
1765
  high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1766
  high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
@@ -1783,13 +1788,11 @@ with col_insights:
1783
  st.info(
1784
  f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
1785
  )
1786
-
1787
  else:
1788
  st.info("Speed data not available for High-Speed Fatigue Analysis.")
1789
 
1790
  # ===================== 3. Shift Pattern Analysis =====================
1791
  if col_shift and col_shift in df.columns:
1792
-
1793
  shift_counts = df[col_shift].value_counts()
1794
  st.markdown(f"**Shift Pattern Risk**")
1795
 
@@ -1813,13 +1816,11 @@ with col_insights:
1813
  st.info(
1814
  f"Shift {shift_val} alert distribution is acceptable ({shift_pct:.1f}%)."
1815
  )
1816
-
1817
  else:
1818
  st.info("Shift data not available for Shift Pattern Analysis.")
1819
 
1820
  # ===================== 4. Operator Risk Profiling =====================
1821
  if col_operator and col_operator in df.columns:
1822
-
1823
  operator_alerts = df[col_operator].value_counts()
1824
  top_risk_operators = operator_alerts.head(5)
1825
 
@@ -1848,129 +1849,148 @@ with col_insights:
1848
  st.info(
1849
  f"Operator {op_name} fatigue risk is within acceptable range ({op_pct:.1f}%)."
1850
  )
1851
-
1852
  else:
1853
  st.info("Operator data not available for Operator Risk Profiling.")
1854
 
1855
-
1856
  # =====================================================================
1857
- # 🔹 KOLOM KANAN — AI RECOMMENDATIONS
1858
  # =====================================================================
1859
  with col_recs:
1860
-
1861
  st.subheader("Recommendations")
1862
- ai_recs = []
1863
- insights_found = []
1864
 
1865
- # Peak Hour
 
 
 
1866
  if "hour" in df.columns and not df.empty:
1867
  peak_hour = df["hour"].value_counts().idxmax()
1868
  critical_hours = [2, 3, 4, 5]
1869
 
1870
  if peak_hour in critical_hours:
1871
- insights_found.append(
1872
- f"Most fatigue risk occurs at **{peak_hour}:00** during critical circadian low period (3-6 AM)."
1873
- )
 
 
1874
  else:
1875
- insights_found.append(
1876
- f"Most fatigue risk occurs at **{peak_hour}:00** — likely due to circadian drop."
1877
- )
1878
-
1879
- # Risk Shift
1880
- if col_shift and col_shift in df.columns and not df.empty:
1881
- worst_shift = df[col_shift].value_counts().idxmax()
1882
- insights_found.append(
1883
- f"Highest fatigue recorded in **Shift {worst_shift}** — review scheduling & workload."
1884
- )
1885
-
1886
- # Worst Operator
1887
- if col_operator and col_operator in df.columns and not df.empty:
1888
- worst_operator = df[col_operator].value_counts().idxmax()
1889
- insights_found.append(
1890
- f"Operator at highest risk: **{worst_operator}** — suggested coaching or rest plan."
1891
- )
1892
-
1893
- # Duration Risk
1894
- if "duration_sec" in df.columns and not df.empty:
1895
- avg_duration = df["duration_sec"].mean()
1896
- if avg_duration > 10:
1897
- insights_found.append(
1898
- "Long fatigue event duration suggests slow response — improve alerting training."
1899
- )
1900
 
1901
- # ===================== AI DECISION ENGINE =====================
1902
- if insights_found:
 
 
 
1903
 
1904
- if any("circadian" in i.lower() for i in insights_found):
1905
- ai_recs.append({
1906
- "recommendation": "Deploy enhanced fatigue monitoring systems during 3-6 AM.",
1907
- "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}%)",
1908
- "reason": "High percentage of alerts during circadian low period."
1909
  })
1910
-
1911
- if any("shift" in i.lower() for i in insights_found):
1912
- ai_recs.append({
1913
- "recommendation": "Review shift rotation schedules.",
1914
- "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts",
1915
- "reason": "This shift shows highest fatigue alerts."
1916
  })
1917
 
1918
- if any("operator" in i.lower() for i in insights_found):
1919
- ai_recs.append({
1920
- "recommendation": "Coaching or mandatory rest for the identified high-risk operator.",
1921
- "data_point": f"Operator {worst_operator}: {df[col_operator].value_counts()[worst_operator]} alerts",
1922
- "reason": "Operator has highest fatigue alerts."
1923
- })
1924
 
1925
- if any("duration" in i.lower() for i in insights_found):
1926
- ai_recs.append({
1927
- "recommendation": "Improve fatigue alert response training.",
1928
- "data_point": f"Avg Duration: {avg_duration:.1f} sec",
1929
- "reason": "Long fatigue event duration indicates slow response."
 
 
 
 
 
 
1930
  })
1931
 
1932
- # Render all recommendations
1933
- import re
 
 
1934
 
1935
- for rec in ai_recs:
 
 
 
 
 
 
 
 
 
 
 
1936
 
1937
- data_point_colored = re.sub(
1938
- r'(\d+\.?\d*%)',
1939
- r'<span style="color: red;">\1</span>',
1940
- rec['data_point']
1941
- )
 
 
1942
 
1943
- reason_colored = re.sub(
1944
- r'(\d+\.?\d*%)',
1945
- r'<span style="color: red;">\1</span>',
1946
- rec['reason']
1947
- )
1948
 
1949
- st.markdown(
1950
- f"""
 
 
 
 
 
 
 
 
1951
  <div style="
1952
- background: #f8f9fa;
1953
- border: 1px solid #dee2e6;
1954
- border-radius: 8px;
1955
- padding: 15px;
1956
- margin: 10px 0;
 
1957
  ">
1958
- <div style="font-weight: bold; background: #e9ecef; padding: 8px; border-radius: 5px;">
1959
- AI Recommendation
1960
- </div>
1961
- <div style="padding-top: 8px;"><strong>Action:</strong> {rec['recommendation']}</div>
1962
- <div style="padding: 8px; background: #e9ecef; border-radius: 5px;">
1963
- <strong>Data Point:</strong> {data_point_colored}
1964
- </div>
1965
- <div style="padding: 8px; background: #f1f1f1; border-radius: 5px;">
1966
- <strong>AI Reasoning:</strong> {reason_colored}
1967
- </div>
1968
  </div>
1969
- """,
1970
- unsafe_allow_html=True
1971
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1972
 
1973
- else:
1974
  st.info(
1975
  "No specific data points available for AI recommendations. "
1976
  "Ensure relevant columns are present (hour, shift, operator, duration, speed)."
@@ -1981,4 +2001,4 @@ st.markdown("---")
1981
  st.markdown(
1982
  '<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>',
1983
  unsafe_allow_html=True
1984
- )
 
503
  # Filter Group Model
504
  if filter_dict.get('group_model') is not None:
505
  df = df[df['group_model'] == filter_dict['group_model']]
506
+
507
+ # UI display mapping (for rendering only — data remains unchanged)
508
+ group_model_display = {
509
+ 'OB HAULLER': 'OB HAULER',
510
+ 'HAULING COAL': 'COAL HAULING'
511
+ }
512
 
513
  # Filter Shift
514
  if filter_dict.get('shift') is not None:
 
1766
 
1767
  # ===================== 2. High-Speed Fatigue Analysis =====================
1768
  if col_speed and col_speed in df.columns:
 
1769
  high_speed_threshold = 20
1770
  high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1771
  high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
 
1788
  st.info(
1789
  f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
1790
  )
 
1791
  else:
1792
  st.info("Speed data not available for High-Speed Fatigue Analysis.")
1793
 
1794
  # ===================== 3. Shift Pattern Analysis =====================
1795
  if col_shift and col_shift in df.columns:
 
1796
  shift_counts = df[col_shift].value_counts()
1797
  st.markdown(f"**Shift Pattern Risk**")
1798
 
 
1816
  st.info(
1817
  f"Shift {shift_val} alert distribution is acceptable ({shift_pct:.1f}%)."
1818
  )
 
1819
  else:
1820
  st.info("Shift data not available for Shift Pattern Analysis.")
1821
 
1822
  # ===================== 4. Operator Risk Profiling =====================
1823
  if col_operator and col_operator in df.columns:
 
1824
  operator_alerts = df[col_operator].value_counts()
1825
  top_risk_operators = operator_alerts.head(5)
1826
 
 
1849
  st.info(
1850
  f"Operator {op_name} fatigue risk is within acceptable range ({op_pct:.1f}%)."
1851
  )
 
1852
  else:
1853
  st.info("Operator data not available for Operator Risk Profiling.")
1854
 
 
1855
  # =====================================================================
1856
+ # 🔹 KOLOM KANAN — AI RECOMMENDATIONS (PER INSIGHT)
1857
  # =====================================================================
1858
  with col_recs:
 
1859
  st.subheader("Recommendations")
 
 
1860
 
1861
+ # Reset list to collect recommendations per insight
1862
+ ai_recommendations = []
1863
+
1864
+ # 1. Critical Hour Insight → AI Rec
1865
  if "hour" in df.columns and not df.empty:
1866
  peak_hour = df["hour"].value_counts().idxmax()
1867
  critical_hours = [2, 3, 4, 5]
1868
 
1869
  if peak_hour in critical_hours:
1870
+ ai_recommendations.append({
1871
+ "action": "Deploy enhanced fatigue monitoring systems during 3-6 AM.",
1872
+ "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}%)",
1873
+ "reasoning": "High percentage of alerts during circadian low period."
1874
+ })
1875
  else:
1876
+ ai_recommendations.append({
1877
+ "action": "Monitor fatigue patterns around peak hour (Hour {peak_hour}).",
1878
+ "data_point": f"Peak Hour: {peak_hour}:00 — {df['hour'].value_counts()[peak_hour]} alerts",
1879
+ "reasoning": "This hour shows highest fatigue occurrence."
1880
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1881
 
1882
+ # 2. High-Speed Insight AI Rec
1883
+ if col_speed and col_speed in df.columns and not df.empty:
1884
+ high_speed_threshold = 20
1885
+ high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1886
+ high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
1887
 
1888
+ if high_speed_pct > 20:
1889
+ ai_recommendations.append({
1890
+ "action": "Implement speed-reduction protocols during fatigue-prone hours.",
1891
+ "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
1892
+ "reasoning": "High-speed alerts increase accident severity potential."
1893
  })
1894
+ else:
1895
+ ai_recommendations.append({
1896
+ "action": "Maintain current speed monitoring — risk level is acceptable.",
1897
+ "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
1898
+ "reasoning": "Current high-speed fatigue rate is within acceptable range."
 
1899
  })
1900
 
1901
+ # 3. Shift Pattern Insight AI Rec
1902
+ if col_shift and col_shift in df.columns and not df.empty:
1903
+ worst_shift = df[col_shift].value_counts().idxmax()
1904
+ shift_pct = (df[col_shift].value_counts()[worst_shift] / len(df)) * 100
 
 
1905
 
1906
+ if shift_pct > 50:
1907
+ ai_recommendations.append({
1908
+ "action": "Review shift rotation schedules for Shift {worst_shift}.",
1909
+ "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
1910
+ "reasoning": "Disproportionately high fatigue alerts indicate scheduling imbalance."
1911
+ })
1912
+ else:
1913
+ ai_recommendations.append({
1914
+ "action": "Continue monitoring all shifts — no dominant risk identified.",
1915
+ "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
1916
+ "reasoning": "Shift distribution is balanced."
1917
  })
1918
 
1919
+ # 4. Operator Risk Insight → AI Rec
1920
+ if col_operator and col_operator in df.columns and not df.empty:
1921
+ worst_operator = df[col_operator].value_counts().idxmax()
1922
+ op_pct = (df[col_operator].value_counts()[worst_operator] / len(df)) * 100
1923
 
1924
+ if op_pct > 5:
1925
+ ai_recommendations.append({
1926
+ "action": "Coaching or mandatory rest for the identified high-risk operator.",
1927
+ "data_point": f"Operator {worst_operator}: {df[col_operator].value_counts()[worst_operator]} alerts ({op_pct:.1f}%)",
1928
+ "reasoning": "Operator has highest fatigue alerts — requires individual intervention."
1929
+ })
1930
+ else:
1931
+ ai_recommendations.append({
1932
+ "action": "Continue general monitoring — no single operator dominates risk.",
1933
+ "data_point": f"Top Operator: {worst_operator} — {df[col_operator].value_counts()[worst_operator]} alerts ({op_pct:.1f}%)",
1934
+ "reasoning": "Risk is distributed across operators — no urgent individual action needed."
1935
+ })
1936
 
1937
+ # Render each recommendation as a card
1938
+ for rec in ai_recommendations:
1939
+ # Highlight percentages in red
1940
+ data_point_colored = rec['data_point'].replace(
1941
+ f"({rec['data_point'].split('(')[-1]}",
1942
+ f"(<span style='color: red;'>{rec['data_point'].split('(')[-1]}"
1943
+ ).replace(")", "</span>)")
1944
 
1945
+ reasoning_colored = rec['reasoning'].replace(
1946
+ f"({rec['reasoning'].split('(')[-1]}",
1947
+ f"(<span style='color: red;'>{rec['reasoning'].split('(')[-1]}"
1948
+ ).replace(")", "</span>)")
 
1949
 
1950
+ st.markdown(
1951
+ f"""
1952
+ <div style="
1953
+ background: #f8f9fa;
1954
+ border: 1px solid #dee2e6;
1955
+ border-radius: 8px;
1956
+ padding: 15px;
1957
+ margin: 10px 0;
1958
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1959
+ ">
1960
  <div style="
1961
+ font-weight: bold;
1962
+ background: #e9ecef;
1963
+ padding: 8px;
1964
+ border-radius: 5px;
1965
+ margin-bottom: 8px;
1966
+ border-left: 4px solid #495057;
1967
  ">
1968
+ AI Recommendation
 
 
 
 
 
 
 
 
 
1969
  </div>
1970
+ <div style="padding: 8px 0;">
1971
+ <strong>Action:</strong> {rec['action']}
1972
+ </div>
1973
+ <div style="
1974
+ padding: 8px;
1975
+ background: #f1f1f1;
1976
+ border-radius: 5px;
1977
+ margin: 8px 0;
1978
+ ">
1979
+ <strong>Data Point:</strong> {data_point_colored}
1980
+ </div>
1981
+ <div style="
1982
+ padding: 8px;
1983
+ background: #f1f1f1;
1984
+ border-radius: 5px;
1985
+ ">
1986
+ <strong>AI Reasoning:</strong> {reasoning_colored}
1987
+ </div>
1988
+ </div>
1989
+ """,
1990
+ unsafe_allow_html=True
1991
+ )
1992
 
1993
+ if not ai_recommendations:
1994
  st.info(
1995
  "No specific data points available for AI recommendations. "
1996
  "Ensure relevant columns are present (hour, shift, operator, duration, speed)."
 
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
+ )