think
16px
820px

PLAN — Implementasi flow Berakhirnya Status Badan Hukum PT di ai-ahu

Untuk: agen implementasi (Opus 4.8, jalan di VM) atas worktree
/home/efran/remote-development/poc-ahu-ai/ahu-ocr-dev/dev_3
branch kerja: fix/berakhirnya-status-badan-hukum (sudah dibuat; verifikasi dengan
git branch --show-current sebelum mulai). Self-contained; semua path diverifikasi
2026-07-14, temuan sample ditambahkan 2026-07-15.
Sumber kebenaran desain: docs/superpowers/specs/2026-07-02-berakhirnya-status-pt-design.md
(316 baris, sudah direview). Baca spec itu SEBELUM mulai; plan ini adalah urutan
eksekusinya + temuan tambahan dari SABH legacy dan inspeksi sample yang tidak ada di spec.
Ditulis: 2026-07-14 · Diperbarui: 2026-07-15.

Data verifikasi (BUKAN data latih): 30 PDF nyata (6 kelas × 5) di
~/remote-development/poc-ahu-ai/sample-berakhirnya/ — sengaja di luar worktree,
dokumen hukum riil, JANGAN pernah masuk git. Pakai untuk verifikasi tiap tahap.
PLAN-unduh-data-berakhirnya.md TIDAK perlu dijalankan.

Latar

Fitur berakhirnya status PT belum dibangun — bundelnya ditolak sebelum Submission dibuat,
dan 5 dari 6 dokumen syaratnya tak dikenal GPU classifier (jatuh ke fallback AKTA
diam-diam). Root cause lengkap per dokumen:
obsidian/Bugs/BUG - Enam dokumen berakhirnya status tidak terklasifikasi.md.
Penting: retraining GPU classifier OUT OF SCOPE (spec eksplisit; lihat juga
obsidian/Decisions/ADR-0012 - ...md). Semua fix di lapisan aplikasi.

Kerjakan bertahap dan berurutan — tiap tahap selesai + test hijau sebelum lanjut.
Tahap 1–2 aman di-ship sendiri; Tahap 3 mengandung launch switch.


Tahap 0 — Baseline sebelum menyentuh kode (wajib, ±30 menit)

Lempar 30 PDF sample ke POST /classify (classifier existing) dan catat per file:
label GPU + confidence + reason seam (kalau terpicu) + potongan rawText. Simpan hasilnya
sebagai file di luar worktree (mis. ~/remote-development/poc-ahu-ai/baseline-berakhirnya.json).

Tujuan:
1. Bukti before/after untuk reviewer — tanpa baseline, klaim "fix bekerja" tidak terukur.
2. Kalibrasi regex — khususnya cek rawText PaddleOCR pada 10 kliping koran: fragmen
teks apa yang benar-benar selamat OCR menentukan keyword axis 149/152 yang realistis.


Tahap 1 — Hentikan fallback diam-diam (kecil, mandatory)

Spec §5 item 4 menandai ini mandatory: hari ini mapToDocumentType
(backend/src/routes/submissions.ts:34-59) diakhiri return "AKTA"; // fallback, sehingga
surat permohonan / laporan likuidasi / kliping koran menjadi "akta" dan dibakar lewat
extractor akta 122B.

  1. Tambah mapping eksplisit di mapToDocumentType:
    - SURAT_PERMOHONAN"SURAT_PERMOHONAN"
    - LAPORAN_LIKUIDASI"LAPORAN_LIKUIDASI"
    - BUKTI_PENGUMUMAN, BUKTI_PENGUMUMAN_149, BUKTI_PENGUMUMAN_152"BUKTI_PENGUMUMAN"
    (Ketiga DocumentType sudah ada di enum Prisma — contract/enums.gen.ts:16-45 — tidak
    perlu migrasi.)
  2. Tambah branch di processDocument (backend/src/services/document-processor.ts:2345-2545)
    untuk ketiga tipe di atas supaya TIDAK masuk cabang "7. Akta handling". Pola contoh:
    case BUKTI_PENGUMUMAN yang sudah ada (:2510, OCR-only via PaddleOCR dengan
    unwarp:false, orient:false). Untuk SURAT_PERMOHONAN dan LAPORAN_LIKUIDASI cukup
    OCR + simpan rawText dulu (extractor menyusul di Tahap 3).
  3. getProcessingOrder: beri ketiga tipe order non-0 supaya tidak bersaing sebagai
    "primary deed".

