SHELLAPANDIANGANHUNGING commited on
Commit
eb4e268
·
verified ·
1 Parent(s): 8b5e6d5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +23 -33
app.py CHANGED
@@ -1982,6 +1982,7 @@ else:
1982
  st.info("No data available for non-positive issue categories with 100% coverage and positive trend.")
1983
 
1984
  st.markdown("<h3 class='section-title'>OBJECTIVE 7 - Insight and Recommendation</h3>", unsafe_allow_html=True)
 
1985
 
1986
 
1987
  def extract_critical_deviations(df: pd.DataFrame):
@@ -2010,7 +2011,6 @@ def extract_critical_deviations(df: pd.DataFrame):
2010
  monthly_agg = monthly_agg[monthly_agg['reporters'] > 0]
2011
  monthly_agg['ratio'] = monthly_agg['findings'] / monthly_agg['reporters']
2012
  loc_avg = monthly_agg.groupby('nama_lokasi_full')['ratio'].mean().reset_index()
2013
- # Ambil yang 0.95 ≤ ratio ≤ 1.05
2014
  near_1 = loc_avg[(loc_avg['ratio'] >= 0.95) & (loc_avg['ratio'] <= 1.05)]
2015
  dev["obj2_locations_ratio_1"] = near_1.nlargest(9, 'ratio')['nama_lokasi_full'].tolist()
2016
 
@@ -2066,14 +2066,12 @@ def extract_critical_deviations(df: pd.DataFrame):
2066
  if cat in cat_counts.index:
2067
  dev["obj4_unsafe_share"][cat] = round(cat_counts[cat], 1)
2068
 
2069
- # === OBJ 5: Risk Matrix kuadran ===
2070
- # Gunakan logika yang sama seperti Objective 5 (X_LIMIT=20, Y_LIMIT=3)
2071
  X_LIMIT, Y_LIMIT = 20, 3
2072
  if 'nama' in df.columns and 'days_to_close' in df.columns:
2073
  df_risk = df.copy()
2074
  df_risk['created_at'] = pd.to_datetime(df_risk['created_at'], errors='coerce')
2075
  df_risk = df_risk.assign(month=df_risk['created_at'].dt.to_period('M').astype(str))
2076
- # Avg bulanan per divisi
2077
  monthly_counts = df_risk.groupby(['nama', 'month'])['kode_temuan'].nunique().reset_index()
2078
  avg_count = monthly_counts.groupby('nama')['kode_temuan'].mean().reset_index(name='Finding Count')
2079
  leadtime = df_risk.groupby('nama')['days_to_close'].mean().reset_index(name='Average Lead Time')
@@ -2089,36 +2087,33 @@ def extract_critical_deviations(df: pd.DataFrame):
2089
  elif cnt < X_LIMIT and lt >= Y_LIMIT:
2090
  dev["obj5_quadrant_II"].append(div)
2091
 
2092
- # === OBJ 6: Whiteboard — 2 bubble terbesar (Avg/Month tertinggi) ===
2093
  if 'kategori' in df.columns and 'temuan_kategori' in df.columns:
2094
- df_nonpos = df[df['temuan_kategori'] != 'Positive']
2095
  if not df_nonpos.empty:
2096
- start_month = df['created_at'].min().to_period('M')
2097
- end_month = df['created_at'].max().to_period('M')
2098
- n_months = len(pd.period_range(start=start_month, end=end_month, freq='M'))
2099
- cat_avg = (
2100
- df_nonpos.groupby('kategori').size() / n_months
2101
- ).sort_values(ascending=False).head(2)
2102
- dev["obj6_top2_bubbles"] = [(cat, round(val, 2)) for cat, val in cat_avg.items()]
2103
 
2104
  return dev
2105
 
2106
- # Jalankan ekstraksi
2107
  deviations = extract_critical_deviations(df_filtered)
2108
 
2109
- # Bangun insight berbasis temuan nyata
2110
  insight_parts = []
2111
- rec_parts = []
2112
 
2113
- # Objective 2
2114
  if deviations["obj2_locations_ratio_1"]:
2115
- locs = ", ".join(deviations["obj2_locations_ratio_1"][:5]) # Tampilkan 5 saja di teks
2116
  insight_parts.append(
2117
  f"Nine locations show near-optimal finding-to-reporter ratio (~1.0), indicating balanced workload: "
2118
  f"{locs}, and others."
2119
  )
2120
 
2121
- # Objective 3
2122
  if deviations["obj3a_lowest_div_ratio"]:
2123
  div, ratio = deviations["obj3a_lowest_div_ratio"]
2124
  insight_parts.append(f"Division {div} has the lowest reporting ratio ({ratio}), suggesting potential under-utilization or resource gaps.")
