How legacy Apostille and the Rebuild handle spesimen matching
2026-07-15 · research for Efran ("check and report back") · read-only survey of both codebases
Legacy: ahu-codebase/ahu-apostille (apostille-service + ap-fe + ap-fe-admin) · Rebuild: ahu-codebase/ahu-rebuild/ahu-rebuild-apostille (ahu-apostille-api + module-apostille)
TL;DR
Neither system matches signatures. Both match records, and a human eyeballs the ink.
| LEGACY | REBUILD | tidyup (ours, today) | |
|---|---|---|---|
| Who picks the pejabat | Applicant, at registration (autocomplete) | Applicant, at registration (cascading select) | System (OCR name/NIP → registry auto-match) + human correction |
| Auto name/NIP matching at verification | None — the doc already carries the applicant's pick | None — same | Yes (resolveSigner, contamination-aware since K.28) |
| Specimen sheet shown to verifikator | Yes — true 50/50 side-by-side (document left, "Spesimen Jabatan" right, one viewport) | Yes, but stacked vertically — verifikator scrolls between document and specimen | Yes — signature crops side-by-side + full-sheet link + AI heatmaps |
| Automated image comparison / scoring | None anywhere | None anywhere | Advisory AI score (dino+SigNet ensemble), verifikator-only |
| Signature-level comparison unit | Whole sheet vs whole document | Whole sheet vs whole document | Detected/manual signature crops |
| Verdict | Human Approve/Reject | Human Terima/Tolak + keterangan | Human verdict (authoritative) + advisory score |
The only "score" that exists in either old system is Elasticsearch text relevance for the officer-name autocomplete in legacy (apostille-service/src/service/spesimen-service.ts:54-78). A full grep for opencv|tensorflow|onnx|siamese|cosine|ssim|phash|imagehash|biometric across all four apps returns nothing real.
LEGACY (ahu-apostille)
Matching = the applicant's own selection, made at registration. The pemohon searches the registry by officer name (Elasticsearch wildcard on officer_name.normalize), picks the pejabat and position, and that spesimenPositionId is persisted onto the request (apostille-service/src/service/apostille-service.ts:748-760). If no specimen exists for an electronic document, status flips to "Menunggu Spesimen Terbaru" and a Surat Pengantar is generated instead (:733-738).
Verification = a genuine side-by-side, fully manual. ap-fe-admin/.../VerificationDetail/DocumentDataFile.component.tsx:11-20 renders two preview boxes in one viewport — left the uploaded document, right the linked specimen sheet ("Spesimen Jabatan"), each 50% wide (style.tsx:40-46). The verifikator eyeballs the two PDFs and hits Approve/Reject (VerificatorForm.template.tsx:73-113). No comparison result exists anywhere — the decision is the comparison.
The one officer-data entry a verifikator can do: for documents still awaiting a specimen, an "update specimen" sub-form captures penyampaian/keabsahan type + free-text officer name/position (ValiditySpecimen.component.tsx) — manual data entry, not matching.
Storage: MySQL spesimen + spesimen_position (the same tables our registry reads), specimen file as a MinIO key on spesimen_position.spesimen_file, presigned at detail time (apostille-service.ts:323-325). FORMULIR 01/02 PDFs are generated by lib/pdf.ts:1209,1281 — the same forms whose indexing produces the contamination we measured (K.23).
REBUILD (ahu-rebuild-apostille)
Matching = the same applicant-selected cascade, on Postgres. PejabatSelect.jsx walks name → jabatan → instansi (ILIKE, DISTINCT ON, LIMIT 20 — spesimen-pejabat.repository.ts:83-157) and sets id_spesimen_pejabat. New idea worth noting: if the applicant's officer isn't in the registry, they can upload their own specimen sheet — a spesimen_tangguhan ("deferred specimen"), status "Menunggu Spesimen Pejabat" (permohonan.service.ts:303-355). No NIP matching, no image logic.
Verification = same page but stacked, still fully manual. detailPermohonan returns presigned URLs for the document files and url_spesimen (verifikasi.service.ts:356-525); DetailValid.jsx renders the document viewer up top and the specimen sheet in separate rows further down (:567-603) — the verifikator scrolls to compare rather than seeing both at once. Decision is Terima/Tolak + keterangan (Verif.jsx:59-149). A per-file "Sudah sesuai / Tidak sesuai" radio component exists in the code (CheckVerif.jsx) but is not wired into the live screen.
Storage: Prisma/Postgres (tr_apl_spesimen_pejabat, tr_apl_berkas_spesimen_pejabat.s3_key, tr_apl_spesimen_tangguhan), files in S3/MinIO under spesimen-pejabat/berkas-…, presigned via s3Service.getFileUrl.
What this means for tidyup
- Our auto-matching is net-new. Neither predecessor ever matched the OCR'd signer against the registry — they pushed that burden onto the applicant at form-fill time. Our
resolveSigner(name+NIP+jabatan composite, contamination-aware since K.28) automates a step that used to be a human's form field, which is exactly the OCR-replaces-form-filling thesis of this project. - The side-by-side has a precedent — the legacy one is the better of the two. Legacy's single-viewport 50/50 beats the rebuild's scroll-to-compare. Ours goes a level deeper (signature crops, not whole sheets, plus the full-sheet link), which is the right direction — the crop is what the eyeball actually needs.
- The AI advisory score is also net-new. Both predecessors are 100% eyeball. Our score is an accelerator on top of the same human-decides model, so the "advisory, verifikator decides" framing matches how this process has always legally worked.
- The rebuild's
spesimen_tangguhan(applicant-uploaded specimen for unregistered officers) is worth stealing — today our unregistered-officer path is acknowledge-and-proceed with nothing to compare against; letting the pemohon attach the officer's specimen sheet would give the verifikator something to eyeball, mirroring a flow the rebuild already designed. - The contamination we measured (K.23) predates us and affected them silently. Legacy/rebuild show the whole sheet with zero attribution checking — a verifikator comparing against a FORMULIR 02 bundle had no warning the ink belongs to the successor. Our sheet-attribution layer is the first thing in this lineage that reads the sheet.
Full file:line evidence gathered read-only from both trees; no engine repos touched.