Files
D1041231027-WebGIS/03/api/kemandirian.php
T
2026-06-10 18:54:25 +07:00

445 lines
18 KiB
PHP

<?php
// api/kemandirian.php
// Fitur 3: Potensi Kemandirian Warga
// Hitung skor kemandirian berdasarkan: penghasilan, status kerja anggota, jumlah tanggungan
// Method: GET (list/detail/statistik), POST (catat perkembangan manual), PUT (set exit plan)
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
require_once '../config/db.php';
require_once 'middleware.php';
/* ─────────────────────────────────────────────
HELPER
───────────────────────────────────────────── */
function getKonfig(PDO $pdo, string $kunci, $default = null) {
$stmt = $pdo->prepare("SELECT nilai, tipe FROM konfigurasi_sistem WHERE kunci = ?");
$stmt->execute([$kunci]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) return $default;
return match($row['tipe']) {
'integer' => (int)$row['nilai'],
'decimal' => (float)$row['nilai'],
'boolean' => filter_var($row['nilai'], FILTER_VALIDATE_BOOLEAN),
default => $row['nilai'],
};
}
function hitungUsia(?string $tanggalLahir): ?int {
if (!$tanggalLahir) return null;
$lahir = new DateTime($tanggalLahir);
$today = new DateTime();
return (int)$today->diff($lahir)->y;
}
/* ─────────────────────────────────────────────
CORE: Hitung Skor Kemandirian
Komponen:
A) Skor Penghasilan (bobot configurable, default 40%)
- 0% : penghasilan = 0
- 50% : penghasilan = 50% threshold
- 100%: penghasilan >= threshold UMR
B) Skor Status Kerja (default 35%)
- Dihitung dari anggota usia produktif (18-55)
- Poin per anggota: bekerja_tetap=100, tidak_tetap=60, tidak_bekerja=0, pelajar=30
- Bonus: sudah bekerja tetap setelah ikut pelatihan
C) Skor Tanggungan (default 25%)
- Tanggungan sedikit (1-2) = skor tinggi
- Tanggungan banyak (> 6) = skor rendah
Status kemandirian:
- 0-20 : sangat_bergantung
- 21-40 : bergantung
- 41-60 : berkembang
- 61-79 : berpotensi_mandiri
- 80+ : mandiri (siap exit)
───────────────────────────────────────────── */
function hitungSkorKemandirian(
PDO $pdo,
array $warga,
array $anggotaList,
float $thresholdPenghasilan,
int $bobotPenghasilan,
int $bobotStatusKerja,
int $bobotTanggungan
): array {
$penghasilan = (float)$warga['penghasilan_bulanan'];
$tanggungan = (int)$warga['jumlah_tanggungan'];
$statusKerja = $warga['status_pekerjaan'];
/* ── A: Skor Penghasilan ── */
$skorPenghasilan = 0;
if ($thresholdPenghasilan > 0) {
$skorPenghasilan = (int)min(100, round($penghasilan / $thresholdPenghasilan * 100));
}
/* ── B: Skor Status Kerja ── */
$bobotStatusKerjaMap = [
'tetap' => 100,
'tidak_tetap' => 60,
'pensiun' => 50,
'tidak_bekerja'=> 0,
];
// Skor kepala keluarga
$poinKK = $bobotStatusKerjaMap[$statusKerja] ?? 0;
$anggotaProd = array_filter($anggotaList, function($a) {
$usia = hitungUsia($a['tanggal_lahir']);
return $usia !== null && $usia >= 18 && $usia <= 55;
});
if (count($anggotaProd) > 0) {
$totalPoin = $poinKK;
foreach ($anggotaProd as $a) {
$stsBekMap = [
'bekerja' => 100,
'pensiunan' => 50,
'pelajar' => 30,
'tidak_bekerja' => 0,
];
$poin = $stsBekMap[$a['status_bekerja']] ?? 0;
// Diskon: tidak bisa bekerja / disabilitas
if (!$a['dapat_bekerja'] || in_array($a['kondisi_kesehatan'], ['sakit_berat','disabilitas'])) {
$poin = (int)($poin * 0.3);
}
$totalPoin += $poin;
}
$jumlahDitimbang = count($anggotaProd) + 1; // +1 untuk KK
$skorStatusKerja = (int)round($totalPoin / ($jumlahDitimbang * 100) * 100);
} else {
// Tidak ada anggota produktif, skor bergantung murni dari KK
$skorStatusKerja = $poinKK;
}
/* ── C: Skor Tanggungan ── */
// Makin sedikit tanggungan relatif terhadap yang bekerja = makin mandiri
$anggotaBekerja = count(array_filter($anggotaList, fn($a) => $a['status_bekerja'] === 'bekerja'));
$pencariNafkah = $anggotaBekerja + ($statusKerja !== 'tidak_bekerja' ? 1 : 0);
if ($tanggungan <= 0) {
$skorTanggungan = 100;
} elseif ($pencariNafkah >= $tanggungan) {
$skorTanggungan = 100; // semua tanggungan punya pemasukan
} else {
$rasio = $pencariNafkah / $tanggungan;
$skorTanggungan = (int)min(100, round($rasio * 100));
}
/* ── Skor Total (weighted) ── */
$totalBobot = $bobotPenghasilan + $bobotStatusKerja + $bobotTanggungan;
if ($totalBobot <= 0) $totalBobot = 100;
$skorTotal = (int)round(
($skorPenghasilan * $bobotPenghasilan
+ $skorStatusKerja * $bobotStatusKerja
+ $skorTanggungan * $bobotTanggungan) / $totalBobot
);
/* ── Status ── */
$status = match(true) {
$skorTotal >= 80 => 'mandiri',
$skorTotal >= 61 => 'berpotensi_mandiri',
$skorTotal >= 41 => 'berkembang',
$skorTotal >= 21 => 'bergantung',
default => 'sangat_bergantung',
};
/* ── Estimasi waktu menuju mandiri ── */
$estimasiKenaikan = 5; // asumsi naik 5 poin/bulan dengan pelatihan
$bulanMenujuMandiri = $skorTotal >= 80 ? 0 : (int)ceil((80 - $skorTotal) / $estimasiKenaikan);
/* ── Rekomendasi aksi ── */
$rekomendasi = [];
if ($skorPenghasilan < 50) $rekomendasi[] = 'Tingkatkan penghasilan melalui pelatihan vokasional';
if ($skorStatusKerja < 50) $rekomendasi[] = 'Dukung anggota produktif untuk mendapat pekerjaan tetap';
if ($skorTanggungan < 50) $rekomendasi[] = 'Pertimbangkan program KB/pemberdayaan tanggungan';
if (count($rekomendasi) === 0) $rekomendasi[] = 'Pertahankan perkembangan yang ada, pantau secara berkala';
return [
'skor_total' => $skorTotal,
'skor_penghasilan' => $skorPenghasilan,
'skor_status_kerja' => $skorStatusKerja,
'skor_tanggungan' => $skorTanggungan,
'status' => $status,
'estimasi_bulan_mandiri' => $bulanMenujuMandiri,
'rekomendasi' => $rekomendasi,
'detail' => [
'penghasilan' => $penghasilan,
'threshold' => $thresholdPenghasilan,
'jumlah_tanggungan' => $tanggungan,
'pencari_nafkah' => $pencariNafkah,
'anggota_produktif' => count($anggotaProd),
],
];
}
/* ─────────────────────────────────────────────
UPDATE skor kemandirian batch + simpan riwayat
───────────────────────────────────────────── */
function updateKemandirianBatch(PDO $pdo, bool $simpanRiwayat = false): int {
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
$wargaList = $pdo->query("
SELECT id, penghasilan_bulanan, jumlah_tanggungan, status_pekerjaan,
skor_kemandirian as skor_lama
FROM rumah_warga
")->fetchAll(PDO::FETCH_ASSOC);
$stmtAnggota = $pdo->prepare("
SELECT nama, tanggal_lahir, status_bekerja, kondisi_kesehatan, dapat_bekerja
FROM anggota_keluarga WHERE rumah_warga_id = ?
");
$stmtUpdate = $pdo->prepare("
UPDATE rumah_warga SET skor_kemandirian = ?, status_kemandirian = ? WHERE id = ?
");
$stmtRiwayat = $pdo->prepare("
INSERT INTO riwayat_kemandirian
(rumah_warga_id, tanggal, skor, status, penghasilan_saat_ini)
VALUES (?, CURDATE(), ?, ?, ?)
");
$updated = 0;
foreach ($wargaList as $w) {
$stmtAnggota->execute([(int)$w['id']]);
$anggota = $stmtAnggota->fetchAll(PDO::FETCH_ASSOC);
$calc = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg);
$stmtUpdate->execute([$calc['skor_total'], $calc['status'], (int)$w['id']]);
if ($simpanRiwayat && $calc['skor_total'] !== (int)$w['skor_lama']) {
$stmtRiwayat->execute([
(int)$w['id'],
$calc['skor_total'],
$calc['status'],
(float)$w['penghasilan_bulanan'],
]);
}
$updated++;
}
return $updated;
}
/* ─────────────────────────────────────────────
ROUTING
───────────────────────────────────────────── */
try {
// Hanya Admin dan Petugas Lapangan yang memiliki akses ke modul kemandirian warga
requireAuth(['admin', 'petugas']);
$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'] ?? 'list';
if ($method === 'GET') {
if ($action === 'list') {
// Recalculate dulu
updateKemandirianBatch($pdo, true);
$statusFilter = $_GET['status'] ?? null;
$minSkor = (int)($_GET['min_skor'] ?? 0);
$limit = (int)($_GET['limit'] ?? 50);
$thresholdSk = getKonfig($pdo, 'threshold_skor_mandiri', 70);
$where = [];
if ($statusFilter) $where[] = "rw.status_kemandirian = " . $pdo->quote($statusFilter);
if ($minSkor > 0) $where[] = "rw.skor_kemandirian >= $minSkor";
$whereStr = $where ? "WHERE " . implode(" AND ", $where) : "";
$rows = $pdo->query("
SELECT rw.id, rw.nama_kepala_keluarga, rw.no_kk,
rw.penghasilan_bulanan, rw.jumlah_tanggungan,
rw.status_pekerjaan, rw.kategori_kemiskinan,
rw.skor_kemandirian, rw.status_kemandirian,
rw.catatan_kemandirian, rw.tanggal_exit_plan,
rw.rekomendasi_bantuan, rw.status_bantuan,
rw.kelurahan, rw.kecamatan,
rw.latitude, rw.longitude
FROM rumah_warga rw
$whereStr
ORDER BY rw.skor_kemandirian DESC
LIMIT $limit
")->fetchAll(PDO::FETCH_ASSOC);
// Tambah detail skor per row
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
$stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?");
foreach ($rows as &$r) {
$stmtA->execute([(int)$r['id']]);
$anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC);
$r['detail_skor'] = hitungSkorKemandirian($pdo, $r, $anggota, $thres, $bPeng, $bKerja, $bTangg);
}
$summary = $pdo->query("
SELECT status_kemandirian, COUNT(*) as jumlah, ROUND(AVG(skor_kemandirian)) as avg_skor
FROM rumah_warga GROUP BY status_kemandirian
")->fetchAll(PDO::FETCH_ASSOC);
$siapExit = $pdo->query("
SELECT COUNT(*) FROM rumah_warga
WHERE skor_kemandirian >= $thresholdSk
AND status_bantuan != 'sudah_terima'
")->fetchColumn();
echo json_encode([
'success' => true,
'data' => $rows,
'summary' => $summary,
'siap_exit' => (int)$siapExit,
]);
} elseif ($action === 'detail') {
$wargaId = (int)($_GET['id'] ?? 0);
$warga = $pdo->prepare("SELECT * FROM rumah_warga WHERE id = ?");
$warga->execute([$wargaId]);
$w = $warga->fetch(PDO::FETCH_ASSOC);
if (!$w) { echo json_encode(['success' => false, 'message' => 'Warga tidak ditemukan']); exit; }
$stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?");
$stmtA->execute([$wargaId]);
$anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC);
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
$skor = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg);
// Riwayat
$riwayat = $pdo->prepare("
SELECT * FROM riwayat_kemandirian WHERE rumah_warga_id = ?
ORDER BY tanggal DESC LIMIT 12
");
$riwayat->execute([$wargaId]);
echo json_encode([
'success' => true,
'data' => $w,
'skor' => $skor,
'anggota' => $anggota,
'riwayat' => $riwayat->fetchAll(PDO::FETCH_ASSOC),
]);
} elseif ($action === 'statistik') {
updateKemandirianBatch($pdo, false);
$thresholdSk = getKonfig($pdo, 'threshold_skor_mandiri', 70);
echo json_encode([
'success' => true,
'data' => [
'distribusi_status' => $pdo->query("
SELECT status_kemandirian as status, COUNT(*) as jumlah,
ROUND(AVG(skor_kemandirian)) as avg_skor,
ROUND(AVG(penghasilan_bulanan)) as avg_penghasilan
FROM rumah_warga GROUP BY status_kemandirian ORDER BY avg_skor DESC
")->fetchAll(PDO::FETCH_ASSOC),
'siap_exit_plan' => (int)$pdo->query("
SELECT COUNT(*) FROM rumah_warga WHERE skor_kemandirian >= $thresholdSk
")->fetchColumn(),
'sudah_punya_exit_plan' => (int)$pdo->query("
SELECT COUNT(*) FROM rumah_warga WHERE tanggal_exit_plan IS NOT NULL
")->fetchColumn(),
'avg_skor_global' => (float)$pdo->query("
SELECT ROUND(AVG(skor_kemandirian),1) FROM rumah_warga
")->fetchColumn(),
'trend_bulan_ini' => $pdo->query("
SELECT DATE(tanggal) as tgl, ROUND(AVG(skor)) as avg_skor, COUNT(*) as jumlah
FROM riwayat_kemandirian
WHERE tanggal >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY DATE(tanggal) ORDER BY tgl
")->fetchAll(PDO::FETCH_ASSOC),
],
]);
}
} elseif ($method === 'POST') {
// Catat perkembangan/catatan manual dari petugas
$body = json_decode(file_get_contents('php://input'), true);
$wargaId = (int)($body['warga_id'] ?? 0);
if (!$wargaId) { echo json_encode(['success' => false, 'message' => 'warga_id wajib']); exit; }
$stmt = $pdo->prepare("
UPDATE rumah_warga
SET catatan_kemandirian = ?, tanggal_exit_plan = ?, updated_at = NOW()
WHERE id = ?
");
$stmt->execute([
$body['catatan'] ?? null,
$body['tanggal_exit_plan'] ?? null,
$wargaId,
]);
// Hitung ulang dan simpan ke riwayat
$warga = $pdo->prepare("SELECT * FROM rumah_warga WHERE id = ?");
$warga->execute([$wargaId]);
$w = $warga->fetch(PDO::FETCH_ASSOC);
$stmtA = $pdo->prepare("SELECT * FROM anggota_keluarga WHERE rumah_warga_id = ?");
$stmtA->execute([$wargaId]);
$anggota = $stmtA->fetchAll(PDO::FETCH_ASSOC);
$thres = getKonfig($pdo, 'threshold_penghasilan_mandiri', 3500000);
$bPeng = getKonfig($pdo, 'bobot_penghasilan', 40);
$bKerja = getKonfig($pdo, 'bobot_status_kerja', 35);
$bTangg = getKonfig($pdo, 'bobot_tanggungan', 25);
$calc = hitungSkorKemandirian($pdo, $w, $anggota, $thres, $bPeng, $bKerja, $bTangg);
$pdo->prepare("
INSERT INTO riwayat_kemandirian
(rumah_warga_id, tanggal, skor, status, penghasilan_saat_ini, catatan)
VALUES (?, CURDATE(), ?, ?, ?, ?)
")->execute([
$wargaId, $calc['skor_total'], $calc['status'],
(float)$w['penghasilan_bulanan'], $body['catatan'] ?? null
]);
echo json_encode([
'success' => true,
'message' => 'Catatan kemandirian berhasil disimpan',
'skor' => $calc,
]);
} elseif ($method === 'PUT') {
// Update status kemandirian manual oleh admin (override sistem)
$body = json_decode(file_get_contents('php://input'), true);
$wargaId = (int)($body['warga_id'] ?? 0);
$stmt = $pdo->prepare("
UPDATE rumah_warga
SET status_kemandirian = ?,
catatan_kemandirian = ?,
tanggal_exit_plan = ?,
updated_at = NOW()
WHERE id = ?
");
$stmt->execute([
$body['status_kemandirian'] ?? null,
$body['catatan'] ?? null,
$body['tanggal_exit_plan'] ?? null,
$wargaId,
]);
echo json_encode(['success' => true, 'message' => 'Status kemandirian diperbarui']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}