@@ -2132,13 +2127,13 @@ if deviations["obj3d_slowest_executor"]:
2132
  name, lt = deviations["obj3d_slowest_executor"]
2133
  insight_parts.append(f"Executor {name} has the longest lead time ({lt} days), requiring workflow review.")
2134
 
2135
- # Objective 4
2136
  if deviations["obj4_unsafe_share"]:
2137
  unsafe_list = [f"{cat} ({pct}%)" for cat, pct in deviations["obj4_unsafe_share"].items()]
2138
  unsafe_str = "; ".join(unsafe_list)
2139
  insight_parts.append(f"Unsafe issues dominate: {unsafe_str} of all findings.")
2140
 
2141
- # Objective 5
2142
  if deviations["obj5_quadrant_I"]:
2143
  q1 = ", ".join(deviations["obj5_quadrant_I"][:3])
2144
  insight_parts.append(f"High-risk divisions (high volume + slow resolution): {q1}.")
@@ -2146,7 +2141,7 @@ if deviations["obj5_quadrant_II"]:
2146
  q2 = ", ".join(deviations["obj5_quadrant_II"][:3])
2147
  insight_parts.append(f"Hidden-risk divisions (low volume but very slow): {q2} — may indicate capacity or priority issues.")
2148
 
2149
- # Objective 6
2150
  if deviations["obj6_top2_bubbles"]:
2151
  bub1, bub2 = deviations["obj6_top2_bubbles"]
2152
  insight_parts.append(
@@ -2154,19 +2149,14 @@ if deviations["obj6_top2_bubbles"]:
2154
  f"and {bub2[0]} ({bub2[1]}/month), indicating systemic root causes."
2155
  )
2156
 
2157
- # Combine insight
2158
  insight_text = " ".join(insight_parts) if insight_parts else "No significant deviations detected based on current filters."
2159
 
2160
- # Rekomendasi & Risk Mitigation
2161
- rec_parts.append(
2162
- "Prioritize capacity assessment and coaching for divisions and individuals with lowest activity or longest resolution times."
2163
- )
2164
- rec_parts.append(
2165
- "Initiate root-cause analysis on top two high-frequency unsafe categories to prevent recurrence."
2166
- )
2167
- rec_parts.append(
2168
  "Review workload distribution for locations with ratio ≈1.0 — they represent a benchmark for sustainable inspection load."
2169
- )
2170
 
