""" templat.py — Template kalimat bahasa Indonesia untuk PewujudKalimat. ANTI-TRANSFORMER: Transformer: urutan kata diputuskan via distribusi probabilitas. AKSARA: urutan kata mengikuti pola S-P-O-K bahasa Indonesia (TBBBI) yang dipilih berdasarkan slot yang tersedia di Proposisi. Template adalah pola deterministik — bukan learned, bukan probabilistik. Setiap template punya justifikasi gramatikal dari TBBBI. Hierarki pemilihan template: 1. Cek slot yang tersedia di Proposisi 2. Pilih template yang paling lengkap yang bisa diisi 3. Terapkan preposisi yang benar untuk setiap keterangan 4. Gabungkan menjadi kalimat Konvensi: {agen} — pengisi peran AGEN (subjek kalimat aktif) {verba} — verba dalam bentuk yang sudah direalisasikan {pasien} — pengisi peran PASIEN (objek kalimat aktif) {penerima} — pengisi peran PENERIMA (prepositional object: "kepada X") {lokasi} — keterangan tempat (prepositional: "di X") {waktu} — keterangan waktu (prepositional: "pada X" / bare) {cara} — keterangan cara ("dengan X") {tujuan} — keterangan tujuan ("untuk X" / "ke X") {sebab} — keterangan sebab ("karena X") """ from __future__ import annotations from dataclasses import dataclass from typing import Dict, List, Optional, Tuple from aksara.primitives.krl.proposition import Proposisi, TipeSlot # ── Preposisi per tipe slot ─────────────────────────────────────────────── # Kunci: TipeSlot → preposisi default yang digunakan dalam realisasi PREPOSISI_SLOT: Dict[TipeSlot, str] = { TipeSlot.LOKASI: "di", TipeSlot.TUJUAN: "ke", TipeSlot.ASAL: "dari", TipeSlot.PENERIMA: "kepada", TipeSlot.CARA: "dengan", TipeSlot.SEBAB: "karena", TipeSlot.WAKTU: "pada", TipeSlot.SYARAT: "jika", } @dataclass class TemplatKalimat: """ Satu pola kalimat beserta metadata gramatikalnya. nama: identifikasi pola pola: string template dengan placeholder {slot} slot_wajib: slot yang HARUS terisi agar template valid slot_opsional: slot yang memperkaya kalimat jika tersedia prioritas: lebih tinggi = dipilih lebih dahulu jika sama-sama valid """ nama: str pola: str slot_wajib: List[TipeSlot] slot_opsional: List[TipeSlot] prioritas: int = 0 # ── Daftar template kalimat bahasa Indonesia ───────────────────────────── # Diurutkan dari paling lengkap ke paling minimal. # Template pertama yang semua slot_wajib-nya terpenuhi akan dipakai. TEMPLAT_AKTIF: List[TemplatKalimat] = [ # ── Kalimat lengkap S-P-O-K ────────────────────────────────────────── TemplatKalimat( nama="AKTIF_TRANS_PENERIMA_LOKASI", pola="{agen} {verba} {pasien} {prep_penerima} {penerima} {prep_lokasi} {lokasi}", slot_wajib=[TipeSlot.AGEN, TipeSlot.PASIEN, TipeSlot.PENERIMA, TipeSlot.LOKASI], slot_opsional=[TipeSlot.WAKTU], prioritas=10, ), TemplatKalimat( nama="AKTIF_TRANS_PENERIMA", pola="{agen} {verba} {pasien} {prep_penerima} {penerima}", slot_wajib=[TipeSlot.AGEN, TipeSlot.PASIEN, TipeSlot.PENERIMA], slot_opsional=[TipeSlot.LOKASI, TipeSlot.WAKTU], prioritas=9, ), TemplatKalimat( nama="AKTIF_TRANS_LOKASI", pola="{agen} {verba} {pasien} {prep_lokasi} {lokasi}", slot_wajib=[TipeSlot.AGEN, TipeSlot.PASIEN, TipeSlot.LOKASI], slot_opsional=[TipeSlot.WAKTU, TipeSlot.CARA], prioritas=8, ), TemplatKalimat( nama="AKTIF_TRANS_TUJUAN", pola="{agen} {verba} {pasien} {prep_tujuan} {tujuan}", slot_wajib=[TipeSlot.AGEN, TipeSlot.PASIEN, TipeSlot.TUJUAN], slot_opsional=[TipeSlot.WAKTU], prioritas=8, ), TemplatKalimat( nama="AKTIF_TRANS_CARA", pola="{agen} {verba} {pasien} {prep_cara} {cara}", slot_wajib=[TipeSlot.AGEN, TipeSlot.PASIEN, TipeSlot.CARA], slot_opsional=[TipeSlot.WAKTU], prioritas=7, ), # ── Kalimat dasar S-P-O ─────────────────────────────────────────────── TemplatKalimat( nama="AKTIF_TRANS", pola="{agen} {verba} {pasien}", slot_wajib=[TipeSlot.AGEN, TipeSlot.PASIEN], slot_opsional=[TipeSlot.LOKASI, TipeSlot.WAKTU], prioritas=6, ), # ── Kalimat intransitif S-P-K ────────────────────────────────────────── TemplatKalimat( nama="INTR_LOKASI", pola="{agen} {verba} {prep_lokasi} {lokasi}", slot_wajib=[TipeSlot.AGEN, TipeSlot.LOKASI], slot_opsional=[TipeSlot.WAKTU], prioritas=5, ), TemplatKalimat( nama="INTR_TUJUAN", pola="{agen} {verba} {prep_tujuan} {tujuan}", slot_wajib=[TipeSlot.AGEN, TipeSlot.TUJUAN], slot_opsional=[TipeSlot.CARA], prioritas=5, ), TemplatKalimat( nama="INTR", pola="{agen} {verba}", slot_wajib=[TipeSlot.AGEN], slot_opsional=[TipeSlot.LOKASI, TipeSlot.WAKTU], prioritas=4, ), ] TEMPLAT_PASIF: List[TemplatKalimat] = [ TemplatKalimat( nama="PASIF_OLEH_LOKASI", pola="{pasien} {verba} oleh {agen} {prep_lokasi} {lokasi}", slot_wajib=[TipeSlot.PASIEN, TipeSlot.AGEN, TipeSlot.LOKASI], slot_opsional=[TipeSlot.WAKTU], prioritas=8, ), TemplatKalimat( nama="PASIF_OLEH", pola="{pasien} {verba} oleh {agen}", slot_wajib=[TipeSlot.PASIEN, TipeSlot.AGEN], slot_opsional=[TipeSlot.LOKASI, TipeSlot.WAKTU], prioritas=7, ), TemplatKalimat( nama="PASIF", pola="{pasien} {verba}", slot_wajib=[TipeSlot.PASIEN], slot_opsional=[TipeSlot.AGEN, TipeSlot.LOKASI], prioritas=5, ), ] TEMPLAT_ATRIBUSI: List[TemplatKalimat] = [ TemplatKalimat( nama="ATRIBUSI_ADALAH", pola="{atribut} adalah {tema}", slot_wajib=[TipeSlot.ATRIBUT, TipeSlot.TEMA], slot_opsional=[], prioritas=6, ), TemplatKalimat( nama="ATRIBUSI_MENJADI", pola="{atribut} menjadi {tema}", slot_wajib=[TipeSlot.ATRIBUT, TipeSlot.TEMA], slot_opsional=[TipeSlot.SEBAB], prioritas=7, ), ] # Template untuk merealisasikan inferensi KRL TEMPLAT_INFERENSI: Dict[str, str] = { "STATUS_MENJADI": "{subjek} menjadi {objek}", "WAJIB": "{subjek} wajib {objek}", "MEMILIKI": "{subjek} memiliki {objek}", "BERADA_DI": "{subjek} berada di {objek}", "TERLIBAT_DALAM": "{subjek} terlibat dalam {objek}", "MENYEBABKAN": "{subjek} menyebabkan {objek}", "KAUSAL": "{subjek} mengakibatkan {objek}", "TELAH_MEMUTUS": "{subjek} telah memutus {objek}", "BERTANGGUNG_JAWAB": "{subjek} bertanggung jawab atas {objek}", "BERHAK_ATAS": "{subjek} berhak atas {objek}", "DIPINDAHKAN": "{subjek} dipindahkan ke {objek}", } def pilih_templat( proposisi: Proposisi, pasif: bool = False, ) -> Optional[TemplatKalimat]: """ Pilih template yang paling sesuai untuk proposisi ini. Algoritma: 1. Tentukan set template yang relevan (aktif/pasif/atribusi) 2. Filter template yang semua slot_wajib-nya tersedia 3. Kembalikan template dengan prioritas tertinggi Returns: TemplatKalimat atau None jika tidak ada template yang cocok. """ slot_tersedia = set(proposisi.slot.keys()) # Kalimat atributif (aksi = "adalah" atau tidak ada verba) if proposisi.aksi in ("adalah", "merupakan", "ialah"): kandidat = TEMPLAT_ATRIBUSI elif pasif: kandidat = TEMPLAT_PASIF else: kandidat = TEMPLAT_AKTIF valid = [ t for t in kandidat if all(s in slot_tersedia for s in t.slot_wajib) ] if not valid: return None return max(valid, key=lambda t: t.prioritas) def isi_templat( templat: TemplatKalimat, proposisi: Proposisi, verba_bentuk: str, ) -> str: """ Isi template dengan nilai slot dari proposisi. Setiap placeholder {xxx} digantikan dengan nilai dari slot proposisi atau preposisi yang sesuai. Args: templat: template yang dipilih proposisi: proposisi sumber verba_bentuk: verba yang sudah direalisasikan oleh MorfologiRealizer Returns: Kalimat lengkap sebagai string. """ def nilai_slot(tipe: TipeSlot) -> str: s = proposisi.slot.get(tipe) return s.nilai if s else "" def prep(tipe: TipeSlot) -> str: return PREPOSISI_SLOT.get(tipe, "") mapping: Dict[str, str] = { "agen": nilai_slot(TipeSlot.AGEN), "verba": verba_bentuk, "pasien": nilai_slot(TipeSlot.PASIEN), "tema": nilai_slot(TipeSlot.TEMA) or nilai_slot(TipeSlot.PASIEN), "atribut": nilai_slot(TipeSlot.ATRIBUT) or nilai_slot(TipeSlot.AGEN), "penerima": nilai_slot(TipeSlot.PENERIMA), "lokasi": nilai_slot(TipeSlot.LOKASI), "tujuan": nilai_slot(TipeSlot.TUJUAN), "asal": nilai_slot(TipeSlot.ASAL), "cara": nilai_slot(TipeSlot.CARA), "sebab": nilai_slot(TipeSlot.SEBAB), "waktu": nilai_slot(TipeSlot.WAKTU), # Preposisi "prep_penerima": prep(TipeSlot.PENERIMA), "prep_lokasi": prep(TipeSlot.LOKASI), "prep_tujuan": prep(TipeSlot.TUJUAN), "prep_asal": prep(TipeSlot.ASAL), "prep_cara": prep(TipeSlot.CARA), "prep_sebab": prep(TipeSlot.SEBAB), "prep_waktu": prep(TipeSlot.WAKTU), } kalimat = templat.pola for kunci, nilai in mapping.items(): kalimat = kalimat.replace("{" + kunci + "}", nilai) # Bersihkan placeholder yang tidak terisi dan spasi ganda import re kalimat = re.sub(r"\{[^}]+\}", "", kalimat) kalimat = re.sub(r"\s{2,}", " ", kalimat).strip() # Kapitalisasi awal kalimat if kalimat: kalimat = kalimat[0].upper() + kalimat[1:] return kalimat