05 — From Analytics to Decision-Support¶
Development dimensions, hazard context, policy signals, dan batas klaim
Notebook ini adalah POC portfolio yang dapat dijalankan ulang dari artefak lokal Pasuruan Lens. Kode produksi tetap berada di python/scripts/ dan python/src/pasuruan365/; notebook berfungsi sebagai narasi analitik yang ringkas, transparan, dan mudah dipresentasikan.
from pathlib import Path
import json
import sys
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
def find_project_root(start: Path | None = None) -> Path:
start = (start or Path.cwd()).resolve()
for candidate in (start, *start.parents):
if (candidate / "package.json").exists() and (candidate / "data").exists():
return candidate
raise RuntimeError("Root Pasuruan Lens tidak ditemukan. Jalankan notebook dari repository ini.")
ROOT = find_project_root()
sys.path.insert(0, str(ROOT / "python" / "src"))
pd.set_option("display.max_columns", 100)
pd.set_option("display.max_colwidth", 100)
sns.set_theme(style="whitegrid", context="notebook")
COLORS = {"A": "#2563eb", "B": "#f59e0b", "C": "#10b981"}
print(f"Project root: {ROOT}")
print(f"Python: {sys.version.split()[0]}")
Project root: PASURUAN365 Python: 3.12.6
Prinsip produk¶
Tidak semua indikator layak digabung menjadi satu angka. Pasuruan Lens memisahkan tiga pertanyaan: variasi dimensi pembangunan, kejadian bahaya yang terdokumentasi, dan policy opportunity yang dapat ditelaah. Pemisahan ini mencegah cluster, hazard, dan service availability berubah menjadi ranking semu.
dimensions = pd.read_csv(ROOT / "data/analytics/development_dimension_profiles.csv", dtype={"village_id": str})
hazards = pd.read_csv(ROOT / "data/analytics/hazard_exposure_profiles.csv", dtype={"village_id": str})
policy = pd.read_csv(ROOT / "data/analytics/policy/village_policy_profiles.csv", dtype={"village_id": str})
components = pd.read_csv(ROOT / "data/analytics/policy/policy_spatial_components.csv")
pd.DataFrame({
"rows": [len(dimensions), len(hazards), len(policy), len(components)],
"villages": [dimensions.village_id.nunique(), hazards.village_id.nunique(), policy.village_id.nunique(), np.nan],
"categories": [dimensions.dimension_id.nunique(), hazards.hazard_id.nunique(), policy.policy_lens.nunique(), components.policyLens.nunique()],
}, index=["development", "hazard", "policy", "spatial components"])
| rows | villages | categories | |
|---|---|---|---|
| development | 2555 | 365.0 | 7 |
| hazard | 1460 | 365.0 | 4 |
| policy | 1825 | 365.0 | 5 |
| spatial components | 33 | NaN | 5 |
POC 1 — Dimension readiness, bukan composite score¶
dimension_status = (
dimensions.groupby(["dimension_id", "dimension_status"])
.agg(villages=("village_id", "nunique"), numeric_values=("dimension_value_if_applicable", "count"))
.reset_index()
)
dimension_status
| dimension_id | dimension_status | villages | numeric_values | |
|---|---|---|---|---|
| 0 | BASIC_SERVICE_AVAILABILITY | INDICATOR_PROFILE | 365 | 0 |
| 1 | BASIC_UTILITIES | INDICATOR_PROFILE | 365 | 0 |
| 2 | DIGITAL_COMMUNICATION_CONNECTIVITY | INSUFFICIENT_INDICATOR_COVERAGE | 15 | 0 |
| 3 | DIGITAL_COMMUNICATION_CONNECTIVITY | PUBLIC_RELATIVE_POSITION | 350 | 350 |
| 4 | ECONOMIC_SERVICE_INFRASTRUCTURE | INDICATOR_PROFILE | 365 | 0 |
| 5 | EDUCATION_SERVICE_AVAILABILITY | NOT_AVAILABLE | 365 | 0 |
| 6 | INSTITUTIONAL_PUBLIC_SERVICE_CAPACITY | DESCRIPTIVE_ONLY | 365 | 0 |
| 7 | TRANSPORT_SERVICE_AVAILABILITY | INDICATOR_PROFILE | 365 | 0 |
assert not any("overall" in c.lower() or "rank" in c.lower() for c in dimensions.columns)
print("✓ Dataset memublikasikan profil per dimensi tanpa overall development score atau ranking.")
✓ Dataset memublikasikan profil per dimensi tanpa overall development score atau ranking.
POC 2 — Hazard tetap berupa kejadian biner terpisah¶
hazard_counts = (
hazards.groupby(["hazard_id", "hazard_name"], as_index=False)
.agg(documented_occurrence=("documented_occurrence", "sum"), coverage=("village_id", "nunique"))
)
hazard_counts
| hazard_id | hazard_name | documented_occurrence | coverage | |
|---|---|---|---|---|
| 0 | EARTHQUAKE | Gempa Bumi | 30 | 365 |
| 1 | FLOOD | Banjir | 64 | 365 |
| 2 | LANDSLIDE | Tanah Longsor | 24 | 365 |
| 3 | TORNADO | Angin Puyuh | 48 | 365 |
ax = hazard_counts.sort_values("documented_occurrence").plot.barh(
x="hazard_name", y="documented_occurrence", legend=False, figsize=(8, 4), color="#ef4444"
)
ax.set(title="Unit dengan kejadian terdokumentasi (referensi 2024)", xlabel="Jumlah desa/kelurahan", ylabel="")
plt.tight_layout()
POC 3 — Policy signal dan konsentrasi spasial¶
policy_summary = (
policy.groupby("policy_lens", as_index=False)
.agg(
signals=("is_opportunity_signal", "sum"),
spatially_concentrated=("spatial_gap_status", lambda s: s.eq("SPATIALLY_CONCENTRATED_GAP").sum()),
data_limited=("evidence_strength", lambda s: s.eq("LIMITED_EVIDENCE").sum()),
)
)
policy_summary
| policy_lens | signals | spatially_concentrated | data_limited | |
|---|---|---|---|---|
| 0 | BASIC_SERVICE_AVAILABILITY | 291 | 278 | 0 |
| 1 | BASIC_UTILITIES | 289 | 277 | 0 |
| 2 | DIGITAL_COMMUNICATION_CONNECTIVITY | 87 | 56 | 15 |
| 3 | ECONOMIC_SERVICE_INFRASTRUCTURE | 310 | 305 | 7 |
| 4 | TRANSPORT_SERVICE_AVAILABILITY | 91 | 58 | 3 |
component_summary = (
components.groupby("policyLens", as_index=False)
.agg(
components=("componentId", "count"),
largest_component=("componentSize", "max"),
cross_district_components=("crossDistrict", "sum"),
)
.sort_values("largest_component", ascending=False)
)
component_summary
| policyLens | components | largest_component | cross_district_components | |
|---|---|---|---|---|
| 3 | ECONOMIC_SERVICE_INFRASTRUCTURE | 2 | 308 | 1 |
| 0 | BASIC_SERVICE_AVAILABILITY | 3 | 288 | 1 |
| 1 | BASIC_UTILITIES | 3 | 286 | 1 |
| 2 | DIGITAL_COMMUNICATION_CONNECTIVITY | 9 | 62 | 3 |
| 4 | TRANSPORT_SERVICE_AVAILABILITY | 16 | 47 | 4 |
POC 4 — Satu desa, evidence yang tetap terpisah¶
sample = policy.loc[policy.is_opportunity_signal].iloc[0]
sample_profile = policy.loc[
policy.village_id.eq(sample.village_id),
["village_name", "district_name", "policy_lens", "signal_basis", "policy_signal", "evidence_strength", "spatial_gap_status", "typology", "hazard_context", "reference_period"],
]
sample_profile
| village_name | district_name | policy_lens | signal_basis | policy_signal | evidence_strength | spatial_gap_status | typology | hazard_context | reference_period | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Gerbo | Purwodadi | DIGITAL_COMMUNICATION_CONNECTIVITY | RELATIVE_PERCENTILE | NO_RELATIVE_GAP_DETECTED | STRONG_EVIDENCE | NOT_APPLICABLE | C | [] | primarily 2024 |
| 1 | Gerbo | Purwodadi | TRANSPORT_SERVICE_AVAILABILITY | VALIDATED_ABSOLUTE_CONDITION | NO_ABSOLUTE_GAP_DETECTED | MODERATE_EVIDENCE | NOT_APPLICABLE | C | [] | primarily 2024 |
| 2 | Gerbo | Purwodadi | BASIC_SERVICE_AVAILABILITY | VALIDATED_ABSOLUTE_CONDITION | NO_ABSOLUTE_GAP_DETECTED | MODERATE_EVIDENCE | NOT_APPLICABLE | C | [] | primarily 2024 |
| 3 | Gerbo | Purwodadi | ECONOMIC_SERVICE_INFRASTRUCTURE | VALIDATED_ABSOLUTE_CONDITION | ABSOLUTE_GAP | MODERATE_EVIDENCE | SPATIALLY_CONCENTRATED_GAP | C | [] | primarily 2024 |
| 4 | Gerbo | Purwodadi | BASIC_UTILITIES | VALIDATED_ABSOLUTE_CONDITION | ABSOLUTE_GAP | MODERATE_EVIDENCE | SPATIALLY_CONCENTRATED_GAP | C | [] | primarily 2024 |
Kesimpulan¶
Actionable di sini berarti evidence dapat ditelusuri dan layak ditelaah, bukan instruksi alokasi anggaran. Lima policy lens menggunakan rule yang sesuai dengan semantik indikator; hazard dan tipologi tetap menjadi konteks terpisah. Overall priority memerlukan objective, bobot normatif, data outcome/equity/accessibility, serta proses governance yang belum tersedia.