Fix report — BSBHP-05 classification gate (berakhirnya flow)
Branch:
fix/berakhirnya-status-badan-hukum
Date: 2026-07-23
Scope: the BSBHP-05 auto-classification gate bug found via manual UI testing, plus
template filename/footer cleanup and full e2e + unit coverage.
TL;DR
A printed-and-scanned BSBHP-05 ("Bukti Pengumuman Koran — Pasal 149") failed to
auto-classify on full-package upload, while Pasal-152, Laporan Likuidasi and Surat Permohonan
landed normally. Root cause: a reachability gate, not the form-code regex.
refineSupportingLabel only ran matchBsbhpFormCode when the GPU label was low-confidence
or in a hard-coded allow-list (CONFIDENTLY_WRONG_SUPPORTING_LABELS). A scanned BSBHP-05
carries a neraca table, so the GPU landed it on BUKTI_TRANSFER-NERACA at scan-noise high
confidence — a label not in that allow-list — so the function returned early and the
printed FORMULIR SABH BSBHP-05 code was never checked.
Fixed by checking the form code first, for every label at any confidence (the printed code
is ~100 % precise), replacing the fragile allow-list. The one deliberate exception: a
high-confidence compact ID card (KTP/NPWP/…) is skipped, since a full-page form can never
be one — this preserves those flows' no-OCR fast path.
1 · Diagnosis evidence (verified, not assumed)
Reproduced against the real GPU classifier + PaddleOCR on the actual bsbhp-05.pdf in
uploads/klasifikasi-temp/.
(a) The form-code anchor itself works. OCR rawText of bsbhp-05.pdf:
HEAD(0..300): "FORMULIR SABH BSBHP-05 — BUKTI PENGUMUMAN SURAT KABAR PASAL 149 UUPT
(PENGUMUMAN KEPADA KREDITOR / RENCANA PEMBAGIAN SISA KEKAYAAN) …"
FOOT(-400): "… Formulir BSBHP-05 v1 — dihasilkan oleh SABH AI Platform. Jangan mengubah …"
contains BSBHP-05 token: true
matchBsbhpFormCode(rawText) → BUKTI_PENGUMUMAN_149 ✅ recognised correctly
(b) The gate skipped it. GPU rawClassification + confidence for the file, and the gate
behaviour over that same OCR text:
Input to refineSupportingLabel |
Before fix | After fix |
|---|---|---|
clean digital bsbhp-05.pdf → GPU BUKTI_TRANSFER-NERACA@0.0001 (low) |
BUKTI_PENGUMUMAN_149 (passes — low-conf gate opens) |
BUKTI_PENGUMUMAN_149 |
scanned form → GPU BUKTI_TRANSFER-NERACA@0.95 (high) |
BUKTI_TRANSFER-NERACA ❌ (form code skipped) |
BUKTI_PENGUMUMAN_149 ✅ |
scanned form → GPU SURAT_PERNYATAAN_DOMISILI@0.99 (high) |
SURAT_PERNYATAAN_DOMISILI ❌ |
BUKTI_PENGUMUMAN_149 ✅ |
The clean digital PDF only escaped the bug because it scored NERACA@0.0001 (below the 0.6
min-confidence, so the re-examine gate opened anyway). The physical scan draws the same label
at high confidence — confirmed live in the e2e below, where a rasterised BSBHP-06 scan drew
a 0.8937 GPU confidence and still auto-landed after the fix.
This is purely a gate bug — there is no OCR problem in the scan; matchBsbhpFormCode
recognises the text every time.
2 · The fix
compact ID card?
(KTP/NPWP/…)"} C -- yes --> G["confidence / allow-list gate
(unchanged fast path)"] C -- no --> D["matchBsbhpFormCode(ocrText)"] D -- "form code found" --> R2["return BSBHP lane
(auto-adopt)"] D -- "null" --> E{"label == BERITA_ACARA_RUPS?"} E -- yes --> F["narrow laporan rescue"] E -- no --> G G --> R3["confidence gate / re-examine
(free korans, etc.)"]
Before: the form code was only reachable via the BERITA_ACARA_RUPS branch or the
CONFIDENTLY_WRONG_SUPPORTING_LABELS allow-list (OTHER, SURAT_KETERANGAN_BANK,
BUKTI_TRANSFER-*). Any high-confidence label outside that set returned early.
After: the form code is checked first, above every gate, for all non-AKTA labels — with
one exception (high-confidence ID cards, which a full-page form can never be, preserving their
no-OCR fast path). The allow-list stays only for free koran clippings (no printed code),
which remain suggest-only by design.
Template cleanup (steps 4–5)
- Renamed the four templates to descriptive names (the
BSBHP-0Ncode stays inside the
document as the OCR anchor):
bsbhp-surat-permohonan.docx,bsbhp-laporan-akhir-likuidasi.docx,
bsbhp-bukti-pengumuman-pasal-149.docx,bsbhp-bukti-pengumuman-pasal-152.docx. - Replaced the AI-generated-looking footer
(Formulir BSBHP-0N v1 — dihasilkan oleh SABH AI Platform. Jangan mengubah baris judul…)
with a plain formal stampKode Formulir: BSBHP-0N— no "AI Platform", no do-not-change
instruction. The code is retained as the footer OCR-fallback anchor. - Hardened
matchBsbhpFormCodewith a distinctive"Kode Formulir: BSBHP-0N"probe so a
title-damaged BSBHP-03/04 scan resolves its own code and never grabs a lampiran-cited
code from the tail window (the shorter footer could otherwise let the 400-char window reach
the lampiran list). Legacy-footer PDFs still recognised.
3 · New / updated tests
| File | Added |
|---|---|
klasifikasi-berakhirnya-suggest.test.ts |
BSBHP-03/04/05/06 each land their lane for any HIGH-confidence document-shaped GPU label outside the allow-list (DOMISILI/NERACA/BANK/BA-RUPS/LAPORAN_KEUANGAN) + autoAcceptable; HIGH-confidence ID-card labels keep the no-OCR fast path; LOW-confidence ID-card falls through to the probe |
supporting-doc-classifier.test.ts |
new "Kode Formulir:" footer stamp; title-damaged BSBHP-03 disambiguation vs lampiran codes; legacy-footer backward-compat |
4 · Regressions fixed (branch-related, surfaced by the full suite)
/api/bsbhp-formsecurity policy — the mount (added earlier on the branch) had no
ROUTE_POLICIESentry → exhaustiveness test failed. Added asAUTHED.aktaPtNameOCR-outage 503 —be0eb411awaitedocrText()forAKTA_*labels outside
any try/catch, so an OCR outage on an akta upload returned 503 instead of the decided label.
Now soft-fails tonulland returns 200.
5 · e2e (Playwright, real UI → real backend → real GPU)
Existing .pw-scratch scripts were run; a scanned-BSBHP case was added (rasterises the
generator's filled form to an image-only PDF to simulate print→scan).
| Transaction | Result | Notes |
|---|---|---|
| sample-1 | 6/6 ✅ | free korans land as correct _149/_152 chips (suggest-only by design) |
| sample-2 | 5/6 | 1 free koran → coarse BUKTI_PENGUMUMAN (axis tie → operator assigns) — pre-existing |
| sample-3 | 4/6 | an akta the GPU calls AKTA_BERAKHIRNYA; an English koran the GPU calls BA-RUPS — pre-existing GPU/keyword limits |
| sample-4 | 5/5 ✅ | incl. the dual-function BAR = LAPORAN_LIKUIDASI |
| sample-5 | 6/6 ✅ | |
| synthetic BSBHP (4 forms + akta + BA RUPS) | 6/6 AUTO ✅ | template path through the UI |
| scanned BSBHP-05/06 (image-only) | 2/2 AUTO ✅, chip=none | bsbhp-06 scan drew conf 0.8937 (HIGH) and still auto-landed — the exact bug scenario |
| tambah-dokumen (post-board add) | ✅ | koran chip after board render |
No BSBHP form landed in a wrong box; every BSBHP form (8/8 across synthetic + scanned)
auto-adopted its correct lane. The sample free-document misses are proven not regressions:
matchBsbhpFormCode returns null for every sample XX file (they carry no printed code), so
refineSupportingLabel falls through to byte-identical behaviour — the misses are the documented
best-effort free-koran / coarse-GPU limitations (TAHAP0 baseline), untouched by this change.
Screenshots: .pw-scratch/{sample-1..5,bsbhp-synthetic,bsbhp-scan,tambah-dokumen}.png.
6 · Test suites — final
| Suite | Result |
|---|---|
backend bunx tsc --noEmit |
0 errors |
backend bun run test |
2620 pass · 1 skip · 2 fail |
frontend bunx tsc --noEmit |
0 errors |
frontend vitest run |
992 pass (140 files) |
| Playwright e2e | BSBHP forms 8/8 AUTO; no wrong-box landing |
The 2 backend failures are pre-existing and unrelated to this change, proven at the
refactor/tidyup-foundation baseline (06c85777):
config gateway defaults > gpuServerDirectUrl falls back … when GPU_SERVER_DIRECT_URL is unset— fails identically at baseline; the dev.envsetsGPU_SERVER_DIRECT_URL,
so the "when unset" assertion can't hold. Pure test-vs-env;config.tsuntouched by this
work.processPendirianPpViaCustomModel — BO persistence— passes standalone (9/9) at baseline
and at HEAD; only fails under full-suite shared-DB test-ordering contention. PP flow,
untouched by this work.
Commits (this session)
fix(berakhirnya): check BSBHP form code first for ALL labels in refineSupportingLabel
test(berakhirnya): BSBHP form code wins for any high-confidence GPU label
chore(berakhirnya): descriptive BSBHP template filenames + formal footer
fix(berakhirnya): lampiran-proof "Kode Formulir:" footer anchor
chore(berakhirnya): register /api/bsbhp-form security policy
fix(berakhirnya): keep no-OCR fast path for confident ID cards in form-code probe
fix(berakhirnya): soft-fail akta PT-name read on OCR outage