irhamni commited on
Commit
50b2d23
Β·
verified Β·
1 Parent(s): d708652

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +16 -42
app.py CHANGED
@@ -465,7 +465,7 @@ def load_default_files(force=False):
465
  after = len(df_raw)
466
 
467
  # =========================
468
- # POP KAB (untuk TOTAL base: sekolah+umum & target_total_68)
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 (untuk TOTAL base 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 (faktor wilayah total, sama untuk semua 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 + tempel faktor_wilayah_total
 
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","n_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
- # format display
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 (wilayah)
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 (tanpa koma untuk integer, coverage 2 desimal)
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 (FINAL: hanya Final & Dasar)
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
- **Mode NO UPLOAD (cache aktif).** File dibaca dari repo/server:
 
1547
  - `DATA_FILE` = **{DATA_FILE}**
1548
  - `POP_KAB` = **{POP_KAB}**
1549
  - `POP_PROV` = **{POP_PROV}**
1550
  - `POP_KHUSUS` = **{POP_KHUSUS}**
1551
 
1552
- **UPDATE UTAMA:**
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