Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 1986 |
-
|
| 1987 |
-
|
| 1988 |
-
|
| 1989 |
-
|
| 1990 |
-
|
| 1991 |
-
|
| 1992 |
-
|
| 1993 |
-
|
| 1994 |
-
|
| 1995 |
-
|
| 1996 |
-
"
|
| 1997 |
-
"
|
| 1998 |
-
|
| 1999 |
-
|
| 2000 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2001 |
|
| 2002 |
-
|
| 2003 |
-
|
| 2004 |
-
|
| 2005 |
-
|
| 2006 |
-
|
| 2007 |
-
|
| 2008 |
-
|
| 2009 |
-
|
| 2010 |
-
|
| 2011 |
-
|
| 2012 |
-
|
| 2013 |
-
|
| 2014 |
-
|
| 2015 |
-
|
| 2016 |
-
|
| 2017 |
-
|
| 2018 |
-
|
| 2019 |
-
|
| 2020 |
-
|
| 2021 |
-
|
| 2022 |
-
|
| 2023 |
-
|
| 2024 |
-
reporters=('creator_nid', 'nunique')
|
| 2025 |
)
|
| 2026 |
-
|
| 2027 |
-
|
| 2028 |
-
|
| 2029 |
-
|
| 2030 |
-
|
| 2031 |
-
|
| 2032 |
-
|
| 2033 |
-
|
| 2034 |
-
|
| 2035 |
-
|
| 2036 |
-
|
| 2037 |
-
|
| 2038 |
-
|
| 2039 |
-
|
| 2040 |
-
|
| 2041 |
-
|
| 2042 |
-
|
| 2043 |
-
|
| 2044 |
-
|
| 2045 |
-
|
| 2046 |
-
|
| 2047 |
-
|
| 2048 |
-
|
| 2049 |
-
|
| 2050 |
-
|
| 2051 |
-
|
| 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 |
-
|
| 2110 |
-
|
| 2111 |
|
| 2112 |
-
|
| 2113 |
-
parts = []
|
| 2114 |
if dev["obj3a_lowest_div"]:
|
| 2115 |
-
|
| 2116 |
if dev["obj3c_lowest_reporter"]:
|
| 2117 |
-
|
| 2118 |
if dev["obj3d_slowest_div"]:
|
| 2119 |
-
|
| 2120 |
if dev["obj3b_slowest_executor"]:
|
| 2121 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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([
|
| 2140 |
-
q2 = ", ".join([
|
| 2141 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2153 |
|
| 2154 |
-
|
| 2155 |
-
|
| 2156 |
|
| 2157 |
-
#
|
| 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: #
|
| 2203 |
-
border-left: 4px solid #
|
| 2204 |
-
padding:
|
| 2205 |
-
margin-bottom:
|
| 2206 |
-
border-radius:
|
| 2207 |
-
box-shadow: 0
|
| 2208 |
">
|
| 2209 |
-
<h4 style="margin-top: 0; color: #
|
| 2210 |
-
<p style="margin-bottom: 0; line-height: 1.6; font-size: 0.98em;">{
|
| 2211 |
</div>
|
| 2212 |
""",
|
| 2213 |
unsafe_allow_html=True
|
| 2214 |
)
|
| 2215 |
|
| 2216 |
-
#
|
| 2217 |
-
|
| 2218 |
-
|
| 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: #
|
| 2230 |
-
border-left: 4px solid #
|
| 2231 |
-
padding:
|
| 2232 |
-
margin-bottom:
|
| 2233 |
-
border-radius:
|
| 2234 |
-
box-shadow: 0
|
| 2235 |
">
|
| 2236 |
-
<h4 style="margin-top: 0; color: #
|
| 2237 |
-
<
|
| 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 |
-
|
| 2252 |
-
|
| 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 |
+
# Fallback — still 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 |
+
)
|
|
|