SHELLAPANDIANGANHUNGING commited on
Commit
a95c654
·
verified ·
1 Parent(s): f0f3fbe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -102
app.py CHANGED
@@ -904,7 +904,7 @@ with col_3b:
904
  # Ambil subset sesuai pilihan
905
  if sort_opt == "Top 10":
906
  # 10 tercepat: ascending (kecil → besar), tetap diurut ascending → tercepat di atas
907
- subset = full_sorted.head(10).sort_values('avg_monthly_leadtime', ascending=True)
908
  else: # "Bottom 10 Slowest"
909
  # 10 terlambat: descending (besar → kecil), agar terlambat di atas
910
  subset = full_sorted.tail(10).sort_values('avg_monthly_leadtime', ascending=False)
@@ -971,7 +971,7 @@ with col_3d:
971
  full_sorted = avg_leadtime_per_indiv.sort_values('avg_monthly_leadtime', ascending=True)
972
 
973
  if sort_opt == "Top 10":
974
- subset = full_sorted.head(10).subset = full_sorted.head(10).sort_values('avg_monthly_leadtime', ascending=True)
975
  else: # "Bottom 10 Slowest"
976
  subset = full_sorted.tail(10).sort_values('avg_monthly_leadtime', ascending=False)
977
 
@@ -1549,7 +1549,7 @@ def predict_creators(df):
1549
  results.append({
1550
  'Creator': creator,
1551
  'Reports/Month': round(avg_rate, 2),
1552
- 'Coverage (%)': round(coverage * 100, 1),
1553
  'Trend Slope': round(slope, 3),
1554
  'Trend': ascii_sparkline_pln(ts.values.tolist()),
1555
  'Reason': reason
@@ -1597,7 +1597,7 @@ def predict_locations(df):
1597
  results.append({
1598
  'Location': lokasi,
1599
  'Reports/Month': round(avg_rate, 2),
1600
- 'Coverage (%)': round(coverage * 100, 1),
1601
  'Trend Slope': round(slope, 3),
1602
  'Trend': ascii_sparkline_pln(ts.values.tolist()),
1603
  'Reason': reason
@@ -1645,7 +1645,7 @@ def predict_divisions(df):
1645
  results.append({
1646
  'Division': div,
1647
  'Reports/Month': round(avg_rate, 2),
1648
- 'Coverage (%)': round(coverage * 100, 1),
1649
  'Trend Slope': round(slope, 3),
1650
  'Trend': ascii_sparkline_pln(ts.values.tolist()),
1651
  'Reason': reason
@@ -1691,7 +1691,7 @@ def predict_categories(df):
1691
  results.append({
1692
  'Category': cat,
1693
  'Avg/Month': round(avg_per_month, 2),
1694
- 'Coverage (%)': round(coverage * 100, 1),
1695
  'Trend Slope': round(slope, 3),
1696
  'Trend': ascii_sparkline_pln(ts_data.values.tolist())
1697
  })
@@ -1724,7 +1724,7 @@ df_category = predict_categories(df_filtered)
1724
  st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
1725
  st.markdown("<div class='predictive-header'>1. Which Reporters Are Predicted to Have Less Future Inspections? (Top 10 Most Declining)</div>", unsafe_allow_html=True)
1726
  if not df_creator.empty:
1727
- cols = ['Creator', 'Reports/Month', 'Coverage (%)', 'Trend Slope', 'Trend']
1728
 
1729
  # 🔥 Rename hanya untuk DISPLAY, bukan data asli
1730
  df_display = df_creator[cols].rename(columns={
@@ -1760,7 +1760,7 @@ st.markdown("</div>", unsafe_allow_html=True)
1760
  st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
1761
  st.markdown("<div class='predictive-header'>2. Which Locations Are Predicted to Have Less Future Inspections? (Top 10 Most Declining)</div>", unsafe_allow_html=True)
1762
  if not df_location.empty:
1763
- cols = ['Location', 'Reports/Month', 'Coverage (%)', 'Trend Slope', 'Trend']
1764
 
1765
  # # 🔥 Rename hanya untuk DISPLAY, bukan data asli
1766
  df_display = df_location[cols].rename(columns={
@@ -1796,7 +1796,7 @@ st.markdown("</div>", unsafe_allow_html=True)
1796
  st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
1797
  st.markdown("<div class='predictive-header'>3. Which Divisions Are Predicted to Have Less Future Inspections? (Top 10 Most Declining)</div>", unsafe_allow_html=True)
1798
  if not df_division.empty:
1799
- cols = ['Division', 'Reports/Month', 'Coverage (%)', 'Trend Slope', 'Trend']
1800
 
1801
  # # 🔥 Rename hanya untuk DISPLAY, bukan data asli
1802
  df_display = df_division[cols].rename(columns={
@@ -1984,9 +1984,11 @@ else:
1984
  st.markdown("<h3 class='section-title'>OBJECTIVE 7 - Insight and Recommendation</h3>", unsafe_allow_html=True)
1985
 
1986
 
 
1987
  # ============================================================== #
1988
- # Helper 1: Hitung Average Monthly Finding-to-Reporter Ratio per Lokasi
1989
  # ============================================================== #
 
1990
  def compute_avg_monthly_ratio_per_location(df: pd.DataFrame) -> pd.DataFrame:
1991
  required = ['nama_lokasi_full', 'creator_nid', 'created_at', 'kode_temuan']
1992
  missing = [col for col in required if col not in df.columns]
@@ -2024,9 +2026,6 @@ def compute_avg_monthly_ratio_per_location(df: pd.DataFrame) -> pd.DataFrame:
2024
 
2025
  return loc_summary
2026
 
2027
- # ============================================================== #
2028
- # Helper 2: Interpretasi Aktivitas Pelaporan secara Adil
2029
- # ============================================================== #
2030
  def interpret_location_safely(df: pd.DataFrame, location_name: str) -> dict:
2031
  loc_df = df[df['nama_lokasi_full'] == location_name].copy()
2032
  if loc_df.empty:
@@ -2080,9 +2079,6 @@ def interpret_location_safely(df: pd.DataFrame, location_name: str) -> dict:
2080
  "positive_rate": perc_positive
2081
  }
2082
 
2083
- # ============================================================== #
2084
- # Helper 3: Deteksi Isu Tidak Aman dari Teks
2085
- # ============================================================== #
2086
  def detect_unsafe_terms(df: pd.DataFrame):
2087
  text_cols = ['hasil_keyword_dan_kondisi', 'judul_dan_kondisi', 'kondisi', 'judul']
2088
  text_col = None
@@ -2097,127 +2093,144 @@ def detect_unsafe_terms(df: pd.DataFrame):
2097
  unsafe_terms = [
2098
  'terbuka', 'tidak terkunci', 'tanpa izin', 'tanpa pelindung', 'tanpa alat',
2099
  'korsleting', 'overload', 'grounding', 'exposed', 'unlocked', 'no ppe',
2100
- 'jatuh', 'slip', 'kebakaran', 'fire', 'fall', 'unauthorized',
2101
  'tidak kompeten', 'untrained', 'prosedur dilanggar', 'bypass'
2102
  ]
2103
  found = [term for term in unsafe_terms if term in all_text]
2104
  return list(set(found))
2105
 
2106
- # ============================================================== #
2107
- # Main: Generate Risk Mitigation Insights
2108
- # ============================================================== #
2109
- def compute_risk_mitigation_insights(df: pd.DataFrame) -> List[dict]:
2110
- insights = []
2111
  if df.empty:
2112
- return insights
2113
 
2114
- # Insight 1: Top 3 Locations by Volume — Interpreted with Safety Maturity
 
 
 
2115
  if {'nama_lokasi_full', 'temuan_kategori', 'creator_nid', 'created_at'}.issubset(df.columns):
2116
  top_locs = df['nama_lokasi_full'].value_counts().head(3).index.tolist()
2117
  for loc in top_locs:
2118
  interp = interpret_location_safely(df, loc)
2119
- insight = f"Location {loc}: {interp['interpretation']}"
2120
 
2121
- signal = interp['risk_signal']
2122
- if signal == "Slight Risk":
2123
- recommendation = (
2124
- "Recognize this location as a safety exemplar. Share their positive findings in internal safety communications. "
2125
- "Facilitate cross-location learning sessions to replicate practices."
2126
- )
2127
- elif signal == "Moderate Risk":
2128
- recommendation = (
2129
- "Conduct a workshop on positive intervention techniques. Train teams to identify and report good practices. "
2130
- "Set a target to increase the positive finding rate to above 60 percent within three months."
2131
- )
2132
- elif signal == "High Risk":
2133
- recommendation = (
2134
- "Assign two additional auditors to rotate into this location for one month. "
2135
- "Administer an anonymous psychological safety survey to assess reporting barriers."
2136
- )
2137
- elif signal == "Very High Risk":
2138
- recommendation = (
2139
- "Escalate to area management. Implement daily safety huddles, scheduled supervisor walkarounds, "
2140
- "and weekly tracking of unsafe finding closure rates."
2141
- )
2142
- else:
2143
- recommendation = (
2144
- "Validate physical inspection coverage. Ensure field presence aligns with digital reporting records."
2145
- )
2146
-
2147
- insights.append({"insight": insight, "recommendation": recommendation})
2148
-
2149
- # Insight 2: Organizational Agentic Safety Maturity
2150
  if 'temuan_kategori' in df.columns:
2151
  total = len(df)
2152
  n_positive = (df['temuan_kategori'] == 'Positive').sum()
2153
  positive_rate = n_positive / total if total > 0 else 0
2154
-
2155
- insight = (
2156
- f"Organization-wide, {positive_rate:.1%} of findings are categorized as Positive, "
2157
- f"indicating proactive safety behaviors. The remaining {100 - positive_rate * 100:.1f} percent are reactive, "
2158
- f"responding to existing hazards."
2159
  )
2160
 
2161
- if positive_rate < 0.4:
2162
- recommendation = (
2163
- "Launch an Agentic Safety Program: incentivize near-miss reporting and safety suggestions, "
2164
- "train designated Safety Coaches per division, and adopt percentage of Positive findings as a leading KPI, "
2165
- "with a six-month target of 50 percent."
2166
- )
2167
- else:
2168
- recommendation = (
2169
- "Sustain current momentum. Formalize recognition for divisions with consistently high positive reporting rates."
2170
- )
2171
-
2172
- insights.append({"insight": insight, "recommendation": recommendation})
2173
-
2174
- # Insight 3: Emerging Unsafe Issues from Text Analysis
2175
  unsafe_terms = detect_unsafe_terms(df)
2176
  if unsafe_terms:
2177
  top_terms = ', '.join(sorted(unsafe_terms)[:5])
2178
- insight = f"Text analysis of findings reveals recurring unsafe conditions related to: {top_terms}."
2179
- recommendation = (
2180
- "Initiate a targeted two-week Risk Blitz focusing on these conditions. "
2181
- "Update inspection checklists to include these items as critical control points. "
2182
- "Require photo documentation for verification of corrective actions."
2183
- )
2184
- insights.append({"insight": insight, "recommendation": recommendation})
2185
 
2186
- # Insight 4: Low-Activity Locations with Potential Silent Risks
2187
  if 'nama_lokasi_full' in df.columns:
2188
  loc_counts = df['nama_lokasi_full'].value_counts()
2189
  low_activity_locs = loc_counts[loc_counts <= 2].index.tolist()
2190
  for loc in low_activity_locs[:3]:
2191
  interp = interpret_location_safely(df, loc)
2192
  if 0 < interp['positive_rate'] < 0.5:
2193
- insight = (
2194
  f"Location {loc} reports low volume ({loc_counts[loc]} findings) with a positive rate of "
2195
  f"{interp['positive_rate']:.0%}, suggesting possible under-reporting or unobserved hazards."
2196
  )
2197
- recommendation = (
2198
- "Conduct an unannounced observational audit by an independent team to assess true field conditions."
2199
- )
2200
- insights.append({"insight": insight, "recommendation": recommendation})
2201
 
2202
- return insights
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2203
 
2204
  # ============================================================== #
2205
- # Execute and Display
2206
  # ============================================================== #
 
2207
  try:
2208
- risk_insights = compute_risk_mitigation_insights(df_filtered)
2209
  except Exception as e:
2210
- st.error(f"Error during insight generation: {str(e)}")
2211
- risk_insights = []
 
2212
 
2213
- if risk_insights:
2214
- for i, item in enumerate(risk_insights, 1):
2215
- st.markdown(f"<div class='ai-insight'><strong>Insight {i}:</strong> {item['insight']}</div>", unsafe_allow_html=True)
2216
- st.markdown(f"<div class='ai-recommendation'><strong>Recommendation {i}:</strong> {item['recommendation']}</div>", unsafe_allow_html=True)
2217
- else:
2218
- st.markdown(
2219
- "<div class='ai-insight'>No risk mitigation insights were generated. "
2220
- "Please ensure the dataset contains the following columns: "
2221
- "nama_lokasi_full, temuan_kategori, creator_nid, and created_at.</div>",
2222
- unsafe_allow_html=True
2223
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
  # Ambil subset sesuai pilihan
905
  if sort_opt == "Top 10":
906
  # 10 tercepat: ascending (kecil → besar), tetap diurut ascending → tercepat di atas
907
+ subset = full_sorted.head(10).sort_values('avg_monthly_leadtime', ascending=False)
908
  else: # "Bottom 10 Slowest"
909
  # 10 terlambat: descending (besar → kecil), agar terlambat di atas
910
  subset = full_sorted.tail(10).sort_values('avg_monthly_leadtime', ascending=False)
 
971
  full_sorted = avg_leadtime_per_indiv.sort_values('avg_monthly_leadtime', ascending=True)
972
 
973
  if sort_opt == "Top 10":
974
+ subset = full_sorted.head(10).subset = full_sorted.head(10).sort_values('avg_monthly_leadtime', ascending=False)
975
  else: # "Bottom 10 Slowest"
976
  subset = full_sorted.tail(10).sort_values('avg_monthly_leadtime', ascending=False)
977
 
 
1549
  results.append({
1550
  'Creator': creator,
1551
  'Reports/Month': round(avg_rate, 2),
1552
+ 'Monthly Consistency (%)': round(coverage * 100, 1),
1553
  'Trend Slope': round(slope, 3),
1554
  'Trend': ascii_sparkline_pln(ts.values.tolist()),
1555
  'Reason': reason
 
1597
  results.append({
1598
  'Location': lokasi,
1599
  'Reports/Month': round(avg_rate, 2),
1600
+ 'Monthly Consistency (%)': round(coverage * 100, 1),
1601
  'Trend Slope': round(slope, 3),
1602
  'Trend': ascii_sparkline_pln(ts.values.tolist()),
1603
  'Reason': reason
 
1645
  results.append({
1646
  'Division': div,
1647
  'Reports/Month': round(avg_rate, 2),
1648
+ 'Monthly Consistency (%)': round(coverage * 100, 1),
1649
  'Trend Slope': round(slope, 3),
1650
  'Trend': ascii_sparkline_pln(ts.values.tolist()),
1651
  'Reason': reason
 
1691
  results.append({
1692
  'Category': cat,
1693
  'Avg/Month': round(avg_per_month, 2),
1694
+ 'Monthly Consistency (%)': round(coverage * 100, 1),
1695
  'Trend Slope': round(slope, 3),
1696
  'Trend': ascii_sparkline_pln(ts_data.values.tolist())
1697
  })
 
1724
  st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
1725
  st.markdown("<div class='predictive-header'>1. Which Reporters Are Predicted to Have Less Future Inspections? (Top 10 Most Declining)</div>", unsafe_allow_html=True)
1726
  if not df_creator.empty:
1727
+ cols = ['Creator', 'Reports/Month', 'Monthly Consistency (%)', 'Trend Slope', 'Trend']
1728
 
1729
  # 🔥 Rename hanya untuk DISPLAY, bukan data asli
1730
  df_display = df_creator[cols].rename(columns={
 
1760
  st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
1761
  st.markdown("<div class='predictive-header'>2. Which Locations Are Predicted to Have Less Future Inspections? (Top 10 Most Declining)</div>", unsafe_allow_html=True)
1762
  if not df_location.empty:
1763
+ cols = ['Location', 'Reports/Month', 'Monthly Consistency (%)', 'Trend Slope', 'Trend']
1764
 
1765
  # # 🔥 Rename hanya untuk DISPLAY, bukan data asli
1766
  df_display = df_location[cols].rename(columns={
 
1796
  st.markdown("<div class='predictive-panel'>", unsafe_allow_html=True)
1797
  st.markdown("<div class='predictive-header'>3. Which Divisions Are Predicted to Have Less Future Inspections? (Top 10 Most Declining)</div>", unsafe_allow_html=True)
1798
  if not df_division.empty:
1799
+ cols = ['Division', 'Reports/Month', 'Monthly Consistency (%)', 'Trend Slope', 'Trend']
1800
 
1801
  # # 🔥 Rename hanya untuk DISPLAY, bukan data asli
1802
  df_display = df_division[cols].rename(columns={
 
1984
  st.markdown("<h3 class='section-title'>OBJECTIVE 7 - Insight and Recommendation</h3>", unsafe_allow_html=True)
1985
 
1986
 
1987
+
1988
  # ============================================================== #
1989
+ # Fungsi Insight & Rekomendasi (sama seperti sebelumnya, tanpa perubahan logika)
1990
  # ============================================================== #
1991
+
1992
  def compute_avg_monthly_ratio_per_location(df: pd.DataFrame) -> pd.DataFrame:
1993
  required = ['nama_lokasi_full', 'creator_nid', 'created_at', 'kode_temuan']
1994
  missing = [col for col in required if col not in df.columns]
 
2026
 
2027
  return loc_summary
2028
 
 
 
 
2029
  def interpret_location_safely(df: pd.DataFrame, location_name: str) -> dict:
2030
  loc_df = df[df['nama_lokasi_full'] == location_name].copy()
2031
  if loc_df.empty:
 
2079
  "positive_rate": perc_positive
2080
  }
2081
 
 
 
 
2082
  def detect_unsafe_terms(df: pd.DataFrame):
2083
  text_cols = ['hasil_keyword_dan_kondisi', 'judul_dan_kondisi', 'kondisi', 'judul']
2084
  text_col = None
 
2093
  unsafe_terms = [
2094
  'terbuka', 'tidak terkunci', 'tanpa izin', 'tanpa pelindung', 'tanpa alat',
2095
  'korsleting', 'overload', 'grounding', 'exposed', 'unlocked', 'no ppe',
2096
+ 'jatuh', 'slip', 'trip', 'kebakaran', 'fire', 'fall', 'unauthorized',
2097
  'tidak kompeten', 'untrained', 'prosedur dilanggar', 'bypass'
2098
  ]
2099
  found = [term for term in unsafe_terms if term in all_text]
2100
  return list(set(found))
2101
 
2102
+ def generate_insight_and_recommendation(df: pd.DataFrame):
 
 
 
 
2103
  if df.empty:
2104
+ return "Insufficient data for insight generation.", "Ensure dataset is populated and filtered appropriately."
2105
 
2106
+ insights_parts = []
2107
+ recommendations_parts = []
2108
+
2109
+ # --- Insight 1: Top Active Locations (Interpreted) ---
2110
  if {'nama_lokasi_full', 'temuan_kategori', 'creator_nid', 'created_at'}.issubset(df.columns):
2111
  top_locs = df['nama_lokasi_full'].value_counts().head(3).index.tolist()
2112
  for loc in top_locs:
2113
  interp = interpret_location_safely(df, loc)
2114
+ insights_parts.append(f"Location {loc}: {interp['interpretation']}")
2115
 
2116
+ # --- Insight 2: Organizational Safety Maturity ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2117
  if 'temuan_kategori' in df.columns:
2118
  total = len(df)
2119
  n_positive = (df['temuan_kategori'] == 'Positive').sum()
2120
  positive_rate = n_positive / total if total > 0 else 0
2121
+ insights_parts.append(
2122
+ f"Organization-wide, {positive_rate:.1%} of findings are Positive (proactive), "
2123
+ f"while {100 - positive_rate * 100:.1f}% are reactive responses to existing hazards."
 
 
2124
  )
2125
 
2126
+ # --- Insight 3: Emerging Unsafe Conditions ---
 
 
 
 
 
 
 
 
 
 
 
 
 
2127
  unsafe_terms = detect_unsafe_terms(df)
2128
  if unsafe_terms:
2129
  top_terms = ', '.join(sorted(unsafe_terms)[:5])
2130
+ insights_parts.append(f"Text analysis identifies recurring unsafe conditions related to: {top_terms}.")
 
 
 
 
 
 
2131
 
2132
+ # --- Insight 4: Low-Activity Locations ---
2133
  if 'nama_lokasi_full' in df.columns:
2134
  loc_counts = df['nama_lokasi_full'].value_counts()
2135
  low_activity_locs = loc_counts[loc_counts <= 2].index.tolist()
2136
  for loc in low_activity_locs[:3]:
2137
  interp = interpret_location_safely(df, loc)
2138
  if 0 < interp['positive_rate'] < 0.5:
2139
+ insights_parts.append(
2140
  f"Location {loc} reports low volume ({loc_counts[loc]} findings) with a positive rate of "
2141
  f"{interp['positive_rate']:.0%}, suggesting possible under-reporting or unobserved hazards."
2142
  )
 
 
 
 
2143
 
2144
+ # --- Build Recommendation + Risk Mitigation Strategy ---
2145
+ rec_parts = []
2146
+ mitigation_parts = []
2147
+
2148
+ # Recommendation: Culture & Capability
2149
+ rec_parts.append(
2150
+ "Strengthen agentic safety behaviors by launching an Agentic Safety Program, including incentives for near-miss reporting, "
2151
+ "training of Safety Coaches per division, and adoption of the percentage of Positive findings as a leading performance indicator."
2152
+ )
2153
+ mitigation_parts.append(
2154
+ "Shift from compliance-driven audits to capability-building engagements. Measure success by reduction in repeat unsafe findings and increase in proactive interventions."
2155
+ )
2156
+
2157
+ # Recommendation: Data-Driven Intervention
2158
+ rec_parts.append(
2159
+ "Conduct targeted Risk Blitz campaigns for high-frequency unsafe conditions identified through text analysis, "
2160
+ "supported by updated checklists and photo-based verification of corrective actions."
2161
+ )
2162
+ mitigation_parts.append(
2163
+ "Integrate text analytics into monthly safety reviews to detect emerging risks earlier. Automate alerts when unsafe keywords exceed baseline thresholds."
2164
+ )
2165
+
2166
+ # Recommendation: Coverage & Equity
2167
+ rec_parts.append(
2168
+ "Improve inspection coverage equity through mandatory auditor rotation, geotagged field validation, and deployment of micro-checklists for frontline personnel."
2169
+ )
2170
+ mitigation_parts.append(
2171
+ "Monitor the Gini coefficient of reporter distribution across locations monthly. Set an organizational target of below 0.5 to ensure balanced surveillance."
2172
+ )
2173
+
2174
+ # Recommendation: Psychological Safety
2175
+ rec_parts.append(
2176
+ "Assess and improve psychological safety in high-risk locations using anonymous surveys and leadership listening sessions, "
2177
+ "particularly where reporting relies on very few individuals."
2178
+ )
2179
+ mitigation_parts.append(
2180
+ "Decouple reporting volume from individual performance evaluation. Reward quality, learning, and prevention impact instead."
2181
+ )
2182
+
2183
+ # Combine all
2184
+ insight_text = " ".join(insights_parts) if insights_parts else "No significant patterns detected in current data."
2185
+ recommendation_text = " ".join(rec_parts)
2186
+ mitigation_text = " ".join(mitigation_parts)
2187
+
2188
+ return insight_text, recommendation_text, mitigation_text
2189
 
2190
  # ============================================================== #
2191
+ # Eksekusi & Tampilan — SATU CARD PER BAGIAN
2192
  # ============================================================== #
2193
+
2194
  try:
2195
+ insight, recommendation, risk_mitigation = generate_insight_and_recommendation(df_filtered)
2196
  except Exception as e:
2197
+ insight = "Error during insight generation."
2198
+ recommendation = f"Review data pipeline: {str(e)}"
2199
+ risk_mitigation = "Ensure required columns are present and datetime formats are consistent."
2200
 
2201
+ # Card Insight
2202
+ st.markdown(
2203
+ f"""
2204
+ <div class="card" style="
2205
+ background-color: #f8f9fa;
2206
+ border-left: 4px solid #2196f3;
2207
+ padding: 16px;
2208
+ margin-bottom: 20px;
2209
+ border-radius: 4px;
2210
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
2211
+ ">
2212
+ <h4 style="margin-top: 0; color: #1976d2;">Insight Summary</h4>
2213
+ <p style="margin-bottom: 0;">{insight}</p>
2214
+ </div>
2215
+ """,
2216
+ unsafe_allow_html=True
2217
+ )
2218
+
2219
+ # Card Recommendation + Risk Mitigation
2220
+ st.markdown(
2221
+ f"""
2222
+ <div class="card" style="
2223
+ background-color: #f0f7ff;
2224
+ border-left: 4px solid #4caf50;
2225
+ padding: 16px;
2226
+ margin-bottom: 20px;
2227
+ border-radius: 4px;
2228
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
2229
+ ">
2230
+ <h4 style="margin-top: 0; color: #2e7d32;">Recommended Actions and Risk Mitigation Strategy</h4>
2231
+ <p><strong>Recommended Actions:</strong> {recommendation}</p>
2232
+ <p><strong>Risk Mitigation Strategy:</strong> {risk_mitigation}</p>
2233
+ </div>
2234
+ """,
2235
+ unsafe_allow_html=True
2236
+ )