Update app.py
Browse files
app.py
CHANGED
|
@@ -465,7 +465,7 @@ def load_default_files(force=False):
|
|
| 465 |
after = len(df_raw)
|
| 466 |
|
| 467 |
# =========================
|
| 468 |
-
# POP KAB (
|
| 469 |
# =========================
|
| 470 |
pk = pd.read_excel(POP_KAB)
|
| 471 |
|
|
@@ -498,13 +498,11 @@ def load_default_files(force=False):
|
|
| 498 |
if c_target_total:
|
| 499 |
pop_kab["target_base_68"] = pd.to_numeric(pk[c_target_total], errors="coerce")
|
| 500 |
else:
|
| 501 |
-
# fallback: target = pop * ratio
|
| 502 |
pop_kab["target_base_68"] = pop_kab["pop_base"] * float(FALLBACK_TARGET_RATIO)
|
| 503 |
|
| 504 |
pop_kab["pop_base"] = pd.to_numeric(pop_kab["pop_base"], errors="coerce")
|
| 505 |
pop_kab["target_base_68"] = pd.to_numeric(pop_kab["target_base_68"], errors="coerce")
|
| 506 |
|
| 507 |
-
# fallback saling isi
|
| 508 |
m_need_pop = pop_kab["pop_base"].isna() & pop_kab["target_base_68"].notna() & (pop_kab["target_base_68"] > 0)
|
| 509 |
pop_kab.loc[m_need_pop, "pop_base"] = pop_kab.loc[m_need_pop, "target_base_68"] / float(FALLBACK_TARGET_RATIO)
|
| 510 |
|
|
@@ -519,7 +517,7 @@ def load_default_files(force=False):
|
|
| 519 |
})
|
| 520 |
|
| 521 |
# =========================
|
| 522 |
-
# POP PROV (
|
| 523 |
# =========================
|
| 524 |
pp = pd.read_excel(POP_PROV)
|
| 525 |
c_pr = pick_col(pp, ["Provinsi","PROVINSI","provinsi","Propinsi","PROPINSI","propinsi"])
|
|
@@ -534,7 +532,6 @@ def load_default_files(force=False):
|
|
| 534 |
pop_prov["Provinsi_Label"] = pp[c_pr].astype(str).str.strip()
|
| 535 |
pop_prov["prov_key"] = pop_prov["Provinsi_Label"].apply(norm_prov_label)
|
| 536 |
|
| 537 |
-
# base pop prov: sekolah = sma+smk+slb, umum = perpus_umum_prop (sesuai pola kamu sebelumnya)
|
| 538 |
sma = pd.to_numeric(pp.get("sma ", pp.get("sma", 0)), errors="coerce").fillna(0.0)
|
| 539 |
smk = pd.to_numeric(pp.get("smk", 0), errors="coerce").fillna(0.0)
|
| 540 |
slb = pd.to_numeric(pp.get("slb", 0), errors="coerce").fillna(0.0)
|
|
@@ -618,7 +615,6 @@ def build_faktor_wilayah_total(
|
|
| 618 |
if df.empty:
|
| 619 |
return pd.DataFrame()
|
| 620 |
|
| 621 |
-
# tentukan level
|
| 622 |
if "PROV" in kew_norm:
|
| 623 |
key_col, label_col, label_name, mode = "prov_key", "PROV_DISP", "Provinsi", "PROV"
|
| 624 |
base_pop = pop_prov.copy() if (pop_prov is not None and not pop_prov.empty) else pd.DataFrame()
|
|
@@ -632,10 +628,8 @@ def build_faktor_wilayah_total(
|
|
| 632 |
base_pop["kab_key"] = base_pop["Kab_Kota_Label"].apply(norm_kab_label) if "Kab_Kota_Label" in base_pop.columns else base_pop.iloc[:, 0].apply(norm_kab_label)
|
| 633 |
base_pop = base_pop.set_index("kab_key") if (not base_pop.empty and "kab_key" in base_pop.columns) else pd.DataFrame().set_index(pd.Index([]))
|
| 634 |
|
| 635 |
-
# semua wilayah dari data
|
| 636 |
base_keys = df[[key_col, label_col]].drop_duplicates().rename(columns={key_col: "group_key", label_col: label_name})
|
| 637 |
|
| 638 |
-
# n_total (TOTAL semua jenis)
|
| 639 |
cnt_total = (
|
| 640 |
df.groupby([key_col, label_col], dropna=False)
|
| 641 |
.size()
|
|
@@ -646,7 +640,6 @@ def build_faktor_wilayah_total(
|
|
| 646 |
out = base_keys.merge(cnt_total, on=["group_key", label_name], how="left")
|
| 647 |
out["n_total"] = pd.to_numeric(out["n_total"], errors="coerce").fillna(0).astype(int)
|
| 648 |
|
| 649 |
-
# base pop/target (KAB/PROV)
|
| 650 |
pop_base_series = pd.Series(dtype=float)
|
| 651 |
tgt_base_series = pd.Series(dtype=float)
|
| 652 |
|
|
@@ -657,7 +650,6 @@ def build_faktor_wilayah_total(
|
|
| 657 |
out["pop_base"] = out["group_key"].map(pop_base_series).fillna(0.0)
|
| 658 |
out["target_base_68"] = out["group_key"].map(tgt_base_series).fillna(0.0)
|
| 659 |
|
| 660 |
-
# khusus (ditambah)
|
| 661 |
pop_khus = pd.Series(dtype=float)
|
| 662 |
tgt_khus = pd.Series(dtype=float)
|
| 663 |
|
|
@@ -686,15 +678,12 @@ def build_faktor_wilayah_total(
|
|
| 686 |
out["pop_khusus"] = out["group_key"].map(pop_khus).fillna(0.0)
|
| 687 |
out["target_khusus_68"] = out["group_key"].map(tgt_khus).fillna(0.0)
|
| 688 |
|
| 689 |
-
# TOTAL gabungan
|
| 690 |
out["pop_total"] = pd.to_numeric(out["pop_base"], errors="coerce").fillna(0.0) + pd.to_numeric(out["pop_khusus"], errors="coerce").fillna(0.0)
|
| 691 |
out["target_total_68"] = pd.to_numeric(out["target_base_68"], errors="coerce").fillna(0.0) + pd.to_numeric(out["target_khusus_68"], errors="coerce").fillna(0.0)
|
| 692 |
|
| 693 |
-
# fallback pop dari target jika pop kosong
|
| 694 |
m_need_pop = (out["pop_total"] <= 0) & (out["target_total_68"] > 0)
|
| 695 |
out.loc[m_need_pop, "pop_total"] = out.loc[m_need_pop, "target_total_68"] / float(FALLBACK_TARGET_RATIO)
|
| 696 |
|
| 697 |
-
# faktor / coverage / gap
|
| 698 |
out["faktor_penyesuaian_wilayah"] = [
|
| 699 |
faktor_penyesuaian_total(n, t)
|
| 700 |
for n, t in zip(
|
|
@@ -719,14 +708,12 @@ def build_faktor_wilayah_total(
|
|
| 719 |
)
|
| 720 |
]
|
| 721 |
|
| 722 |
-
# display format (REQUEST)
|
| 723 |
out["target_total_68"] = pd.to_numeric(out["target_total_68"], errors="coerce").fillna(0).round(0).astype(int)
|
| 724 |
out["pop_total"] = pd.to_numeric(out["pop_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 725 |
out["coverage_total_%"] = pd.to_numeric(out["coverage_total_%"], errors="coerce").fillna(0.0).round(2)
|
| 726 |
out["faktor_penyesuaian_wilayah"] = pd.to_numeric(out["faktor_penyesuaian_wilayah"], errors="coerce").fillna(1.0).round(3)
|
| 727 |
out["gap_target68_total"] = pd.to_numeric(out["gap_target68_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 728 |
|
| 729 |
-
# rapikan kolom
|
| 730 |
keep = [
|
| 731 |
"group_key", label_name,
|
| 732 |
"n_total",
|
|
@@ -734,7 +721,6 @@ def build_faktor_wilayah_total(
|
|
| 734 |
"coverage_total_%",
|
| 735 |
"faktor_penyesuaian_wilayah",
|
| 736 |
"gap_target68_total",
|
| 737 |
-
# opsional debug:
|
| 738 |
"pop_base","pop_khusus","target_base_68","target_khusus_68",
|
| 739 |
]
|
| 740 |
keep = [c for c in keep if c in out.columns]
|
|
@@ -742,7 +728,7 @@ def build_faktor_wilayah_total(
|
|
| 742 |
|
| 743 |
|
| 744 |
# ============================================================
|
| 745 |
-
# 7) AGREGAT WILAYAH Γ JENIS
|
| 746 |
# ============================================================
|
| 747 |
|
| 748 |
def build_agg_wilayah_jenis(df_filtered: pd.DataFrame, faktor_wilayah_total: pd.DataFrame, kew_value: str):
|
|
@@ -763,7 +749,6 @@ def build_agg_wilayah_jenis(df_filtered: pd.DataFrame, faktor_wilayah_total: pd.
|
|
| 763 |
|
| 764 |
jenis_list = ["sekolah", "umum", "khusus"]
|
| 765 |
|
| 766 |
-
# grid wilayah Γ 3 jenis
|
| 767 |
base_keys = df[[key_col, label_col]].drop_duplicates().rename(columns={key_col: "group_key", label_col: label_name})
|
| 768 |
full = base_keys.assign(_tmp=1).merge(
|
| 769 |
pd.DataFrame({"Jenis": jenis_list, "_tmp": 1}),
|
|
@@ -790,7 +775,6 @@ def build_agg_wilayah_jenis(df_filtered: pd.DataFrame, faktor_wilayah_total: pd.
|
|
| 790 |
|
| 791 |
agg["Jumlah"] = agg["Jumlah"].round(0).astype(int)
|
| 792 |
|
| 793 |
-
# merge faktor wilayah total (sama untuk semua jenis)
|
| 794 |
if faktor_wilayah_total is None or faktor_wilayah_total.empty:
|
| 795 |
agg["faktor_penyesuaian_wilayah"] = 1.0
|
| 796 |
else:
|
|
@@ -819,7 +803,8 @@ def build_agg_wilayah_jenis(df_filtered: pd.DataFrame, faktor_wilayah_total: pd.
|
|
| 819 |
|
| 820 |
|
| 821 |
# ============================================================
|
| 822 |
-
# 8) AGREGAT WILAYAH (KESELURUHAN) β avg3 dari 3 jenis
|
|
|
|
| 823 |
# ============================================================
|
| 824 |
|
| 825 |
def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_total: pd.DataFrame, kew_value: str):
|
|
@@ -866,19 +851,19 @@ def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_t
|
|
| 866 |
Indeks_Final_Wilayah_0_100=("Indeks_Final_Agregat_0_100", "mean"),
|
| 867 |
)
|
| 868 |
|
| 869 |
-
# tempel Pop/Target/Coverage/Faktor TOTAL
|
|
|
|
| 870 |
if faktor_wilayah_total is not None and not faktor_wilayah_total.empty:
|
| 871 |
fw = faktor_wilayah_total.copy()
|
| 872 |
cols = [c for c in [
|
| 873 |
"group_key", label_name,
|
| 874 |
"pop_total","target_total_68","coverage_total_%",
|
| 875 |
-
"faktor_penyesuaian_wilayah","gap_target68_total"
|
| 876 |
] if c in fw.columns]
|
| 877 |
fw = fw[cols].copy()
|
| 878 |
out = out.merge(fw, on=["group_key", label_name], how="left")
|
| 879 |
|
| 880 |
-
|
| 881 |
-
for c in ["pop_total","target_total_68","gap_target68_total","n_total"]:
|
| 882 |
if c in out.columns:
|
| 883 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0).round(0).astype(int)
|
| 884 |
if "coverage_total_%" in out.columns:
|
|
@@ -897,14 +882,13 @@ def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_t
|
|
| 897 |
if c in out.columns:
|
| 898 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0.0).round(2)
|
| 899 |
|
|
|
|
| 900 |
out["n_total"] = pd.to_numeric(out["n_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 901 |
return out
|
| 902 |
|
| 903 |
|
| 904 |
# ============================================================
|
| 905 |
# 9) SUMMARY (PER JENIS) + KESELURUHAN
|
| 906 |
-
# Pop/Target/Coverage per jenis TIDAK dipakai (karena total-only),
|
| 907 |
-
# tapi tetap ditampilkan agar 4 baris konsisten.
|
| 908 |
# ============================================================
|
| 909 |
|
| 910 |
def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame, faktor_wilayah_total: pd.DataFrame = None):
|
|
@@ -967,7 +951,6 @@ def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame, fa
|
|
| 967 |
+ rows_by_jenis["umum"]["Indeks_Final_Disesuaikan_0_100"]
|
| 968 |
+ rows_by_jenis["khusus"]["Indeks_Final_Disesuaikan_0_100"]) / 3.0
|
| 969 |
|
| 970 |
-
# TOTAL pop/target/coverage ditarik dari faktor_wilayah_total (nasional/filtered)
|
| 971 |
pop_all = 0
|
| 972 |
target_all = 0
|
| 973 |
terkumpul_all = int(rows_by_jenis["sekolah"]["Terkumpul_Jenis"]
|
|
@@ -1013,7 +996,7 @@ def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame, fa
|
|
| 1013 |
|
| 1014 |
|
| 1015 |
# ============================================================
|
| 1016 |
-
# 10) DETAIL ENTITAS: Final menempel dari agg_total
|
| 1017 |
# ============================================================
|
| 1018 |
|
| 1019 |
def attach_final_to_detail(df_filtered: pd.DataFrame, agg_total: pd.DataFrame, meta: dict, kew_value: str):
|
|
@@ -1068,7 +1051,7 @@ def attach_final_to_detail(df_filtered: pd.DataFrame, agg_total: pd.DataFrame, m
|
|
| 1068 |
|
| 1069 |
|
| 1070 |
# ============================================================
|
| 1071 |
-
# 11) VERIFIKASI TOTAL
|
| 1072 |
# ============================================================
|
| 1073 |
|
| 1074 |
def build_verif_total(faktor_wilayah_total: pd.DataFrame, kew_value: str):
|
|
@@ -1211,7 +1194,7 @@ def _make_bell_curve(dfp: pd.DataFrame, xcol: str, title: str, label_col: str |
|
|
| 1211 |
|
| 1212 |
|
| 1213 |
# ============================================================
|
| 1214 |
-
# 13) KPI DASHBOARD
|
| 1215 |
# ============================================================
|
| 1216 |
|
| 1217 |
def compute_dashboard_kpis(summary_jenis: pd.DataFrame):
|
|
@@ -1385,7 +1368,6 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1385 |
if df_all is None or df_all.empty or df_raw is None or df_raw.empty:
|
| 1386 |
return _empty_outputs("β οΈ Data belum ter-load. Pastikan file tersedia di repo/server.")
|
| 1387 |
|
| 1388 |
-
# FILTER (df_all)
|
| 1389 |
df = df_all.copy()
|
| 1390 |
if prov_value and prov_value != "(Semua)":
|
| 1391 |
df = df[df["PROV_DISP"] == prov_value]
|
|
@@ -1397,23 +1379,17 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1397 |
if df.empty:
|
| 1398 |
return _empty_outputs("Tidak ada data untuk filter ini.")
|
| 1399 |
|
| 1400 |
-
# === TOTAL-only faktor wilayah
|
| 1401 |
faktor_wilayah_total = build_faktor_wilayah_total(df, pop_kab, pop_prov, pop_khusus, kew_value or "(Semua)")
|
| 1402 |
|
| 1403 |
-
# agregat jenis + total
|
| 1404 |
agg_jenis_full = build_agg_wilayah_jenis(df, faktor_wilayah_total, kew_value or "(Semua)")
|
| 1405 |
agg_total = build_agg_wilayah_total_from_jenis(agg_jenis_full, faktor_wilayah_total, kew_value or "(Semua)")
|
| 1406 |
|
| 1407 |
-
# summary (4 baris)
|
| 1408 |
summary_jenis = build_summary_per_jenis(agg_jenis_full, agg_total, faktor_wilayah_total=faktor_wilayah_total)
|
| 1409 |
|
| 1410 |
-
# verifikasi total
|
| 1411 |
verif_total = build_verif_total(faktor_wilayah_total, kew_value or "(Semua)")
|
| 1412 |
|
| 1413 |
-
# detail entitas
|
| 1414 |
detail_view = attach_final_to_detail(df, agg_total, meta, kew_value or "(Semua)")
|
| 1415 |
|
| 1416 |
-
# view agg_jenis (UI cuma sampai indeks dasar)
|
| 1417 |
if agg_jenis_full is None or agg_jenis_full.empty:
|
| 1418 |
agg_jenis_view = agg_jenis_full
|
| 1419 |
else:
|
|
@@ -1431,7 +1407,6 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1431 |
cols_upto = [c for c in cols_upto if c in agg_jenis_full.columns]
|
| 1432 |
agg_jenis_view = agg_jenis_full[cols_upto].copy()
|
| 1433 |
|
| 1434 |
-
# FILTER RAW DOWNLOAD (df_raw)
|
| 1435 |
raw = df_raw.copy()
|
| 1436 |
if prov_value and prov_value != "(Semua)":
|
| 1437 |
raw = raw[raw["PROV_DISP"] == prov_value]
|
|
@@ -1440,7 +1415,6 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1440 |
if kew_value and kew_value != "(Semua)":
|
| 1441 |
raw = raw[raw["KEW_NORM"] == kew_value]
|
| 1442 |
|
| 1443 |
-
# bell curve per jenis (entitas)
|
| 1444 |
if detail_view is None or detail_view.empty:
|
| 1445 |
fig_sekolah = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Sekolah", min_points=2)
|
| 1446 |
fig_umum = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Umum", min_points=2)
|
|
@@ -1458,7 +1432,6 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1458 |
fig_umum = _fig_jenis_ent("umum", "Bell Curve β Jenis: Umum (Indeks per Entitas)")
|
| 1459 |
fig_khusus = _fig_jenis_ent("khusus", "Bell Curve β Jenis: Khusus (Indeks per Entitas)")
|
| 1460 |
|
| 1461 |
-
# KPI
|
| 1462 |
kpi_md = build_kpi_markdown(summary_jenis)
|
| 1463 |
|
| 1464 |
tmpdir = tempfile.mkdtemp()
|
|
@@ -1543,13 +1516,14 @@ def on_prov_change(prov_value):
|
|
| 1543 |
with gr.Blocks() as demo:
|
| 1544 |
gr.Markdown(f"""
|
| 1545 |
# IPLM 2025 β Final (TOTAL-only Pop/Target68: gabungan sekolah+umum+khusus)
|
| 1546 |
-
|
|
|
|
| 1547 |
- `DATA_FILE` = **{DATA_FILE}**
|
| 1548 |
- `POP_KAB` = **{POP_KAB}**
|
| 1549 |
- `POP_PROV` = **{POP_PROV}**
|
| 1550 |
- `POP_KHUSUS` = **{POP_KHUSUS}**
|
| 1551 |
|
| 1552 |
-
|
| 1553 |
- Penyesuaian sampel berbasis TOTAL wilayah: n_total vs target_total_68 (gabungan base + khusus)
|
| 1554 |
- Pop/Target = (base KAB/PROV) + (KHUSUS)
|
| 1555 |
- Faktor wilayah dipakai sama untuk semua jenis
|
|
|
|
| 465 |
after = len(df_raw)
|
| 466 |
|
| 467 |
# =========================
|
| 468 |
+
# POP KAB (TOTAL base: umum+sekolah)
|
| 469 |
# =========================
|
| 470 |
pk = pd.read_excel(POP_KAB)
|
| 471 |
|
|
|
|
| 498 |
if c_target_total:
|
| 499 |
pop_kab["target_base_68"] = pd.to_numeric(pk[c_target_total], errors="coerce")
|
| 500 |
else:
|
|
|
|
| 501 |
pop_kab["target_base_68"] = pop_kab["pop_base"] * float(FALLBACK_TARGET_RATIO)
|
| 502 |
|
| 503 |
pop_kab["pop_base"] = pd.to_numeric(pop_kab["pop_base"], errors="coerce")
|
| 504 |
pop_kab["target_base_68"] = pd.to_numeric(pop_kab["target_base_68"], errors="coerce")
|
| 505 |
|
|
|
|
| 506 |
m_need_pop = pop_kab["pop_base"].isna() & pop_kab["target_base_68"].notna() & (pop_kab["target_base_68"] > 0)
|
| 507 |
pop_kab.loc[m_need_pop, "pop_base"] = pop_kab.loc[m_need_pop, "target_base_68"] / float(FALLBACK_TARGET_RATIO)
|
| 508 |
|
|
|
|
| 517 |
})
|
| 518 |
|
| 519 |
# =========================
|
| 520 |
+
# POP PROV (TOTAL base prov)
|
| 521 |
# =========================
|
| 522 |
pp = pd.read_excel(POP_PROV)
|
| 523 |
c_pr = pick_col(pp, ["Provinsi","PROVINSI","provinsi","Propinsi","PROPINSI","propinsi"])
|
|
|
|
| 532 |
pop_prov["Provinsi_Label"] = pp[c_pr].astype(str).str.strip()
|
| 533 |
pop_prov["prov_key"] = pop_prov["Provinsi_Label"].apply(norm_prov_label)
|
| 534 |
|
|
|
|
| 535 |
sma = pd.to_numeric(pp.get("sma ", pp.get("sma", 0)), errors="coerce").fillna(0.0)
|
| 536 |
smk = pd.to_numeric(pp.get("smk", 0), errors="coerce").fillna(0.0)
|
| 537 |
slb = pd.to_numeric(pp.get("slb", 0), errors="coerce").fillna(0.0)
|
|
|
|
| 615 |
if df.empty:
|
| 616 |
return pd.DataFrame()
|
| 617 |
|
|
|
|
| 618 |
if "PROV" in kew_norm:
|
| 619 |
key_col, label_col, label_name, mode = "prov_key", "PROV_DISP", "Provinsi", "PROV"
|
| 620 |
base_pop = pop_prov.copy() if (pop_prov is not None and not pop_prov.empty) else pd.DataFrame()
|
|
|
|
| 628 |
base_pop["kab_key"] = base_pop["Kab_Kota_Label"].apply(norm_kab_label) if "Kab_Kota_Label" in base_pop.columns else base_pop.iloc[:, 0].apply(norm_kab_label)
|
| 629 |
base_pop = base_pop.set_index("kab_key") if (not base_pop.empty and "kab_key" in base_pop.columns) else pd.DataFrame().set_index(pd.Index([]))
|
| 630 |
|
|
|
|
| 631 |
base_keys = df[[key_col, label_col]].drop_duplicates().rename(columns={key_col: "group_key", label_col: label_name})
|
| 632 |
|
|
|
|
| 633 |
cnt_total = (
|
| 634 |
df.groupby([key_col, label_col], dropna=False)
|
| 635 |
.size()
|
|
|
|
| 640 |
out = base_keys.merge(cnt_total, on=["group_key", label_name], how="left")
|
| 641 |
out["n_total"] = pd.to_numeric(out["n_total"], errors="coerce").fillna(0).astype(int)
|
| 642 |
|
|
|
|
| 643 |
pop_base_series = pd.Series(dtype=float)
|
| 644 |
tgt_base_series = pd.Series(dtype=float)
|
| 645 |
|
|
|
|
| 650 |
out["pop_base"] = out["group_key"].map(pop_base_series).fillna(0.0)
|
| 651 |
out["target_base_68"] = out["group_key"].map(tgt_base_series).fillna(0.0)
|
| 652 |
|
|
|
|
| 653 |
pop_khus = pd.Series(dtype=float)
|
| 654 |
tgt_khus = pd.Series(dtype=float)
|
| 655 |
|
|
|
|
| 678 |
out["pop_khusus"] = out["group_key"].map(pop_khus).fillna(0.0)
|
| 679 |
out["target_khusus_68"] = out["group_key"].map(tgt_khus).fillna(0.0)
|
| 680 |
|
|
|
|
| 681 |
out["pop_total"] = pd.to_numeric(out["pop_base"], errors="coerce").fillna(0.0) + pd.to_numeric(out["pop_khusus"], errors="coerce").fillna(0.0)
|
| 682 |
out["target_total_68"] = pd.to_numeric(out["target_base_68"], errors="coerce").fillna(0.0) + pd.to_numeric(out["target_khusus_68"], errors="coerce").fillna(0.0)
|
| 683 |
|
|
|
|
| 684 |
m_need_pop = (out["pop_total"] <= 0) & (out["target_total_68"] > 0)
|
| 685 |
out.loc[m_need_pop, "pop_total"] = out.loc[m_need_pop, "target_total_68"] / float(FALLBACK_TARGET_RATIO)
|
| 686 |
|
|
|
|
| 687 |
out["faktor_penyesuaian_wilayah"] = [
|
| 688 |
faktor_penyesuaian_total(n, t)
|
| 689 |
for n, t in zip(
|
|
|
|
| 708 |
)
|
| 709 |
]
|
| 710 |
|
|
|
|
| 711 |
out["target_total_68"] = pd.to_numeric(out["target_total_68"], errors="coerce").fillna(0).round(0).astype(int)
|
| 712 |
out["pop_total"] = pd.to_numeric(out["pop_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 713 |
out["coverage_total_%"] = pd.to_numeric(out["coverage_total_%"], errors="coerce").fillna(0.0).round(2)
|
| 714 |
out["faktor_penyesuaian_wilayah"] = pd.to_numeric(out["faktor_penyesuaian_wilayah"], errors="coerce").fillna(1.0).round(3)
|
| 715 |
out["gap_target68_total"] = pd.to_numeric(out["gap_target68_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 716 |
|
|
|
|
| 717 |
keep = [
|
| 718 |
"group_key", label_name,
|
| 719 |
"n_total",
|
|
|
|
| 721 |
"coverage_total_%",
|
| 722 |
"faktor_penyesuaian_wilayah",
|
| 723 |
"gap_target68_total",
|
|
|
|
| 724 |
"pop_base","pop_khusus","target_base_68","target_khusus_68",
|
| 725 |
]
|
| 726 |
keep = [c for c in keep if c in out.columns]
|
|
|
|
| 728 |
|
| 729 |
|
| 730 |
# ============================================================
|
| 731 |
+
# 7) AGREGAT WILAYAH Γ JENIS
|
| 732 |
# ============================================================
|
| 733 |
|
| 734 |
def build_agg_wilayah_jenis(df_filtered: pd.DataFrame, faktor_wilayah_total: pd.DataFrame, kew_value: str):
|
|
|
|
| 749 |
|
| 750 |
jenis_list = ["sekolah", "umum", "khusus"]
|
| 751 |
|
|
|
|
| 752 |
base_keys = df[[key_col, label_col]].drop_duplicates().rename(columns={key_col: "group_key", label_col: label_name})
|
| 753 |
full = base_keys.assign(_tmp=1).merge(
|
| 754 |
pd.DataFrame({"Jenis": jenis_list, "_tmp": 1}),
|
|
|
|
| 775 |
|
| 776 |
agg["Jumlah"] = agg["Jumlah"].round(0).astype(int)
|
| 777 |
|
|
|
|
| 778 |
if faktor_wilayah_total is None or faktor_wilayah_total.empty:
|
| 779 |
agg["faktor_penyesuaian_wilayah"] = 1.0
|
| 780 |
else:
|
|
|
|
| 803 |
|
| 804 |
|
| 805 |
# ============================================================
|
| 806 |
+
# 8) AGREGAT WILAYAH (KESELURUHAN) β avg3 dari 3 jenis
|
| 807 |
+
# β
FIX DI SINI: JANGAN MERGE fw kolom n_total (hindari bentrok)
|
| 808 |
# ============================================================
|
| 809 |
|
| 810 |
def build_agg_wilayah_total_from_jenis(agg_jenis: pd.DataFrame, faktor_wilayah_total: pd.DataFrame, kew_value: str):
|
|
|
|
| 851 |
Indeks_Final_Wilayah_0_100=("Indeks_Final_Agregat_0_100", "mean"),
|
| 852 |
)
|
| 853 |
|
| 854 |
+
# β
tempel Pop/Target/Coverage/Faktor TOTAL
|
| 855 |
+
# β JANGAN tempel n_total dari fw (biar tidak bentrok)
|
| 856 |
if faktor_wilayah_total is not None and not faktor_wilayah_total.empty:
|
| 857 |
fw = faktor_wilayah_total.copy()
|
| 858 |
cols = [c for c in [
|
| 859 |
"group_key", label_name,
|
| 860 |
"pop_total","target_total_68","coverage_total_%",
|
| 861 |
+
"faktor_penyesuaian_wilayah","gap_target68_total"
|
| 862 |
] if c in fw.columns]
|
| 863 |
fw = fw[cols].copy()
|
| 864 |
out = out.merge(fw, on=["group_key", label_name], how="left")
|
| 865 |
|
| 866 |
+
for c in ["pop_total","target_total_68","gap_target68_total"]:
|
|
|
|
| 867 |
if c in out.columns:
|
| 868 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0).round(0).astype(int)
|
| 869 |
if "coverage_total_%" in out.columns:
|
|
|
|
| 882 |
if c in out.columns:
|
| 883 |
out[c] = pd.to_numeric(out[c], errors="coerce").fillna(0.0).round(2)
|
| 884 |
|
| 885 |
+
# β
sekarang aman, n_total tetap ada (tidak bentrok)
|
| 886 |
out["n_total"] = pd.to_numeric(out["n_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 887 |
return out
|
| 888 |
|
| 889 |
|
| 890 |
# ============================================================
|
| 891 |
# 9) SUMMARY (PER JENIS) + KESELURUHAN
|
|
|
|
|
|
|
| 892 |
# ============================================================
|
| 893 |
|
| 894 |
def build_summary_per_jenis(agg_jenis: pd.DataFrame, agg_total: pd.DataFrame, faktor_wilayah_total: pd.DataFrame = None):
|
|
|
|
| 951 |
+ rows_by_jenis["umum"]["Indeks_Final_Disesuaikan_0_100"]
|
| 952 |
+ rows_by_jenis["khusus"]["Indeks_Final_Disesuaikan_0_100"]) / 3.0
|
| 953 |
|
|
|
|
| 954 |
pop_all = 0
|
| 955 |
target_all = 0
|
| 956 |
terkumpul_all = int(rows_by_jenis["sekolah"]["Terkumpul_Jenis"]
|
|
|
|
| 996 |
|
| 997 |
|
| 998 |
# ============================================================
|
| 999 |
+
# 10) DETAIL ENTITAS: Final menempel dari agg_total
|
| 1000 |
# ============================================================
|
| 1001 |
|
| 1002 |
def attach_final_to_detail(df_filtered: pd.DataFrame, agg_total: pd.DataFrame, meta: dict, kew_value: str):
|
|
|
|
| 1051 |
|
| 1052 |
|
| 1053 |
# ============================================================
|
| 1054 |
+
# 11) VERIFIKASI TOTAL
|
| 1055 |
# ============================================================
|
| 1056 |
|
| 1057 |
def build_verif_total(faktor_wilayah_total: pd.DataFrame, kew_value: str):
|
|
|
|
| 1194 |
|
| 1195 |
|
| 1196 |
# ============================================================
|
| 1197 |
+
# 13) KPI DASHBOARD
|
| 1198 |
# ============================================================
|
| 1199 |
|
| 1200 |
def compute_dashboard_kpis(summary_jenis: pd.DataFrame):
|
|
|
|
| 1368 |
if df_all is None or df_all.empty or df_raw is None or df_raw.empty:
|
| 1369 |
return _empty_outputs("β οΈ Data belum ter-load. Pastikan file tersedia di repo/server.")
|
| 1370 |
|
|
|
|
| 1371 |
df = df_all.copy()
|
| 1372 |
if prov_value and prov_value != "(Semua)":
|
| 1373 |
df = df[df["PROV_DISP"] == prov_value]
|
|
|
|
| 1379 |
if df.empty:
|
| 1380 |
return _empty_outputs("Tidak ada data untuk filter ini.")
|
| 1381 |
|
|
|
|
| 1382 |
faktor_wilayah_total = build_faktor_wilayah_total(df, pop_kab, pop_prov, pop_khusus, kew_value or "(Semua)")
|
| 1383 |
|
|
|
|
| 1384 |
agg_jenis_full = build_agg_wilayah_jenis(df, faktor_wilayah_total, kew_value or "(Semua)")
|
| 1385 |
agg_total = build_agg_wilayah_total_from_jenis(agg_jenis_full, faktor_wilayah_total, kew_value or "(Semua)")
|
| 1386 |
|
|
|
|
| 1387 |
summary_jenis = build_summary_per_jenis(agg_jenis_full, agg_total, faktor_wilayah_total=faktor_wilayah_total)
|
| 1388 |
|
|
|
|
| 1389 |
verif_total = build_verif_total(faktor_wilayah_total, kew_value or "(Semua)")
|
| 1390 |
|
|
|
|
| 1391 |
detail_view = attach_final_to_detail(df, agg_total, meta, kew_value or "(Semua)")
|
| 1392 |
|
|
|
|
| 1393 |
if agg_jenis_full is None or agg_jenis_full.empty:
|
| 1394 |
agg_jenis_view = agg_jenis_full
|
| 1395 |
else:
|
|
|
|
| 1407 |
cols_upto = [c for c in cols_upto if c in agg_jenis_full.columns]
|
| 1408 |
agg_jenis_view = agg_jenis_full[cols_upto].copy()
|
| 1409 |
|
|
|
|
| 1410 |
raw = df_raw.copy()
|
| 1411 |
if prov_value and prov_value != "(Semua)":
|
| 1412 |
raw = raw[raw["PROV_DISP"] == prov_value]
|
|
|
|
| 1415 |
if kew_value and kew_value != "(Semua)":
|
| 1416 |
raw = raw[raw["KEW_NORM"] == kew_value]
|
| 1417 |
|
|
|
|
| 1418 |
if detail_view is None or detail_view.empty:
|
| 1419 |
fig_sekolah = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Sekolah", min_points=2)
|
| 1420 |
fig_umum = _make_bell_curve(pd.DataFrame(), "Indeks_Dasar_0_100", "Bell Curve β Jenis: Umum", min_points=2)
|
|
|
|
| 1432 |
fig_umum = _fig_jenis_ent("umum", "Bell Curve β Jenis: Umum (Indeks per Entitas)")
|
| 1433 |
fig_khusus = _fig_jenis_ent("khusus", "Bell Curve β Jenis: Khusus (Indeks per Entitas)")
|
| 1434 |
|
|
|
|
| 1435 |
kpi_md = build_kpi_markdown(summary_jenis)
|
| 1436 |
|
| 1437 |
tmpdir = tempfile.mkdtemp()
|
|
|
|
| 1516 |
with gr.Blocks() as demo:
|
| 1517 |
gr.Markdown(f"""
|
| 1518 |
# IPLM 2025 β Final (TOTAL-only Pop/Target68: gabungan sekolah+umum+khusus)
|
| 1519 |
+
|
| 1520 |
+
Mode NO UPLOAD (cache aktif). File dibaca dari repo/server:
|
| 1521 |
- `DATA_FILE` = **{DATA_FILE}**
|
| 1522 |
- `POP_KAB` = **{POP_KAB}**
|
| 1523 |
- `POP_PROV` = **{POP_PROV}**
|
| 1524 |
- `POP_KHUSUS` = **{POP_KHUSUS}**
|
| 1525 |
|
| 1526 |
+
UPDATE UTAMA:
|
| 1527 |
- Penyesuaian sampel berbasis TOTAL wilayah: n_total vs target_total_68 (gabungan base + khusus)
|
| 1528 |
- Pop/Target = (base KAB/PROV) + (KHUSUS)
|
| 1529 |
- Faktor wilayah dipakai sama untuk semua jenis
|