Akta Classifier Study — 5 transaction families (2026-07-09)
Task: study the uploaded sample akta (Akuisisi, Berakhirnya status PT, Pembubaran, Merger, Peleburan) and ensure the classifier classifies them correctly.
Method: extracted the 4 zips (88 PDFs), pulled the 28 classification-driving akta (the primary deed of each flow), OCR'd each via PaddleOCR /layout, then ran them through (a) the akta-txn-classifier with the live Qwen tier and (b) the full staging pipeline end-to-end (coarse GPU classifier → OCR → refine). All deeds are genuine — verified by reading each keputusan.
Result: 24/28 → 26/28 end-to-end (both remaining are unfixable from the deed text)
| Family | Final e2e | Notes |
|---|---|---|
| Pembubaran | 5/5 ✅ | |
| Merger (Penggabungan) | 5/5 ✅ | incl. one titled "PERUBAHAN ANGGARAN DASAR" |
| Laporan RUPS Tahunan* | 5/5 ✅ | *bundled in the merger zip |
| Berakhirnya status PT | 5/5 ✅ | KAYU ARUM rescued from a confident coarse mislabel |
| Akuisisi | 4/5 ✅ | 03008126 is a genuinely-ambiguous intra-group reshuffle |
| Peleburan | 2/3 ✅ | DANA-MASTER has no peleburan recital anywhere in the deed |
Fixes shipped this session (5, all deployed + e2e-verified)
- Peleburan read as Pendirian. A peleburan produces a new PT, so its deed is titled "AKTA PENDIRIAN". Added
detectPeleburanRecital()— a deterministic guard on the specific peleburan-action wording ({akta|izin|hasil|penegasan|rancangan} peleburan,meleburkan diri), NOT the bare-"peleburan" Pasal-26 boilerplate every PT carries. Fixes BPR-RAY (recital in head) and AKTE-BPR-KBU (recital past the 16k LLM window). - Akuisisi via plain share transfer read as Perubahan. Prompt now treats transferring a controlling block (one party becomes pemegang saham pengendali) as akuisisi even when worded "jual-beli/peralihan saham".
- Coarse classifier fumbles a genuine akta to a garbage label.
refineAktaLabelrescues an unconfident non-akta coarse label when the kepala akta is clearly an "AKTA …". PENETAPAN_GANTI_NAMAalways 2nd-layered behind a strong-signal gate (see below) — fixes the TOPTEK akuisisi.- berakhirnya keyword-confirm broadened to the acceptance phrasing (
pertanggungjawaban … likuidasi,hasil akhir likuidasi), matching the prompt's own definition — fixes KAYU ARUM. Verified to match 4/5 berakhirnya and 0/5 pembubaran.
The core finding — and the recommended architecture
The coarse GPU classifier (LayoutLMv3) classifies by visual layout, but akta pendirian ≈ akta perubahan ≈ Berita Acara RUPS ≈ Penetapan Ganti Nama are ~95% layout-identical. No amount of retraining on layout features can separate near-identical layouts — this is architectural, not a data-quantity problem. Real evidence: the same PENETAPAN_GANTI_NAMA label came back at 0.0016 for one genuine akuisisi and 0.9886 for one genuine berakhirnya — confidence is meaningless for this class.
Recommendation: stop asking the coarse layer to sub-type this family. Its reliable, well-posed job is "which document FAMILY is this?" (notarial-deed vs KTP/NPWP/koran/laporan-keuangan — those ARE visually distinct). Sub-typing within the notarial-deed family belongs entirely to the 2nd (OCR+LLM) layer, which reads the keputusan content where the distinction actually lives. Concretely:
- Notarial-deed family =
{AKTA_PENDIRIAN, AKTA_PERUBAHAN, BERITA_ACARA_RUPS, PENETAPAN_GANTI_NAMA, AKTA_PEMINDAHAN_HAK}→ coarse label is a weak prior only; the 2nd layer decides the sub-type. (All 5 wired as of0f297337—AKTA_PEMINDAHAN_HAKwas the last member: the share-transfer instrument, a supporting doc when it moves a minor block but the akuisisi deed itself when it transfers a controlling block, so it promotes toAKTA_AKUISISIonly on a pengambilalihan-confirmed read. No real sample was on hand, so it's wired by the proven pattern + unit tests rather than e2e-verified.) - Promotion policy by member:
- Primary-akta-authoritative (
PENDIRIAN,PERUBAHAN): adopt any new-type content read (coarse is right that it's a primary akta, just blind to the sub-type). - Supporting-doc-capable (
BERITA_ACARA_RUPS,PENETAPAN_GANTI_NAMA,AKTA_PEMINDAHAN_HAK): promote only on a strong signal — a deterministic tier (title rule / recital) or a keyword-confirmed LLM read ≥ 0.9 — so a genuine supporting doc is never promoted to a primary akta on a weak guess. - Rescue (already built): catches a family member the coarse model fumbled out of the family to a non-deed label.
- Invest 2nd-layer content signals (recital detectors, keyword confirms) — that's the real source of truth — rather than the coarse model.
Optional, if you ever DO touch the coarse model: don't retrain to differentiate the family (you're right, that's near-impossible) — collapse it into ONE coarse class ("AKTA_NOTARIL"). That's the opposite move: give up the impossible distinction and let the model do the easy thing (recognize the notarial-deed layout as a single class), then the 2nd layer does 100% of sub-typing. But this is optional — the in-app handling above already achieves the same result with no retrain.
The 2 residuals — not classifier defects
| Deed | e2e | Why |
|---|---|---|
| 03008126 (akuisisi) | AKTA_PERUBAHAN |
Genuinely ambiguous — an intra-group reshuffle (PT BACH MULTI GLOBAL → …INVESTAMA, same group). Both layers read perubahan; arguably not wrong. |
| DANA-MASTER (peleburan) | AKTA_PENDIRIAN |
Undetectable from the deed — reads as a pure pendirian; its only "peleburan" mentions are the Pasal-26 boilerplate. No recital to key on. |
Commits: 2d2cb7e · ed4d520 · 1502b5a6 · a26ebe16 · 0f297337. Backend classifier tests green; tsc clean; all changes deployed via clean git archive (your in-progress PP working-tree changes untouched).