2171
  mitigation_parts = [
2172
  "Establish SLA thresholds: max 7 days lead time, min 0.5 findings/reporter/month for active status.",
@@ -2177,7 +2167,7 @@ mitigation_parts = [
2177
  recommendation_text = " ".join(rec_parts)
2178
  mitigation_text = " ".join(mitigation_parts)
2179
 
2180
- # Tampilkan — SATU CARD INSIGHT, SATU CARD REKOMENDASI + MITIGASI
2181
  st.markdown(
2182
  f"""
2183
  <div class="card" style="
 
1982
  st.info("No data available for non-positive issue categories with 100% coverage and positive trend.")
1983
 
1984
  st.markdown("<h3 class='section-title'>OBJECTIVE 7 - Insight and Recommendation</h3>", unsafe_allow_html=True)
1985
+ # =================== OBJECTIVE 7 - Insight and Recommendation (Revised per Deviasi Aktual) ===================
1986
 
1987
 
1988
  def extract_critical_deviations(df: pd.DataFrame):
 
2011
  monthly_agg = monthly_agg[monthly_agg['reporters'] > 0]
2012
  monthly_agg['ratio'] = monthly_agg['findings'] / monthly_agg['reporters']
2013
  loc_avg = monthly_agg.groupby('nama_lokasi_full')['ratio'].mean().reset_index()
 
2014
  near_1 = loc_avg[(loc_avg['ratio'] >= 0.95) & (loc_avg['ratio'] <= 1.05)]
2015
  dev["obj2_locations_ratio_1"] = near_1.nlargest(9, 'ratio')['nama_lokasi_full'].tolist()
2016
 
 
2066
  if cat in cat_counts.index:
2067
  dev["obj4_unsafe_share"][cat] = round(cat_counts[cat], 1)
2068
 
2069
+ # === OBJ 5: Risk Matrix kuadran (X_LIMIT=20, Y_LIMIT=3) ===
 
2070
  X_LIMIT, Y_LIMIT = 20, 3
2071
  if 'nama' in df.columns and 'days_to_close' in df.columns:
2072
  df_risk = df.copy()
2073
  df_risk['created_at'] = pd.to_datetime(df_risk['created_at'], errors='coerce')
2074
  df_risk = df_risk.assign(month=df_risk['created_at'].dt.to_period('M').astype(str))
 
2075
  monthly_counts = df_risk.groupby(['nama', 'month'])['kode_temuan'].nunique().reset_index()
2076
  avg_count = monthly_counts.groupby('nama')['kode_temuan'].mean().reset_index(name='Finding Count')
2077
  leadtime = df_risk.groupby('nama')['days_to_close'].mean().reset_index(name='Average Lead Time')
 
2087
  elif cnt < X_LIMIT and lt >= Y_LIMIT:
2088
  dev["obj5_quadrant_II"].append(div)
2089
 
2090
+ # === OBJ 6: Whiteboard — 2 bubble terbesar (Avg/Month non-Positive) ===
2091
  if 'kategori' in df.columns and 'temuan_kategori' in df.columns:
2092
+ df_nonpos = df[df['temuan_kategori'] != 'Positive'].copy()
2093
  if not df_nonpos.empty:
2094
+ start = df['created_at'].min().to_period('M')
2095
+ end = df['created_at'].max().to_period('M')
2096
+ n_months = len(pd.period_range(start=start, end=end, freq='M'))
2097
+ cat_avg = (df_nonpos.groupby('kategori').size() / n_months).sort_values(ascending=False).head(2)
2098
+ dev["obj6_top2_bubbles"] = [(cat, round(val, 1)) for cat, val in cat_avg.items()]
 
 
2099
 
2100
  return dev
2101
 
2102
+ # Jalankan ekstraksi deviasi
2103
  deviations = extract_critical_deviations(df_filtered)
2104
 
2105
+ # Bangun Insight Summary
2106
  insight_parts = []
 
2107
 
2108
+ # Obj 2: 9 lokasi ratio ~1.0
2109
  if deviations["obj2_locations_ratio_1"]:
2110
+ locs = ", ".join(deviations["obj2_locations_ratio_1"][:5])
2111
  insight_parts.append(
2112
  f"Nine locations show near-optimal finding-to-reporter ratio (~1.0), indicating balanced workload: "
2113
  f"{locs}, and others."
2114
  )
2115
 
2116
+ # Obj 3a–d
2117
  if deviations["obj3a_lowest_div_ratio"]:
2118
  div, ratio = deviations["obj3a_lowest_div_ratio"]
2119
  insight_parts.append(f"Division {div} has the lowest reporting ratio ({ratio}), suggesting potential under-utilization or resource gaps.")
 
2127
  name, lt = deviations["obj3d_slowest_executor"]
2128
  insight_parts.append(f"Executor {name} has the longest lead time ({lt} days), requiring workflow review.")
2129
 
2130
+ # Obj 4: Unsafe share
2131
  if deviations["obj4_unsafe_share"]:
2132
  unsafe_list = [f"{cat} ({pct}%)" for cat, pct in deviations["obj4_unsafe_share"].items()]
2133
  unsafe_str = "; ".join(unsafe_list)
2134
  insight_parts.append(f"Unsafe issues dominate: {unsafe_str} of all findings.")
2135
 
2136
+ # Obj 5: Kuadran risiko
2137
  if deviations["obj5_quadrant_I"]:
2138
  q1 = ", ".join(deviations["obj5_quadrant_I"][:3])
2139
  insight_parts.append(f"High-risk divisions (high volume + slow resolution): {q1}.")
 
2141
  q2 = ", ".join(deviations["obj5_quadrant_II"][:3])
2142
  insight_parts.append(f"Hidden-risk divisions (low volume but very slow): {q2} — may indicate capacity or priority issues.")
2143
 
2144
+ # Obj 6: Top 2 bubble
2145
  if deviations["obj6_top2_bubbles"]:
2146
  bub1, bub2 = deviations["obj6_top2_bubbles"]
2147
  insight_parts.append(
 
2149
  f"and {bub2[0]} ({bub2[1]}/month), indicating systemic root causes."
2150
  )
2151
 
 
2152
  insight_text = " ".join(insight_parts) if insight_parts else "No significant deviations detected based on current filters."
2153
 
2154
+ # Rekomendasi & Risk Mitigation Strategy
2155
+ rec_parts = [
2156
+ "Prioritize capacity assessment and coaching for divisions and individuals with lowest activity or longest resolution times.",
2157
+ "Initiate root-cause analysis on top two high-frequency unsafe categories to prevent recurrence.",
 
 
 
 
2158
  "Review workload distribution for locations with ratio ≈1.0 — they represent a benchmark for sustainable inspection load."
2159
+ ]
2160
 
2161
  mitigation_parts = [
2162
  "Establish SLA thresholds: max 7 days lead time, min 0.5 findings/reporter/month for active status.",
 
2167
  recommendation_text = " ".join(rec_parts)
2168
  mitigation_text = " ".join(mitigation_parts)
2169
 
2170
+ # Tampilkan — dua card terpisah
2171
  st.markdown(
2172
  f"""
2173
  <div class="card" style="