""" pewujud.py — PewujudKalimat: realisasi proposisi → kalimat bahasa Indonesia. ANTI-TRANSFORMER: Transformer: "generasi" = distribusi probabilitas atas vocab token demi token. AKSARA: "perwujudan" = realisasi deterministik proposisi terstruktur menggunakan aturan morfologi TBBBI + template kalimat + verifikasi CPE. Input: Proposisi{agen, aksi, pasien, slot...} ← makna yang sudah dipahami Output: "Hakim menjatuhkan hukuman kepada terdakwa." ← perwujudan makna Loop unik AKSARA (tidak mungkin di Transformer): Proposisi → [Perwujudan] → kalimat_draft kalimat_draft → [AksaraFramework verifikasi] → skor_linguistik skor < 0.4? → revisi morfologi/template → ulang Pipeline: 1. Tentukan transitifitas verba (dari KB + pola slot proposisi) 2. Realisasikan verba via morfologi TBBBI 3. Pilih template kalimat terbaik berdasarkan slot tersedia 4. Isi template dengan nilai slot 5. Verifikasi via framework (opsional) 6. Return kalimat + metadata audit """ from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, List, Optional, Tuple from aksara.primitives.krl.proposition import Proposisi, TipeSlot from aksara.primitives.krl.inference import Inferensi from aksara.realisasi.morfologi import ( pilih_bentuk_verba, VERBA_INTRANSITIF, VERBA_SUFIKS_I, ) from aksara.realisasi.templat import ( pilih_templat, isi_templat, TEMPLAT_INFERENSI, ) # Verba yang secara inheren membutuhkan penerima (PENERIMA slot) VERBA_PERLU_PENERIMA: set = { "beri", "kirim", "serah", "kasih", "titip", "jual", "hadiahi", "sampaikan", "laporkan", "ceritakan", } # Verba yang mengindikasikan kalimat pasif lebih natural VERBA_PASIF_NATURAL: set = { "tangkap", "tahan", "hukum", "vonis", "jatuh", "putus", "tetap", "baca", "tulis", "buat", "bentuk", "bangun", } @dataclass class HasilPerwujudan: """ Hasil perwujudan satu proposisi atau inferensi. Setiap hasil bisa diaudit lengkap — bukan opaque string dari model. """ kalimat: str # kalimat yang dihasilkan proposisi_asal: Optional[Proposisi] # proposisi sumber (jika dari proposisi) inferensi_asal: Optional[Inferensi] # inferensi sumber (jika dari inferensi) verba_bentuk: str # bentuk verba yang digunakan templat_nama: str # nama template yang dipilih skor_verifikasi: Optional[float] = None # skor dari verifikasi CPE (jika dijalankan) valid: bool = True # apakah lolos verifikasi catatan: List[str] = field(default_factory=list) def ke_dict(self) -> dict: return { "kalimat": self.kalimat, "verba_bentuk": self.verba_bentuk, "templat": self.templat_nama, "skor_verifikasi": self.skor_verifikasi, "valid": self.valid, "catatan": self.catatan, } class PewujudKalimat: """ Pewujud proposisi → kalimat bahasa Indonesia. Mengubah representasi proposisi terstruktur (output KRL) menjadi kalimat bahasa Indonesia yang gramatikal menggunakan: - Aturan morfologi verba TBBBI (morfologi.py) - Template kalimat S-P-O-K (templat.py) - Verifikasi opsional via AksaraFramework (CPE constraint check) TIDAK menggunakan: - Distribusi probabilitas - Parameter yang dilatih - Sampling atau beam search Contoh: pewujud = PewujudKalimat(framework) # Dari proposisi prop = framework.proses("Hakim menjatuhkan hukuman.").krl_result.proposisi hasil = pewujud.wujudkan(prop) print(hasil.kalimat) # "Hakim menjatuhkan hukuman." # Dari inferensi KRL inf = Inferensi(relasi="STATUS_MENJADI", subjek="terdakwa", objek="terpidana", ...) hasil = pewujud.wujudkan_inferensi(inf) print(hasil.kalimat) # "Terdakwa menjadi terpidana." # Jawab pertanyaan dari wacana jawaban = pewujud.jawab("Apa status terdakwa?", jendela) print(jawaban["kalimat"]) # "Terdakwa menjadi terpidana." """ def __init__(self, framework=None) -> None: """ Args: framework: AksaraFramework opsional — digunakan untuk verifikasi. Jika None, verifikasi dilewati. """ self.framework = framework # ── Operasi utama ────────────────────────────────────────────────────── def wujudkan( self, proposisi: Proposisi, pasif: bool = False, verifikasi: bool = True, ) -> HasilPerwujudan: """ Wujudkan proposisi menjadi kalimat bahasa Indonesia. Alur: 1. Tentukan apakah verba transitif atau intransitif 2. Realisasikan verba dengan morfologi yang tepat 3. Pilih template berdasarkan slot yang tersedia 4. Isi template → kalimat draft 5. Verifikasi via CPE (jika framework tersedia) Args: proposisi: Proposisi terstruktur dari KRL pasif: True → gunakan struktur pasif di- verifikasi: True → jalankan verifikasi CPE setelah perwujudan Returns: HasilPerwujudan dengan kalimat + metadata audit lengkap """ catatan: List[str] = [] # ── Langkah 1: tentukan sifat verba ────────────────────────────── transitif = self._apakah_transitif(proposisi) if pasif and TipeSlot.PASIEN not in proposisi.slot: pasif = False catatan.append("pasif dibatalkan: tidak ada slot PASIEN") sufiks = self._pilih_sufiks(proposisi) # ── Langkah 2: realisasikan verba ──────────────────────────────── verba_bentuk = pilih_bentuk_verba( root=proposisi.aksi, transitif=transitif, pasif=pasif, sufiks=sufiks, ) # Tambahkan negasi jika proposisi negatif if not proposisi.polaritas: verba_bentuk = "tidak " + verba_bentuk # Tambahkan modalitas jika ada if proposisi.modalitas: verba_bentuk = proposisi.modalitas + " " + verba_bentuk # ── Langkah 3: pilih template ───────────────────────────────────── templat = pilih_templat(proposisi, pasif=pasif) if templat is None: # Fallback: kalimat minimal dengan agen + verba saja agen = proposisi.agen or "" kalimat = f"{agen} {verba_bentuk}".strip() kalimat = kalimat[0].upper() + kalimat[1:] if kalimat else kalimat catatan.append("fallback: template tidak ditemukan, gunakan S+P minimal") return HasilPerwujudan( kalimat=kalimat, proposisi_asal=proposisi, inferensi_asal=None, verba_bentuk=verba_bentuk, templat_nama="FALLBACK_MINIMAL", catatan=catatan, ) # ── Langkah 4: isi template ──────────────────────────────────────── kalimat = isi_templat(templat, proposisi, verba_bentuk) # ── Langkah 5: verifikasi (opsional) ────────────────────────────── skor_verifikasi = None valid = True if verifikasi and self.framework is not None and kalimat: skor_verifikasi, valid, catatan_vf = self._verifikasi(kalimat) catatan.extend(catatan_vf) if not valid and not pasif: # Coba sekali lagi dengan pasif jika tersedia slot AGEN + PASIEN if (TipeSlot.AGEN in proposisi.slot and TipeSlot.PASIEN in proposisi.slot): verba_pasif = pilih_bentuk_verba( root=proposisi.aksi, transitif=True, pasif=True ) templat_pasif = pilih_templat(proposisi, pasif=True) if templat_pasif: kalimat_alt = isi_templat(templat_pasif, proposisi, verba_pasif) skor_alt, valid_alt, _ = self._verifikasi(kalimat_alt) if valid_alt or (skor_alt is not None and skor_verifikasi is not None and skor_alt > skor_verifikasi): kalimat = kalimat_alt verba_bentuk = verba_pasif skor_verifikasi = skor_alt valid = valid_alt catatan.append( f"revisi: aktif gagal verifikasi, ganti pasif " f"(skor={skor_alt:.3f})" ) return HasilPerwujudan( kalimat=kalimat, proposisi_asal=proposisi, inferensi_asal=None, verba_bentuk=verba_bentuk, templat_nama=templat.nama, skor_verifikasi=skor_verifikasi, valid=valid, catatan=catatan, ) def wujudkan_inferensi(self, inferensi: Inferensi) -> HasilPerwujudan: """ Wujudkan satu inferensi KRL menjadi kalimat pernyataan. Menggunakan template inferensi yang spesifik per jenis relasi. Template dipilih dari TEMPLAT_INFERENSI berdasarkan inferensi.relasi. Contoh: STATUS_MENJADI(terdakwa, terpidana) → "Terdakwa menjadi terpidana." """ templat_pola = TEMPLAT_INFERENSI.get(inferensi.relasi) if templat_pola is None: # Fallback: relasi(subjek, objek) dalam bentuk natural kalimat = f"{inferensi.subjek} {inferensi.relasi.lower().replace('_', ' ')} {inferensi.objek}" kalimat = kalimat[0].upper() + kalimat[1:] + "." return HasilPerwujudan( kalimat=kalimat, proposisi_asal=None, inferensi_asal=inferensi, verba_bentuk="", templat_nama="FALLBACK_INFERENSI", catatan=[f"relasi '{inferensi.relasi}' tidak ada di TEMPLAT_INFERENSI"], ) kalimat = templat_pola.format( subjek=inferensi.subjek, objek=inferensi.objek, ) if kalimat and not kalimat.endswith("."): kalimat += "." kalimat = kalimat[0].upper() + kalimat[1:] if kalimat else kalimat return HasilPerwujudan( kalimat=kalimat, proposisi_asal=None, inferensi_asal=inferensi, verba_bentuk="", templat_nama=f"INFERENSI_{inferensi.relasi}", valid=True, ) def jawab( self, query: str, jendela, # JendelaWacana — hindari circular import dengan type hint string verifikasi: bool = True, ) -> Optional[Dict]: """ Jawab pertanyaan dari konteks wacana. Alur: 1. Tanya JendelaWacana → dapat inferensi paling relevan 2. Wujudkan inferensi → kalimat jawaban 3. Return kalimat + penjelasan audit trail lengkap Returns: Dict dengan kunci: kalimat — jawaban dalam bentuk kalimat Indonesia inferensi — Inferensi sumber keyakinan — float [0,1] penjelasan — sumber aturan + kalimat asli audit — metadata perwujudan (verba, template, skor) atau None jika tidak ada jawaban. """ hasil_tanya = jendela.tanya(query) if hasil_tanya is None: return None inferensi = hasil_tanya["inferensi"] hasil = self.wujudkan_inferensi(inferensi) return { "kalimat": hasil.kalimat, "inferensi": inferensi, "keyakinan": hasil_tanya["keyakinan"], "penjelasan": hasil_tanya["penjelasan"], "audit": hasil.ke_dict(), } def wujudkan_semua( self, proposisi_list: List[Proposisi], ) -> List[HasilPerwujudan]: """Wujudkan daftar proposisi menjadi kalimat-kalimat.""" return [self.wujudkan(p, verifikasi=False) for p in proposisi_list] # ── Internals ────────────────────────────────────────────────────────── def _apakah_transitif(self, proposisi: Proposisi) -> bool: """ Tentukan apakah verba bersifat transitif dari slot proposisi. Transitif jika: - ada slot PASIEN atau TEMA, ATAU - verba ada di daftar transitif eksplisit Intransitif jika verba ada di VERBA_INTRANSITIF. """ if proposisi.aksi in VERBA_INTRANSITIF: return False if TipeSlot.PASIEN in proposisi.slot or TipeSlot.TEMA in proposisi.slot: return True return False def _pilih_sufiks(self, proposisi: Proposisi) -> Optional[str]: """ Pilih sufiks verba berdasarkan slot proposisi. - Ada PENERIMA → -kan (transfer ke penerima) - Verba dalam VERBA_SUFIKS_I → -i - Lain → None (pilih otomatis) """ if TipeSlot.PENERIMA in proposisi.slot: return "kan" if proposisi.aksi in VERBA_SUFIKS_I: return "i" return None def _verifikasi(self, kalimat: str) -> Tuple[Optional[float], bool, List[str]]: """ Verifikasi kalimat yang dihasilkan via AksaraFramework. Menjalankan 6 primitif pada kalimat hasil perwujudan untuk memastikan tidak ada pelanggaran constraint berat. Returns: (skor_linguistik, valid, catatan) """ catatan: List[str] = [] try: state = self.framework.proses(kalimat) skor = state.skor_linguistik valid = skor >= 0.4 and not any( p.severitas >= 0.8 for p in state.pelanggaran ) if not valid: catatan.append( f"verifikasi GAGAL: skor={skor:.3f}, " f"n_pelanggaran_berat=" f"{sum(1 for p in state.pelanggaran if p.severitas >= 0.8)}" ) return skor, valid, catatan except Exception as e: catatan.append(f"verifikasi error: {e}") return None, True, catatan # gagal silently, asumsikan valid