import os
import re
import time
import fitz
import gradio as gr
from transformers import pipeline
# Load model
ner = pipeline("ner", model="cahya/NusaBert-ner-v1.3", aggregation_strategy="simple")
# Label mapping
LABEL_MAP = {
"CRD": "Kardinal", "DAT": "Tanggal", "EVT": "Peristiwa",
"FAC": "Fasilitas", "GPE": "Entitas Geopolitik", "LAW": "Peraturan / Undang-Undang",
"LOC": "Lokasi", "MON": "Uang", "NOR": "Organisasi Politik",
"ORD": "Ordinal", "ORG": "Organisasi", "PER": "Orang",
"PRC": "Persentase", "PRD": "Produk", "QTY": "Kuantitas",
"REG": "Agama", "TIM": "Waktu", "WOA": "Karya Seni", "LAN": "Bahasa",
}
LABEL_HEX = {
"PER":"#FFBFBF","ORG":"#AEDDFF","NOR":"#8DC7FF","LOC":"#B8FFB8",
"GPE":"#99F2CC","FAC":"#D9FFA5","DAT":"#FFE58C","TIM":"#FFCC66",
"MON":"#CCFFDA","CRD":"#F2CCFF","ORD":"#E0BFFF","PRC":"#FFF2B2",
"QTY":"#C7F2F2","LAW":"#FFBABA","EVT":"#FFD9A5","PRD":"#BFDFFF",
"REG":"#E6DAFF","WOA":"#FFE6DA","LAN":"#CCFFF2",
}
MAX_PDF_PAGES = 5
MAX_CHUNK_CHARS = 2000
OVERLAP_CHARS = 150
EXAMPLES = [
("Contoh 1 – RUPS & Dana Cadangan",
"Berdasarkan Rapat Umum Pemegang Saham (RUPS) pada tanggal 24 Juni 2024 yang disahkan "
"oleh notaris Ashoya Ratam, S.H., M.Kn., Risalah No.124/VI/2024, Perusahaan memutuskan "
"antara lain menyisihkan 5% dari laba bersih untuk tahun yang berakhir 31 Desember 2023 "
"atau sebesar Rp5.299.075.507 sebagai dana cadangan jaminan."),
("Contoh 2 – Akta Jual Beli Saham PEFINDO",
"Berdasarkan Akta Notaris Melinda, S.Sos., S.H., M.Kn dengan No. 17 tanggal 21 Januari "
"2025, Perusahaan dan Dana Pensiun Pertamina telah menandatangani Akta Jual Beli saham "
"dan Perusahaan telah melakukan pembayaran penuh untuk pembelian 5.170 lembar saham "
"PEFINDO yang dimiliki Dana Pensiun Pertamina. Dengan demikian total kepemilikan saham "
"Perusahaan pada tanggal 21 Januari 2025 menjadi sebanyak 37.548 lembar saham atau sama "
"dengan 31,92% kepemilikan di PEFINDO."),
("Contoh 3 – Fasilitas Kredit Bank Permata",
"Pada tanggal 12 Desember 2022, PEI, entitas anak, dan PT Bank Permata Tbk "
"menandatangani perjanjian fasilitas money market dengan fasilitas kredit maksimum "
"sebesar Rp50.000.000.000. Pinjaman ini digunakan untuk keperluan stand by facility "
"dengan jangka waktu penarikan antara 3 (tiga) hari sampai dengan 3 (tiga) bulan "
"semenjak tanggal penarikan pinjaman dilakukan."),
("Contoh 4 – Dividen PEFINDO Biro Kredit",
"Berdasarkan Rapat Umum Pemegang Saham Tahunan tanggal 28 Juni 2024, pemegang saham "
"PEFINDO Biro Kredit menyetujui pembagian dividen untuk Perusahaan sebesar Rp6.637.962.683."),
("Contoh 5 – Regulasi Bursa Karbon",
"Peraturan Presiden RI No. 98 Tahun 2021 tentang Penyelenggaraan Nilai Ekonomi Karbon "
"untuk Pencapaian Target Kontribusi yang Ditetapkan Secara Nasional dan Pengendalian "
"Emisi Gas Rumah Kaca dalam Pembangunan Nasional mengatur mengenai mekanisme pencapaian "
"NDC. Undang-undang RI No. 4 Tahun 2023 tentang Pengembangan dan Penguatan Sektor "
"Keuangan menegaskan bahwa tugas pengaturan dan pengawasan bursa karbon dilakukan oleh "
"Otoritas Jasa Keuangan."),
]
# Helpers
def clean_word(word: str) -> str:
return word.replace("▁", " ").replace("##", "").strip()
def get_label_id(raw_label: str) -> str:
label_id = raw_label.replace("B-","").replace("I-","").replace("B_","").replace("I_","")
return label_id.split("-")[-1].upper().strip()
def highlight_html(text: str, entity_map: dict) -> str:
sorted_entities = sorted(entity_map.items(), key=lambda x: len(x[0]), reverse=True)
spans = []
used = [False] * len(text)
for entity_lower, label_id in sorted_entities:
if not entity_lower:
continue
pattern = re.compile(re.escape(entity_lower), re.IGNORECASE)
for m in pattern.finditer(text):
s, e = m.start(), m.end()
if any(used[i] for i in range(s, e)):
continue
spans.append((s, e, label_id))
for i in range(s, e):
used[i] = True
spans.sort(key=lambda x: x[0])
parts = []
cursor = 0
for s, e, label_id in spans:
if cursor < s:
parts.append(text[cursor:s].replace("\n", "
"))
hex_color = LABEL_HEX.get(label_id, "#e2e8f0")
label_idn = LABEL_MAP.get(label_id, label_id)
word = text[s:e]
parts.append(
f'{word}'
)
cursor = e
if cursor < len(text):
parts.append(text[cursor:].replace("\n", "
"))
return (
'
Masukkan teks terlebih dahulu.
" results = ner(text.strip()) if not results: return "Tidak ada entitas yang ditemukan.
" rows_html = "" row_num = 1 seen_words = set() for ent in results: raw_label = ent["entity_group"] label_id = get_label_id(raw_label) label_idn = LABEL_MAP.get(label_id, raw_label) word = clean_word(ent["word"]) if not word: continue word_key = word.lower() if word_key in seen_words: continue seen_words.add(word_key) hex_color = LABEL_HEX.get(label_id, "#e2e8f0") score = f"{ent['score']:.2%}" row_bg = "#f8faff" if row_num % 2 == 0 else "#ffffff" rows_html += f"""Tidak ada entitas yang ditemukan.
" return f"""| NO | KATA / FRASA | ENTITAS | SKOR |
|---|
Unggah file terlebih dahulu.
", "" file_path = upload_file if isinstance(upload_file, str) else upload_file.name ext = os.path.splitext(file_path)[-1].lower() if ext == ".txt": with open(file_path, "r", encoding="utf-8", errors="replace") as f: full_text = f.read() page_count = 1 elif ext == ".pdf": doc = fitz.open(file_path) page_count = len(doc) if page_count > MAX_PDF_PAGES: doc.close() return ( f"PDF terlalu banyak halaman " f"({page_count}). Maks {MAX_PDF_PAGES} halaman.
", "" ) full_text = "\n\n".join(page.get_text() for page in doc) doc.close() else: return "Format tidak didukung.
", "" # Chunking chunks = [] start = 0 while start < len(full_text): end = min(start + MAX_CHUNK_CHARS, len(full_text)) chunks.append(full_text[start:end]) if end == len(full_text): break start = end - OVERLAP_CHARS # NER all_ner_results = [] for chunk in chunks: all_ner_results.extend(ner(chunk.strip())) if not all_ner_results: return "Tidak ada entitas ditemukan.
", "" # Bangun entity_map entity_map: dict[str, str] = {} for ent in all_ner_results: word = clean_word(ent["word"]) if len(word) < 2: continue label_id = get_label_id(ent["entity_group"]) w_lower = word.lower() if w_lower not in entity_map: entity_map[w_lower] = label_id if not entity_map: return "Tidak ada entitas ditemukan.
", "" highlighted = highlight_html(full_text, entity_map) # Badge legend found_labels = set(entity_map.values()) badges = "".join( f'' f'{LABEL_MAP.get(l, l)}' for l in sorted(found_labels) ) legend_html = f'Tugas Kelompok · NLP & Text Mining
Implementasi Named Entity Recognition pada Kumpulan
Laporan-laporan Keuangan Bahasa Indonesia
Masukkan teks lalu klik Lakukan Analisis.
") with gr.Tab("Analisis File"): with gr.Column(elem_id="center-col"): gr.HTML('' 'Unggah file .pdf (maks 5 halaman) atau .txt.
' ) gr.HTML('