Verifikasi: unit test untuk mapToDocumentType (tiga label baru + label tak dikenal —
putuskan bersama reviewer apakah fallback AKTA diganti error/flag eksplisit; minimal
log warning). bun test regression hijau, khususnya
backend/src/services/__tests__/supporting-doc-classifier.test.ts.


Tahap 2 — Rule batas tanggal (permintaan user, server-side)

Temuan dari SABH legacy (~/work/ahu-online, view
protected/modules/sabh/views/perseroan/_formPerubahanStatusDetail.php:454-528): dua batas
tanggal di form SABH hanya ditegakkan client-side jQuery datepicker — server SABH cuma
validasi format dd-MM-yyyy dan strtotime yang gagal malah jadi null diam-diam
(TransaksiPerseroan.php:2258). ai-ahu harus menegakkannya server-side.

Buat backend/src/flow-engine/rules/berakhirnya/ (ikuti pola rules flow peleburan yang
sudah ada; severity ikut konvensi flow-engine):

  1. BSBHP_AKTA_RECENCYtanggal_akta_notaris ∈ [hari-ini − 30 hari, hari ini].
    (SABH: minDate: -30, maxDate: 0.)
  2. BSBHP_PENGUMUMAN_152_WINDOWtanggal_surat_kabar_152
    [tanggal_rups, tanggal_rups + 30 hari] (Pasal 152(3) UUPT). Bila tanggal_rups
    tidak terekstrak → WARN "tidak dapat divalidasi", bukan FAIL.
  3. BSBHP_PENGUMUMAN_ORDER — urutan pengumuman 149 mendahului akta/RUPS pembubaran,
    sesuai desain spec §4.3 (mengutip UU 40/2007 Ps.147(1)/152(3)).

Sumber tanggal = hasil ekstraksi OCR (focused pass), bukan input manual — rule berjalan
setelah ekstraksi, persis pola peleburanPtFlow. Tanggal dokumen ditulis notaris dalam
format Indonesia — pakai util parsing tanggal yang sudah ada di repo (cari parseIndoDate
/ setara di backend/src/services/; jangan tulis parser baru kalau sudah ada).

Verifikasi: unit test pola backend/src/flow-engine/__tests__/pembubaran-*.test.ts:
dalam-batas, luar-batas (kedua arah), RUPS kosong, tanggal tak ter-parse.


Tahap 3 — Flow penuh (ikut spec §4–5)

