Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -519,7 +519,7 @@ if 'temuan_kode_distrik' in df_local.columns:
|
|
| 519 |
col1, col2 = st.columns(2)
|
| 520 |
|
| 521 |
with col1:
|
| 522 |
-
st.markdown("<h5>Unit Pembangkit:
|
| 523 |
fig_pg = create_polar_bar_chart(avg_ratio_pg, 'PG')
|
| 524 |
if fig_pg:
|
| 525 |
st.plotly_chart(fig_pg, use_container_width=True)
|
|
@@ -539,7 +539,7 @@ if 'temuan_kode_distrik' in df_local.columns:
|
|
| 539 |
st.warning("No data for PG area.")
|
| 540 |
|
| 541 |
with col2:
|
| 542 |
-
st.markdown("<h5>Unit Maintenance:
|
| 543 |
fig_um = create_polar_bar_chart(avg_ratio_um, 'UM')
|
| 544 |
if fig_um:
|
| 545 |
st.plotly_chart(fig_um, use_container_width=True)
|
|
@@ -549,7 +549,7 @@ if 'temuan_kode_distrik' in df_local.columns:
|
|
| 549 |
st.markdown("### Insight")
|
| 550 |
st.markdown(
|
| 551 |
f"<div class='ai-insight'>"
|
| 552 |
-
f"Across all companies, the finding-per-person ratio is similar
|
| 553 |
|
| 554 |
f"</div>",
|
| 555 |
unsafe_allow_html=True
|
|
@@ -614,20 +614,35 @@ avg_ratio_per_location = avg_ratio_per_location.dropna(subset=['avg_monthly_rati
|
|
| 614 |
|
| 615 |
# Plot Treemap dengan gradasi warna
|
| 616 |
if not avg_ratio_per_location.empty:
|
| 617 |
-
# Gunakan color_continuous_scale untuk gradasi warna: merah → kuning → hijau
|
| 618 |
fig_treemap = px.treemap(
|
| 619 |
avg_ratio_per_location,
|
| 620 |
-
path=['nama_lokasi_full'],
|
| 621 |
-
values='avg_monthly_ratio',
|
| 622 |
-
title='
|
| 623 |
-
labels={
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
| 625 |
color_continuous_scale=[
|
| 626 |
-
[0.0, '#D32F2F'],
|
| 627 |
-
[0.5, '#FFB300'],
|
| 628 |
-
[1.0, '#4CAF50']
|
| 629 |
]
|
| 630 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 631 |
# Format hover
|
| 632 |
fig_treemap.update_traces(
|
| 633 |
hovertemplate="<b>%{label}</b><br>Avg Ratio: %{value:.2f}<extra></extra>"
|
|
@@ -650,7 +665,7 @@ if not avg_ratio_per_location.empty:
|
|
| 650 |
# f"Locations with <span style='color:#D32F2F; font-weight:bold;'>red</span> color have a low ratio, indicating lower activity levels or potentially under-reporting. "
|
| 651 |
f"<strong>{top_location['nama_lokasi_full']}</strong> shows the highest activity level "
|
| 652 |
f"(<strong>{top_location['avg_monthly_ratio']:.2f}</strong>). "
|
| 653 |
-
f"<strong>{low_location['nama_lokasi_full']}</strong>
|
| 654 |
f"(<strong>{low_location['avg_monthly_ratio']:.2f}</strong>). "
|
| 655 |
f"Areas with high activity (green) warrant inspection into the underlying causes of frequent findings. "
|
| 656 |
f"Areas with low activity (red) should be reviewed to ensure reporting completeness and identify any hidden risks."
|
|
@@ -805,7 +820,7 @@ col_3a, col_3c = st.columns(2)
|
|
| 805 |
|
| 806 |
# ─── 3a: Reporter by Division (Rasio Temuan/Orang) ───────────────────────────
|
| 807 |
with col_3a:
|
| 808 |
-
st.markdown("<h5 style='text-align:center;'>3a.
|
| 809 |
if avg_ratio_per_nama.empty:
|
| 810 |
st.warning("No data for division-level reporter analysis.")
|
| 811 |
else:
|
|
@@ -825,7 +840,7 @@ with col_3a:
|
|
| 825 |
fig = px.bar(
|
| 826 |
subset, x='avg_monthly_ratio', y='nama', orientation='h',
|
| 827 |
title=f'{sort_opt} Divisions',
|
| 828 |
-
labels={'avg_monthly_ratio': '
|
| 829 |
color='color', color_discrete_map={c: c for c in subset['color'].unique()},
|
| 830 |
text=subset['avg_monthly_ratio'].apply(lambda x: f'{x:.2f}')
|
| 831 |
)
|
|
@@ -839,8 +854,9 @@ with col_3a:
|
|
| 839 |
best, worst = full_sorted.iloc[0]['nama'], full_sorted.iloc[-1]['nama']
|
| 840 |
st.markdown(
|
| 841 |
f"<div class='ai-insight'>"
|
| 842 |
-
f"<strong>Insight:</strong> Division reporting
|
| 843 |
-
f"<strong>{best}</strong> leads
|
|
|
|
| 844 |
f"<strong>Recommendation:</strong> Benchmark processes from {best}; assess capacity/tooling gaps in {worst}."
|
| 845 |
f"</div>",
|
| 846 |
unsafe_allow_html=True
|
|
@@ -849,7 +865,7 @@ with col_3a:
|
|
| 849 |
|
| 850 |
# ─── 3c: Reporter by Individual ──────────────────────────────────────────────
|
| 851 |
with col_3c:
|
| 852 |
-
st.markdown("<h5 style='text-align:center;'>3c.
|
| 853 |
if avg_rate_per_creator.empty:
|
| 854 |
st.warning("No data for individual reporter analysis.")
|
| 855 |
else:
|
|
@@ -867,7 +883,7 @@ with col_3c:
|
|
| 867 |
fig = px.bar(
|
| 868 |
subset, x='avg_monthly_rate', y='creator_name', orientation='h',
|
| 869 |
title=f'{sort_opt} Reporters',
|
| 870 |
-
labels={'avg_monthly_rate': '
|
| 871 |
color='color', color_discrete_map={c: c for c in subset['color'].unique()},
|
| 872 |
text=subset['avg_monthly_rate'].apply(lambda x: f'{x:.2f}')
|
| 873 |
)
|
|
@@ -893,7 +909,7 @@ col_3b, col_3d = st.columns(2)
|
|
| 893 |
|
| 894 |
# ─── 3b: Executor by Division (Lead Time) ────────────────────────────────────
|
| 895 |
with col_3b:
|
| 896 |
-
st.markdown("<h5 style='text-align:center;'>3b.
|
| 897 |
if avg_leadtime_nama.empty:
|
| 898 |
st.warning("No data for division-level executor analysis.")
|
| 899 |
else:
|
|
@@ -929,7 +945,7 @@ with col_3b:
|
|
| 929 |
y='nama',
|
| 930 |
orientation='h',
|
| 931 |
title=sort_opt,
|
| 932 |
-
labels={'avg_monthly_leadtime': '
|
| 933 |
color='color',
|
| 934 |
color_discrete_map={c: c for c in subset['color'].unique()},
|
| 935 |
text=subset['avg_monthly_leadtime'].apply(lambda x: f'{x:.1f}')
|
|
@@ -961,7 +977,7 @@ with col_3b:
|
|
| 961 |
# ─── 3d: Executor by Individual ──────────────────────────────────────────────
|
| 962 |
with col_3d:
|
| 963 |
st.markdown(
|
| 964 |
-
f"<h5 style='text-align:center;'>3d.
|
| 965 |
unsafe_allow_html=True
|
| 966 |
)
|
| 967 |
if avg_leadtime_per_indiv.empty:
|
|
@@ -996,7 +1012,7 @@ with col_3d:
|
|
| 996 |
y=id_col,
|
| 997 |
orientation='h',
|
| 998 |
title=sort_opt,
|
| 999 |
-
labels={'avg_monthly_leadtime': '
|
| 1000 |
color='color',
|
| 1001 |
color_discrete_map={c: c for c in subset['color'].unique()},
|
| 1002 |
text=subset['avg_monthly_leadtime'].apply(lambda x: f'{x:.1f}')
|
|
@@ -1032,7 +1048,7 @@ try:
|
|
| 1032 |
except ImportError:
|
| 1033 |
WORDCLOUD_AVAILABLE = False
|
| 1034 |
|
| 1035 |
-
st.markdown("<h3 class='section-title'>OBJECTIVE 4 -
|
| 1036 |
unsafe_allow_html=True)
|
| 1037 |
|
| 1038 |
# 🔹 Fungsi untuk membuat judul seragam
|
|
@@ -1220,7 +1236,7 @@ try:
|
|
| 1220 |
operator_lead = (
|
| 1221 |
df_local_matrix.groupby('nama')['lead_time_days']
|
| 1222 |
.mean()
|
| 1223 |
-
.reset_index(name='
|
| 1224 |
)
|
| 1225 |
# ============================
|
| 1226 |
# 6. Merge Risk Matrix
|
|
@@ -1307,7 +1323,7 @@ import numpy as np
|
|
| 1307 |
import pandas as pd
|
| 1308 |
|
| 1309 |
# =================== OBJECTIVE 6 - Predictive Dashboard & Early Warning Signals ===================
|
| 1310 |
-
st.markdown("<h3 class='section-title'>OBJECTIVE 6 —
|
| 1311 |
|
| 1312 |
# ✅ Enhanced CSS + Minimal Sortable JS
|
| 1313 |
st.markdown("""
|
|
@@ -1915,7 +1931,7 @@ if not df_category.empty:
|
|
| 1915 |
# title=dict(text="<b>Issue Category Trend vs Frequency (Non-Positive)</b>", x=0.5, y=0.95),
|
| 1916 |
xaxis=dict(
|
| 1917 |
title="Category",
|
| 1918 |
-
tickangle=
|
| 1919 |
showgrid=True,
|
| 1920 |
gridcolor="#e0e0e0",
|
| 1921 |
gridwidth=1,
|
|
@@ -1940,7 +1956,6 @@ if not df_category.empty:
|
|
| 1940 |
fig.add_annotation(
|
| 1941 |
xref="paper", yref="paper",
|
| 1942 |
x=0.05, y=0.95,
|
| 1943 |
-
text="Trend<br>(Slope)",
|
| 1944 |
showarrow=False,
|
| 1945 |
font=dict(size=12, color="#003DA5"),
|
| 1946 |
textangle=-90,
|
|
@@ -1949,7 +1964,7 @@ if not df_category.empty:
|
|
| 1949 |
fig.add_annotation(
|
| 1950 |
xref="paper", yref="paper",
|
| 1951 |
x=0.5, y=0.05,
|
| 1952 |
-
text="
|
| 1953 |
showarrow=False,
|
| 1954 |
font=dict(size=12, color="#003DA5"),
|
| 1955 |
align="center"
|
|
@@ -1957,7 +1972,7 @@ if not df_category.empty:
|
|
| 1957 |
fig.add_annotation(
|
| 1958 |
xref="paper", yref="paper",
|
| 1959 |
x=0.95, y=0.95,
|
| 1960 |
-
text="<b>
|
| 1961 |
showarrow=False,
|
| 1962 |
font=dict(size=12, color="#003DA5"),
|
| 1963 |
align="left"
|
|
@@ -1965,7 +1980,7 @@ if not df_category.empty:
|
|
| 1965 |
fig.add_annotation(
|
| 1966 |
xref="paper", yref="paper",
|
| 1967 |
x=0.05, y=0.1,
|
| 1968 |
-
text="<b>Semakin tinggi = semakin sering ditemukan deviasi</b><br><b>Semakin besar = semakin banyak ditemukan deviasi</b><br><b
|
| 1969 |
showarrow=False,
|
| 1970 |
font=dict(size=12, color="#003DA5"),
|
| 1971 |
align="left"
|
|
@@ -2146,7 +2161,7 @@ import math
|
|
| 2146 |
# OBJECTIVE 7 — INSIGHT & RECOMMENDATION (LLM FIRST, RULE-BASED IF FAIL)
|
| 2147 |
# =====================================================================
|
| 2148 |
|
| 2149 |
-
st.markdown("<h3 class='section-title'>OBJECTIVE 7 —
|
| 2150 |
|
| 2151 |
|
| 2152 |
# ============================================================
|
|
@@ -2354,7 +2369,7 @@ if dev["obj3b_slowest_executor"]:
|
|
| 2354 |
parts.append(f"executor <strong>{dev['obj3b_slowest_executor'][0]}</strong> ({dev['obj3b_slowest_executor'][1]} days)")
|
| 2355 |
|
| 2356 |
if parts:
|
| 2357 |
-
insight_lines.append("2.
|
| 2358 |
|
| 2359 |
uc, ua, nm = dev["obj4_unsafe_condition_pct"], dev["obj4_unsafe_action_pct"], dev["obj4_near_miss_pct"]
|
| 2360 |
if uc + ua + nm > 0:
|
|
@@ -2369,7 +2384,7 @@ if dev["obj6_top2_categories"]:
|
|
| 2369 |
c1, c2 = dev["obj6_top2_categories"]
|
| 2370 |
insight_lines.append(f"5. Top recurring non-Positive categories: <strong>{c1[0]}</strong> ({c1[1]}/mo) & <strong>{c2[0]}</strong> ({c2[1]}/mo).")
|
| 2371 |
|
| 2372 |
-
insight_text = "<br>".join(insight_lines)
|
| 2373 |
|
| 2374 |
|
| 2375 |
# ============================================================
|
|
@@ -2387,7 +2402,7 @@ else:
|
|
| 2387 |
recs.append({"point":"1","rec":"Launch spot-inspection sprint at across the 9 lowest-ratio locations.","mit":"Enable 3-min QR checklist + automated WhatsApp reminders."})
|
| 2388 |
|
| 2389 |
if parts:
|
| 2390 |
-
recs.append({"point":"2","rec":"
|
| 2391 |
|
| 2392 |
if uc + ua + nm > 0:
|
| 2393 |
recs.append({"point":"3","rec":"Enforce photo-based validation for Unsafe Condition/Action/Near Miss submissions to ensure accurate categorization.","mit":"System blocks submission if photo evidence or category justification is missing."})
|
|
@@ -2408,7 +2423,7 @@ else:
|
|
| 2408 |
st.markdown(
|
| 2409 |
f"""
|
| 2410 |
<div class="card" style="background-color:#f8f9fa;border-left:4px solid #003DA5;padding:16px;margin-bottom:20px;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.05);">
|
| 2411 |
-
<h4 style="margin-top:0;color:#FF6B6B;">
|
| 2412 |
<p style="margin-bottom:0;line-height:1.6;font-size:0.98em;">{insight_text}</p>
|
| 2413 |
</div>
|
| 2414 |
""",
|
|
|
|
| 519 |
col1, col2 = st.columns(2)
|
| 520 |
|
| 521 |
with col1:
|
| 522 |
+
st.markdown("<h5>Unit Pembangkit: Monthly Finding by Company</h5>", unsafe_allow_html=True)
|
| 523 |
fig_pg = create_polar_bar_chart(avg_ratio_pg, 'PG')
|
| 524 |
if fig_pg:
|
| 525 |
st.plotly_chart(fig_pg, use_container_width=True)
|
|
|
|
| 539 |
st.warning("No data for PG area.")
|
| 540 |
|
| 541 |
with col2:
|
| 542 |
+
st.markdown("<h5>Unit Maintenance: Monthly Finding by Company</h5>", unsafe_allow_html=True)
|
| 543 |
fig_um = create_polar_bar_chart(avg_ratio_um, 'UM')
|
| 544 |
if fig_um:
|
| 545 |
st.plotly_chart(fig_um, use_container_width=True)
|
|
|
|
| 549 |
st.markdown("### Insight")
|
| 550 |
st.markdown(
|
| 551 |
f"<div class='ai-insight'>"
|
| 552 |
+
f"Across all companies, the finding-per-person ratio is similar in the UM Area "
|
| 553 |
|
| 554 |
f"</div>",
|
| 555 |
unsafe_allow_html=True
|
|
|
|
| 614 |
|
| 615 |
# Plot Treemap dengan gradasi warna
|
| 616 |
if not avg_ratio_per_location.empty:
|
|
|
|
| 617 |
fig_treemap = px.treemap(
|
| 618 |
avg_ratio_per_location,
|
| 619 |
+
path=['nama_lokasi_full'],
|
| 620 |
+
values='avg_monthly_ratio',
|
| 621 |
+
title='Monthly Finding by Location',
|
| 622 |
+
labels={
|
| 623 |
+
'avg_monthly_ratio': 'Monthly Finding/Person Ratio',
|
| 624 |
+
'nama_lokasi_full': 'Location'
|
| 625 |
+
},
|
| 626 |
+
color='avg_monthly_ratio',
|
| 627 |
color_continuous_scale=[
|
| 628 |
+
[0.0, '#D32F2F'],
|
| 629 |
+
[0.5, '#FFB300'],
|
| 630 |
+
[1.0, '#4CAF50']
|
| 631 |
]
|
| 632 |
)
|
| 633 |
+
|
| 634 |
+
# --- Set global font size 12 ---
|
| 635 |
+
fig_treemap.update_layout(
|
| 636 |
+
font=dict(size=12)
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
# --- Set label font size inside the treemap ---
|
| 640 |
+
fig_treemap.update_traces(
|
| 641 |
+
textfont=dict(size=12)
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
st.plotly_chart(fig_treemap)
|
| 645 |
+
|
| 646 |
# Format hover
|
| 647 |
fig_treemap.update_traces(
|
| 648 |
hovertemplate="<b>%{label}</b><br>Avg Ratio: %{value:.2f}<extra></extra>"
|
|
|
|
| 665 |
# f"Locations with <span style='color:#D32F2F; font-weight:bold;'>red</span> color have a low ratio, indicating lower activity levels or potentially under-reporting. "
|
| 666 |
f"<strong>{top_location['nama_lokasi_full']}</strong> shows the highest activity level "
|
| 667 |
f"(<strong>{top_location['avg_monthly_ratio']:.2f}</strong>). "
|
| 668 |
+
f"<strong>{low_location['nama_lokasi_full']}</strong> is one of areas with the lowest activity level "
|
| 669 |
f"(<strong>{low_location['avg_monthly_ratio']:.2f}</strong>). "
|
| 670 |
f"Areas with high activity (green) warrant inspection into the underlying causes of frequent findings. "
|
| 671 |
f"Areas with low activity (red) should be reviewed to ensure reporting completeness and identify any hidden risks."
|
|
|
|
| 820 |
|
| 821 |
# ─── 3a: Reporter by Division (Rasio Temuan/Orang) ───────────────────────────
|
| 822 |
with col_3a:
|
| 823 |
+
st.markdown("<h5 style='text-align:center;'>3a. Finding/Person Ratio by Division (Reporter)</h5>", unsafe_allow_html=True)
|
| 824 |
if avg_ratio_per_nama.empty:
|
| 825 |
st.warning("No data for division-level reporter analysis.")
|
| 826 |
else:
|
|
|
|
| 840 |
fig = px.bar(
|
| 841 |
subset, x='avg_monthly_ratio', y='nama', orientation='h',
|
| 842 |
title=f'{sort_opt} Divisions',
|
| 843 |
+
labels={'avg_monthly_ratio': 'Monthly Ratio', 'nama': 'Division'},
|
| 844 |
color='color', color_discrete_map={c: c for c in subset['color'].unique()},
|
| 845 |
text=subset['avg_monthly_ratio'].apply(lambda x: f'{x:.2f}')
|
| 846 |
)
|
|
|
|
| 854 |
best, worst = full_sorted.iloc[0]['nama'], full_sorted.iloc[-1]['nama']
|
| 855 |
st.markdown(
|
| 856 |
f"<div class='ai-insight'>"
|
| 857 |
+
f"<strong>Insight:</strong> Division reporting ratio ranges from {min_r:.2f} to {max_r:.2f} (avg: {mean_r:.2f}). "
|
| 858 |
+
f"<strong>{best}</strong> leads."
|
| 859 |
+
# <strong>{worst}</strong> lags.
|
| 860 |
f"<strong>Recommendation:</strong> Benchmark processes from {best}; assess capacity/tooling gaps in {worst}."
|
| 861 |
f"</div>",
|
| 862 |
unsafe_allow_html=True
|
|
|
|
| 865 |
|
| 866 |
# ─── 3c: Reporter by Individual ──────────────────────────────────────────────
|
| 867 |
with col_3c:
|
| 868 |
+
st.markdown("<h5 style='text-align:center;'>3c. Monthly Findings per Reporter (Individual)</h5>", unsafe_allow_html=True)
|
| 869 |
if avg_rate_per_creator.empty:
|
| 870 |
st.warning("No data for individual reporter analysis.")
|
| 871 |
else:
|
|
|
|
| 883 |
fig = px.bar(
|
| 884 |
subset, x='avg_monthly_rate', y='creator_name', orientation='h',
|
| 885 |
title=f'{sort_opt} Reporters',
|
| 886 |
+
labels={'avg_monthly_rate': 'Monthly Findings', 'creator_name': 'Reporter'},
|
| 887 |
color='color', color_discrete_map={c: c for c in subset['color'].unique()},
|
| 888 |
text=subset['avg_monthly_rate'].apply(lambda x: f'{x:.2f}')
|
| 889 |
)
|
|
|
|
| 909 |
|
| 910 |
# ─── 3b: Executor by Division (Lead Time) ────────────────────────────────────
|
| 911 |
with col_3b:
|
| 912 |
+
st.markdown("<h5 style='text-align:center;'>3b. Monthly Lead Time by Division (Executor)</h5>", unsafe_allow_html=True)
|
| 913 |
if avg_leadtime_nama.empty:
|
| 914 |
st.warning("No data for division-level executor analysis.")
|
| 915 |
else:
|
|
|
|
| 945 |
y='nama',
|
| 946 |
orientation='h',
|
| 947 |
title=sort_opt,
|
| 948 |
+
labels={'avg_monthly_leadtime': 'Monthly Lead Time (Days)', 'nama': 'Division'},
|
| 949 |
color='color',
|
| 950 |
color_discrete_map={c: c for c in subset['color'].unique()},
|
| 951 |
text=subset['avg_monthly_leadtime'].apply(lambda x: f'{x:.1f}')
|
|
|
|
| 977 |
# ─── 3d: Executor by Individual ──────────────────────────────────────────────
|
| 978 |
with col_3d:
|
| 979 |
st.markdown(
|
| 980 |
+
f"<h5 style='text-align:center;'>3d. Monthly Lead Time per Executor (Individual)</h5>",
|
| 981 |
unsafe_allow_html=True
|
| 982 |
)
|
| 983 |
if avg_leadtime_per_indiv.empty:
|
|
|
|
| 1012 |
y=id_col,
|
| 1013 |
orientation='h',
|
| 1014 |
title=sort_opt,
|
| 1015 |
+
labels={'avg_monthly_leadtime': 'Monthly Lead Time (Days)', id_col: 'Executor'},
|
| 1016 |
color='color',
|
| 1017 |
color_discrete_map={c: c for c in subset['color'].unique()},
|
| 1018 |
text=subset['avg_monthly_leadtime'].apply(lambda x: f'{x:.1f}')
|
|
|
|
| 1048 |
except ImportError:
|
| 1049 |
WORDCLOUD_AVAILABLE = False
|
| 1050 |
|
| 1051 |
+
st.markdown("<h3 class='section-title'>OBJECTIVE 4 - Unsafe Issues: Which One is the Most Often?</h3>",
|
| 1052 |
unsafe_allow_html=True)
|
| 1053 |
|
| 1054 |
# 🔹 Fungsi untuk membuat judul seragam
|
|
|
|
| 1236 |
operator_lead = (
|
| 1237 |
df_local_matrix.groupby('nama')['lead_time_days']
|
| 1238 |
.mean()
|
| 1239 |
+
.reset_index(name='Lead Time')
|
| 1240 |
)
|
| 1241 |
# ============================
|
| 1242 |
# 6. Merge Risk Matrix
|
|
|
|
| 1323 |
import pandas as pd
|
| 1324 |
|
| 1325 |
# =================== OBJECTIVE 6 - Predictive Dashboard & Early Warning Signals ===================
|
| 1326 |
+
st.markdown("<h3 class='section-title'>OBJECTIVE 6 — Prediction for Early Warning Signals: Which Ones Have Less Future Inspections?</h3>", unsafe_allow_html=True)
|
| 1327 |
|
| 1328 |
# ✅ Enhanced CSS + Minimal Sortable JS
|
| 1329 |
st.markdown("""
|
|
|
|
| 1931 |
# title=dict(text="<b>Issue Category Trend vs Frequency (Non-Positive)</b>", x=0.5, y=0.95),
|
| 1932 |
xaxis=dict(
|
| 1933 |
title="Category",
|
| 1934 |
+
tickangle=135,
|
| 1935 |
showgrid=True,
|
| 1936 |
gridcolor="#e0e0e0",
|
| 1937 |
gridwidth=1,
|
|
|
|
| 1956 |
fig.add_annotation(
|
| 1957 |
xref="paper", yref="paper",
|
| 1958 |
x=0.05, y=0.95,
|
|
|
|
| 1959 |
showarrow=False,
|
| 1960 |
font=dict(size=12, color="#003DA5"),
|
| 1961 |
textangle=-90,
|
|
|
|
| 1964 |
fig.add_annotation(
|
| 1965 |
xref="paper", yref="paper",
|
| 1966 |
x=0.5, y=0.05,
|
| 1967 |
+
text="",
|
| 1968 |
showarrow=False,
|
| 1969 |
font=dict(size=12, color="#003DA5"),
|
| 1970 |
align="center"
|
|
|
|
| 1972 |
fig.add_annotation(
|
| 1973 |
xref="paper", yref="paper",
|
| 1974 |
x=0.95, y=0.95,
|
| 1975 |
+
text="<b>Number of Findings</b><br>• 10<br>•• 20<br>••• 30<br>•••• 40",
|
| 1976 |
showarrow=False,
|
| 1977 |
font=dict(size=12, color="#003DA5"),
|
| 1978 |
align="left"
|
|
|
|
| 1980 |
fig.add_annotation(
|
| 1981 |
xref="paper", yref="paper",
|
| 1982 |
x=0.05, y=0.1,
|
| 1983 |
+
text="<b>Semakin tinggi = semakin sering ditemukan deviasi</b><br><b>Semakin besar = semakin banyak ditemukan deviasi</b><br><b></b>",
|
| 1984 |
showarrow=False,
|
| 1985 |
font=dict(size=12, color="#003DA5"),
|
| 1986 |
align="left"
|
|
|
|
| 2161 |
# OBJECTIVE 7 — INSIGHT & RECOMMENDATION (LLM FIRST, RULE-BASED IF FAIL)
|
| 2162 |
# =====================================================================
|
| 2163 |
|
| 2164 |
+
st.markdown("<h3 class='section-title'>OBJECTIVE 7 — Risk Mitigation: What are the Insights and Recommendations?</h3>", unsafe_allow_html=True)
|
| 2165 |
|
| 2166 |
|
| 2167 |
# ============================================================
|
|
|
|
| 2369 |
parts.append(f"executor <strong>{dev['obj3b_slowest_executor'][0]}</strong> ({dev['obj3b_slowest_executor'][1]} days)")
|
| 2370 |
|
| 2371 |
if parts:
|
| 2372 |
+
insight_lines.append("2. Uneven operational capacity: " + "; ".join(parts))
|
| 2373 |
|
| 2374 |
uc, ua, nm = dev["obj4_unsafe_condition_pct"], dev["obj4_unsafe_action_pct"], dev["obj4_near_miss_pct"]
|
| 2375 |
if uc + ua + nm > 0:
|
|
|
|
| 2384 |
c1, c2 = dev["obj6_top2_categories"]
|
| 2385 |
insight_lines.append(f"5. Top recurring non-Positive categories: <strong>{c1[0]}</strong> ({c1[1]}/mo) & <strong>{c2[0]}</strong> ({c2[1]}/mo).")
|
| 2386 |
|
| 2387 |
+
insight_text = "<br><br>".join(insight_lines)
|
| 2388 |
|
| 2389 |
|
| 2390 |
# ============================================================
|
|
|
|
| 2402 |
recs.append({"point":"1","rec":"Launch spot-inspection sprint at across the 9 lowest-ratio locations.","mit":"Enable 3-min QR checklist + automated WhatsApp reminders."})
|
| 2403 |
|
| 2404 |
if parts:
|
| 2405 |
+
recs.append({"point":"2","rec":"Real-time monitoring of finding/reporter ratios and resolution lead times per division/individual.","mit":"Trigger coaching alerts to Area PICs & Division"})
|
| 2406 |
|
| 2407 |
if uc + ua + nm > 0:
|
| 2408 |
recs.append({"point":"3","rec":"Enforce photo-based validation for Unsafe Condition/Action/Near Miss submissions to ensure accurate categorization.","mit":"System blocks submission if photo evidence or category justification is missing."})
|
|
|
|
| 2423 |
st.markdown(
|
| 2424 |
f"""
|
| 2425 |
<div class="card" style="background-color:#f8f9fa;border-left:4px solid #003DA5;padding:16px;margin-bottom:20px;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.05);">
|
| 2426 |
+
<h4 style="margin-top:0;color:#FF6B6B;">Summary</h4>
|
| 2427 |
<p style="margin-bottom:0;line-height:1.6;font-size:0.98em;">{insight_text}</p>
|
| 2428 |
</div>
|
| 2429 |
""",
|