SHELLAPANDIANGANHUNGING commited on
Commit
cb5c397
·
verified ·
1 Parent(s): 48b7846

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +358 -54
app.py CHANGED
@@ -1743,6 +1743,287 @@ else:
1743
 
1744
 
1745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1746
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1747
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1748
 
@@ -1872,7 +2153,7 @@ with col_insights:
1872
  st.info("Operator data not available for Operator Risk Profiling.")
1873
 
1874
  # =====================================================================
1875
- # 🔹 KOLOM KANAN — AI RECOMMENDATIONS (PER INSIGHT + PER OPERATOR)
1876
  # =====================================================================
1877
  with col_recs:
1878
  st.subheader("Recommendations")
@@ -1886,12 +2167,14 @@ with col_recs:
1886
 
1887
  if peak_hour in critical_hours:
1888
  ai_recommendations.append({
 
1889
  "action": "Deploy enhanced fatigue monitoring systems during 3-6 AM.",
1890
  "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}%)",
1891
  "reasoning": "High percentage of alerts during circadian low period."
1892
  })
1893
  else:
1894
  ai_recommendations.append({
 
1895
  "action": "Monitor fatigue patterns around peak hour (Hour {peak_hour}).",
1896
  "data_point": f"Peak Hour: {peak_hour}:00 — {df['hour'].value_counts()[peak_hour]} alerts",
1897
  "reasoning": "This hour shows highest fatigue occurrence."
@@ -1905,12 +2188,14 @@ with col_recs:
1905
 
1906
  if high_speed_pct > 20:
1907
  ai_recommendations.append({
 
1908
  "action": "Implement speed-reduction protocols during fatigue-prone hours.",
1909
  "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
1910
  "reasoning": "High-speed alerts increase accident severity potential."
1911
  })
1912
  else:
1913
  ai_recommendations.append({
 
1914
  "action": "Maintain current speed monitoring — risk level is acceptable.",
1915
  "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
1916
  "reasoning": "Current high-speed fatigue rate is within acceptable range."
@@ -1923,18 +2208,20 @@ with col_recs:
1923
 
1924
  if shift_pct > 50:
1925
  ai_recommendations.append({
 
1926
  "action": "Review shift rotation schedules for Shift {worst_shift}.",
1927
  "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
1928
  "reasoning": "Disproportionately high fatigue alerts indicate scheduling imbalance."
1929
  })
1930
  else:
1931
  ai_recommendations.append({
 
1932
  "action": "Continue monitoring all shifts — no dominant risk identified.",
1933
  "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
1934
  "reasoning": "Shift distribution is balanced."
1935
  })
1936
 
1937
- # 4. Operator Risk Profiling → AI Rec for EACH of Top 5 Operators
1938
  if col_operator and col_operator in df.columns and not df.empty:
1939
  top_operators = df[col_operator].value_counts().head(5)
1940
  for op_name, count in top_operators.items():
@@ -1942,72 +2229,89 @@ with col_recs:
1942
 
1943
  if op_pct > 5:
1944
  ai_recommendations.append({
 
1945
  "action": f"Coaching or mandatory rest for Operator {op_name}.",
1946
- "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)",
1947
- "reasoning": f"Operator has high fatigue alerts — requires individual intervention."
1948
  })
1949
  else:
1950
  ai_recommendations.append({
 
1951
  "action": f"Continue general monitoring for Operator {op_name}.",
1952
- "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)",
1953
- "reasoning": f"Risk is within acceptable range — no urgent action needed."
1954
  })
1955
 
1956
- # Render each recommendation as a card
1957
  for rec in ai_recommendations:
1958
- # Highlight percentages in red
1959
- data_point_colored = rec['data_point'].replace(
1960
- f"({rec['data_point'].split('(')[-1]}",
1961
- f"(<span style='color: red;'>{rec['data_point'].split('(')[-1]}"
1962
- ).replace(")", "</span>)")
1963
-
1964
- reasoning_colored = rec['reasoning'].replace(
1965
- f"({rec['reasoning'].split('(')[-1]}",
1966
- f"(<span style='color: red;'>{rec['reasoning'].split('(')[-1]}"
1967
- ).replace(")", "</span>)")
1968
 
1969
- st.markdown(
1970
- f"""
1971
- <div style="
1972
- background: #f8f9fa;
1973
- border: 1px solid #dee2e6;
1974
- border-radius: 8px;
1975
- padding: 15px;
1976
- margin: 10px 0;
1977
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1978
- ">
1979
- <div style="
1980
- font-weight: bold;
1981
- background: #e9ecef;
1982
- padding: 8px;
1983
- border-radius: 5px;
1984
- margin-bottom: 8px;
1985
- border-left: 4px solid #495057;
1986
- ">
1987
- AI Recommendation
1988
- </div>
1989
- <div style="padding: 8px 0;">
1990
- <strong>Action:</strong> {rec['action']}
1991
- </div>
1992
- <div style="
1993
- padding: 8px;
1994
- background: #f1f1f1;
1995
- border-radius: 5px;
1996
- margin: 8px 0;
1997
- ">
1998
  <strong>Data Point:</strong> {data_point_colored}
1999
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2000
  <div style="
2001
- padding: 8px;
2002
- background: #f1f1f1;
2003
- border-radius: 5px;
 
 
 
2004
  ">
2005
- <strong>AI Reasoning:</strong> {reasoning_colored}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2006
  </div>
2007
- </div>
2008
- """,
2009
- unsafe_allow_html=True
2010
- )
2011
 
2012
  if not ai_recommendations:
2013
  st.info(
 
1743
 
1744
 
1745
 
1746
+ # # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1747
+ # st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1748
+
1749
+ # # Membagi tampilan menjadi dua kolom
1750
+ # col_insights, col_recs = st.columns(2)
1751
+
1752
+ # # =====================================================================
1753
+ # # 🔹 KOLOM KIRI — INSIGHTS BY ADVANCED ANALYTICS
1754
+ # # =====================================================================
1755
+ # with col_insights:
1756
+ # st.subheader("Insights by Advanced Analytics")
1757
+
1758
+ # # ===================== 1. Critical Hour Analysis =====================
1759
+ # critical_hours = [2, 3, 4, 5]
1760
+ # critical_alerts = df[df['hour'].isin(critical_hours)]
1761
+ # critical_pct = (len(critical_alerts) / len(df)) * 100 if len(df) > 0 else 0
1762
+
1763
+ # st.markdown(f"**Critical Hour Risk (3-6 AM)**")
1764
+ # bg_color = (
1765
+ # "#ffcccc" if critical_pct > 50 else
1766
+ # "#ffebcc" if critical_pct > 25 else
1767
+ # "#ffffcc" if critical_pct > 10 else
1768
+ # "#e6ffe6"
1769
+ # )
1770
+ # st.markdown(
1771
+ # f'<div style="background-color: {bg_color}; padding: 10px; border-radius: 5px;">'
1772
+ # f'Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}% of total alerts)</div>',
1773
+ # unsafe_allow_html=True
1774
+ # )
1775
+
1776
+ # if critical_pct > 10:
1777
+ # st.warning(
1778
+ # f"High risk: {critical_pct:.1f}% of fatigue alerts occur during critical hours (3-6 AM). "
1779
+ # f"This is a known circadian dip period."
1780
+ # )
1781
+ # else:
1782
+ # st.info(
1783
+ # f"{critical_pct:.1f}% of alerts occur during critical hours. This is within acceptable range."
1784
+ # )
1785
+
1786
+ # # ===================== 2. High-Speed Fatigue Analysis =====================
1787
+ # if col_speed and col_speed in df.columns:
1788
+ # high_speed_threshold = 20
1789
+ # high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1790
+ # high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
1791
+
1792
+ # st.markdown(f"**High-Speed Fatigue Risk (Speed > {high_speed_threshold} km/h)**")
1793
+ # st.markdown(
1794
+ # f"""
1795
+ # <div style="font-size: 24px; font-weight: bold;">{len(high_speed_fatigue)}</div>
1796
+ # <div style="color: red; font-size: 14px; margin-top: -5px;">↑ {high_speed_pct:.1f}% of total alerts</div>
1797
+ # """,
1798
+ # unsafe_allow_html=True
1799
+ # )
1800
+
1801
+ # if high_speed_pct > 20:
1802
+ # st.warning(
1803
+ # f"High risk: {high_speed_pct:.1f}% of fatigue alerts occur at high speeds. "
1804
+ # f"This increases accident severity potential."
1805
+ # )
1806
+ # else:
1807
+ # st.info(
1808
+ # f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
1809
+ # )
1810
+ # else:
1811
+ # st.info("Speed data not available for High-Speed Fatigue Analysis.")
1812
+
1813
+ # # ===================== 3. Shift Pattern Analysis =====================
1814
+ # if col_shift and col_shift in df.columns:
1815
+ # shift_counts = df[col_shift].value_counts()
1816
+ # st.markdown(f"**Shift Pattern Risk**")
1817
+
1818
+ # for shift_val in shift_counts.index:
1819
+ # shift_pct = (shift_counts[shift_val] / len(df)) * 100
1820
+
1821
+ # st.markdown(
1822
+ # f"""
1823
+ # <div style="font-size: 24px; font-weight: bold;">{shift_counts[shift_val]}</div>
1824
+ # <div style="color: red; font-size: 14px; margin-top: -5px;">↑ {shift_pct:.1f}% of total alerts</div>
1825
+ # """,
1826
+ # unsafe_allow_html=True
1827
+ # )
1828
+
1829
+ # if shift_pct > 50:
1830
+ # st.warning(
1831
+ # f"Shift {shift_val} has disproportionately high alerts ({shift_pct:.1f}%). "
1832
+ # f"Review shift scheduling and workload."
1833
+ # )
1834
+ # else:
1835
+ # st.info(
1836
+ # f"Shift {shift_val} alert distribution is acceptable ({shift_pct:.1f}%)."
1837
+ # )
1838
+ # else:
1839
+ # st.info("Shift data not available for Shift Pattern Analysis.")
1840
+
1841
+ # # ===================== 4. Operator Risk Profiling =====================
1842
+ # if col_operator and col_operator in df.columns:
1843
+ # operator_alerts = df[col_operator].value_counts()
1844
+ # top_risk_operators = operator_alerts.head(5)
1845
+
1846
+ # st.markdown("**High-Risk Operator Identification**")
1847
+ # colors = ["#d32f2f", "#e57373", "#ef9a9a", "#ffcdd2", "#ffe1e4"]
1848
+
1849
+ # for idx, (op_name, count) in enumerate(top_risk_operators.items()):
1850
+ # op_pct = (count / len(df)) * 100
1851
+ # color = colors[idx] if idx < len(colors) else colors[-1]
1852
+
1853
+ # st.markdown(
1854
+ # f"**Operator:** {op_name} \n**Alerts:** {count}"
1855
+ # )
1856
+ # st.markdown(
1857
+ # f"<span style='font-weight:600'>Share:</span> "
1858
+ # f"<span style='color:{color}; font-weight:700'>{op_pct:.1f}% of total alerts</span>",
1859
+ # unsafe_allow_html=True
1860
+ # )
1861
+
1862
+ # if op_pct > 5:
1863
+ # st.warning(
1864
+ # f"Operator {op_name} has high fatigue risk ({op_pct:.1f}%). "
1865
+ # f"Consider coaching or rest plan."
1866
+ # )
1867
+ # else:
1868
+ # st.info(
1869
+ # f"Operator {op_name} fatigue risk is within acceptable range ({op_pct:.1f}%)."
1870
+ # )
1871
+ # else:
1872
+ # st.info("Operator data not available for Operator Risk Profiling.")
1873
+
1874
+ # # =====================================================================
1875
+ # # 🔹 KOLOM KANAN — AI RECOMMENDATIONS (PER INSIGHT + PER OPERATOR)
1876
+ # # =====================================================================
1877
+ # with col_recs:
1878
+ # st.subheader("Recommendations")
1879
+
1880
+ # ai_recommendations = []
1881
+
1882
+ # # 1. Critical Hour Insight → AI Rec
1883
+ # if "hour" in df.columns and not df.empty:
1884
+ # peak_hour = df["hour"].value_counts().idxmax()
1885
+ # critical_hours = [2, 3, 4, 5]
1886
+
1887
+ # if peak_hour in critical_hours:
1888
+ # ai_recommendations.append({
1889
+ # "action": "Deploy enhanced fatigue monitoring systems during 3-6 AM.",
1890
+ # "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}%)",
1891
+ # "reasoning": "High percentage of alerts during circadian low period."
1892
+ # })
1893
+ # else:
1894
+ # ai_recommendations.append({
1895
+ # "action": "Monitor fatigue patterns around peak hour (Hour {peak_hour}).",
1896
+ # "data_point": f"Peak Hour: {peak_hour}:00 — {df['hour'].value_counts()[peak_hour]} alerts",
1897
+ # "reasoning": "This hour shows highest fatigue occurrence."
1898
+ # })
1899
+
1900
+ # # 2. High-Speed Insight → AI Rec
1901
+ # if col_speed and col_speed in df.columns and not df.empty:
1902
+ # high_speed_threshold = 20
1903
+ # high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1904
+ # high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
1905
+
1906
+ # if high_speed_pct > 20:
1907
+ # ai_recommendations.append({
1908
+ # "action": "Implement speed-reduction protocols during fatigue-prone hours.",
1909
+ # "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
1910
+ # "reasoning": "High-speed alerts increase accident severity potential."
1911
+ # })
1912
+ # else:
1913
+ # ai_recommendations.append({
1914
+ # "action": "Maintain current speed monitoring — risk level is acceptable.",
1915
+ # "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
1916
+ # "reasoning": "Current high-speed fatigue rate is within acceptable range."
1917
+ # })
1918
+
1919
+ # # 3. Shift Pattern Insight → AI Rec
1920
+ # if col_shift and col_shift in df.columns and not df.empty:
1921
+ # worst_shift = df[col_shift].value_counts().idxmax()
1922
+ # shift_pct = (df[col_shift].value_counts()[worst_shift] / len(df)) * 100
1923
+
1924
+ # if shift_pct > 50:
1925
+ # ai_recommendations.append({
1926
+ # "action": "Review shift rotation schedules for Shift {worst_shift}.",
1927
+ # "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
1928
+ # "reasoning": "Disproportionately high fatigue alerts indicate scheduling imbalance."
1929
+ # })
1930
+ # else:
1931
+ # ai_recommendations.append({
1932
+ # "action": "Continue monitoring all shifts — no dominant risk identified.",
1933
+ # "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
1934
+ # "reasoning": "Shift distribution is balanced."
1935
+ # })
1936
+
1937
+ # # 4. Operator Risk Profiling → AI Rec for EACH of Top 5 Operators
1938
+ # if col_operator and col_operator in df.columns and not df.empty:
1939
+ # top_operators = df[col_operator].value_counts().head(5)
1940
+ # for op_name, count in top_operators.items():
1941
+ # op_pct = (count / len(df)) * 100
1942
+
1943
+ # if op_pct > 5:
1944
+ # ai_recommendations.append({
1945
+ # "action": f"Coaching or mandatory rest for Operator {op_name}.",
1946
+ # "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)",
1947
+ # "reasoning": f"Operator has high fatigue alerts — requires individual intervention."
1948
+ # })
1949
+ # else:
1950
+ # ai_recommendations.append({
1951
+ # "action": f"Continue general monitoring for Operator {op_name}.",
1952
+ # "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)",
1953
+ # "reasoning": f"Risk is within acceptable range — no urgent action needed."
1954
+ # })
1955
+
1956
+ # # Render each recommendation as a card
1957
+ # for rec in ai_recommendations:
1958
+ # # Highlight percentages in red
1959
+ # data_point_colored = rec['data_point'].replace(
1960
+ # f"({rec['data_point'].split('(')[-1]}",
1961
+ # f"(<span style='color: red;'>{rec['data_point'].split('(')[-1]}"
1962
+ # ).replace(")", "</span>)")
1963
+
1964
+ # reasoning_colored = rec['reasoning'].replace(
1965
+ # f"({rec['reasoning'].split('(')[-1]}",
1966
+ # f"(<span style='color: red;'>{rec['reasoning'].split('(')[-1]}"
1967
+ # ).replace(")", "</span>)")
1968
+
1969
+ # st.markdown(
1970
+ # f"""
1971
+ # <div style="
1972
+ # background: #f8f9fa;
1973
+ # border: 1px solid #dee2e6;
1974
+ # border-radius: 8px;
1975
+ # padding: 15px;
1976
+ # margin: 10px 0;
1977
+ # box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1978
+ # ">
1979
+ # <div style="
1980
+ # font-weight: bold;
1981
+ # background: #e9ecef;
1982
+ # padding: 8px;
1983
+ # border-radius: 5px;
1984
+ # margin-bottom: 8px;
1985
+ # border-left: 4px solid #495057;
1986
+ # ">
1987
+ # AI Recommendation
1988
+ # </div>
1989
+ # <div style="padding: 8px 0;">
1990
+ # <strong>Action:</strong> {rec['action']}
1991
+ # </div>
1992
+ # <div style="
1993
+ # padding: 8px;
1994
+ # background: #f1f1f1;
1995
+ # border-radius: 5px;
1996
+ # margin: 8px 0;
1997
+ # ">
1998
+ # <strong>Data Point:</strong> {data_point_colored}
1999
+ # </div>
2000
+ # <div style="
2001
+ # padding: 8px;
2002
+ # background: #f1f1f1;
2003
+ # border-radius: 5px;
2004
+ # ">
2005
+ # <strong>AI Reasoning:</strong> {reasoning_colored}
2006
+ # </div>
2007
+ # </div>
2008
+ # """,
2009
+ # unsafe_allow_html=True
2010
+ # )
2011
+
2012
+ # if not ai_recommendations:
2013
+ # st.info(
2014
+ # "No specific data points available for AI recommendations. "
2015
+ # "Ensure relevant columns are present (hour, shift, operator, duration, speed)."
2016
+ # )
2017
+
2018
+ # # ================= FOOTER ===========================
2019
+ # st.markdown("---")
2020
+ # st.markdown(
2021
+ # '<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>',
2022
+ # unsafe_allow_html=True
2023
+
2024
+
2025
+ # )
2026
+
2027
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
2028
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
2029
 
 
2153
  st.info("Operator data not available for Operator Risk Profiling.")
2154
 
2155
  # =====================================================================
2156
+ # 🔹 KOLOM KANAN — AI RECOMMENDATIONS
2157
  # =====================================================================
2158
  with col_recs:
2159
  st.subheader("Recommendations")
 
2167
 
2168
  if peak_hour in critical_hours:
2169
  ai_recommendations.append({
2170
+ "type": "critical_hour",
2171
  "action": "Deploy enhanced fatigue monitoring systems during 3-6 AM.",
2172
  "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}%)",
2173
  "reasoning": "High percentage of alerts during circadian low period."
2174
  })
2175
  else:
2176
  ai_recommendations.append({
2177
+ "type": "critical_hour",
2178
  "action": "Monitor fatigue patterns around peak hour (Hour {peak_hour}).",
2179
  "data_point": f"Peak Hour: {peak_hour}:00 — {df['hour'].value_counts()[peak_hour]} alerts",
2180
  "reasoning": "This hour shows highest fatigue occurrence."
 
2188
 
2189
  if high_speed_pct > 20:
2190
  ai_recommendations.append({
2191
+ "type": "high_speed",
2192
  "action": "Implement speed-reduction protocols during fatigue-prone hours.",
2193
  "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
2194
  "reasoning": "High-speed alerts increase accident severity potential."
2195
  })
2196
  else:
2197
  ai_recommendations.append({
2198
+ "type": "high_speed",
2199
  "action": "Maintain current speed monitoring — risk level is acceptable.",
2200
  "data_point": f"High-Speed Alerts: {len(high_speed_fatigue)} ({high_speed_pct:.1f}%)",
2201
  "reasoning": "Current high-speed fatigue rate is within acceptable range."
 
2208
 
2209
  if shift_pct > 50:
2210
  ai_recommendations.append({
2211
+ "type": "shift_pattern",
2212
  "action": "Review shift rotation schedules for Shift {worst_shift}.",
2213
  "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
2214
  "reasoning": "Disproportionately high fatigue alerts indicate scheduling imbalance."
2215
  })
2216
  else:
2217
  ai_recommendations.append({
2218
+ "type": "shift_pattern",
2219
  "action": "Continue monitoring all shifts — no dominant risk identified.",
2220
  "data_point": f"Shift {worst_shift}: {df[col_shift].value_counts()[worst_shift]} alerts ({shift_pct:.1f}%)",
2221
  "reasoning": "Shift distribution is balanced."
2222
  })
2223
 
2224
+ # 4. Operator Risk Profiling → Simple Recommendations (No AI Reasoning, No Box)
2225
  if col_operator and col_operator in df.columns and not df.empty:
2226
  top_operators = df[col_operator].value_counts().head(5)
2227
  for op_name, count in top_operators.items():
 
2229
 
2230
  if op_pct > 5:
2231
  ai_recommendations.append({
2232
+ "type": "operator",
2233
  "action": f"Coaching or mandatory rest for Operator {op_name}.",
2234
+ "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)"
 
2235
  })
2236
  else:
2237
  ai_recommendations.append({
2238
+ "type": "operator",
2239
  "action": f"Continue general monitoring for Operator {op_name}.",
2240
+ "data_point": f"Operator {op_name}: {count} alerts ({op_pct:.1f}%)"
 
2241
  })
2242
 
2243
+ # Render each recommendation based on type
2244
  for rec in ai_recommendations:
2245
+ if rec["type"] == "operator":
2246
+ # Simple format: Action + Data Point only
2247
+ data_point_colored = rec['data_point'].replace(
2248
+ f"({rec['data_point'].split('(')[-1]}",
2249
+ f"(<span style='color: red;'>{rec['data_point'].split('(')[-1]}"
2250
+ ).replace(")", "</span>)")
 
 
 
 
2251
 
2252
+ st.markdown(
2253
+ f"""
2254
+ <div style="margin: 10px 0; padding: 10px; background: #f8f9fa; border-left: 4px solid #495057; border-radius: 5px;">
2255
+ <strong>Action:</strong> {rec['action']}<br>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2256
  <strong>Data Point:</strong> {data_point_colored}
2257
  </div>
2258
+ """,
2259
+ unsafe_allow_html=True
2260
+ )
2261
+ else:
2262
+ # Standard format with AI Reasoning and box
2263
+ data_point_colored = rec['data_point'].replace(
2264
+ f"({rec['data_point'].split('(')[-1]}",
2265
+ f"(<span style='color: red;'>{rec['data_point'].split('(')[-1]}"
2266
+ ).replace(")", "</span>)")
2267
+
2268
+ reasoning_colored = rec['reasoning'].replace(
2269
+ f"({rec['reasoning'].split('(')[-1]}",
2270
+ f"(<span style='color: red;'>{rec['reasoning'].split('(')[-1]}"
2271
+ ).replace(")", "</span>)")
2272
+
2273
+ st.markdown(
2274
+ f"""
2275
  <div style="
2276
+ background: #f8f9fa;
2277
+ border: 1px solid #dee2e6;
2278
+ border-radius: 8px;
2279
+ padding: 15px;
2280
+ margin: 10px 0;
2281
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
2282
  ">
2283
+ <div style="
2284
+ font-weight: bold;
2285
+ background: #e9ecef;
2286
+ padding: 8px;
2287
+ border-radius: 5px;
2288
+ margin-bottom: 8px;
2289
+ border-left: 4px solid #495057;
2290
+ ">
2291
+ AI Recommendation
2292
+ </div>
2293
+ <div style="padding: 8px 0;">
2294
+ <strong>Action:</strong> {rec['action']}
2295
+ </div>
2296
+ <div style="
2297
+ padding: 8px;
2298
+ background: #f1f1f1;
2299
+ border-radius: 5px;
2300
+ margin: 8px 0;
2301
+ ">
2302
+ <strong>Data Point:</strong> {data_point_colored}
2303
+ </div>
2304
+ <div style="
2305
+ padding: 8px;
2306
+ background: #f1f1f1;
2307
+ border-radius: 5px;
2308
+ ">
2309
+ <strong>AI Reasoning:</strong> {reasoning_colored}
2310
+ </div>
2311
  </div>
2312
+ """,
2313
+ unsafe_allow_html=True
2314
+ )
 
2315
 
2316
  if not ai_recommendations:
2317
  st.info(