think
16px
820px

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 at GPU_SERVER_URL, which already hosts
/pendirian-pp/surat-pernyataan/extract, /bukti-setor/extract, etc.).
Not for the dev_4 OCR 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/extract
  • POST /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_dokumen reads as 'Akta Pendirian Yayasan'.
  • nama_perkumpulannama_yayasan.
  • organ.rapat_anggotaorgan.pembina (Yayasan's top organ is Pembina, not a members' assembly). pengurus/pengawas unchanged.
  • No adart block — Yayasan has no required AD/ART clause set. Omit it.
  • kekayaan_awal is 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 in jabatan.
  • 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.tanggal to ISO YYYY-MM-DD.
  • Return null for 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.
  • confidence keyed 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).