Urutan eksekusi (launch switch PALING AKHIR):

  1. Enum BERAKHIRNYA_STATUS_PT ke SubmissionType (contract/enums.gen.ts:58-72
    perhatikan file *.gen.ts: cari generator-nya, jangan edit hasil generate langsung).
  2. flows/berakhirnya-status-pt.ts di backend/src/flow-engine/flows/
    requiredDocs dari spec §4.2:
    ["AKTA", "BERITA_ACARA_RUPS", "SURAT_PERMOHONAN", "LAPORAN_LIKUIDASI", "BUKTI_PENGUMUMAN", "KTP", "NPWP"]
    + REQUIRED_SUPPORTING_DOCS FAIL kalau kurang: AKTA_BERAKHIRNYA, BERITA_ACARA_RUPS,
    SURAT_PERMOHONAN, LAPORAN_LIKUIDASI, BUKTI_PENGUMUMAN_149 (≥1),
    BUKTI_PENGUMUMAN_152 (≥1), KTP, NPWP + rules Tahap 2 + focused pass
    BUKTI_PENGUMUMAN (ganti stub PEMBUBARAN_PT: {engine:"v1-fork"} di registry.ts:31-32
    TIDAK termasuk scope ini — biarkan).
    ⚠️ Checklist harus mengizinkan satu dokumen memenuhi DUA syarat sekaligus — bukti
    dari sample nyata (2026-07-15): PT Bukit Lentera memakai PDF yang sama persis untuk
    BERITA_ACARA_RUPS dan LAPORAN_LIKUIDASI (BAR yang memuat penerimaan pertanggung-
    jawaban likuidator). Jangan implementasikan "6 syarat = 6 file berbeda"; syarat dianggap
    terpenuhi bila ADA dokumen (boleh sama) yang cocok.
  3. Board: BERAKHIRNYA_DOC_TYPES di backend/src/routes/klasifikasi.ts (pola
    PELEBURAN_DOC_TYPES :96-192) — kliping koran maxCount: 2 (satu per pasal;
    board peleburan cap-nya 1, jangan ditiru), + label display di LABEL_MAP.
  4. Update keyword seam supporting-doc-classifier.ts — temuan dari inspeksi 30 sample
    (2026-07-15), tulis unit test-nya di test file yang sudah ada:
    - Anchor nama PT untuk kliping koran. Sample koran adalah HALAMAN KORAN UTUH berisi
    banyak boks pengumuman PT lain (pengambilalihan, reksadana, likuidasi PT lain) —
    keyword scan atas seluruh halaman salah sasaran. Nama PT pemohon sudah diketahui dari
    submission: cari fragmen rawText yang memuat nama perseroan, klasifikasi semantik
    hanya pada window teks di sekitarnya (± beberapa ratus karakter — kalibrasi dari
    baseline Tahap 0, karena PaddleOCR memecah teks koran jadi fragmen).
    - Perluas axis 149: sekarang KREDITOR|PEMBUBARAN; sample nyata 149 ada yang berupa
    pengumuman pengakhiran pailit via putusan pengadilan → tambah
    PAILIT|KURATOR|PUTUSAN|KEBERATAN|TAGIHAN. (Axis 152 sudah tepat:
    HASIL LIKUIDASI|PERTANGGUNGJAWABAN — boleh tambah HASIL AKHIR.)
    - Aliasing sirkuler → BAR: 3 dari 5 sample BAR sebenarnya "KEPUTUSAN SIRKULER"
    (pengganti rapat fisik, sah menurut UUPT) — pattern KEPUTUSAN SIRKULER|SIRKULER PARA PEMEGANG SAHAM + konteks pembubaran/likuidasi harus disarankan sebagai
    BERITA_ACARA_RUPS.
    - Varian laporan likuidasi: format sangat beragam (pernyataan 1 halaman bermeterai,
    laporan bilingual kantor hukum puluhan halaman, "surat pernyataan dan neraca") tapi
    semantik konvergen — pastikan keyword menutup
    PERNYATAAN .* HASIL AKHIR .* LIKUIDASI dan LAPORAN PERTANGGUNGJAWABAN LIKUIDATOR.
    - Kliping koran tetap suggest-only / never auto-accept — jangan diubah.
  5. Extractor:
    - bukti-pengumuman-extract.ts — prompt sekarang hardcode Pasal 127(2) peleburan;
    parametrisasi per pasal (149: pengumuman pembubaran + panggilan kreditor; 152: hasil
    akhir likuidasi). Wire recoverDocNumberViaLlm (ada di apostille-generic-extract.ts)
    untuk nomor_pengumuman — spec §7 mengasumsikan ini tapi belum di-wire.
    - Extractor baru surat-permohonan-extract.ts (nomor + tanggal surat) dan
    laporan-likuidasi-extract.ts (nama likuidator, nomor/tanggal surat, sisa kekayaan) —
    daftar field dari riset docs/research/sabh-pt-untouched-flows-2026-07-01.md §Berakhirnya.
    Daftarkan di FOCUSED_PASS_HANDLERS (flow-engine/focused-passes.ts).
  6. TITLE_RULES di akta-txn-classifier.ts:57: tambah
    { re: /^\s*AKTA\s+BERAKHIRNYA/im, txnType: "berakhirnya" } (spec open question 6 —
    resolve sebagai: tambah). Tambahkan juga key AKTA_BERAKHIRNYA ke AKTA_SUBTYPE_MAP
    di backend/src/ocr/classifier.ts (sekarang throw).
  7. Company lookup: dissolved PT tersaring status_transaksi = 1
    (backend/src/services/company-lookup.ts, spec §7 "Dissolved PT invisible") — tambah
    param allowedStatusTransaksi dan set dari flow berakhirnya. Tanpa ini submission stall
    di pemilihan perusahaan dengan daftar kosong tanpa error.
  8. Launch switch, komit terpisah paling akhir: BUILT_AKTA_TXN_TYPES += "berakhirnya"
    (akta-txn-classifier.ts:36-42) + inferSubmissionType (submissions.ts:100-122)
    mengembalikan BERAKHIRNYA_STATUS_PT.

