SHELLAPANDIANGANHUNGING commited on
Commit
a138896
·
verified ·
1 Parent(s): 7a6d211

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -239
app.py CHANGED
@@ -1982,272 +1982,158 @@ else:
1982
  st.info("No data available for non-positive issue categories with 100% coverage and positive trend.")
1983
  # =================== OBJECTIVE 7 — Insight and Recommendation (Agentic AI LLM Style — Final) ===================
1984
  # =================== OBJECTIVE 7 — Insight and Recommendation (Final — Agentic AI, No markdown bold) ===================
1985
- st.markdown("<h3 class='section-title'>OBJECTIVE 7 Insight and Recommendation</h3>", unsafe_allow_html=True)
1986
-
1987
- def extract_agentic_insights_v5(df: pd.DataFrame):
1988
- dev = {
1989
- "lowest_ratio_9_locs": [],
1990
- "obj3a_lowest_div": None,
1991
- "obj3b_slowest_executor": None,
1992
- "obj3c_lowest_reporter": None,
1993
- "obj3d_slowest_div": None,
1994
- "obj4_unsafe_condition_pct": 0.0,
1995
- "obj4_unsafe_action_pct": 0.0,
1996
- "obj4_near_miss_pct": 0.0,
1997
- "obj5_q1_divs": [],
1998
- "obj5_q2_divs": [],
1999
- "obj6_top2_categories": [],
2000
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2001
 
2002
- # === 1. 9 locations with lowest finding-to-reporter ratio ===
2003
- if {'nama_lokasi_full', 'creator_nid', 'created_at', 'kode_temuan'}.issubset(df.columns):
2004
- calc = df[['nama_lokasi_full', 'creator_nid', 'created_at', 'kode_temuan']].copy()
2005
- calc['created_at'] = pd.to_datetime(calc['created_at'], errors='coerce')
2006
- calc = calc.dropna(subset=['created_at', 'nama_lokasi_full', 'creator_nid'])
2007
- calc['bulan'] = calc['created_at'].dt.to_period('M')
2008
- monthly = calc.groupby(['nama_lokasi_full', 'bulan']).agg(
2009
- findings=('kode_temuan', 'size'),
2010
- reporters=('creator_nid', 'nunique')
2011
- ).reset_index()
2012
- monthly = monthly[monthly['reporters'] > 0]
2013
- monthly['ratio'] = monthly['findings'] / monthly['reporters']
2014
- loc_avg = monthly.groupby('nama_lokasi_full')['ratio'].mean()
2015
- lowest_9 = loc_avg.nsmallest(9)
2016
- dev["lowest_ratio_9_locs"] = [(loc, round(ratio, 3)) for loc, ratio in lowest_9.items()]
2017
-
2018
- # === 2a: Division — lowest finding-to-reporter ratio (Obj 3a) ===
2019
- if {'nama', 'creator_nid', 'created_at', 'kode_temuan'}.issubset(df.columns):
2020
- calc = df[['nama', 'creator_nid', 'created_at', 'kode_temuan']].copy()
2021
- calc['bulan'] = pd.to_datetime(calc['created_at']).dt.to_period('M')
2022
- agg = calc.groupby(['nama', 'bulan']).agg(
2023
- findings=('kode_temuan', 'size'),
2024
- reporters=('creator_nid', 'nunique')
2025
  )
2026
- agg = agg[agg['reporters'] > 0].reset_index()
2027
- agg['ratio'] = agg['findings'] / agg['reporters']
2028
- div_ratio = agg.groupby('nama')['ratio'].mean()
2029
- if not div_ratio.empty:
2030
- name = div_ratio.idxmin()
2031
- val = round(div_ratio.min(), 2)
2032
- dev["obj3a_lowest_div"] = (name, val)
2033
-
2034
- # === 2b: Executor longest average resolution time (Obj 3b) ===
2035
- if 'days_to_close' in df.columns:
2036
- valid = df[df['days_to_close'].notna() & (df['days_to_close'] >= 0)]
2037
- exec_col = 'nama_pic' if 'nama_pic' in valid.columns else 'creator_name'
2038
- if exec_col in valid.columns:
2039
- lead = valid.groupby(exec_col)['days_to_close'].mean()
2040
- if not lead.empty:
2041
- name = lead.idxmax()
2042
- val = round(lead.max(), 1)
2043
- dev["obj3b_slowest_executor"] = (name, val)
2044
-
2045
- # === 2c: Reporter lowest reporting frequency (Obj 3c) ===
2046
- if {'creator_name', 'created_at'}.issubset(df.columns):
2047
- calc = df[['creator_name', 'created_at']].copy()
2048
- calc['bulan'] = pd.to_datetime(calc['created_at']).dt.to_period('M')
2049
- monthly = calc.groupby(['creator_name', 'bulan']).size().reset_index(name='count')
2050
- avg = monthly.groupby('creator_name')['count'].mean()
2051
- avg = avg[avg > 0]
2052
- if not avg.empty:
2053
- name = avg.idxmin()
2054
- val = round(avg.min(), 2)
2055
- dev["obj3c_lowest_reporter"] = (name, val)
2056
-
2057
- # === 2d: Division — longest average resolution time (Obj 3d) ===
2058
- if 'days_to_close' in df.columns and 'nama' in df.columns:
2059
- valid = df[df['days_to_close'].notna() & (df['days_to_close'] >= 0)]
2060
- if not valid.empty:
2061
- lead = valid.groupby('nama')['days_to_close'].mean()
2062
- if not lead.empty:
2063
- name = lead.idxmax()
2064
- val = round(lead.max(), 1)
2065
- dev["obj3d_slowest_div"] = (name, val)
2066
-
2067
- # === 3. Composition of non-Positive findings ===
2068
- if 'temuan_kategori' in df.columns:
2069
- cnt = df['temuan_kategori'].value_counts(normalize=True) * 100
2070
- dev["obj4_unsafe_condition_pct"] = round(cnt.get("Unsafe Condition", 0), 1)
2071
- dev["obj4_unsafe_action_pct"] = round(cnt.get("Unsafe Action", 0), 1)
2072
- dev["obj4_near_miss_pct"] = round(cnt.get("Near Miss", 0), 1)
2073
-
2074
- # === 4. Risk Quadrants (X=20 findings/month, Y=3 days avg lead time) ===
2075
- X_LIMIT, Y_LIMIT = 20, 3
2076
- if {'nama', 'created_at', 'days_to_close', 'kode_temuan'}.issubset(df.columns):
2077
- calc = df.copy()
2078
- calc['created_at'] = pd.to_datetime(calc['created_at'], errors='coerce')
2079
- calc = calc.assign(month=calc['created_at'].dt.to_period('M').astype(str))
2080
- monthly_counts = calc.groupby(['nama', 'month'])['kode_temuan'].nunique().reset_index()
2081
- avg_count = monthly_counts.groupby('nama')['kode_temuan'].mean().reset_index(name='Finding Count')
2082
- leadtime = calc.groupby('nama')['days_to_close'].mean().reset_index(name='Avg Lead Time')
2083
- mat = avg_count.merge(leadtime, on='nama', how='left').fillna(0)
2084
- for _, r in mat.iterrows():
2085
- if r['Finding Count'] >= X_LIMIT and r['Avg Lead Time'] >= Y_LIMIT:
2086
- dev["obj5_q1_divs"].append(r['nama'])
2087
- elif r['Finding Count'] < X_LIMIT and r['Avg Lead Time'] >= Y_LIMIT:
2088
- dev["obj5_q2_divs"].append(r['nama'])
2089
-
2090
- # === 5. Top 2 non-Positive categories (avg per month) ===
2091
- if {'kategori', 'temuan_kategori', 'created_at'}.issubset(df.columns):
2092
- nonpos = df[df['temuan_kategori'] != 'Positive']
2093
- if not nonpos.empty:
2094
- start = nonpos['created_at'].min().to_period('M')
2095
- end = nonpos['created_at'].max().to_period('M')
2096
- n_months = len(pd.period_range(start=start, end=end, freq='M'))
2097
- cat_avg = (nonpos.groupby('kategori').size() / n_months).sort_values(ascending=False).head(2)
2098
- dev["obj6_top2_categories"] = [(cat, round(val, 1)) for cat, val in cat_avg.items()]
2099
-
2100
- return dev
2101
-
2102
- dev = extract_agentic_insights_v5(df_filtered)
2103
-
2104
- # === INSIGHT SUMMARY ===
2105
- insight_lines = []
2106
-
2107
- # 1. 9 lowest-ratio locations
2108
  if dev["lowest_ratio_9_locs"]:
2109
- loc_list = ", ".join([f"<strong>{loc}</strong> ({ratio})" for loc, ratio in dev["lowest_ratio_9_locs"]])
2110
- insight_lines.append(f"1. Nine locations with the <em>lowest</em> finding-to-reporter ratio: {loc_list}.")
2111
 
2112
- # 2. Combined lowest-performer insights (3a–3d)
2113
- parts = []
2114
  if dev["obj3a_lowest_div"]:
2115
- parts.append(f"division <strong>{dev['obj3a_lowest_div'][0]}</strong> (ratio: {dev['obj3a_lowest_div'][1]})")
2116
  if dev["obj3c_lowest_reporter"]:
2117
- parts.append(f"reporter <strong>{dev['obj3c_lowest_reporter'][0]}</strong> ({dev['obj3c_lowest_reporter'][1]} findings/month)")
2118
  if dev["obj3d_slowest_div"]:
2119
- parts.append(f"division <strong>{dev['obj3d_slowest_div'][0]}</strong> (avg. resolution: {dev['obj3d_slowest_div'][1]} days)")
2120
  if dev["obj3b_slowest_executor"]:
2121
- parts.append(f"executor <strong>{dev['obj3b_slowest_executor'][0]}</strong> (avg. resolution: {dev['obj3b_slowest_executor'][1]} days)")
 
 
2122
 
2123
- if parts:
2124
- joined = "; ".join(parts)
2125
- insight_lines.append(
2126
- f"2. Agentic AI detection flags uneven operational capacity: {joined}. "
2127
- "This suggests imbalance in reporting engagement and resolution efficiency across divisions and individuals."
2128
- )
2129
-
2130
- # 3. Non-Positive composition
2131
- uc, ua, nm = dev["obj4_unsafe_condition_pct"], dev["obj4_unsafe_action_pct"], dev["obj4_near_miss_pct"]
2132
  if uc + ua + nm > 0:
2133
- insight_lines.append(
2134
- f"3. Non-Positive finding composition: Unsafe Condition ({uc}%), Unsafe Action ({ua}%), Near Miss ({nm}%)."
2135
- )
2136
 
2137
- # 4. Risk Quadrants
2138
  if dev["obj5_q1_divs"] or dev["obj5_q2_divs"]:
2139
- q1 = ", ".join([f"<strong>{d}</strong>" for d in dev["obj5_q1_divs"][:5]]) # cap for readability
2140
- q2 = ", ".join([f"<strong>{d}</strong>" for d in dev["obj5_q2_divs"][:5]])
2141
- q1_str = q1 if q1 else "—"
2142
- q2_str = q2 if q2 else "—"
2143
- insight_lines.append(f"4. High-risk divisions (Quadrant I): {q1_str}. Hidden-risk divisions (Quadrant II): {q2_str}.")
2144
 
2145
- # 5. Top non-Positive categories
2146
  if dev["obj6_top2_categories"]:
2147
  c1, c2 = dev["obj6_top2_categories"]
2148
- insight_lines.append(
2149
- f"5. Top two recurring non-Positive categories: <strong>{c1[0]}</strong> ({c1[1]}/month) and <strong>{c2[0]}</strong> ({c2[1]}/month)."
2150
- )
2151
 
2152
- insight_text = "<br>".join(insight_lines)
 
 
 
 
 
 
 
2153
 
2154
- # === RECOMMENDATIONS & MITIGATION (Agentic AI Style) ===
2155
- recs = []
2156
 
2157
- # Rec 1: Low-ratio locations
2158
- if dev["lowest_ratio_9_locs"]:
2159
- recs.append({
2160
- "point": "1",
2161
- "rec": "Launch <em>Agency Activation Sprint</em> across the 9 lowest-ratio locations: Area PICs to conduct ≥1 spot inspection/week.",
2162
- "mit": "Deploy 3-minute QR-code checklists + automated WhatsApp reminders. Target: ratio ≥ 0.5 within 45 days."
2163
- })
2164
-
2165
- # Rec 2: Capacity imbalance
2166
- if parts:
2167
- recs.append({
2168
- "point": "2",
2169
- "rec": "Activate <em>Agentic Capacity Dashboard</em>: real-time monitoring of finding/reporter ratios and resolution lead times per division/individual.",
2170
- "mit": "Auto-trigger <em>coaching alerts</em> to Area PICs & Safety Managers if deviation >20% from divisional baseline, with peer benchmarking insights."
2171
- })
2172
-
2173
- # Rec 3: Category validation
2174
- if uc + ua + nm > 0:
2175
- recs.append({
2176
- "point": "3",
2177
- "rec": "Enforce photo-based validation for Unsafe Condition/Action/Near Miss submissions to ensure accurate categorization.",
2178
- "mit": "System blocks submission if photo evidence or category justification is missing."
2179
- })
2180
-
2181
- # Rec 4: Quadrant-based intervention
2182
- if dev["obj5_q1_divs"] or dev["obj5_q2_divs"]:
2183
- recs.append({
2184
- "point": "4",
2185
- "rec": "Assign <em>dedicated safety crews</em> to Quadrant I divisions; enforce <em>One Finding, One Day</em> closure policy for Quadrant II.",
2186
- "mit": "Auto-generate executive escalation reports to VP Operations if any division remains in QI/QII for ≥2 consecutive months."
2187
- })
2188
-
2189
- # Rec 5: Top categories RCA
2190
- if dev["obj6_top2_categories"]:
2191
- c1, c2 = dev["obj6_top2_categories"]
2192
- recs.append({
2193
- "point": "5",
2194
- "rec": f"Form cross-functional <em>RCA Task Force</em> (Civil, Electrical, HSE, Contractors) for <strong>{c1[0]}</strong> and <strong>{c2[0]}</strong>.",
2195
- "mit": "Update technical specifications & tender templates: all bids must include mitigations based on historical findings for these categories."
2196
- })
2197
-
2198
- # === RENDER OUTPUT ===
2199
  st.markdown(
2200
  f"""
2201
  <div class="card" style="
2202
- background-color: #f8f9fa;
2203
- border-left: 4px solid #003DA5;
2204
- padding: 16px;
2205
- margin-bottom: 20px;
2206
- border-radius: 4px;
2207
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
2208
  ">
2209
- <h4 style="margin-top: 0; color: #FF6B6B;">Insight Summary</h4>
2210
- <p style="margin-bottom: 0; line-height: 1.6; font-size: 0.98em;">{insight_text}</p>
2211
  </div>
2212
  """,
2213
  unsafe_allow_html=True
2214
  )
2215
 
2216
- # Recommendations table
2217
- if recs:
2218
- rows = []
2219
- for r in recs:
2220
- rows.append(
2221
- f"<tr>"
2222
- f"<td style='text-align:center; font-weight:bold; width:5%;'>{r['point']}</td>"
2223
- f"<td style='padding:8px;'>{r['rec']}</td>"
2224
- f"<td style='padding:8px;'>{r['mit']}</td>"
2225
- f"</tr>"
2226
- )
2227
- table_html = f"""
2228
  <div class="card" style="
2229
- background-color: #e8f5e9;
2230
- border-left: 4px solid #4CAF50;
2231
- padding: 16px;
2232
- margin-bottom: 20px;
2233
- border-radius: 4px;
2234
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
2235
  ">
2236
- <h4 style="margin-top: 0; color: #2E7D32;">Recommended Actions & Agentic Risk Mitigation</h4>
2237
- <table style="width:100%; border-collapse:collapse; font-size:0.95em; margin-top:12px;">
2238
- <thead>
2239
- <tr style="background-color:#e8f5ee;">
2240
- <th style="padding:10px; text-align:center; border:1px solid #ccc;">#</th>
2241
- <th style="padding:10px; text-align:left; border:1px solid #ccc;">Recommended Action</th>
2242
- <th style="padding:10px; text-align:left; border:1px solid #ccc;">Risk Mitigation Strategy</th>
2243
- </tr>
2244
- </thead>
2245
- <tbody>
2246
- {"".join(rows)}
2247
- </tbody>
2248
- </table>
2249
  </div>
2250
- """
2251
- st.markdown(table_html, unsafe_allow_html=True)
2252
- else:
2253
- st.info("No actionable insights generated. Ensure data contains required columns.")
 
1982
  st.info("No data available for non-positive issue categories with 100% coverage and positive trend.")
1983
  # =================== OBJECTIVE 7 — Insight and Recommendation (Agentic AI LLM Style — Final) ===================
1984
  # =================== OBJECTIVE 7 — Insight and Recommendation (Final — Agentic AI, No markdown bold) ===================
1985
+ # === RENDER INSIGHT CARD (unchanged logic, improved styling & center header) ===
1986
+ st.markdown(
1987
+ f"""
1988
+ <div class="card" style="
1989
+ background-color: #f8f9fa;
1990
+ border-left: 4px solid #003DA5;
1991
+ padding: 18px;
1992
+ margin-bottom: 24px;
1993
+ border-radius: 6px;
1994
+ box-shadow: 0 3px 6px rgba(0,0,0,0.06);
1995
+ ">
1996
+ <h4 style="margin-top: 0; color: #003DA5; text-align: center;">🔍 Insight Summary</h4>
1997
+ <p style="margin-bottom: 0; line-height: 1.6; font-size: 0.98em;">{insight_text}</p>
1998
+ </div>
1999
+ """,
2000
+ unsafe_allow_html=True
2001
+ )
2002
+
2003
+ # === GENERATE RECOMMENDATION & MITIGATION VIA LLM (DeepSeek-7B) ===
2004
+ @st.cache_resource
2005
+ def get_pipe():
2006
+ return pipeline(
2007
+ "text-generation",
2008
+ model="deepseek-ai/deepseek-llm-7b-chat",
2009
+ device_map="auto",
2010
+ torch_dtype="auto",
2011
+ trust_remote_code=True
2012
+ )
2013
+
2014
+ pipe = get_pipe()
2015
 
2016
+ def safe_llm_call(insight: str, mode: str = "rec"): # mode: 'rec' or 'mit'
2017
+ sys_msg = (
2018
+ "You are PLN's Lead Safety AI. Output ONLY a short, professional sentence. "
2019
+ "Be directive, system-aware, and action-oriented. No markdown, no emoticons."
2020
+ )
2021
+ if mode == "rec":
2022
+ sys_msg += " Focus on high-leverage intervention: 'Deploy...', 'Establish...', 'Launch...'"
2023
+ else:
2024
+ sys_msg += " Focus on automated/systemic control: 'Enforce...', 'Trigger...', 'Block...'"
2025
+
2026
+ prompt = f"""<|begin▁of▁sentence|><|User|>Insight: {insight}
2027
+
2028
+ {sys_msg}
2029
+ <|Assistant|>"""
2030
+
2031
+ try:
2032
+ out = pipe(
2033
+ prompt,
2034
+ max_new_tokens=128,
2035
+ do_sample=False,
2036
+ temperature=0.1,
2037
+ return_full_text=False
 
2038
  )
2039
+ text = out[0]["generated_text"].strip()
2040
+ # Clean common artifacts
2041
+ text = re.sub(r"^.*?:\s*", "", text) # remove "Recommendation: "
2042
+ text = re.sub(r"[\n\"`]", " ", text).strip()
2043
+ if len(text) < 10 or len(text) > 200:
2044
+ raise ValueError("Invalid length")
2045
+ return text
2046
+ except Exception as e:
2047
+ # Fallbackstill your preferred phrasing
2048
+ fallbacks = {
2049
+ ("1", "rec"): "Launch Agency Activation Sprint: ≥1 spot inspection/week per low-ratio location.",
2050
+ ("1", "mit"): "Deploy QR-code checklists + WhatsApp reminders; target ratio ≥0.5 in 45 days.",
2051
+ ("2", "rec"): "Activate Agentic Capacity Dashboard for real-time ratio & lead-time monitoring.",
2052
+ ("2", "mit"): "Auto-trigger coaching alerts if deviation >20% from divisional baseline.",
2053
+ ("3", "rec"): "Enforce photo-based validation for all Unsafe Condition/Action/Near Miss submissions.",
2054
+ ("3", "mit"): "System blocks submission if photo evidence or justification is missing.",
2055
+ ("4", "rec"): "Assign dedicated safety crews to Quadrant I; enforce ‘One Finding, One Day’ for Quadrant II.",
2056
+ ("4", "mit"): "Auto-generate VP escalation reports if division remains in QI/QII ≥2 months.",
2057
+ ("5", "rec"): "Form cross-functional RCA Task Force (Civil, Electrical, HSE, Contractors) for top categories.",
2058
+ ("5", "mit"): "Update tender templates: all bids must include mitigations for these historical findings.",
2059
+ }
2060
+ return fallbacks.get((insight_lines.index(insight)+1 if insight in insight_lines else "1", mode),
2061
+ "Review insight and implement targeted action.")
2062
+
2063
+ # Extract clean insight strings (for LLM input)
2064
+ clean_insights = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2065
  if dev["lowest_ratio_9_locs"]:
2066
+ locs = ", ".join([f"{loc} ({ratio})" for loc, ratio in dev["lowest_ratio_9_locs"]])
2067
+ clean_insights.append(f"Nine locations with the lowest finding-to-reporter ratio: {locs}.")
2068
 
2069
+ parts_desc = []
 
2070
  if dev["obj3a_lowest_div"]:
2071
+ parts_desc.append(f"division {dev['obj3a_lowest_div'][0]} (ratio: {dev['obj3a_lowest_div'][1]})")
2072
  if dev["obj3c_lowest_reporter"]:
2073
+ parts_desc.append(f"reporter {dev['obj3c_lowest_reporter'][0]} ({dev['obj3c_lowest_reporter'][1]} findings/month)")
2074
  if dev["obj3d_slowest_div"]:
2075
+ parts_desc.append(f"division {dev['obj3d_slowest_div'][0]} (avg. resolution: {dev['obj3d_slowest_div'][1]} days)")
2076
  if dev["obj3b_slowest_executor"]:
2077
+ parts_desc.append(f"executor {dev['obj3b_slowest_executor'][0]} (avg. resolution: {dev['obj3b_slowest_executor'][1]} days)")
2078
+ if parts_desc:
2079
+ clean_insights.append(f"Uneven operational capacity: {'; '.join(parts_desc)}.")
2080
 
 
 
 
 
 
 
 
 
 
2081
  if uc + ua + nm > 0:
2082
+ clean_insights.append(f"Non-Positive finding composition: Unsafe Condition ({uc}%), Unsafe Action ({ua}%), Near Miss ({nm}%).")
 
 
2083
 
 
2084
  if dev["obj5_q1_divs"] or dev["obj5_q2_divs"]:
2085
+ q1 = ", ".join([d for d in dev["obj5_q1_divs"][:3]]) or "—"
2086
+ q2 = ", ".join([d for d in dev["obj5_q2_divs"][:3]]) or "—"
2087
+ clean_insights.append(f"High-risk divisions (Q1): {q1}; Hidden-risk divisions (Q2): {q2}.")
 
 
2088
 
 
2089
  if dev["obj6_top2_categories"]:
2090
  c1, c2 = dev["obj6_top2_categories"]
2091
+ clean_insights.append(f"Top non-Positive categories: {c1[0]} ({c1[1]}/month) and {c2[0]} ({c2[1]}/month).")
 
 
2092
 
2093
+ # Generate paired rec & mit
2094
+ rec_lines, mit_lines = [], []
2095
+ with st.spinner("🧠 Generating Recommendation & Risk Mitigation with DeepSeek-7B..."):
2096
+ for i, ins in enumerate(clean_insights, 1):
2097
+ rec = safe_llm_call(ins, "rec")
2098
+ mit = safe_llm_call(ins, "mit")
2099
+ rec_lines.append(f"{i}. {rec}")
2100
+ mit_lines.append(f"{i}. {mit}")
2101
 
2102
+ rec_text = "<br>".join(rec_lines)
2103
+ mit_text = "<br>".join(mit_lines)
2104
 
2105
+ # === RENDER RECOMMENDATION CARD ===
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2106
  st.markdown(
2107
  f"""
2108
  <div class="card" style="
2109
+ background-color: #e8f5e9;
2110
+ border-left: 4px solid #4CAF50;
2111
+ padding: 18px;
2112
+ margin-bottom: 24px;
2113
+ border-radius: 6px;
2114
+ box-shadow: 0 3px 6px rgba(0,0,0,0.06);
2115
  ">
2116
+ <h4 style="margin-top: 0; color: #2E7D32; text-align: center;">✅ Recommendation</h4>
2117
+ <p style="margin-bottom: 0; line-height: 1.6; font-size: 0.98em;">{rec_text}</p>
2118
  </div>
2119
  """,
2120
  unsafe_allow_html=True
2121
  )
2122
 
2123
+ # === RENDER RISK MITIGATION CARD ===
2124
+ st.markdown(
2125
+ f"""
 
 
 
 
 
 
 
 
 
2126
  <div class="card" style="
2127
+ background-color: #e3f2fd;
2128
+ border-left: 4px solid #1976D2;
2129
+ padding: 18px;
2130
+ margin-bottom: 24px;
2131
+ border-radius: 6px;
2132
+ box-shadow: 0 3px 6px rgba(0,0,0,0.06);
2133
  ">
2134
+ <h4 style="margin-top: 0; color: #0D47A1; text-align: center;">🛡️ Risk Mitigation Strategy</h4>
2135
+ <p style="margin-bottom: 0; line-height: 1.6; font-size: 0.98em;">{mit_text}</p>
 
 
 
 
 
 
 
 
 
 
 
2136
  </div>
2137
+ """,
2138
+ unsafe_allow_html=True
2139
+ )