Update app.py
Browse files
app.py
CHANGED
|
@@ -36,11 +36,6 @@ PERMINTAAN UPDATE (HANYA INI):
|
|
| 36 |
- coverage_total_% -> decimal 2 digit
|
| 37 |
2) TABEL "Agregat Wilayah × Jenis" (UI) hanya sampai kolom Indeks_Dasar_Agregat_0_100
|
| 38 |
(kolom setelah itu tidak ditampilkan)
|
| 39 |
-
|
| 40 |
-
✅ UPDATE TAMBAHAN (SESUSAI PERMINTAAN TERAKHIR):
|
| 41 |
-
- KHUSUS (Data_populasi_perp_khusus.xlsx) DITAMBAHKAN ke pop_total & target_total_68 untuk WILAYAH (Keseluruhan)
|
| 42 |
-
-> caranya: pada faktor_wilayah, pop_total += Pop_Total_Jenis dan target_total_68 += Target68_Total_Jenis
|
| 43 |
-
-> tanpa menambah kolom baru khusus (hanya nilai pop_total & target_total_68 yang berubah)
|
| 44 |
"""
|
| 45 |
|
| 46 |
import os
|
|
@@ -610,10 +605,9 @@ def load_default_files(force=False):
|
|
| 610 |
|
| 611 |
# ============================================================
|
| 612 |
# 6) FAKTOR WILAYAH (TOTAL) — hanya untuk faktor/target/pop/coverage
|
| 613 |
-
# ✅ UPDATE: tambah KHUSUS ke pop_total & target_total_68 (tanpa kolom baru)
|
| 614 |
# ============================================================
|
| 615 |
|
| 616 |
-
def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_prov: pd.DataFrame,
|
| 617 |
if df_filtered is None or df_filtered.empty:
|
| 618 |
return pd.DataFrame()
|
| 619 |
|
|
@@ -628,17 +622,6 @@ def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_p
|
|
| 628 |
target_field = "Target68_Total"
|
| 629 |
pop_field = "Pop_Total"
|
| 630 |
name_field = "Kab_Kota_Label"
|
| 631 |
-
# khusus: by kab_key
|
| 632 |
-
pk_add = None
|
| 633 |
-
if pop_khusus is not None and not pop_khusus.empty:
|
| 634 |
-
pk_add = pop_khusus.copy()
|
| 635 |
-
pk_add["kab_key"] = pk_add["kab_key"].astype(str)
|
| 636 |
-
pk_add["Target68_Total_Jenis"] = pd.to_numeric(pk_add.get("Target68_Total_Jenis", 0), errors="coerce").fillna(0.0)
|
| 637 |
-
pk_add["Pop_Total_Jenis"] = pd.to_numeric(pk_add.get("Pop_Total_Jenis", 0), errors="coerce").fillna(0.0)
|
| 638 |
-
pk_add = pk_add.groupby("kab_key", as_index=True).agg(
|
| 639 |
-
add_target=("Target68_Total_Jenis", "sum"),
|
| 640 |
-
add_pop=("Pop_Total_Jenis", "sum")
|
| 641 |
-
)
|
| 642 |
elif "PROV" in kew_norm:
|
| 643 |
key_col = "prov_key"
|
| 644 |
label_col = "PROV_DISP"
|
|
@@ -647,17 +630,6 @@ def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_p
|
|
| 647 |
target_field = "Target68_Total_Prov"
|
| 648 |
pop_field = "Pop_Total_Prov"
|
| 649 |
name_field = "Provinsi_Label"
|
| 650 |
-
# khusus: by prov_key (sum kab_key khusus per prov)
|
| 651 |
-
pk_add = None
|
| 652 |
-
if pop_khusus is not None and not pop_khusus.empty:
|
| 653 |
-
pk_add = pop_khusus.copy()
|
| 654 |
-
pk_add["prov_key"] = pk_add["prov_key"].astype(str)
|
| 655 |
-
pk_add["Target68_Total_Jenis"] = pd.to_numeric(pk_add.get("Target68_Total_Jenis", 0), errors="coerce").fillna(0.0)
|
| 656 |
-
pk_add["Pop_Total_Jenis"] = pd.to_numeric(pk_add.get("Pop_Total_Jenis", 0), errors="coerce").fillna(0.0)
|
| 657 |
-
pk_add = pk_add.groupby("prov_key", as_index=True).agg(
|
| 658 |
-
add_target=("Target68_Total_Jenis", "sum"),
|
| 659 |
-
add_pop=("Pop_Total_Jenis", "sum")
|
| 660 |
-
)
|
| 661 |
else:
|
| 662 |
key_col = "kab_key"
|
| 663 |
label_col = "KAB_DISP"
|
|
@@ -666,16 +638,6 @@ def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_p
|
|
| 666 |
target_field = "Target68_Total"
|
| 667 |
pop_field = "Pop_Total"
|
| 668 |
name_field = "Kab_Kota_Label"
|
| 669 |
-
pk_add = None
|
| 670 |
-
if pop_khusus is not None and not pop_khusus.empty:
|
| 671 |
-
pk_add = pop_khusus.copy()
|
| 672 |
-
pk_add["kab_key"] = pk_add["kab_key"].astype(str)
|
| 673 |
-
pk_add["Target68_Total_Jenis"] = pd.to_numeric(pk_add.get("Target68_Total_Jenis", 0), errors="coerce").fillna(0.0)
|
| 674 |
-
pk_add["Pop_Total_Jenis"] = pd.to_numeric(pk_add.get("Pop_Total_Jenis", 0), errors="coerce").fillna(0.0)
|
| 675 |
-
pk_add = pk_add.groupby("kab_key", as_index=True).agg(
|
| 676 |
-
add_target=("Target68_Total_Jenis", "sum"),
|
| 677 |
-
add_pop=("Pop_Total_Jenis", "sum")
|
| 678 |
-
)
|
| 679 |
|
| 680 |
base = df.groupby([key_col, label_col], dropna=False).agg(
|
| 681 |
n_total=("Indeks_Dasar_0_100", "size"),
|
|
@@ -698,30 +660,9 @@ def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_p
|
|
| 698 |
base["target_total_68"] = pd.to_numeric(pd.Series(target_vals), errors="coerce")
|
| 699 |
base["pop_total"] = pd.to_numeric(pd.Series(pop_vals), errors="coerce")
|
| 700 |
|
| 701 |
-
# fallback pop dari target (jika pop kosong)
|
| 702 |
m = base["pop_total"].isna() & base["target_total_68"].notna() & (base["target_total_68"] > 0)
|
| 703 |
base.loc[m, "pop_total"] = base.loc[m, "target_total_68"] / float(FALLBACK_TARGET_RATIO)
|
| 704 |
|
| 705 |
-
# ============================================================
|
| 706 |
-
# ✅ UPDATE TAMBAHAN: TAMBAHKAN POP/TARGET KHUSUS KE TOTAL WILAYAH
|
| 707 |
-
# tanpa menambah kolom baru khusus
|
| 708 |
-
# ============================================================
|
| 709 |
-
if "pk_add" in locals() and pk_add is not None and not pk_add.empty:
|
| 710 |
-
add_targets = []
|
| 711 |
-
add_pops = []
|
| 712 |
-
for _, r in base.iterrows():
|
| 713 |
-
gk = str(r["group_key"])
|
| 714 |
-
if gk in pk_add.index:
|
| 715 |
-
add_targets.append(float(pk_add.loc[gk, "add_target"]))
|
| 716 |
-
add_pops.append(float(pk_add.loc[gk, "add_pop"]))
|
| 717 |
-
else:
|
| 718 |
-
add_targets.append(0.0)
|
| 719 |
-
add_pops.append(0.0)
|
| 720 |
-
|
| 721 |
-
base["target_total_68"] = pd.to_numeric(base["target_total_68"], errors="coerce").fillna(0.0) + pd.Series(add_targets, index=base.index)
|
| 722 |
-
base["pop_total"] = pd.to_numeric(base["pop_total"], errors="coerce").fillna(0.0) + pd.Series(add_pops, index=base.index)
|
| 723 |
-
|
| 724 |
-
# faktor & coverage (pakai total yang sudah terupdate)
|
| 725 |
base["faktor_penyesuaian"] = [
|
| 726 |
faktor_penyesuaian_total(n, t)
|
| 727 |
for n, t in zip(
|
|
@@ -734,7 +675,7 @@ def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_p
|
|
| 734 |
(safe_div(n, p) * 100) if (p is not None and not pd.isna(p) and float(p) > 0) else np.nan
|
| 735 |
for n, p in zip(
|
| 736 |
pd.to_numeric(base["n_total"], errors="coerce").fillna(0).astype(float).tolist(),
|
| 737 |
-
|
| 738 |
)
|
| 739 |
]
|
| 740 |
|
|
@@ -745,6 +686,7 @@ def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_p
|
|
| 745 |
base["target_total_68"] = pd.to_numeric(base["target_total_68"], errors="coerce").fillna(0).round(0).astype(int)
|
| 746 |
base["pop_total"] = pd.to_numeric(base["pop_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 747 |
base["coverage_total_%"] = pd.to_numeric(base["coverage_total_%"], errors="coerce").fillna(0.0).round(2)
|
|
|
|
| 748 |
base["faktor_penyesuaian"] = pd.to_numeric(base["faktor_penyesuaian"], errors="coerce").fillna(1.0).round(3)
|
| 749 |
|
| 750 |
return base
|
|
@@ -1347,7 +1289,6 @@ def build_context(summary_jenis: pd.DataFrame, agg_total: pd.DataFrame, verif_to
|
|
| 1347 |
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.")
|
| 1348 |
lines.append("Rumus penyesuaian: faktor = min(total_terkumpul / target_total_68, 1.0). Faktor wilayah dipakai untuk semua jenis.")
|
| 1349 |
lines.append("Rumus keseluruhan wilayah (FIX): nilai keseluruhan wilayah diambil dari rata-rata 3 jenis (sekolah+umum+khusus) ÷ 3 (missing=0, tetap ÷3).")
|
| 1350 |
-
lines.append("Catatan update: pop_total & target_total_68 wilayah telah ditambah komponen KHUSUS dari Data_populasi_perp_khusus.xlsx (tanpa kolom tambahan).")
|
| 1351 |
|
| 1352 |
if summary_jenis is not None and not summary_jenis.empty:
|
| 1353 |
lines.append("\nRingkasan (jenis + keseluruhan):")
|
|
@@ -1426,7 +1367,6 @@ def generate_word_report(wilayah, summary_jenis, agg_total, agg_jenis, analysis_
|
|
| 1426 |
doc.add_paragraph(f"Indeks Dasar (Tanpa Penyesuaian): {k['dasar_all']:.2f} (rata-rata 3 jenis, tetap ÷3; missing=0)")
|
| 1427 |
doc.add_paragraph(f"Cakupan Sampel (berdasarkan target 68%): {k['cakupan_pct']:.0f}% (min(total_terkumpul/target_68, 1.0))")
|
| 1428 |
doc.add_paragraph(f"Penyesuaian Nilai (rata-rata): {k['dampak']:.2f} poin (faktor penyesuaian mean: {k['faktor_mean']:.3f})")
|
| 1429 |
-
doc.add_paragraph("Catatan: pop_total & target_total_68 wilayah telah ditambah komponen KHUSUS dari Data_populasi_perp_khusus.xlsx.")
|
| 1430 |
|
| 1431 |
doc.add_paragraph("Ringkasan (Jenis + Keseluruhan):")
|
| 1432 |
show = summary_jenis.copy()
|
|
@@ -1517,7 +1457,7 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1517 |
return _empty_outputs("Tidak ada data untuk filter ini.")
|
| 1518 |
|
| 1519 |
# ==== PIPELINE BARU (KUNCI KONSISTENSI) ====
|
| 1520 |
-
faktor_wilayah = build_faktor_wilayah(df, pop_kab, pop_prov,
|
| 1521 |
agg_jenis_full = build_agg_wilayah_jenis(df, faktor_wilayah, pop_khusus, kew_value or "(Semua)")
|
| 1522 |
agg_total = build_agg_wilayah_total_from_jenis(agg_jenis_full, faktor_wilayah, kew_value or "(Semua)")
|
| 1523 |
summary_jenis = build_summary_per_jenis(agg_jenis_full, agg_total)
|
|
@@ -1597,168 +1537,156 @@ def run_calc(prov_value, kab_value, kew_value, df_all, df_raw, pop_kab, pop_prov
|
|
| 1597 |
|
| 1598 |
msg = (
|
| 1599 |
f"✅ Selesai: raw={len(raw)} | entitas={len(detail_view)} | wilayah(keseluruhan)={len(agg_total)} | "
|
| 1600 |
-
f"jenis(agregat)={len(agg_jenis_full)} | FIX: Agregat Wilayah (Keseluruhan) = avg3 dari 3 jenis (÷3)
|
| 1601 |
-
f"UPDATE: pop_total & target_total_68 wilayah sudah ditambah KHUSUS (tanpa kolom baru) | "
|
| 1602 |
-
f"DISPLAY: faktor_wilayah target/pop integer, coverage 2 desimal | "
|
| 1603 |
-
f"UI: tabel Agregat Wilayah×Jenis hanya sampai Indeks_Dasar_Agregat_0_100"
|
| 1604 |
)
|
| 1605 |
|
| 1606 |
return (
|
| 1607 |
kpi_md,
|
| 1608 |
-
summary_jenis, agg_total, agg_jenis_view,
|
| 1609 |
-
p_summary, p_total,
|
| 1610 |
-
|
| 1611 |
-
msg, analysis_text
|
| 1612 |
)
|
|
|
|
| 1613 |
except Exception as e:
|
| 1614 |
-
return _empty_outputs(f"⚠️
|
| 1615 |
|
| 1616 |
|
| 1617 |
# ============================================================
|
| 1618 |
-
# 16)
|
| 1619 |
# ============================================================
|
| 1620 |
|
| 1621 |
-
def
|
| 1622 |
-
|
| 1623 |
-
|
| 1624 |
-
|
| 1625 |
-
|
| 1626 |
-
|
| 1627 |
-
|
|
|
|
|
|
|
| 1628 |
|
| 1629 |
-
|
| 1630 |
-
|
| 1631 |
-
|
| 1632 |
-
return provs, kabs, kews
|
| 1633 |
|
|
|
|
|
|
|
|
|
|
| 1634 |
|
| 1635 |
-
|
| 1636 |
-
|
| 1637 |
-
|
| 1638 |
-
|
| 1639 |
-
|
| 1640 |
-
return gr.update(choices=["(Semua)"] + kabs, value="(Semua)")
|
| 1641 |
-
sub = df_raw[df_raw["PROV_DISP"] == prov_value]
|
| 1642 |
-
kabs = sorted([k for k in sub["KAB_DISP"].dropna().unique().tolist() if str(k).strip() != ""])
|
| 1643 |
-
return gr.update(choices=["(Semua)"] + kabs, value="(Semua)")
|
| 1644 |
-
|
| 1645 |
-
|
| 1646 |
-
with gr.Blocks(title="IPLM 2025 — FINAL (NO UPLOAD)") as demo:
|
| 1647 |
-
|
| 1648 |
-
# Load default (cache) once at startup
|
| 1649 |
-
df_all, df_raw, pop_kab, pop_prov, pop_khusus, meta, info = load_default_files(force=False)
|
| 1650 |
-
prov_choices, kab_choices, kew_choices = init_choices(df_raw)
|
| 1651 |
-
|
| 1652 |
-
gr.Markdown(
|
| 1653 |
-
"""
|
| 1654 |
-
# IPLM 2025 — Dashboard FINAL (NO UPLOAD)
|
| 1655 |
-
- Agregat wilayah **keseluruhan** = **rata-rata 3 jenis (sekolah+umum+khusus) ÷ 3** (missing=0, tetap ÷3)
|
| 1656 |
-
- Penyesuaian 68% = **min(total_terkumpul / target_total_68, 1.0)** pada level wilayah (faktor sama untuk semua jenis)
|
| 1657 |
-
- **UPDATE**: `pop_total` & `target_total_68` wilayah sudah **ditambah komponen KHUSUS** dari `Data_populasi_perp_khusus.xlsx` (tanpa kolom baru).
|
| 1658 |
-
- **DISPLAY**: tabel faktor_wilayah target/pop integer, coverage 2 desimal.
|
| 1659 |
-
"""
|
| 1660 |
)
|
| 1661 |
|
| 1662 |
-
|
| 1663 |
-
|
| 1664 |
-
|
| 1665 |
-
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1671 |
|
| 1672 |
with gr.Row():
|
| 1673 |
-
|
| 1674 |
-
|
| 1675 |
-
|
| 1676 |
-
status = gr.Markdown(value="")
|
| 1677 |
|
| 1678 |
-
|
| 1679 |
-
with gr.Tab("Ringkasan (Jenis + Keseluruhan)"):
|
| 1680 |
-
tbl_summary = gr.Dataframe(label="Ringkasan (selalu 4 baris: sekolah, umum, khusus, keseluruhan)")
|
| 1681 |
|
| 1682 |
-
|
|
|
|
| 1683 |
|
| 1684 |
-
|
| 1685 |
-
tbl_total = gr.Dataframe(label="Agregat Wilayah (Keseluruhan) — FIX avg3 ÷3")
|
| 1686 |
-
dl_total = gr.File(label="Download Agregat Wilayah Keseluruhan (Excel)")
|
| 1687 |
|
| 1688 |
-
|
| 1689 |
-
|
| 1690 |
-
tbl_jenis = gr.Dataframe(label="Agregat Wilayah × Jenis (UI dipotong sampai Indeks_Dasar_Agregat_0_100)")
|
| 1691 |
|
| 1692 |
-
|
| 1693 |
-
|
| 1694 |
-
dl_verif = gr.File(label="Download Verifikasi 68% (Excel)")
|
| 1695 |
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
dl_detail = gr.File(label="Download Detail Entitas (Excel)")
|
| 1699 |
|
| 1700 |
-
|
| 1701 |
-
|
| 1702 |
-
fig_umum = gr.Plot(label="Umum")
|
| 1703 |
-
fig_khusus = gr.Plot(label="Khusus")
|
| 1704 |
|
| 1705 |
-
|
| 1706 |
-
|
| 1707 |
-
dl_raw = gr.File(label="Download RAW Data (Excel)")
|
| 1708 |
-
dl_word = gr.File(label="Download Word Report (.docx)")
|
| 1709 |
|
| 1710 |
-
#
|
| 1711 |
-
#
|
| 1712 |
-
|
| 1713 |
|
| 1714 |
-
|
| 1715 |
-
|
| 1716 |
-
prov, kab, kew,
|
| 1717 |
-
df_all, df_raw, pop_kab, pop_prov, pop_khusus, meta
|
| 1718 |
-
)
|
| 1719 |
-
|
| 1720 |
-
def _reload():
|
| 1721 |
-
global df_all, df_raw, pop_kab, pop_prov, pop_khusus, meta, info
|
| 1722 |
|
| 1723 |
-
|
| 1724 |
-
|
| 1725 |
|
| 1726 |
-
|
| 1727 |
-
|
| 1728 |
-
gr.update(choices=provs, value="(Semua)"),
|
| 1729 |
-
gr.update(choices=kabs, value="(Semua)"),
|
| 1730 |
-
gr.update(choices=kews, value="(Semua)"),
|
| 1731 |
-
"✅ Reload selesai."
|
| 1732 |
-
)
|
| 1733 |
|
| 1734 |
-
|
| 1735 |
-
|
| 1736 |
-
|
| 1737 |
-
|
| 1738 |
-
|
|
|
|
| 1739 |
|
| 1740 |
run_btn.click(
|
| 1741 |
-
fn=
|
| 1742 |
-
inputs=[
|
| 1743 |
outputs=[
|
| 1744 |
-
|
| 1745 |
-
|
| 1746 |
-
dl_summary, dl_total,
|
| 1747 |
-
|
| 1748 |
-
|
| 1749 |
]
|
| 1750 |
)
|
| 1751 |
|
| 1752 |
-
|
| 1753 |
-
fn=
|
| 1754 |
inputs=[],
|
| 1755 |
-
outputs=[
|
| 1756 |
)
|
| 1757 |
|
| 1758 |
-
|
| 1759 |
-
# ============================================================
|
| 1760 |
-
# 17) LAUNCH
|
| 1761 |
-
# ============================================================
|
| 1762 |
-
|
| 1763 |
-
if __name__ == "__main__":
|
| 1764 |
-
demo.queue().launch(share=True)
|
|
|
|
| 36 |
- coverage_total_% -> decimal 2 digit
|
| 37 |
2) TABEL "Agregat Wilayah × Jenis" (UI) hanya sampai kolom Indeks_Dasar_Agregat_0_100
|
| 38 |
(kolom setelah itu tidak ditampilkan)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
"""
|
| 40 |
|
| 41 |
import os
|
|
|
|
| 605 |
|
| 606 |
# ============================================================
|
| 607 |
# 6) FAKTOR WILAYAH (TOTAL) — hanya untuk faktor/target/pop/coverage
|
|
|
|
| 608 |
# ============================================================
|
| 609 |
|
| 610 |
+
def build_faktor_wilayah(df_filtered: pd.DataFrame, pop_kab: pd.DataFrame, pop_prov: pd.DataFrame, kew_value: str):
|
| 611 |
if df_filtered is None or df_filtered.empty:
|
| 612 |
return pd.DataFrame()
|
| 613 |
|
|
|
|
| 622 |
target_field = "Target68_Total"
|
| 623 |
pop_field = "Pop_Total"
|
| 624 |
name_field = "Kab_Kota_Label"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
elif "PROV" in kew_norm:
|
| 626 |
key_col = "prov_key"
|
| 627 |
label_col = "PROV_DISP"
|
|
|
|
| 630 |
target_field = "Target68_Total_Prov"
|
| 631 |
pop_field = "Pop_Total_Prov"
|
| 632 |
name_field = "Provinsi_Label"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
else:
|
| 634 |
key_col = "kab_key"
|
| 635 |
label_col = "KAB_DISP"
|
|
|
|
| 638 |
target_field = "Target68_Total"
|
| 639 |
pop_field = "Pop_Total"
|
| 640 |
name_field = "Kab_Kota_Label"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
|
| 642 |
base = df.groupby([key_col, label_col], dropna=False).agg(
|
| 643 |
n_total=("Indeks_Dasar_0_100", "size"),
|
|
|
|
| 660 |
base["target_total_68"] = pd.to_numeric(pd.Series(target_vals), errors="coerce")
|
| 661 |
base["pop_total"] = pd.to_numeric(pd.Series(pop_vals), errors="coerce")
|
| 662 |
|
|
|
|
| 663 |
m = base["pop_total"].isna() & base["target_total_68"].notna() & (base["target_total_68"] > 0)
|
| 664 |
base.loc[m, "pop_total"] = base.loc[m, "target_total_68"] / float(FALLBACK_TARGET_RATIO)
|
| 665 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
base["faktor_penyesuaian"] = [
|
| 667 |
faktor_penyesuaian_total(n, t)
|
| 668 |
for n, t in zip(
|
|
|
|
| 675 |
(safe_div(n, p) * 100) if (p is not None and not pd.isna(p) and float(p) > 0) else np.nan
|
| 676 |
for n, p in zip(
|
| 677 |
pd.to_numeric(base["n_total"], errors="coerce").fillna(0).astype(float).tolist(),
|
| 678 |
+
base["pop_total"].tolist()
|
| 679 |
)
|
| 680 |
]
|
| 681 |
|
|
|
|
| 686 |
base["target_total_68"] = pd.to_numeric(base["target_total_68"], errors="coerce").fillna(0).round(0).astype(int)
|
| 687 |
base["pop_total"] = pd.to_numeric(base["pop_total"], errors="coerce").fillna(0).round(0).astype(int)
|
| 688 |
base["coverage_total_%"] = pd.to_numeric(base["coverage_total_%"], errors="coerce").fillna(0.0).round(2)
|
| 689 |
+
# tetap seperti sebelumnya:
|
| 690 |
base["faktor_penyesuaian"] = pd.to_numeric(base["faktor_penyesuaian"], errors="coerce").fillna(1.0).round(3)
|
| 691 |
|
| 692 |
return base
|
|
|
|
| 1289 |
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.")
|
| 1290 |
lines.append("Rumus penyesuaian: faktor = min(total_terkumpul / target_total_68, 1.0). Faktor wilayah dipakai untuk semua jenis.")
|
| 1291 |
lines.append("Rumus keseluruhan wilayah (FIX): nilai keseluruhan wilayah diambil dari rata-rata 3 jenis (sekolah+umum+khusus) ÷ 3 (missing=0, tetap ÷3).")
|
|
|
|
| 1292 |
|
| 1293 |
if summary_jenis is not None and not summary_jenis.empty:
|
| 1294 |
lines.append("\nRingkasan (jenis + keseluruhan):")
|
|
|
|
| 1367 |
doc.add_paragraph(f"Indeks Dasar (Tanpa Penyesuaian): {k['dasar_all']:.2f} (rata-rata 3 jenis, tetap ÷3; missing=0)")
|
| 1368 |
doc.add_paragraph(f"Cakupan Sampel (berdasarkan target 68%): {k['cakupan_pct']:.0f}% (min(total_terkumpul/target_68, 1.0))")
|
| 1369 |
doc.add_paragraph(f"Penyesuaian Nilai (rata-rata): {k['dampak']:.2f} poin (faktor penyesuaian mean: {k['faktor_mean']:.3f})")
|
|
|
|
| 1370 |
|
| 1371 |
doc.add_paragraph("Ringkasan (Jenis + Keseluruhan):")
|
| 1372 |
show = summary_jenis.copy()
|
|
|
|
| 1457 |
return _empty_outputs("Tidak ada data untuk filter ini.")
|
| 1458 |
|
| 1459 |
# ==== PIPELINE BARU (KUNCI KONSISTENSI) ====
|
| 1460 |
+
faktor_wilayah = build_faktor_wilayah(df, pop_kab, pop_prov, kew_value or "(Semua)")
|
| 1461 |
agg_jenis_full = build_agg_wilayah_jenis(df, faktor_wilayah, pop_khusus, kew_value or "(Semua)")
|
| 1462 |
agg_total = build_agg_wilayah_total_from_jenis(agg_jenis_full, faktor_wilayah, kew_value or "(Semua)")
|
| 1463 |
summary_jenis = build_summary_per_jenis(agg_jenis_full, agg_total)
|
|
|
|
| 1537 |
|
| 1538 |
msg = (
|
| 1539 |
f"✅ Selesai: raw={len(raw)} | entitas={len(detail_view)} | wilayah(keseluruhan)={len(agg_total)} | "
|
| 1540 |
+
f"jenis(agregat)={len(agg_jenis_full)} | FIX: Agregat Wilayah (Keseluruhan) = avg3 dari 3 jenis (÷3)"
|
|
|
|
|
|
|
|
|
|
| 1541 |
)
|
| 1542 |
|
| 1543 |
return (
|
| 1544 |
kpi_md,
|
| 1545 |
+
summary_jenis, agg_total, agg_jenis_view, detail_view, verif_total,
|
| 1546 |
+
p_summary, p_total, p_jenis, p_detail, word_path,
|
| 1547 |
+
fig_umum, fig_sekolah, fig_khusus,
|
| 1548 |
+
msg, analysis_text
|
| 1549 |
)
|
| 1550 |
+
|
| 1551 |
except Exception as e:
|
| 1552 |
+
return _empty_outputs(f"⚠️ Runtime error: {repr(e)}")
|
| 1553 |
|
| 1554 |
|
| 1555 |
# ============================================================
|
| 1556 |
+
# 16) UI (NO UPLOAD)
|
| 1557 |
# ============================================================
|
| 1558 |
|
| 1559 |
+
def ui_load(force=False):
|
| 1560 |
+
df_all, df_raw, pop_kab, pop_prov, pop_khusus, meta, info = load_default_files(force=force)
|
| 1561 |
+
if df_all is None or (isinstance(df_all, pd.DataFrame) and df_all.empty):
|
| 1562 |
+
return (
|
| 1563 |
+
None, None, None, None, None, {}, info,
|
| 1564 |
+
gr.update(choices=["(Semua)"], value="(Semua)"),
|
| 1565 |
+
gr.update(choices=["(Semua)"], value="(Semua)"),
|
| 1566 |
+
gr.update(choices=["(Semua)"], value="(Semua)"),
|
| 1567 |
+
)
|
| 1568 |
|
| 1569 |
+
prov_vals = df_all["PROV_DISP"].dropna().astype(str).tolist()
|
| 1570 |
+
prov_vals = [v for v in prov_vals if v and v.strip()]
|
| 1571 |
+
prov_choices = ["(Semua)"] + sorted(set(prov_vals))
|
|
|
|
| 1572 |
|
| 1573 |
+
kab_choices = ["(Semua)"] + sorted([x for x in df_all["KAB_DISP"].dropna().unique().tolist() if x])
|
| 1574 |
+
kew_choices = ["(Semua)"] + sorted([x for x in df_all["KEW_NORM"].dropna().unique().tolist() if x])
|
| 1575 |
+
default_kew = "PROVINSI" if "PROVINSI" in kew_choices else ("KAB/KOTA" if "KAB/KOTA" in kew_choices else "(Semua)")
|
| 1576 |
|
| 1577 |
+
return (
|
| 1578 |
+
df_all, df_raw, pop_kab, pop_prov, pop_khusus, meta, info,
|
| 1579 |
+
gr.update(choices=prov_choices, value="(Semua)"),
|
| 1580 |
+
gr.update(choices=kab_choices, value="(Semua)"),
|
| 1581 |
+
gr.update(choices=kew_choices, value=default_kew),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1582 |
)
|
| 1583 |
|
| 1584 |
+
def on_prov_change(prov_value):
|
| 1585 |
+
df_all, _, _, _, _, _, _ = load_default_files(force=False)
|
| 1586 |
+
if df_all is None or df_all.empty:
|
| 1587 |
+
return gr.update(choices=["(Semua)"], value="(Semua)")
|
| 1588 |
+
if prov_value is None or prov_value == "(Semua)":
|
| 1589 |
+
vals = df_all["KAB_DISP"].dropna().unique().tolist()
|
| 1590 |
+
else:
|
| 1591 |
+
vals = df_all.loc[df_all["PROV_DISP"] == prov_value, "KAB_DISP"].dropna().unique().tolist()
|
| 1592 |
+
vals = sorted([v for v in vals if v])
|
| 1593 |
+
return gr.update(choices=["(Semua)"] + vals, value="(Semua)")
|
| 1594 |
+
|
| 1595 |
+
|
| 1596 |
+
with gr.Blocks() as demo:
|
| 1597 |
+
gr.Markdown(f"""
|
| 1598 |
+
# IPLM 2025 — Final (Penyesuaian Berbasis Kecukupan Sampel 68%)
|
| 1599 |
+
**Mode NO UPLOAD (cache aktif).** File dibaca dari repo/server:
|
| 1600 |
+
- `DATA_FILE` = **{DATA_FILE}**
|
| 1601 |
+
- `POP_KAB` = **{POP_KAB}**
|
| 1602 |
+
- `POP_PROV` = **{POP_PROV}**
|
| 1603 |
+
- `POP_KHUSUS` = **{POP_KHUSUS}**
|
| 1604 |
+
|
| 1605 |
+
**FIX UTAMA (konsistensi nilai):**
|
| 1606 |
+
- **Agregat Wilayah (Keseluruhan) = rata-rata 3 jenis (sekolah+umum+khusus) ÷ 3 (missing=0, tetap ÷3)**
|
| 1607 |
+
- Ringkasan selalu tampil **sekolah, umum, khusus, keseluruhan** (walau 0)
|
| 1608 |
+
- KPI FINAL dashboard sumber dari Ringkasan
|
| 1609 |
+
- Download Data Mentah = RAW hasil filter
|
| 1610 |
+
- Kecukupan Sampel 68%: tanpa angka koma
|
| 1611 |
+
|
| 1612 |
+
**UPDATE (tampilan):**
|
| 1613 |
+
- target_total_68 & pop_total di faktor_wilayah = bilangan bulat
|
| 1614 |
+
- coverage_total_% = 2 desimal
|
| 1615 |
+
- Tabel "Agregat Wilayah × Jenis" ditampilkan hanya sampai Indeks_Dasar_Agregat_0_100
|
| 1616 |
+
""")
|
| 1617 |
+
|
| 1618 |
+
state_df = gr.State(None)
|
| 1619 |
+
state_raw = gr.State(None)
|
| 1620 |
+
state_pop_kab = gr.State(None)
|
| 1621 |
+
state_pop_prov = gr.State(None)
|
| 1622 |
+
state_pop_khusus = gr.State(None)
|
| 1623 |
+
state_meta = gr.State({})
|
| 1624 |
+
|
| 1625 |
+
info_box = gr.Markdown()
|
| 1626 |
|
| 1627 |
with gr.Row():
|
| 1628 |
+
dd_prov = gr.Dropdown(label="Provinsi", choices=["(Semua)"], value="(Semua)")
|
| 1629 |
+
dd_kab = gr.Dropdown(label="Kab/Kota", choices=["(Semua)"], value="(Semua)")
|
| 1630 |
+
dd_kew = gr.Dropdown(label="Kewenangan", choices=["(Semua)"], value="(Semua)")
|
|
|
|
| 1631 |
|
| 1632 |
+
dd_prov.change(fn=on_prov_change, inputs=[dd_prov], outputs=dd_kab)
|
|
|
|
|
|
|
| 1633 |
|
| 1634 |
+
run_btn = gr.Button("Jalankan Perhitungan")
|
| 1635 |
+
msg_out = gr.Markdown()
|
| 1636 |
|
| 1637 |
+
kpi_out = gr.Markdown()
|
|
|
|
|
|
|
| 1638 |
|
| 1639 |
+
gr.Markdown("## Ringkasan (Jenis + Keseluruhan)")
|
| 1640 |
+
out_summary = gr.DataFrame(interactive=False)
|
|
|
|
| 1641 |
|
| 1642 |
+
gr.Markdown("## Agregat Wilayah (Keseluruhan) — FIX: avg3 dari 3 jenis")
|
| 1643 |
+
out_agg_total = gr.DataFrame(interactive=False)
|
|
|
|
| 1644 |
|
| 1645 |
+
gr.Markdown("## Agregat Wilayah × Jenis (Sekolah, Umum, Khusus) — (ditampilkan sampai Indeks_Dasar_Agregat_0_100)")
|
| 1646 |
+
out_agg_jenis = gr.DataFrame(interactive=False)
|
|
|
|
| 1647 |
|
| 1648 |
+
gr.Markdown("## Detail Entitas (Final menempel dari wilayah)")
|
| 1649 |
+
out_detail = gr.DataFrame(interactive=False)
|
|
|
|
|
|
|
| 1650 |
|
| 1651 |
+
gr.Markdown("## Kecukupan Sampel 68% (tanpa angka koma)")
|
| 1652 |
+
out_verif = gr.DataFrame(interactive=False)
|
|
|
|
|
|
|
| 1653 |
|
| 1654 |
+
gr.Markdown("## Bell Curve — per Jenis Perpustakaan (Indeks per Entitas)")
|
| 1655 |
+
gr.Markdown("### Perpustakaan Umum")
|
| 1656 |
+
bell_umum = gr.Plot(scale=1)
|
| 1657 |
|
| 1658 |
+
gr.Markdown("### Perpustakaan Sekolah")
|
| 1659 |
+
bell_sekolah = gr.Plot(scale=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1660 |
|
| 1661 |
+
gr.Markdown("### Perpustakaan Khusus")
|
| 1662 |
+
bell_khusus = gr.Plot(scale=1)
|
| 1663 |
|
| 1664 |
+
gr.Markdown("## Analisis Otomatis (LLM)")
|
| 1665 |
+
analysis_out = gr.Markdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1666 |
|
| 1667 |
+
with gr.Row():
|
| 1668 |
+
dl_summary = gr.DownloadButton(label="Download Ringkasan (.xlsx)")
|
| 1669 |
+
dl_total = gr.DownloadButton(label="Download Agregat Wilayah (.xlsx)")
|
| 1670 |
+
dl_jenis = gr.DownloadButton(label="Download Data Mentah (.xlsx)")
|
| 1671 |
+
dl_detail = gr.DownloadButton(label="Download Detail Entitas (.xlsx)")
|
| 1672 |
+
dl_word = gr.DownloadButton(label="Download Laporan Word (.docx)")
|
| 1673 |
|
| 1674 |
run_btn.click(
|
| 1675 |
+
fn=run_calc,
|
| 1676 |
+
inputs=[dd_prov, dd_kab, dd_kew, state_df, state_raw, state_pop_kab, state_pop_prov, state_pop_khusus, state_meta],
|
| 1677 |
outputs=[
|
| 1678 |
+
kpi_out,
|
| 1679 |
+
out_summary, out_agg_total, out_agg_jenis, out_detail, out_verif,
|
| 1680 |
+
dl_summary, dl_total, dl_jenis, dl_detail, dl_word,
|
| 1681 |
+
bell_umum, bell_sekolah, bell_khusus,
|
| 1682 |
+
msg_out, analysis_out
|
| 1683 |
]
|
| 1684 |
)
|
| 1685 |
|
| 1686 |
+
demo.load(
|
| 1687 |
+
fn=lambda: ui_load(force=False),
|
| 1688 |
inputs=[],
|
| 1689 |
+
outputs=[state_df, state_raw, state_pop_kab, state_pop_prov, state_pop_khusus, state_meta, info_box, dd_prov, dd_kab, dd_kew]
|
| 1690 |
)
|
| 1691 |
|
| 1692 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|