Update app.py
Browse files
app.py
CHANGED
|
@@ -903,7 +903,7 @@ def build_agg_wilayah_jenis(df_filtered: pd.DataFrame, faktor_wilayah_jenis: pd.
|
|
| 903 |
|
| 904 |
return agg
|
| 905 |
# ============================================================
|
| 906 |
-
# 8) AGREGAT WILAYAH (KESELURUHAN) β FIX: avg3 + tampilkan POP/TARGET/
|
| 907 |
# ============================================================
|
| 908 |
|
| 909 |
def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_jenis: pd.DataFrame, kew_value: str):
|
|
@@ -956,32 +956,69 @@ def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_j
|
|
| 956 |
Indeks_Final_Wilayah_0_100=("Indeks_Final_Agregat_0_100", "mean"),
|
| 957 |
)
|
| 958 |
|
| 959 |
-
# --- tempel POP/TARGET/
|
| 960 |
if faktor_wilayah_jenis is not None and not faktor_wilayah_jenis.empty:
|
| 961 |
fw = faktor_wilayah_jenis.copy()
|
| 962 |
fw["Jenis"] = fw["Jenis"].astype(str).str.lower().str.strip()
|
| 963 |
|
| 964 |
-
# pivot
|
| 965 |
piv = fw.pivot_table(
|
| 966 |
index=["group_key", label_name],
|
| 967 |
columns="Jenis",
|
| 968 |
-
values=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 969 |
aggfunc="first"
|
| 970 |
)
|
| 971 |
-
|
| 972 |
-
# flatten columns
|
| 973 |
piv.columns = [f"{v}_{k}" for v, k in piv.columns]
|
| 974 |
piv = piv.reset_index()
|
| 975 |
|
| 976 |
out = out.merge(piv, on=["group_key", label_name], how="left")
|
| 977 |
|
| 978 |
-
# rapihin NaN -> 0
|
| 979 |
-
for j in ["sekolah","umum","khusus"]:
|
| 980 |
for basecol in ["pop_total_jenis", "target_total_68_jenis", "n_jenis", "gap_target68_jenis"]:
|
| 981 |
c = f"{basecol}_{j}"
|
| 982 |
if c in out.columns:
|
| 983 |
-
|
| 984 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 985 |
|
| 986 |
# rounding index
|
| 987 |
for c in [
|
|
@@ -995,14 +1032,17 @@ def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_j
|
|
| 995 |
if c in out.columns:
|
| 996 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0.0).round(2)
|
| 997 |
|
| 998 |
-
|
|
|
|
|
|
|
| 999 |
|
|
|
|
| 1000 |
|
| 1001 |
# ============================================================
|
| 1002 |
-
# 9) SUMMARY (PER JENIS) + KESELURUHAN (FIX Γ·3 +
|
| 1003 |
# ============================================================
|
| 1004 |
|
| 1005 |
-
def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame):
|
| 1006 |
jenis_list = ["sekolah", "umum", "khusus"]
|
| 1007 |
|
| 1008 |
def _row_default(jenis):
|
|
@@ -1010,44 +1050,92 @@ def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame):
|
|
| 1010 |
"Jenis": jenis,
|
| 1011 |
"Jumlah_Wilayah": 0,
|
| 1012 |
"Total_Perpus": 0,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1013 |
"Rata2_sub_koleksi": 0.0,
|
| 1014 |
"Rata2_sub_sdm": 0.0,
|
| 1015 |
"Rata2_sub_pelayanan": 0.0,
|
| 1016 |
"Rata2_sub_pengelolaan": 0.0,
|
| 1017 |
"Rata2_dim_kepatuhan": 0.0,
|
| 1018 |
"Rata2_dim_kinerja": 0.0,
|
|
|
|
|
|
|
|
|
|
| 1019 |
"Indeks_Final_Disesuaikan_0_100": 0.0,
|
|
|
|
| 1020 |
}
|
| 1021 |
|
| 1022 |
rows_by_jenis = {j: _row_default(j) for j in jenis_list}
|
| 1023 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1024 |
if agg_jenis is not None and not agg_jenis.empty:
|
| 1025 |
for jenis in jenis_list:
|
| 1026 |
sub = agg_jenis[agg_jenis["Jenis"].astype(str).str.lower() == jenis].copy()
|
| 1027 |
if sub.empty:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1028 |
continue
|
| 1029 |
|
|
|
|
|
|
|
|
|
|
| 1030 |
rows_by_jenis[jenis] = {
|
| 1031 |
"Jenis": jenis,
|
| 1032 |
"Jumlah_Wilayah": int(sub.shape[0]),
|
| 1033 |
"Total_Perpus": int(pd.to_numeric(sub["Jumlah"], errors="coerce").fillna(0).sum()),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1034 |
"Rata2_sub_koleksi": float(pd.to_numeric(sub["Rata2_sub_koleksi"], errors="coerce").fillna(0).mean()),
|
| 1035 |
"Rata2_sub_sdm": float(pd.to_numeric(sub["Rata2_sub_sdm"], errors="coerce").fillna(0).mean()),
|
| 1036 |
"Rata2_sub_pelayanan": float(pd.to_numeric(sub["Rata2_sub_pelayanan"], errors="coerce").fillna(0).mean()),
|
| 1037 |
"Rata2_sub_pengelolaan": float(pd.to_numeric(sub["Rata2_sub_pengelolaan"], errors="coerce").fillna(0).mean()),
|
| 1038 |
"Rata2_dim_kepatuhan": float(pd.to_numeric(sub["Rata2_dim_kepatuhan"], errors="coerce").fillna(0).mean()),
|
| 1039 |
"Rata2_dim_kinerja": float(pd.to_numeric(sub["Rata2_dim_kinerja"], errors="coerce").fillna(0).mean()),
|
| 1040 |
-
|
|
|
|
|
|
|
|
|
|
| 1041 |
}
|
| 1042 |
|
| 1043 |
rows = [rows_by_jenis[j] for j in jenis_list]
|
| 1044 |
|
| 1045 |
-
|
| 1046 |
-
float(rows_by_jenis["sekolah"]["Indeks_Final_Disesuaikan_0_100"])
|
| 1047 |
-
+ float(rows_by_jenis["umum"]["Indeks_Final_Disesuaikan_0_100"])
|
| 1048 |
-
+ float(rows_by_jenis["khusus"]["Indeks_Final_Disesuaikan_0_100"])
|
| 1049 |
-
) / 3.0
|
| 1050 |
-
|
| 1051 |
def _avg3(field):
|
| 1052 |
return (
|
| 1053 |
float(rows_by_jenis["sekolah"][field])
|
|
@@ -1055,34 +1143,59 @@ def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame):
|
|
| 1055 |
+ float(rows_by_jenis["khusus"][field])
|
| 1056 |
) / 3.0
|
| 1057 |
|
|
|
|
|
|
|
|
|
|
| 1058 |
total_perpus_all = int(rows_by_jenis["sekolah"]["Total_Perpus"] + rows_by_jenis["umum"]["Total_Perpus"] + rows_by_jenis["khusus"]["Total_Perpus"])
|
| 1059 |
jumlah_wilayah_all = int(agg_total.shape[0]) if (agg_total is not None and not agg_total.empty) else int(max(rows_by_jenis["sekolah"]["Jumlah_Wilayah"], rows_by_jenis["umum"]["Jumlah_Wilayah"], rows_by_jenis["khusus"]["Jumlah_Wilayah"]))
|
| 1060 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1061 |
rows.append({
|
| 1062 |
"Jenis": "keseluruhan",
|
| 1063 |
"Jumlah_Wilayah": jumlah_wilayah_all,
|
| 1064 |
"Total_Perpus": total_perpus_all,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1065 |
"Rata2_sub_koleksi": _avg3("Rata2_sub_koleksi"),
|
| 1066 |
"Rata2_sub_sdm": _avg3("Rata2_sub_sdm"),
|
| 1067 |
"Rata2_sub_pelayanan": _avg3("Rata2_sub_pelayanan"),
|
| 1068 |
"Rata2_sub_pengelolaan": _avg3("Rata2_sub_pengelolaan"),
|
| 1069 |
"Rata2_dim_kepatuhan": _avg3("Rata2_dim_kepatuhan"),
|
| 1070 |
"Rata2_dim_kinerja": _avg3("Rata2_dim_kinerja"),
|
|
|
|
|
|
|
| 1071 |
"Indeks_Final_Disesuaikan_0_100": final_all,
|
|
|
|
| 1072 |
})
|
| 1073 |
|
| 1074 |
out = pd.DataFrame(rows)
|
| 1075 |
|
|
|
|
| 1076 |
for c in [
|
| 1077 |
"Rata2_sub_koleksi","Rata2_sub_sdm","Rata2_sub_pelayanan","Rata2_sub_pengelolaan",
|
| 1078 |
"Rata2_dim_kepatuhan","Rata2_dim_kinerja"
|
| 1079 |
]:
|
| 1080 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0.0).round(3)
|
| 1081 |
|
| 1082 |
-
|
|
|
|
| 1083 |
|
| 1084 |
-
|
|
|
|
|
|
|
| 1085 |
|
|
|
|
|
|
|
|
|
|
| 1086 |
|
| 1087 |
# ============================================================
|
| 1088 |
# 10) DETAIL ENTITAS: Final menempel dari agg_total (wilayah)
|
|
@@ -1287,75 +1400,29 @@ def _make_bell_curve(dfp: pd.DataFrame, xcol: str, title: str, label_col: str |
|
|
| 1287 |
|
| 1288 |
|
| 1289 |
# ============================================================
|
| 1290 |
-
# 13) KPI DASHBOARD (PATCH:
|
| 1291 |
# ============================================================
|
| 1292 |
|
| 1293 |
-
def compute_dashboard_kpis(summary_jenis: pd.DataFrame, agg_total: pd.DataFrame, agg_jenis: pd.DataFrame
|
| 1294 |
-
def
|
| 1295 |
sub = summary_jenis[summary_jenis["Jenis"].astype(str).str.lower() == j]
|
| 1296 |
if sub.empty:
|
| 1297 |
return 0.0
|
| 1298 |
-
return float(pd.to_numeric(sub[
|
| 1299 |
-
|
| 1300 |
-
final_sekolah = _get_final("sekolah")
|
| 1301 |
-
final_umum = _get_final("umum")
|
| 1302 |
-
final_khusus = _get_final("khusus")
|
| 1303 |
-
final_all = (final_sekolah + final_umum + final_khusus) / 3.0
|
| 1304 |
-
|
| 1305 |
-
def _get_dasar(j):
|
| 1306 |
-
if agg_jenis is None or agg_jenis.empty:
|
| 1307 |
-
return 0.0
|
| 1308 |
-
sub = agg_jenis[agg_jenis["Jenis"].astype(str).str.lower() == j]
|
| 1309 |
-
if sub.empty:
|
| 1310 |
-
return 0.0
|
| 1311 |
-
return float(pd.to_numeric(sub["Indeks_Dasar_Agregat_0_100"], errors="coerce").fillna(0).mean())
|
| 1312 |
|
| 1313 |
-
|
| 1314 |
-
|
| 1315 |
-
dasar_khusus = _get_dasar("khusus")
|
| 1316 |
-
dasar_all = (dasar_sekolah + dasar_umum + dasar_khusus) / 3.0
|
| 1317 |
|
| 1318 |
-
|
| 1319 |
-
cakupan_pct = 0.0
|
| 1320 |
-
faktor_mean = 1.0
|
| 1321 |
-
if faktor_wilayah_jenis is not None and (not faktor_wilayah_jenis.empty):
|
| 1322 |
-
fw = faktor_wilayah_jenis.copy()
|
| 1323 |
-
fw["Jenis"] = fw["Jenis"].astype(str).str.lower().str.strip()
|
| 1324 |
-
|
| 1325 |
-
factors = []
|
| 1326 |
-
for j in ["sekolah","umum","khusus"]:
|
| 1327 |
-
sub = fw[fw["Jenis"] == j]
|
| 1328 |
-
if sub.empty:
|
| 1329 |
-
factors.append(0.0)
|
| 1330 |
-
continue
|
| 1331 |
-
|
| 1332 |
-
n_sum = float(pd.to_numeric(sub.get("n_jenis", 0), errors="coerce").fillna(0).sum())
|
| 1333 |
-
t_sum = float(pd.to_numeric(sub.get("target_total_68_jenis", 0), errors="coerce").fillna(0).sum())
|
| 1334 |
-
f = min(n_sum / t_sum, 1.0) if (t_sum and t_sum > 0) else 0.0
|
| 1335 |
-
factors.append(f)
|
| 1336 |
-
|
| 1337 |
-
cakupan_pct = (sum(factors) / 3.0) * 100.0
|
| 1338 |
-
|
| 1339 |
-
# faktor_mean juga dibuat avg3 dari faktor total per jenis (opsional)
|
| 1340 |
-
# kalau mau: mean faktor per-wilayahΓjenis
|
| 1341 |
-
faktor_mean = float(pd.to_numeric(fw.get("faktor_penyesuaian_jenis", 1.0), errors="coerce").fillna(1.0).mean())
|
| 1342 |
-
|
| 1343 |
-
dampak = final_all - dasar_all
|
| 1344 |
-
|
| 1345 |
-
return {
|
| 1346 |
-
"final_all": final_all,
|
| 1347 |
-
"dasar_all": dasar_all,
|
| 1348 |
-
"cakupan_pct": cakupan_pct,
|
| 1349 |
-
"faktor_mean": faktor_mean,
|
| 1350 |
-
"dampak": dampak
|
| 1351 |
-
}
|
| 1352 |
|
| 1353 |
|
| 1354 |
def build_kpi_markdown(summary_jenis: pd.DataFrame, agg_total: pd.DataFrame, agg_jenis: pd.DataFrame, faktor_wilayah_jenis: pd.DataFrame | None = None) -> str:
|
|
|
|
|
|
|
| 1355 |
if summary_jenis is None or summary_jenis.empty:
|
| 1356 |
return ""
|
| 1357 |
|
| 1358 |
-
k = compute_dashboard_kpis(summary_jenis, agg_total, agg_jenis
|
| 1359 |
|
| 1360 |
def fmt(x, nd=2):
|
| 1361 |
return "NA" if pd.isna(x) else f"{x:.{nd}f}"
|
|
@@ -1373,131 +1440,42 @@ def build_kpi_markdown(summary_jenis: pd.DataFrame, agg_total: pd.DataFrame, agg
|
|
| 1373 |
<div style="font-size:26px; font-weight:700;">{fmt(k["dasar_all"],2)}</div>
|
| 1374 |
<div style="opacity:0.7;">Rata-rata 3 jenis (tetap Γ·3)</div>
|
| 1375 |
</div>
|
| 1376 |
-
|
| 1377 |
-
<div style="border:1px solid #333; border-radius:10px; padding:10px 12px; min-width:220px;">
|
| 1378 |
-
<div style="opacity:0.8;">Cakupan Sampel (berdasarkan target 68%)</div>
|
| 1379 |
-
<div style="font-size:26px; font-weight:700;">{fmt(k["cakupan_pct"],0)}%</div>
|
| 1380 |
-
<div style="opacity:0.7;">Rumus: min(total_terkumpul/target_68, 1.0)</div>
|
| 1381 |
-
</div>
|
| 1382 |
-
|
| 1383 |
-
<div style="border:1px solid #333; border-radius:10px; padding:10px 12px; min-width:220px;">
|
| 1384 |
-
<div style="opacity:0.8;">Penyesuaian Nilai (rata-rata)</div>
|
| 1385 |
-
<div style="font-size:26px; font-weight:700;">{fmt(k["dampak"],2)} poin</div>
|
| 1386 |
-
<div style="opacity:0.7;">Faktor penyesuaian (mean): {fmt(k["faktor_mean"],3)}</div>
|
| 1387 |
-
</div>
|
| 1388 |
</div>
|
| 1389 |
""".strip()
|
| 1390 |
|
| 1391 |
-
|
| 1392 |
# ============================================================
|
| 1393 |
# 14) LLM + WORD
|
| 1394 |
# ============================================================
|
| 1395 |
|
| 1396 |
-
|
| 1397 |
-
|
| 1398 |
-
def get_llm_client():
|
| 1399 |
-
global _HF_CLIENT
|
| 1400 |
-
if _HF_CLIENT is not None:
|
| 1401 |
-
return _HF_CLIENT
|
| 1402 |
-
try:
|
| 1403 |
-
_HF_CLIENT = InferenceClient(model=LLM_MODEL_NAME, token=HF_TOKEN) if HF_TOKEN else InferenceClient(model=LLM_MODEL_NAME)
|
| 1404 |
-
return _HF_CLIENT
|
| 1405 |
-
except Exception:
|
| 1406 |
-
_HF_CLIENT = None
|
| 1407 |
-
return None
|
| 1408 |
-
|
| 1409 |
-
def build_context(summary_jenis: pd.DataFrame, agg_total: pd.DataFrame, verif_total: pd.DataFrame, wilayah: str, kew: str) -> str:
|
| 1410 |
-
lines = []
|
| 1411 |
-
lines.append(f"Wilayah filter: {wilayah}")
|
| 1412 |
-
lines.append(f"Kewenangan: {kew}")
|
| 1413 |
-
lines.append("Metode: Indeks dasar dihitung per entitas (Yeo-Johnson + MinMax nasional per indikator), lalu diagregasi per wilayahΓjenis. Setelah itu dilakukan penyesuaian berbasis kecukupan sampel minimum 68% pada level wilayah.")
|
| 1414 |
-
lines.append("Rumus penyesuaian: faktor = min(total_terkumpul / target_total_68, 1.0). Faktor wilayah dipakai untuk semua jenis.")
|
| 1415 |
-
lines.append("Rumus keseluruhan wilayah (FIX): nilai keseluruhan wilayah diambil dari rata-rata 3 jenis (sekolah+umum+khusus) Γ· 3 (missing=0, tetap Γ·3).")
|
| 1416 |
-
|
| 1417 |
-
if summary_jenis is not None and not summary_jenis.empty:
|
| 1418 |
-
lines.append("\nRingkasan (jenis + keseluruhan):")
|
| 1419 |
-
for _, r in summary_jenis.iterrows():
|
| 1420 |
-
lines.append(
|
| 1421 |
-
f"- {r['Jenis']}: wilayah={int(r['Jumlah_Wilayah'])}, total_perpus={int(r['Total_Perpus'])}, "
|
| 1422 |
-
f"dim_kepatuhan={float(r['Rata2_dim_kepatuhan']):.3f}, dim_kinerja={float(r['Rata2_dim_kinerja']):.3f}, "
|
| 1423 |
-
f"final_disesuaikan={float(r['Indeks_Final_Disesuaikan_0_100']):.2f}"
|
| 1424 |
-
)
|
| 1425 |
-
|
| 1426 |
-
if agg_total is not None and not agg_total.empty:
|
| 1427 |
-
label_col = "Kab/Kota" if "Kab/Kota" in agg_total.columns else ("Provinsi" if "Provinsi" in agg_total.columns else None)
|
| 1428 |
-
lines.append("\nTop 5 wilayah (Final disesuaikan tertinggi):")
|
| 1429 |
-
top = agg_total.sort_values("Indeks_Final_Wilayah_0_100", ascending=False).head(5)
|
| 1430 |
-
for _, r in top.iterrows():
|
| 1431 |
-
wl = r.get(label_col, "(wilayah)") if label_col else "(wilayah)"
|
| 1432 |
-
lines.append(f"- {wl}: Final={float(r['Indeks_Final_Wilayah_0_100']):.2f} | Faktor={float(r.get('faktor_penyesuaian', 1.0)):.3f} | total={int(r.get('n_total', 0))}")
|
| 1433 |
-
|
| 1434 |
-
if verif_total is not None and not verif_total.empty and "GAP_Ke_Target68_Total" in verif_total.columns:
|
| 1435 |
-
lines.append("\nTop 5 wilayah (GAP ke target 68% terbesar):")
|
| 1436 |
-
tmp = verif_total.sort_values("GAP_Ke_Target68_Total", ascending=False).head(5)
|
| 1437 |
-
name_col = "Kab/Kota" if "Kab/Kota" in tmp.columns else ("Provinsi" if "Provinsi" in tmp.columns else None)
|
| 1438 |
-
for _, r in tmp.iterrows():
|
| 1439 |
-
nm = r.get(name_col, "") if name_col else ""
|
| 1440 |
-
lines.append(f"- {nm}: GAP={int(r['GAP_Ke_Target68_Total'])}")
|
| 1441 |
-
|
| 1442 |
-
return "\n".join(lines)
|
| 1443 |
-
|
| 1444 |
-
def generate_llm_analysis(summary_jenis, agg_total, verif_total, wilayah, kew):
|
| 1445 |
-
ctx = build_context(summary_jenis, agg_total, verif_total, wilayah, kew)
|
| 1446 |
-
client = get_llm_client()
|
| 1447 |
-
if client is None or not USE_LLM:
|
| 1448 |
-
return "Analisis otomatis (LLM) tidak tersedia. Pastikan token HuggingFace tersedia dan model bisa diakses."
|
| 1449 |
-
|
| 1450 |
-
system_prompt = (
|
| 1451 |
-
"Anda adalah analis kebijakan perpustakaan dan literasi di Indonesia. "
|
| 1452 |
-
"Tugas Anda menyusun analisis berbasis data IPLM secara formal, tajam, dan operasional."
|
| 1453 |
-
)
|
| 1454 |
-
user_prompt = f"""
|
| 1455 |
-
DATA RINGKAS IPLM (PENYESUAIAN BERBASIS KECUKUPAN SAMPEL 68% DI LEVEL WILAYAH):
|
| 1456 |
-
|
| 1457 |
-
{ctx}
|
| 1458 |
-
|
| 1459 |
-
TULISKAN ANALISIS BAHASA INDONESIA FORMAL, STRUKTUR:
|
| 1460 |
-
1) Gambaran umum hasil wilayah (1 paragraf).
|
| 1461 |
-
2) Analisis jenis sekolah, umum, khusus serta indeks keseluruhan (2 paragraf).
|
| 1462 |
-
3) Penjelasan makna penyesuaian berbasis kecukupan sampel (1 paragraf, bahasa netral non-konfrontatif).
|
| 1463 |
-
4) Rekomendasi program 3β5 tahun (2 paragraf, konkret dan dapat dieksekusi).
|
| 1464 |
-
|
| 1465 |
-
ATURAN:
|
| 1466 |
-
- Jangan memakai label eksplisit "rendah/sedang/tinggi".
|
| 1467 |
-
- Gunakan frasa netral: "memerlukan penguatan", "memerlukan konsolidasi", dsb.
|
| 1468 |
-
- Fokus pada Indeks FINAL WILAYAH (disesuaikan), bukan individu.
|
| 1469 |
-
"""
|
| 1470 |
-
try:
|
| 1471 |
-
resp = client.chat_completion(
|
| 1472 |
-
model=LLM_MODEL_NAME,
|
| 1473 |
-
messages=[{"role":"system","content":system_prompt},{"role":"user","content":user_prompt}],
|
| 1474 |
-
max_tokens=1100,
|
| 1475 |
-
temperature=0.25,
|
| 1476 |
-
top_p=0.9,
|
| 1477 |
-
)
|
| 1478 |
-
text = resp.choices[0].message.content.strip()
|
| 1479 |
-
return text if text else "LLM mengembalikan respon kosong."
|
| 1480 |
-
except Exception as e:
|
| 1481 |
-
return f"β οΈ Error saat memanggil LLM: {repr(e)}"
|
| 1482 |
-
|
| 1483 |
-
def generate_word_report(wilayah, summary_jenis, agg_total, agg_jenis, analysis_text):
|
| 1484 |
doc = Document()
|
| 1485 |
doc.add_heading(f"Laporan IPLM β {wilayah}", level=1)
|
| 1486 |
|
| 1487 |
doc.add_heading("Ringkasan Dashboard", level=2)
|
| 1488 |
|
| 1489 |
-
|
| 1490 |
-
|
| 1491 |
-
|
| 1492 |
-
|
| 1493 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1494 |
|
| 1495 |
-
doc.add_paragraph("
|
|
|
|
|
|
|
|
|
|
| 1496 |
show = summary_jenis.copy()
|
|
|
|
| 1497 |
preferred = [
|
| 1498 |
-
"Jenis","Jumlah_Wilayah","Total_Perpus",
|
| 1499 |
-
"
|
| 1500 |
-
"
|
|
|
|
| 1501 |
]
|
| 1502 |
show = show[[c for c in preferred if c in show.columns]]
|
| 1503 |
|
|
@@ -1512,15 +1490,17 @@ def generate_word_report(wilayah, summary_jenis, agg_total, agg_jenis, analysis_
|
|
| 1512 |
v = row[c]
|
| 1513 |
if pd.isna(v):
|
| 1514 |
cells[i].text = ""
|
|
|
|
|
|
|
| 1515 |
elif isinstance(v, (float, np.floating)):
|
| 1516 |
-
if "
|
| 1517 |
cells[i].text = f"{float(v):.2f}"
|
| 1518 |
elif "Rata2_" in c:
|
| 1519 |
cells[i].text = f"{float(v):.3f}"
|
|
|
|
|
|
|
| 1520 |
else:
|
| 1521 |
-
cells[i].text = f"{float(v):.
|
| 1522 |
-
elif isinstance(v, (int, np.integer)):
|
| 1523 |
-
cells[i].text = str(int(v))
|
| 1524 |
else:
|
| 1525 |
cells[i].text = str(v)
|
| 1526 |
|
|
@@ -1568,7 +1548,9 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1568 |
if df_all is None or df_all.empty or df_raw is None or df_raw.empty:
|
| 1569 |
return _empty_outputs("β οΈ Data belum ter-load. Pastikan file tersedia di repo/server.")
|
| 1570 |
|
|
|
|
| 1571 |
# FILTER ANALISIS (df_all)
|
|
|
|
| 1572 |
df = df_all.copy()
|
| 1573 |
if prov_value and prov_value != "(Semua)":
|
| 1574 |
df = df[df["PROV_DISP"] == prov_value]
|
|
@@ -1580,21 +1562,51 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1580 |
if df.empty:
|
| 1581 |
return _empty_outputs("Tidak ada data untuk filter ini.")
|
| 1582 |
|
| 1583 |
-
# ====
|
| 1584 |
-
|
| 1585 |
-
|
| 1586 |
-
|
| 1587 |
-
|
| 1588 |
-
|
| 1589 |
-
|
| 1590 |
-
|
| 1591 |
-
|
| 1592 |
-
|
| 1593 |
-
detail_view = attach_final_to_detail(df, agg_total, meta, kew_value or "(Semua)")
|
| 1594 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1595 |
|
| 1596 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1597 |
# Tabel Agregat Wilayah Γ Jenis cukup sampai Indeks_Dasar_Agregat_0_100
|
|
|
|
| 1598 |
if agg_jenis_full is None or agg_jenis_full.empty:
|
| 1599 |
agg_jenis_view = agg_jenis_full
|
| 1600 |
else:
|
|
@@ -1605,14 +1617,16 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1605 |
label_name,
|
| 1606 |
"Jenis",
|
| 1607 |
"Jumlah",
|
| 1608 |
-
"Rata2_sub_koleksi","Rata2_sub_sdm","Rata2_sub_pelayanan","Rata2_sub_pengelolaan",
|
| 1609 |
-
"Rata2_dim_kepatuhan","Rata2_dim_kinerja",
|
| 1610 |
"Indeks_Dasar_Agregat_0_100",
|
| 1611 |
]
|
| 1612 |
cols_upto = [c for c in cols_upto if c in agg_jenis_full.columns]
|
| 1613 |
agg_jenis_view = agg_jenis_full[cols_upto].copy()
|
| 1614 |
|
|
|
|
| 1615 |
# FILTER RAW DOWNLOAD (df_raw)
|
|
|
|
| 1616 |
raw = df_raw.copy()
|
| 1617 |
if prov_value and prov_value != "(Semua)":
|
| 1618 |
raw = raw[raw["PROV_DISP"] == prov_value]
|
|
@@ -1621,7 +1635,9 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1621 |
if kew_value and kew_value != "(Semua)":
|
| 1622 |
raw = raw[raw["KEW_NORM"] == kew_value]
|
| 1623 |
|
|
|
|
| 1624 |
# Bell curve per JENIS (per entitas)
|
|
|
|
| 1625 |
if detail_view is None or detail_view.empty:
|
| 1626 |
fig_sekolah = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Sekolah", min_points=2)
|
| 1627 |
fig_umum = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Umum", min_points=2)
|
|
@@ -1633,15 +1649,32 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1633 |
|
| 1634 |
def _fig_jenis_ent(jenis_key: str, judul: str):
|
| 1635 |
d = detail_view[detail_view["Jenis"].astype(str).str.lower() == jenis_key].copy()
|
| 1636 |
-
return _make_bell_curve(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1637 |
|
| 1638 |
fig_sekolah = _fig_jenis_ent("sekolah", "Bell Curve β Jenis: Sekolah (Indeks per Entitas)")
|
| 1639 |
fig_umum = _fig_jenis_ent("umum", "Bell Curve β Jenis: Umum (Indeks per Entitas)")
|
| 1640 |
fig_khusus = _fig_jenis_ent("khusus", "Bell Curve β Jenis: Khusus (Indeks per Entitas)")
|
| 1641 |
|
| 1642 |
-
#
|
| 1643 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1644 |
|
|
|
|
|
|
|
|
|
|
| 1645 |
tmpdir = tempfile.mkdtemp()
|
| 1646 |
prov_slug = (_canon(prov_value or "SEMUA").upper() or "SEMUA")
|
| 1647 |
kab_slug = (_canon(kab_value or "SEMUA").upper() or "SEMUA")
|
|
@@ -1660,9 +1693,22 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1660 |
verif_total.to_excel(p_verif, index=False)
|
| 1661 |
|
| 1662 |
wilayah_txt = kab_value if (kab_value and kab_value != "(Semua)") else (prov_value if (prov_value and prov_value != "(Semua)") else "Nasional/All")
|
| 1663 |
-
analysis_text = generate_llm_analysis(summary_jenis, agg_total, verif_total, wilayah_txt, kew_value or "(Semua)")
|
| 1664 |
|
| 1665 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1666 |
|
| 1667 |
msg = (
|
| 1668 |
f"β
Selesai: raw={len(raw)} | entitas={len(detail_view)} | wilayah(keseluruhan)={len(agg_total)} | "
|
|
@@ -1699,9 +1745,9 @@ def ui_load(force=False):
|
|
| 1699 |
prov_vals = [v for v in prov_vals if v and v.strip()]
|
| 1700 |
prov_choices = ["(Semua)"] + sorted(set(prov_vals))
|
| 1701 |
|
| 1702 |
-
kab_choices
|
| 1703 |
-
kew_choices
|
| 1704 |
-
default_kew
|
| 1705 |
|
| 1706 |
return (
|
| 1707 |
df_all, df_raw, pop_kab, pop_prov, pop_khusus, meta, info,
|
|
@@ -1734,13 +1780,13 @@ with gr.Blocks() as demo:
|
|
| 1734 |
**FIX UTAMA (konsistensi nilai):**
|
| 1735 |
- **Agregat Wilayah (Keseluruhan) = rata-rata 3 jenis (sekolah+umum+khusus) Γ· 3 (missing=0, tetap Γ·3)**
|
| 1736 |
- Ringkasan selalu tampil **sekolah, umum, khusus, keseluruhan** (walau 0)
|
| 1737 |
-
- KPI
|
|
|
|
| 1738 |
- Download Data Mentah = RAW hasil filter
|
| 1739 |
-
- Kecukupan Sampel 68%: tanpa angka koma
|
| 1740 |
|
| 1741 |
**UPDATE (tampilan):**
|
| 1742 |
-
-
|
| 1743 |
-
-
|
| 1744 |
- Tabel "Agregat Wilayah Γ Jenis" ditampilkan hanya sampai Indeks_Dasar_Agregat_0_100
|
| 1745 |
""")
|
| 1746 |
|
|
@@ -1755,20 +1801,21 @@ with gr.Blocks() as demo:
|
|
| 1755 |
|
| 1756 |
with gr.Row():
|
| 1757 |
dd_prov = gr.Dropdown(label="Provinsi", choices=["(Semua)"], value="(Semua)")
|
| 1758 |
-
dd_kab
|
| 1759 |
-
dd_kew
|
| 1760 |
|
| 1761 |
dd_prov.change(fn=on_prov_change, inputs=[dd_prov], outputs=dd_kab)
|
| 1762 |
|
| 1763 |
run_btn = gr.Button("Jalankan Perhitungan")
|
| 1764 |
msg_out = gr.Markdown()
|
| 1765 |
|
|
|
|
| 1766 |
kpi_out = gr.Markdown()
|
| 1767 |
|
| 1768 |
-
gr.Markdown("## Ringkasan (Jenis + Keseluruhan)")
|
| 1769 |
out_summary = gr.DataFrame(interactive=False)
|
| 1770 |
|
| 1771 |
-
gr.Markdown("## Agregat Wilayah (Keseluruhan) β FIX: avg3 dari 3 jenis")
|
| 1772 |
out_agg_total = gr.DataFrame(interactive=False)
|
| 1773 |
|
| 1774 |
gr.Markdown("## Agregat Wilayah Γ Jenis (Sekolah, Umum, Khusus) β (ditampilkan sampai Indeks_Dasar_Agregat_0_100)")
|
|
@@ -1795,10 +1842,10 @@ with gr.Blocks() as demo:
|
|
| 1795 |
|
| 1796 |
with gr.Row():
|
| 1797 |
dl_summary = gr.DownloadButton(label="Download Ringkasan (.xlsx)")
|
| 1798 |
-
dl_total
|
| 1799 |
-
dl_jenis
|
| 1800 |
-
dl_detail
|
| 1801 |
-
dl_word
|
| 1802 |
|
| 1803 |
run_btn.click(
|
| 1804 |
fn=run_calc,
|
|
|
|
| 903 |
|
| 904 |
return agg
|
| 905 |
# ============================================================
|
| 906 |
+
# 8) AGREGAT WILAYAH (KESELURUHAN) β FIX: avg3 + tampilkan POP/TARGET/N per jenis
|
| 907 |
# ============================================================
|
| 908 |
|
| 909 |
def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_jenis: pd.DataFrame, kew_value: str):
|
|
|
|
| 956 |
Indeks_Final_Wilayah_0_100=("Indeks_Final_Agregat_0_100", "mean"),
|
| 957 |
)
|
| 958 |
|
| 959 |
+
# --- tempel POP/TARGET/N/COVERAGE/FAKTOR per jenis ke agg_total ---
|
| 960 |
if faktor_wilayah_jenis is not None and not faktor_wilayah_jenis.empty:
|
| 961 |
fw = faktor_wilayah_jenis.copy()
|
| 962 |
fw["Jenis"] = fw["Jenis"].astype(str).str.lower().str.strip()
|
| 963 |
|
| 964 |
+
# pivot per jenis
|
| 965 |
piv = fw.pivot_table(
|
| 966 |
index=["group_key", label_name],
|
| 967 |
columns="Jenis",
|
| 968 |
+
values=[
|
| 969 |
+
"pop_total_jenis",
|
| 970 |
+
"target_total_68_jenis",
|
| 971 |
+
"n_jenis",
|
| 972 |
+
"coverage_jenis_%",
|
| 973 |
+
"faktor_penyesuaian_jenis",
|
| 974 |
+
"gap_target68_jenis"
|
| 975 |
+
],
|
| 976 |
aggfunc="first"
|
| 977 |
)
|
|
|
|
|
|
|
| 978 |
piv.columns = [f"{v}_{k}" for v, k in piv.columns]
|
| 979 |
piv = piv.reset_index()
|
| 980 |
|
| 981 |
out = out.merge(piv, on=["group_key", label_name], how="left")
|
| 982 |
|
| 983 |
+
# rapihin NaN -> 0 + tipe
|
| 984 |
+
for j in ["sekolah", "umum", "khusus"]:
|
| 985 |
for basecol in ["pop_total_jenis", "target_total_68_jenis", "n_jenis", "gap_target68_jenis"]:
|
| 986 |
c = f"{basecol}_{j}"
|
| 987 |
if c in out.columns:
|
| 988 |
+
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0).round(0).astype(int)
|
| 989 |
+
|
| 990 |
+
c_cov = f"coverage_jenis_%_{j}"
|
| 991 |
+
if c_cov in out.columns:
|
| 992 |
+
out[c_cov] = pd.to_numeric(out[c_cov], errors="coerce").fillna(0.0).round(2)
|
| 993 |
+
|
| 994 |
+
c_fac = f"faktor_penyesuaian_jenis_{j}"
|
| 995 |
+
if c_fac in out.columns:
|
| 996 |
+
out[c_fac] = pd.to_numeric(out[c_fac], errors="coerce").fillna(0.0).round(3)
|
| 997 |
+
|
| 998 |
+
# === TOTAL UPDATE (sesuai data POP yang ada) ===
|
| 999 |
+
# Total Pop dan Target = sum 3 jenis (sekolah+umum+khusus)
|
| 1000 |
+
def _sum3(a, b, c):
|
| 1001 |
+
return (
|
| 1002 |
+
pd.to_numeric(a, errors="coerce").fillna(0)
|
| 1003 |
+
+ pd.to_numeric(b, errors="coerce").fillna(0)
|
| 1004 |
+
+ pd.to_numeric(c, errors="coerce").fillna(0)
|
| 1005 |
+
)
|
| 1006 |
+
|
| 1007 |
+
if ("pop_total_jenis_sekolah" in out.columns) and ("pop_total_jenis_umum" in out.columns) and ("pop_total_jenis_khusus" in out.columns):
|
| 1008 |
+
out["Pop_Total_Update"] = _sum3(out["pop_total_jenis_sekolah"], out["pop_total_jenis_umum"], out["pop_total_jenis_khusus"]).round(0).astype(int)
|
| 1009 |
+
|
| 1010 |
+
if ("target_total_68_jenis_sekolah" in out.columns) and ("target_total_68_jenis_umum" in out.columns) and ("target_total_68_jenis_khusus" in out.columns):
|
| 1011 |
+
out["Target68_Total_Update"] = _sum3(out["target_total_68_jenis_sekolah"], out["target_total_68_jenis_umum"], out["target_total_68_jenis_khusus"]).round(0).astype(int)
|
| 1012 |
+
|
| 1013 |
+
if ("n_jenis_sekolah" in out.columns) and ("n_jenis_umum" in out.columns) and ("n_jenis_khusus" in out.columns):
|
| 1014 |
+
out["Terkumpul_Total_Update"] = _sum3(out["n_jenis_sekolah"], out["n_jenis_umum"], out["n_jenis_khusus"]).round(0).astype(int)
|
| 1015 |
+
|
| 1016 |
+
# coverage_total_update = min(terkumpul/target,1)*100
|
| 1017 |
+
if ("Terkumpul_Total_Update" in out.columns) and ("Target68_Total_Update" in out.columns):
|
| 1018 |
+
den = pd.to_numeric(out["Target68_Total_Update"], errors="coerce").fillna(0).astype(float)
|
| 1019 |
+
num = pd.to_numeric(out["Terkumpul_Total_Update"], errors="coerce").fillna(0).astype(float)
|
| 1020 |
+
cov = np.where(den > 0, np.minimum(num / den, 1.0) * 100.0, 0.0)
|
| 1021 |
+
out["Coverage_Target68_Total_Update_%"] = pd.Series(cov).round(2)
|
| 1022 |
|
| 1023 |
# rounding index
|
| 1024 |
for c in [
|
|
|
|
| 1032 |
if c in out.columns:
|
| 1033 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0.0).round(2)
|
| 1034 |
|
| 1035 |
+
# n_total integer
|
| 1036 |
+
if "n_total" in out.columns:
|
| 1037 |
+
out["n_total"] = pd.to_numeric(out["n_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 1038 |
|
| 1039 |
+
return out
|
| 1040 |
|
| 1041 |
# ============================================================
|
| 1042 |
+
# 9) SUMMARY (PER JENIS) + KESELURUHAN (FIX Γ·3 + tambah cakupan & penyesuaian)
|
| 1043 |
# ============================================================
|
| 1044 |
|
| 1045 |
+
def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame, faktor_wilayah_jenis: pd.DataFrame | None = None):
|
| 1046 |
jenis_list = ["sekolah", "umum", "khusus"]
|
| 1047 |
|
| 1048 |
def _row_default(jenis):
|
|
|
|
| 1050 |
"Jenis": jenis,
|
| 1051 |
"Jumlah_Wilayah": 0,
|
| 1052 |
"Total_Perpus": 0,
|
| 1053 |
+
|
| 1054 |
+
# POP/TARGET/N per jenis (sum nasional pada scope filter)
|
| 1055 |
+
"Pop_Total_Jenis": 0,
|
| 1056 |
+
"Target68_Total_Jenis": 0,
|
| 1057 |
+
"Terkumpul_Jenis": 0,
|
| 1058 |
+
"Coverage_Target68_Jenis_%": 0.00,
|
| 1059 |
+
|
| 1060 |
+
# skor
|
| 1061 |
"Rata2_sub_koleksi": 0.0,
|
| 1062 |
"Rata2_sub_sdm": 0.0,
|
| 1063 |
"Rata2_sub_pelayanan": 0.0,
|
| 1064 |
"Rata2_sub_pengelolaan": 0.0,
|
| 1065 |
"Rata2_dim_kepatuhan": 0.0,
|
| 1066 |
"Rata2_dim_kinerja": 0.0,
|
| 1067 |
+
|
| 1068 |
+
# dasar & final + penyesuaian poin
|
| 1069 |
+
"Indeks_Dasar_0_100": 0.0,
|
| 1070 |
"Indeks_Final_Disesuaikan_0_100": 0.0,
|
| 1071 |
+
"Penyesuaian_Poin": 0.0,
|
| 1072 |
}
|
| 1073 |
|
| 1074 |
rows_by_jenis = {j: _row_default(j) for j in jenis_list}
|
| 1075 |
|
| 1076 |
+
# ===== ambil POP/TARGET/N per jenis dari faktor_wilayah_jenis =====
|
| 1077 |
+
fw_sum = {}
|
| 1078 |
+
if faktor_wilayah_jenis is not None and (not faktor_wilayah_jenis.empty):
|
| 1079 |
+
fw = faktor_wilayah_jenis.copy()
|
| 1080 |
+
fw["Jenis"] = fw["Jenis"].astype(str).str.lower().str.strip()
|
| 1081 |
+
|
| 1082 |
+
for j in jenis_list:
|
| 1083 |
+
sub = fw[fw["Jenis"] == j].copy()
|
| 1084 |
+
if sub.empty:
|
| 1085 |
+
fw_sum[j] = {"pop": 0, "target": 0, "n": 0, "cov": 0.0}
|
| 1086 |
+
continue
|
| 1087 |
+
|
| 1088 |
+
pop = int(pd.to_numeric(sub.get("pop_total_jenis", 0), errors="coerce").fillna(0).sum())
|
| 1089 |
+
target = int(pd.to_numeric(sub.get("target_total_68_jenis", 0), errors="coerce").fillna(0).sum())
|
| 1090 |
+
n = int(pd.to_numeric(sub.get("n_jenis", 0), errors="coerce").fillna(0).sum())
|
| 1091 |
+
|
| 1092 |
+
cov = 0.0
|
| 1093 |
+
if target > 0:
|
| 1094 |
+
cov = float(min(n / float(target), 1.0) * 100.0)
|
| 1095 |
+
|
| 1096 |
+
fw_sum[j] = {"pop": pop, "target": target, "n": n, "cov": cov}
|
| 1097 |
+
|
| 1098 |
+
# ===== isi ringkasan dari agg_jenis =====
|
| 1099 |
if agg_jenis is not None and not agg_jenis.empty:
|
| 1100 |
for jenis in jenis_list:
|
| 1101 |
sub = agg_jenis[agg_jenis["Jenis"].astype(str).str.lower() == jenis].copy()
|
| 1102 |
if sub.empty:
|
| 1103 |
+
# tetap isi pop/target/n kalau ada
|
| 1104 |
+
if jenis in fw_sum:
|
| 1105 |
+
rows_by_jenis[jenis]["Pop_Total_Jenis"] = fw_sum[jenis]["pop"]
|
| 1106 |
+
rows_by_jenis[jenis]["Target68_Total_Jenis"] = fw_sum[jenis]["target"]
|
| 1107 |
+
rows_by_jenis[jenis]["Terkumpul_Jenis"] = fw_sum[jenis]["n"]
|
| 1108 |
+
rows_by_jenis[jenis]["Coverage_Target68_Jenis_%"] = fw_sum[jenis]["cov"]
|
| 1109 |
continue
|
| 1110 |
|
| 1111 |
+
dasar = float(pd.to_numeric(sub["Indeks_Dasar_Agregat_0_100"], errors="coerce").fillna(0).mean())
|
| 1112 |
+
final = float(pd.to_numeric(sub["Indeks_Final_Agregat_0_100"], errors="coerce").fillna(0).mean())
|
| 1113 |
+
|
| 1114 |
rows_by_jenis[jenis] = {
|
| 1115 |
"Jenis": jenis,
|
| 1116 |
"Jumlah_Wilayah": int(sub.shape[0]),
|
| 1117 |
"Total_Perpus": int(pd.to_numeric(sub["Jumlah"], errors="coerce").fillna(0).sum()),
|
| 1118 |
+
|
| 1119 |
+
"Pop_Total_Jenis": int(fw_sum.get(jenis, {}).get("pop", 0)),
|
| 1120 |
+
"Target68_Total_Jenis": int(fw_sum.get(jenis, {}).get("target", 0)),
|
| 1121 |
+
"Terkumpul_Jenis": int(fw_sum.get(jenis, {}).get("n", 0)),
|
| 1122 |
+
"Coverage_Target68_Jenis_%": float(fw_sum.get(jenis, {}).get("cov", 0.0)),
|
| 1123 |
+
|
| 1124 |
"Rata2_sub_koleksi": float(pd.to_numeric(sub["Rata2_sub_koleksi"], errors="coerce").fillna(0).mean()),
|
| 1125 |
"Rata2_sub_sdm": float(pd.to_numeric(sub["Rata2_sub_sdm"], errors="coerce").fillna(0).mean()),
|
| 1126 |
"Rata2_sub_pelayanan": float(pd.to_numeric(sub["Rata2_sub_pelayanan"], errors="coerce").fillna(0).mean()),
|
| 1127 |
"Rata2_sub_pengelolaan": float(pd.to_numeric(sub["Rata2_sub_pengelolaan"], errors="coerce").fillna(0).mean()),
|
| 1128 |
"Rata2_dim_kepatuhan": float(pd.to_numeric(sub["Rata2_dim_kepatuhan"], errors="coerce").fillna(0).mean()),
|
| 1129 |
"Rata2_dim_kinerja": float(pd.to_numeric(sub["Rata2_dim_kinerja"], errors="coerce").fillna(0).mean()),
|
| 1130 |
+
|
| 1131 |
+
"Indeks_Dasar_0_100": dasar,
|
| 1132 |
+
"Indeks_Final_Disesuaikan_0_100": final,
|
| 1133 |
+
"Penyesuaian_Poin": float(final - dasar),
|
| 1134 |
}
|
| 1135 |
|
| 1136 |
rows = [rows_by_jenis[j] for j in jenis_list]
|
| 1137 |
|
| 1138 |
+
# ===== keseluruhan = avg3 tetap Γ·3 (missing=0) =====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1139 |
def _avg3(field):
|
| 1140 |
return (
|
| 1141 |
float(rows_by_jenis["sekolah"][field])
|
|
|
|
| 1143 |
+ float(rows_by_jenis["khusus"][field])
|
| 1144 |
) / 3.0
|
| 1145 |
|
| 1146 |
+
final_all = _avg3("Indeks_Final_Disesuaikan_0_100")
|
| 1147 |
+
dasar_all = _avg3("Indeks_Dasar_0_100")
|
| 1148 |
+
|
| 1149 |
total_perpus_all = int(rows_by_jenis["sekolah"]["Total_Perpus"] + rows_by_jenis["umum"]["Total_Perpus"] + rows_by_jenis["khusus"]["Total_Perpus"])
|
| 1150 |
jumlah_wilayah_all = int(agg_total.shape[0]) if (agg_total is not None and not agg_total.empty) else int(max(rows_by_jenis["sekolah"]["Jumlah_Wilayah"], rows_by_jenis["umum"]["Jumlah_Wilayah"], rows_by_jenis["khusus"]["Jumlah_Wilayah"]))
|
| 1151 |
|
| 1152 |
+
# Total POP/TARGET/N keseluruhan = sum3 (bukan avg3), karena ini memang total cakupan
|
| 1153 |
+
pop_all = int(rows_by_jenis["sekolah"]["Pop_Total_Jenis"] + rows_by_jenis["umum"]["Pop_Total_Jenis"] + rows_by_jenis["khusus"]["Pop_Total_Jenis"])
|
| 1154 |
+
target_all = int(rows_by_jenis["sekolah"]["Target68_Total_Jenis"] + rows_by_jenis["umum"]["Target68_Total_Jenis"] + rows_by_jenis["khusus"]["Target68_Total_Jenis"])
|
| 1155 |
+
n_all = int(rows_by_jenis["sekolah"]["Terkumpul_Jenis"] + rows_by_jenis["umum"]["Terkumpul_Jenis"] + rows_by_jenis["khusus"]["Terkumpul_Jenis"])
|
| 1156 |
+
cov_all = float(min(n_all / float(target_all), 1.0) * 100.0) if target_all > 0 else 0.0
|
| 1157 |
+
|
| 1158 |
rows.append({
|
| 1159 |
"Jenis": "keseluruhan",
|
| 1160 |
"Jumlah_Wilayah": jumlah_wilayah_all,
|
| 1161 |
"Total_Perpus": total_perpus_all,
|
| 1162 |
+
|
| 1163 |
+
"Pop_Total_Jenis": pop_all,
|
| 1164 |
+
"Target68_Total_Jenis": target_all,
|
| 1165 |
+
"Terkumpul_Jenis": n_all,
|
| 1166 |
+
"Coverage_Target68_Jenis_%": cov_all,
|
| 1167 |
+
|
| 1168 |
"Rata2_sub_koleksi": _avg3("Rata2_sub_koleksi"),
|
| 1169 |
"Rata2_sub_sdm": _avg3("Rata2_sub_sdm"),
|
| 1170 |
"Rata2_sub_pelayanan": _avg3("Rata2_sub_pelayanan"),
|
| 1171 |
"Rata2_sub_pengelolaan": _avg3("Rata2_sub_pengelolaan"),
|
| 1172 |
"Rata2_dim_kepatuhan": _avg3("Rata2_dim_kepatuhan"),
|
| 1173 |
"Rata2_dim_kinerja": _avg3("Rata2_dim_kinerja"),
|
| 1174 |
+
|
| 1175 |
+
"Indeks_Dasar_0_100": dasar_all,
|
| 1176 |
"Indeks_Final_Disesuaikan_0_100": final_all,
|
| 1177 |
+
"Penyesuaian_Poin": float(final_all - dasar_all),
|
| 1178 |
})
|
| 1179 |
|
| 1180 |
out = pd.DataFrame(rows)
|
| 1181 |
|
| 1182 |
+
# rounding
|
| 1183 |
for c in [
|
| 1184 |
"Rata2_sub_koleksi","Rata2_sub_sdm","Rata2_sub_pelayanan","Rata2_sub_pengelolaan",
|
| 1185 |
"Rata2_dim_kepatuhan","Rata2_dim_kinerja"
|
| 1186 |
]:
|
| 1187 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0.0).round(3)
|
| 1188 |
|
| 1189 |
+
for c in ["Indeks_Dasar_0_100","Indeks_Final_Disesuaikan_0_100","Penyesuaian_Poin"]:
|
| 1190 |
+
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0.0).round(2)
|
| 1191 |
|
| 1192 |
+
# integer columns
|
| 1193 |
+
for c in ["Jumlah_Wilayah","Total_Perpus","Pop_Total_Jenis","Target68_Total_Jenis","Terkumpul_Jenis"]:
|
| 1194 |
+
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0).round(0).astype(int)
|
| 1195 |
|
| 1196 |
+
out["Coverage_Target68_Jenis_%"] = pd.to_numeric(out["Coverage_Target68_Jenis_%"], errors="coerce").fillna(0.0).round(2)
|
| 1197 |
+
|
| 1198 |
+
return out
|
| 1199 |
|
| 1200 |
# ============================================================
|
| 1201 |
# 10) DETAIL ENTITAS: Final menempel dari agg_total (wilayah)
|
|
|
|
| 1400 |
|
| 1401 |
|
| 1402 |
# ============================================================
|
| 1403 |
+
# 13) KPI DASHBOARD (PATCH: hanya FINAL & DASAR)
|
| 1404 |
# ============================================================
|
| 1405 |
|
| 1406 |
+
def compute_dashboard_kpis(summary_jenis: pd.DataFrame, agg_total: pd.DataFrame, agg_jenis: pd.DataFrame):
|
| 1407 |
+
def _get_val(j, col):
|
| 1408 |
sub = summary_jenis[summary_jenis["Jenis"].astype(str).str.lower() == j]
|
| 1409 |
if sub.empty:
|
| 1410 |
return 0.0
|
| 1411 |
+
return float(pd.to_numeric(sub[col], errors="coerce").fillna(0).iloc[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1412 |
|
| 1413 |
+
final_all = _get_val("keseluruhan", "Indeks_Final_Disesuaikan_0_100")
|
| 1414 |
+
dasar_all = _get_val("keseluruhan", "Indeks_Dasar_0_100")
|
|
|
|
|
|
|
| 1415 |
|
| 1416 |
+
return {"final_all": final_all, "dasar_all": dasar_all}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1417 |
|
| 1418 |
|
| 1419 |
def build_kpi_markdown(summary_jenis: pd.DataFrame, agg_total: pd.DataFrame, agg_jenis: pd.DataFrame, faktor_wilayah_jenis: pd.DataFrame | None = None) -> str:
|
| 1420 |
+
# faktor_wilayah_jenis tetap diterima supaya pemanggil run_calc tidak perlu diubah banyak,
|
| 1421 |
+
# tapi tidak dipakai lagi di KPI dashboard (dipindah ke tabel ringkasan).
|
| 1422 |
if summary_jenis is None or summary_jenis.empty:
|
| 1423 |
return ""
|
| 1424 |
|
| 1425 |
+
k = compute_dashboard_kpis(summary_jenis, agg_total, agg_jenis)
|
| 1426 |
|
| 1427 |
def fmt(x, nd=2):
|
| 1428 |
return "NA" if pd.isna(x) else f"{x:.{nd}f}"
|
|
|
|
| 1440 |
<div style="font-size:26px; font-weight:700;">{fmt(k["dasar_all"],2)}</div>
|
| 1441 |
<div style="opacity:0.7;">Rata-rata 3 jenis (tetap Γ·3)</div>
|
| 1442 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1443 |
</div>
|
| 1444 |
""".strip()
|
| 1445 |
|
|
|
|
| 1446 |
# ============================================================
|
| 1447 |
# 14) LLM + WORD
|
| 1448 |
# ============================================================
|
| 1449 |
|
| 1450 |
+
_def generate_word_report(wilayah, summary_jenis, agg_total, agg_jenis, analysis_text):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1451 |
doc = Document()
|
| 1452 |
doc.add_heading(f"Laporan IPLM β {wilayah}", level=1)
|
| 1453 |
|
| 1454 |
doc.add_heading("Ringkasan Dashboard", level=2)
|
| 1455 |
|
| 1456 |
+
# KPI hanya FINAL & DASAR (cakupan + penyesuaian dipindah ke tabel ringkasan)
|
| 1457 |
+
k_final = 0.0
|
| 1458 |
+
k_dasar = 0.0
|
| 1459 |
+
try:
|
| 1460 |
+
if summary_jenis is not None and (not summary_jenis.empty):
|
| 1461 |
+
row_all = summary_jenis[summary_jenis["Jenis"].astype(str).str.lower() == "keseluruhan"]
|
| 1462 |
+
if not row_all.empty:
|
| 1463 |
+
k_final = float(pd.to_numeric(row_all["Indeks_Final_Disesuaikan_0_100"], errors="coerce").fillna(0).iloc[0])
|
| 1464 |
+
k_dasar = float(pd.to_numeric(row_all["Indeks_Dasar_0_100"], errors="coerce").fillna(0).iloc[0])
|
| 1465 |
+
except Exception:
|
| 1466 |
+
k_final, k_dasar = 0.0, 0.0
|
| 1467 |
|
| 1468 |
+
doc.add_paragraph(f"Indeks IPLM FINAL (Disesuaikan): {k_final:.2f} (rata-rata 3 jenis, tetap Γ·3; missing=0)")
|
| 1469 |
+
doc.add_paragraph(f"Indeks Dasar (Tanpa Penyesuaian): {k_dasar:.2f} (rata-rata 3 jenis, tetap Γ·3; missing=0)")
|
| 1470 |
+
|
| 1471 |
+
doc.add_paragraph("Ringkasan (Jenis + Keseluruhan) β termasuk Pop/Target68/Terkumpul/Coverage + Penyesuaian Poin:")
|
| 1472 |
show = summary_jenis.copy()
|
| 1473 |
+
|
| 1474 |
preferred = [
|
| 1475 |
+
"Jenis", "Jumlah_Wilayah", "Total_Perpus",
|
| 1476 |
+
"Pop_Total_Jenis", "Target68_Total_Jenis", "Terkumpul_Jenis", "Coverage_Target68_Jenis_%",
|
| 1477 |
+
"Rata2_dim_kepatuhan", "Rata2_dim_kinerja",
|
| 1478 |
+
"Indeks_Dasar_0_100", "Indeks_Final_Disesuaikan_0_100", "Penyesuaian_Poin"
|
| 1479 |
]
|
| 1480 |
show = show[[c for c in preferred if c in show.columns]]
|
| 1481 |
|
|
|
|
| 1490 |
v = row[c]
|
| 1491 |
if pd.isna(v):
|
| 1492 |
cells[i].text = ""
|
| 1493 |
+
elif isinstance(v, (int, np.integer)):
|
| 1494 |
+
cells[i].text = str(int(v))
|
| 1495 |
elif isinstance(v, (float, np.floating)):
|
| 1496 |
+
if "Coverage" in c:
|
| 1497 |
cells[i].text = f"{float(v):.2f}"
|
| 1498 |
elif "Rata2_" in c:
|
| 1499 |
cells[i].text = f"{float(v):.3f}"
|
| 1500 |
+
elif "Indeks" in c or "Penyesuaian" in c:
|
| 1501 |
+
cells[i].text = f"{float(v):.2f}"
|
| 1502 |
else:
|
| 1503 |
+
cells[i].text = f"{float(v):.2f}"
|
|
|
|
|
|
|
| 1504 |
else:
|
| 1505 |
cells[i].text = str(v)
|
| 1506 |
|
|
|
|
| 1548 |
if df_all is None or df_all.empty or df_raw is None or df_raw.empty:
|
| 1549 |
return _empty_outputs("β οΈ Data belum ter-load. Pastikan file tersedia di repo/server.")
|
| 1550 |
|
| 1551 |
+
# =========================
|
| 1552 |
# FILTER ANALISIS (df_all)
|
| 1553 |
+
# =========================
|
| 1554 |
df = df_all.copy()
|
| 1555 |
if prov_value and prov_value != "(Semua)":
|
| 1556 |
df = df[df["PROV_DISP"] == prov_value]
|
|
|
|
| 1562 |
if df.empty:
|
| 1563 |
return _empty_outputs("Tidak ada data untuk filter ini.")
|
| 1564 |
|
| 1565 |
+
# ==================================================
|
| 1566 |
+
# PIPELINE BARU (FAKTOR 68% PER JENIS)
|
| 1567 |
+
# ==================================================
|
| 1568 |
+
faktor_wilayah_jenis = build_faktor_wilayah_jenis(
|
| 1569 |
+
df,
|
| 1570 |
+
pop_kab,
|
| 1571 |
+
pop_prov,
|
| 1572 |
+
pop_khusus,
|
| 1573 |
+
kew_value or "(Semua)"
|
| 1574 |
+
)
|
|
|
|
| 1575 |
|
| 1576 |
+
agg_jenis_full = build_agg_wilayah_jenis(
|
| 1577 |
+
df,
|
| 1578 |
+
faktor_wilayah_jenis,
|
| 1579 |
+
kew_value or "(Semua)"
|
| 1580 |
+
)
|
| 1581 |
|
| 1582 |
+
agg_total = build_agg_wilayah_total_from_jenis(
|
| 1583 |
+
agg_jenis_full,
|
| 1584 |
+
faktor_wilayah_jenis,
|
| 1585 |
+
kew_value or "(Semua)"
|
| 1586 |
+
)
|
| 1587 |
+
|
| 1588 |
+
summary_jenis = build_summary_per_jenis(
|
| 1589 |
+
agg_jenis_full,
|
| 1590 |
+
agg_total,
|
| 1591 |
+
faktor_wilayah_jenis=faktor_wilayah_jenis
|
| 1592 |
+
)
|
| 1593 |
+
|
| 1594 |
+
verif_total = build_verif_jenis(
|
| 1595 |
+
faktor_wilayah_jenis,
|
| 1596 |
+
kew_value or "(Semua)"
|
| 1597 |
+
)
|
| 1598 |
+
|
| 1599 |
+
detail_view = attach_final_to_detail(
|
| 1600 |
+
df,
|
| 1601 |
+
agg_total,
|
| 1602 |
+
meta,
|
| 1603 |
+
kew_value or "(Semua)"
|
| 1604 |
+
)
|
| 1605 |
+
|
| 1606 |
+
# ==================================================
|
| 1607 |
+
# UPDATE SESUAI PERMINTAAN (UI ONLY)
|
| 1608 |
# Tabel Agregat Wilayah Γ Jenis cukup sampai Indeks_Dasar_Agregat_0_100
|
| 1609 |
+
# ==================================================
|
| 1610 |
if agg_jenis_full is None or agg_jenis_full.empty:
|
| 1611 |
agg_jenis_view = agg_jenis_full
|
| 1612 |
else:
|
|
|
|
| 1617 |
label_name,
|
| 1618 |
"Jenis",
|
| 1619 |
"Jumlah",
|
| 1620 |
+
"Rata2_sub_koleksi", "Rata2_sub_sdm", "Rata2_sub_pelayanan", "Rata2_sub_pengelolaan",
|
| 1621 |
+
"Rata2_dim_kepatuhan", "Rata2_dim_kinerja",
|
| 1622 |
"Indeks_Dasar_Agregat_0_100",
|
| 1623 |
]
|
| 1624 |
cols_upto = [c for c in cols_upto if c in agg_jenis_full.columns]
|
| 1625 |
agg_jenis_view = agg_jenis_full[cols_upto].copy()
|
| 1626 |
|
| 1627 |
+
# =========================
|
| 1628 |
# FILTER RAW DOWNLOAD (df_raw)
|
| 1629 |
+
# =========================
|
| 1630 |
raw = df_raw.copy()
|
| 1631 |
if prov_value and prov_value != "(Semua)":
|
| 1632 |
raw = raw[raw["PROV_DISP"] == prov_value]
|
|
|
|
| 1635 |
if kew_value and kew_value != "(Semua)":
|
| 1636 |
raw = raw[raw["KEW_NORM"] == kew_value]
|
| 1637 |
|
| 1638 |
+
# =========================
|
| 1639 |
# Bell curve per JENIS (per entitas)
|
| 1640 |
+
# =========================
|
| 1641 |
if detail_view is None or detail_view.empty:
|
| 1642 |
fig_sekolah = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Sekolah", min_points=2)
|
| 1643 |
fig_umum = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Umum", min_points=2)
|
|
|
|
| 1649 |
|
| 1650 |
def _fig_jenis_ent(jenis_key: str, judul: str):
|
| 1651 |
d = detail_view[detail_view["Jenis"].astype(str).str.lower() == jenis_key].copy()
|
| 1652 |
+
return _make_bell_curve(
|
| 1653 |
+
d,
|
| 1654 |
+
xcol=xcol_ent,
|
| 1655 |
+
title=judul,
|
| 1656 |
+
label_col=label_col_e,
|
| 1657 |
+
hover_cols=hover_cols_e,
|
| 1658 |
+
min_points=2
|
| 1659 |
+
)
|
| 1660 |
|
| 1661 |
fig_sekolah = _fig_jenis_ent("sekolah", "Bell Curve β Jenis: Sekolah (Indeks per Entitas)")
|
| 1662 |
fig_umum = _fig_jenis_ent("umum", "Bell Curve β Jenis: Umum (Indeks per Entitas)")
|
| 1663 |
fig_khusus = _fig_jenis_ent("khusus", "Bell Curve β Jenis: Khusus (Indeks per Entitas)")
|
| 1664 |
|
| 1665 |
+
# =========================
|
| 1666 |
+
# KPI markdown (FINAL sumber Ringkasan) β hanya 2 kartu
|
| 1667 |
+
# =========================
|
| 1668 |
+
kpi_md = build_kpi_markdown(
|
| 1669 |
+
summary_jenis,
|
| 1670 |
+
agg_total,
|
| 1671 |
+
agg_jenis_full,
|
| 1672 |
+
faktor_wilayah_jenis=faktor_wilayah_jenis
|
| 1673 |
+
)
|
| 1674 |
|
| 1675 |
+
# =========================
|
| 1676 |
+
# SAVE OUTPUTS (Excel + Word)
|
| 1677 |
+
# =========================
|
| 1678 |
tmpdir = tempfile.mkdtemp()
|
| 1679 |
prov_slug = (_canon(prov_value or "SEMUA").upper() or "SEMUA")
|
| 1680 |
kab_slug = (_canon(kab_value or "SEMUA").upper() or "SEMUA")
|
|
|
|
| 1693 |
verif_total.to_excel(p_verif, index=False)
|
| 1694 |
|
| 1695 |
wilayah_txt = kab_value if (kab_value and kab_value != "(Semua)") else (prov_value if (prov_value and prov_value != "(Semua)") else "Nasional/All")
|
|
|
|
| 1696 |
|
| 1697 |
+
analysis_text = generate_llm_analysis(
|
| 1698 |
+
summary_jenis,
|
| 1699 |
+
agg_total,
|
| 1700 |
+
verif_total,
|
| 1701 |
+
wilayah_txt,
|
| 1702 |
+
kew_value or "(Semua)"
|
| 1703 |
+
)
|
| 1704 |
+
|
| 1705 |
+
word_path = generate_word_report(
|
| 1706 |
+
wilayah_txt,
|
| 1707 |
+
summary_jenis,
|
| 1708 |
+
agg_total,
|
| 1709 |
+
agg_jenis_full,
|
| 1710 |
+
analysis_text
|
| 1711 |
+
)
|
| 1712 |
|
| 1713 |
msg = (
|
| 1714 |
f"β
Selesai: raw={len(raw)} | entitas={len(detail_view)} | wilayah(keseluruhan)={len(agg_total)} | "
|
|
|
|
| 1745 |
prov_vals = [v for v in prov_vals if v and v.strip()]
|
| 1746 |
prov_choices = ["(Semua)"] + sorted(set(prov_vals))
|
| 1747 |
|
| 1748 |
+
kab_choices = ["(Semua)"] + sorted([x for x in df_all["KAB_DISP"].dropna().unique().tolist() if x])
|
| 1749 |
+
kew_choices = ["(Semua)"] + sorted([x for x in df_all["KEW_NORM"].dropna().unique().tolist() if x])
|
| 1750 |
+
default_kew = "PROVINSI" if "PROVINSI" in kew_choices else ("KAB/KOTA" if "KAB/KOTA" in kew_choices else "(Semua)")
|
| 1751 |
|
| 1752 |
return (
|
| 1753 |
df_all, df_raw, pop_kab, pop_prov, pop_khusus, meta, info,
|
|
|
|
| 1780 |
**FIX UTAMA (konsistensi nilai):**
|
| 1781 |
- **Agregat Wilayah (Keseluruhan) = rata-rata 3 jenis (sekolah+umum+khusus) Γ· 3 (missing=0, tetap Γ·3)**
|
| 1782 |
- Ringkasan selalu tampil **sekolah, umum, khusus, keseluruhan** (walau 0)
|
| 1783 |
+
- KPI dashboard hanya menampilkan **FINAL** dan **DASAR**
|
| 1784 |
+
- **Cakupan (Target 68%) dan Penyesuaian Poin dipindahkan ke tabel Ringkasan**
|
| 1785 |
- Download Data Mentah = RAW hasil filter
|
|
|
|
| 1786 |
|
| 1787 |
**UPDATE (tampilan):**
|
| 1788 |
+
- Tabel Ringkasan memuat: Pop/Target68/Terkumpul/Coverage per jenis + Penyesuaian Poin
|
| 1789 |
+
- Tabel Agregat Wilayah memuat: Pop/Target68/Terkumpul per jenis + Total Update (sum 3 jenis)
|
| 1790 |
- Tabel "Agregat Wilayah Γ Jenis" ditampilkan hanya sampai Indeks_Dasar_Agregat_0_100
|
| 1791 |
""")
|
| 1792 |
|
|
|
|
| 1801 |
|
| 1802 |
with gr.Row():
|
| 1803 |
dd_prov = gr.Dropdown(label="Provinsi", choices=["(Semua)"], value="(Semua)")
|
| 1804 |
+
dd_kab = gr.Dropdown(label="Kab/Kota", choices=["(Semua)"], value="(Semua)")
|
| 1805 |
+
dd_kew = gr.Dropdown(label="Kewenangan", choices=["(Semua)"], value="(Semua)")
|
| 1806 |
|
| 1807 |
dd_prov.change(fn=on_prov_change, inputs=[dd_prov], outputs=dd_kab)
|
| 1808 |
|
| 1809 |
run_btn = gr.Button("Jalankan Perhitungan")
|
| 1810 |
msg_out = gr.Markdown()
|
| 1811 |
|
| 1812 |
+
# KPI dashboard (hanya 2 kartu)
|
| 1813 |
kpi_out = gr.Markdown()
|
| 1814 |
|
| 1815 |
+
gr.Markdown("## Ringkasan (Jenis + Keseluruhan) β Pop/Target68/Terkumpul/Coverage + Penyesuaian")
|
| 1816 |
out_summary = gr.DataFrame(interactive=False)
|
| 1817 |
|
| 1818 |
+
gr.Markdown("## Agregat Wilayah (Keseluruhan) β FIX: avg3 dari 3 jenis + Pop/Target68/Terkumpul (per jenis & total)")
|
| 1819 |
out_agg_total = gr.DataFrame(interactive=False)
|
| 1820 |
|
| 1821 |
gr.Markdown("## Agregat Wilayah Γ Jenis (Sekolah, Umum, Khusus) β (ditampilkan sampai Indeks_Dasar_Agregat_0_100)")
|
|
|
|
| 1842 |
|
| 1843 |
with gr.Row():
|
| 1844 |
dl_summary = gr.DownloadButton(label="Download Ringkasan (.xlsx)")
|
| 1845 |
+
dl_total = gr.DownloadButton(label="Download Agregat Wilayah (.xlsx)")
|
| 1846 |
+
dl_jenis = gr.DownloadButton(label="Download Data Mentah (.xlsx)")
|
| 1847 |
+
dl_detail = gr.DownloadButton(label="Download Detail Entitas (.xlsx)")
|
| 1848 |
+
dl_word = gr.DownloadButton(label="Download Laporan Word (.docx)")
|
| 1849 |
|
| 1850 |
run_btn.click(
|
| 1851 |
fn=run_calc,
|