| """ |
| 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_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 |
|
|
|
|
| |
| |
| |
|
|
| TEMPLAT_AKTIF: List[TemplatKalimat] = [ |
|
|
| |
| 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, |
| ), |
|
|
| |
| TemplatKalimat( |
| nama="AKTIF_TRANS", |
| pola="{agen} {verba} {pasien}", |
| slot_wajib=[TipeSlot.AGEN, TipeSlot.PASIEN], |
| slot_opsional=[TipeSlot.LOKASI, TipeSlot.WAKTU], |
| prioritas=6, |
| ), |
|
|
| |
| 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, |
| ), |
| ] |
|
|
| |
| 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()) |
|
|
| |
| 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), |
| |
| "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) |
|
|
| |
| import re |
| kalimat = re.sub(r"\{[^}]+\}", "", kalimat) |
| kalimat = re.sub(r"\s{2,}", " ", kalimat).strip() |
|
|
| |
| if kalimat: |
| kalimat = kalimat[0].upper() + kalimat[1:] |
|
|
| return kalimat |
|
|