Integration brief — nirlaba akta extraction endpoints (Perkumpulan + Yayasan)
For: a Claude Code session (or engineer) working inside the GPU server codebase
(the service this backend calls atGPU_SERVER_URL, which already hosts
/pendirian-pp/surat-pernyataan/extract,/bukti-setor/extract, etc.).
Not for thedev_4OCR backend — that side only gets the thin client callers (see §5).Written 2026-08-10 by the dev_4 session that built the nirlaba flow skeleton.
Status of the ask: BLOCKED-ON-S5 for prompt tuning — the endpoint contract +
schema below are final, but the extraction prompt must be developed and validated
against real akta samples (synthetic rejected). Build the contract now; tune when akta land.
0. Why this exists
The dev_4 backend has two new nirlaba pendirian flows — Perkumpulan and Yayasan —
whose rules/context/schema are already built and green. The one missing piece is the
akta → structured JSON extraction, which runs on the GPU server, not in dev_4.
This brief specifies the two endpoints to add, their exact output schemas, and the field
semantics, so the GPU side is a fill-in + tune rather than a from-scratch design.
The two flows are near-identical (same organ shape, single akta document, PP-style
async job endpoint). Do them together.
1. Endpoints to add (mirror the PP pattern)
Follow the existing /pendirian-pp/surat-pernyataan/extract contract exactly (async
Celery job: submit → poll → { data, confidence, bounding_boxes }; the dev_4 client
uses submitAndPollJob). Add:
POST /pendirian-perkumpulan/akta/extractPOST /pendirian-yayasan/akta/extract
Request body (same as PP): { ocr_text: string, ocr_words: OcrWord[] }.
Response: { data: <schema below>, confidence: Record<dottedPath, number 0..100>, bounding_boxes: GpuBBox[] }.
Use vLLM guided_json (structured output) so data is schema-valid. Qwen thinking
model: pass enable_thinking=false or you get reasoning text instead of JSON.
2. Output schema — Perkumpulan (AktaPerkumpulanData)
Authoritative shape (from dev_4/backend/src/schema/akta-perkumpulan.ts):
{
"variant": "perkumpulan", // constant
"jenis_dokumen": "string|null", // must read as 'Akta Pendirian Perkumpulan'
"jenis_transaksi": "'nasional'|'asing'|null", // SABH vocab (verified); 'asing' if any organ member is a foreign national
"nama_perkumpulan": "string|null",
"nama_singkat": "string|null",
"nama_asing": "string|null",
"kedudukan": { "provinsi": "string|null", "kabupaten": "string|null" },
"organ": {
"rapat_anggota": [ OrganMember ], // the members' assembly
"pengurus": [ OrganMember ], // management
"pengawas": [ OrganMember ] // supervisory
},
"adart": { // the 5 REQUIRED AD/ART clauses
"asas_tujuan": "string|null", // principles & purpose
"hak_kewajiban": "string|null", // members' rights & obligations
"pengelola": "string|null", // management/governance
"mekanisme": "string|null", // mechanism to amend the AD/ART
"pembubaran": "string|null" // dissolution & asset distribution
},
"kekayaan_awal": "number|null", // OPTIONAL — extract if stated, no rule depends on it
"akta": { "nomor": "string|null", "tanggal": "string|null (ISO YYYY-MM-DD)", "notaris": "string|null" }
}
OrganMember (shared shape — same for Yayasan):
{ "nama": "string|null", "jabatan": "string|null", "nik": "string|null",
"npwp": "string|null", "paspor": "string|null", "kitas": "string|null", "negara": "string|null",
"tanggal_lahir": "string|null (ISO YYYY-MM-DD)" } // from the penghadap block — cross-checks the NIK
For WNI fill nik/npwp; for asing fill paspor/kitas/negara (leave the others null).
tanggal_lahir: extract each member's birth date from the penghadap block (e.g. "lahir di
Kebumen, pada tanggal 08-07-1974" → 1974-07-08). It powers a NIK self-check
(digits 7-12 of a NIK encode DDMMYY, +40 on the day for women), so accuracy here matters.
3. Output schema — Yayasan (AktaYayasanData)
Same shape as Perkumpulan with these differences (dev5 owns akta-yayasan.ts; confirm final field names with them, but the semantics are):
variant:"yayasan"(constant);jenis_dokumenreads as 'Akta Pendirian Yayasan'.nama_perkumpulan→nama_yayasan.organ.rapat_anggota→organ.pembina(Yayasan's top organ is Pembina, not a members' assembly).pengurus/pengawasunchanged.- No
adartblock — Yayasan has no required AD/ART clause set. Omit it. kekayaan_awalis required for Yayasan (a rule depends on it) — extract it reliably. For asing, there's also a minimum Rp100 juta expectation.- Asing Yayasan additionally involves two companion documents (2026-08-11 decision), both SEPARATE from the akta — never fields in this schema:
SURAT_PERTIMBANGAN_KEMENLU— Kemenlu recommendation/consideration letter (required). No sample/template exists yet, so its classifier keywords stay generic ("Kementerian Luar Negeri" letterhead, "pertimbangan", …) until real tuning.RESIDENCE_PERMIT— residence permit (optional). New DocumentType + classifier label (dev4 will add it with the classifier taxonomy). One real positive sample exists (syarat-asing/residence-permit_*).
4. Field-extraction guidance (to develop against real akta)
- Organ roles: map by heading. "Rapat Anggota" →
rapat_anggota(Perkumpulan) / "Pembina" →pembina(Yayasan). "Pengurus" (Ketua/Sekretaris/Bendahara etc.) →pengurus. "Pengawas" →pengawas. Put each person's title injabatan. - AD/ART (Perkumpulan only): these are clauses in the deed's articles. Capture the substantive text/summary of each of the 5.
nama_lambang(emblem) is NOT one of them — do not force it in. - asing detection: set
jenis_transaksi: "asing"when any organ member is identified by passport/KITAS / foreign nationality rather than NIK. - Dates: normalize
akta.tanggalto ISOYYYY-MM-DD. - Return
nullfor anything not present; never hallucinate.
5. The dev_4 backend side (already planned here — for reference, not your task)
When these endpoints exist, dev_4 adds thin clients mirroring llm/pendirian-pp-extract.ts:
llm/perkumpulan-akta-extract.ts (+ yayasan) POST to the endpoints, validate the schema,
and a document-processor.ts branch persists data to the akta document's
rawExtractionJson. The flow's buildContext already reads that blob. So once the
endpoints return schema-valid data, the flows light up end-to-end with no rule/context changes.
6. Acceptance
- Endpoint returns schema-valid
data(guided_json enforced) for a real akta sample. confidencekeyed by dotted paths (e.g.organ.pengurus.0.nama), 0..100.- Organ members land in the right slot; the 5 AD/ART clauses populate for a Perkumpulan deed.
- No reasoning text leaks into
data(thinking disabled).