Verifikasi Tahap 3: unit test flow (pola pembubaran-context.test.ts /
pembubaran-schema.test.ts); e2e: upload set sample dari
~/remote-development/poc-ahu-ai/sample-berakhirnya/ (mulai dari 1 perusahaan = 6 PDF
dengan prefix nomor transaksi sama, lalu kelima-limanya) → semua terklasifikasi benar di
board, rules tanggal menyala, submission terbentuk. Bandingkan dengan baseline Tahap 0
sebagai bukti before/after. Regression penuh bun test.


Jebakan yang sudah diketahui (jangan ulangi)

  • Confident-wrong GPU: GPU melabeli dokumen tak dikenal ke kelas terdekat >0.6 —
    koreksinya adalah seam + operator board, jangan naikkan threshold global.
  • Kliping koran = permukaan OCR terlemah: PaddleOCR memecah teks koran; nilai yang tak
    verbatim-present di rawText akan ditolak grounding (→ null). Cek rawText dulu sebelum
    menyalahkan extractor.
  • refineAktaLabel bisa mem-promote BA RUPS ke AKTA_BERAKHIRNYA (BA RUPS-lah yang memuat
    frasa "berakhirnya") — pastikan setelah Tahap 3 promosi ini tidak membuat bundel kehilangan
    BERITA_ACARA_RUPS-nya di checklist.
  • Flow ini di SABH bernama PerubahanStatus* tapi judul bisnisnya "Berakhirnya Status
    Badan Hukum" — flow yang sama, jangan dibangun dua kali (riset 2026-07-01 baris 5 vs 8).
  • Sample 30 PDF = data verifikasi, BUKAN data latih — jangan usulkan fine-tune/retrain
    dari sample ini, dan jangan pindahkan/copy ke dalam worktree (dokumen hukum riil,
    risiko ke-commit).
  • 149 vs 152 tidak akan pernah terpisah secara visual — sample membuktikan keduanya
    halaman koran utuh yang identik bentuknya; pembedanya murni semantik di boks yang
    memuat nama PT pemohon.

Out of scope branch ini (follow-up terpisah, jangan dikerjakan)

  • Template surat permohonan/pernyataan yang di-generate sistem — pola sudah ada di repo
    (services/surat-keterangan-generator.ts, docxtemplater → LibreOffice → PDF +
    scripts/sk-roundtrip-diag.ts sebagai guard drift). Akan dibuatkan spec sendiri setelah
    flow ini ship; butuh keputusan produk (wajib vs opsional bagi notaris).
  • Slot upload bernama ala FIELD_TO_DOCTYPE (routes/perbaikan.ts:34) — mengubah
    klasifikasi dari "tebak jenis" jadi "verifikasi isi slot", sekaligus menyelesaikan
    dokumen multi-peran secara natural. Layak diangkat ke reviewer sebagai arah desain,
    tapi spec 2026-07-02 memakai bundle+board — jangan menyimpang dari spec di branch ini.
  • Retraining GPU classifier — permanen out of scope (ADR-0012).
  • PLAN-unduh-data-berakhirnya.md — tidak blocking; hanya untuk eval set besar nanti.

Konteks vault (opsional)

  • obsidian/Concepts/Klasifikasi Tiga Tingkat.md — arsitektur klasifikasi
  • obsidian/Components/Akta Txn Classifier.md, Supporting Doc Classifier.md
  • obsidian/Flows/Flow - Permohonan berakhirnya status badan hukum (SABH legacy).md
    detail alur + batas tanggal SABH