from pathlib import Path from zipfile import ZIP_DEFLATED, ZipFile import tempfile import xml.etree.ElementTree as ET SOURCE_DOC = Path("docs/Penjelasan_Rinci_KMeans_Rekomendasi_JMW_Gabungan_Update.docx") OUT_DOC = Path("docs/Penjelasan_Rinci_KMeans_Rekomendasi_JMW_Gabungan_Update_K.docx") WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" ET.register_namespace("w", WORD_NS) def qn(name): return f"{{{WORD_NS}}}{name}" def paragraph(text="", style=None): p = ET.Element(qn("p")) if style: p_pr = ET.SubElement(p, qn("pPr")) p_style = ET.SubElement(p_pr, qn("pStyle")) p_style.set(qn("val"), style) run = ET.SubElement(p, qn("r")) t = ET.SubElement(run, qn("t")) t.set("{http://www.w3.org/XML/1998/namespace}space", "preserve") t.text = text return p def code_paragraph(text): p = ET.Element(qn("p")) p_pr = ET.SubElement(p, qn("pPr")) p_style = ET.SubElement(p_pr, qn("pStyle")) p_style.set(qn("val"), "Code") run = ET.SubElement(p, qn("r")) t = ET.SubElement(run, qn("t")) t.set("{http://www.w3.org/XML/1998/namespace}space", "preserve") t.text = text return p def page_break(): p = ET.Element(qn("p")) r = ET.SubElement(p, qn("r")) br = ET.SubElement(r, qn("br")) br.set(qn("type"), "page") return p def code_block(text): return [code_paragraph(line) for line in text.strip("\n").splitlines()] def new_section(): items = [] items.append(page_break()) items.append(paragraph("18. Cara Menentukan Nilai k pada K-Means", "Heading1")) items.append(paragraph( "Nilai k pada K-Means menunjukkan jumlah cluster atau jumlah kelompok yang akan dibentuk. Dalam sistem " "rekomendasi mobil JMW Showroom, nilai k digunakan untuk membagi data mobil ke dalam beberapa kelompok " "berdasarkan kemiripan harga, tahun, dan mesin_cc." )) items.append(paragraph("18.1 Pengertian Nilai k", "Heading2")) items.append(paragraph( "Jika k bernilai 4, maka sistem akan membentuk empat kelompok mobil. Setiap mobil akan masuk ke salah satu " "cluster berdasarkan jarak terdekat terhadap centroid. Semakin tepat nilai k yang digunakan, semakin baik pula " "pembagian kelompok mobil yang dihasilkan." )) items.append(paragraph("18.2 Nilai k pada Implementasi Sistem", "Heading2")) items.append(paragraph( "Pada implementasi sistem, nilai k yang digunakan adalah 4. Nilai ini dikirim oleh Laravel ke Flask API melalui " "parameter jumlah_cluster. Dengan demikian, ketika fitur rekomendasi budget dijalankan, Flask API akan membuat " "model K-Means dengan empat cluster." )) items.extend(code_block(""" 'filter' => [ 'toleransi_harga_percent' => 10, 'jumlah_cluster' => 4, ], """)) items.append(paragraph( "Parameter tersebut diterima oleh Flask API dan digunakan pada fungsi apply_kmeans_budget_filter(). Jika " "parameter jumlah_cluster tersedia, maka nilai tersebut dipakai sebagai jumlah cluster. Jika tidak tersedia, " "sistem menggunakan nilai default 4." )) items.extend(code_block(""" model = build_kmeans_model( cars, cluster_count=to_int(filters.get("jumlah_cluster"), default=4) ) """)) items.append(paragraph("18.3 Pengujian Nilai k Menggunakan Silhouette Score", "Heading2")) items.append(paragraph( "Agar nilai k tidak dipilih secara sembarang, jumlah cluster dapat diuji menggunakan silhouette score. " "Silhouette score digunakan untuk mengukur seberapa baik data berada di dalam cluster-nya dan seberapa jauh " "data tersebut dari cluster lain. Nilai silhouette yang lebih tinggi menunjukkan hasil cluster yang lebih baik." )) items.extend(code_block(""" for k in range(2, 7): kmeans = KMeans( n_clusters=k, random_state=42, n_init=10 ) labels = kmeans.fit_predict(fitur_scaled) score = silhouette_score(fitur_scaled, labels) print(f"Jumlah Cluster: {k}, Silhouette Score: {score}") """)) items.append(paragraph( "Kode tersebut mencoba beberapa nilai k, yaitu 2 sampai 6. Setiap nilai k diuji dengan K-Means, kemudian " "hasil cluster-nya dievaluasi menggunakan silhouette score. Dari hasil tersebut, peneliti dapat melihat nilai " "k mana yang memberikan kualitas cluster terbaik." )) items.append(paragraph("18.4 Contoh Hasil Pengujian", "Heading2")) items.append(paragraph( "Misalnya hasil pengujian silhouette score menghasilkan nilai sebagai berikut:" )) items.extend(code_block(""" Jumlah Cluster: 2, Silhouette Score: 0,2281 Jumlah Cluster: 3, Silhouette Score: 0,2345 Jumlah Cluster: 4, Silhouette Score: 0,2294 Jumlah Cluster: 5, Silhouette Score: 0,2394 Jumlah Cluster: 6, Silhouette Score: 0,2570 """)) items.append(paragraph( "Dari contoh tersebut, k = 6 memiliki nilai silhouette score tertinggi. Namun pada implementasi sistem, k = 4 " "digunakan karena sistem ingin membagi mobil ke dalam empat kelompok utama yang lebih mudah diterapkan pada " "fitur rekomendasi budget, misalnya kelompok mobil ekonomis, menengah, tinggi, dan premium. Pemilihan k = 4 " "juga disesuaikan dengan konfigurasi yang sudah digunakan pada Flask API." )) items.append(paragraph("18.5 Alasan Sistem Menggunakan k = 4", "Heading2")) items.append(paragraph( "Nilai k = 4 digunakan karena pembagian empat cluster dianggap cukup untuk merepresentasikan kelompok mobil " "berdasarkan harga, tahun, dan mesin_cc. Dengan empat cluster, sistem dapat memisahkan mobil ke dalam beberapa " "kelompok karakteristik tanpa membuat pembagian terlalu banyak dan sulit diinterpretasikan." )) items.append(paragraph( "Secara sederhana, empat cluster dapat diinterpretasikan sebagai kelompok mobil dengan karakteristik berikut: " "cluster harga rendah atau ekonomis, cluster harga menengah, cluster harga tinggi, dan cluster premium atau " "spesifikasi lebih tinggi. Nama atau karakteristik akhir setiap cluster tetap perlu dilihat dari rata-rata " "data pada masing-masing cluster." )) items.append(paragraph("18.6 Kalimat untuk Laporan", "Heading2")) items.append(paragraph( "Nilai k pada metode K-Means ditentukan dengan mempertimbangkan hasil pengujian jumlah cluster dan kebutuhan " "implementasi sistem. Pada sistem ini, jumlah cluster yang digunakan adalah k = 4. Nilai tersebut digunakan " "untuk membagi data mobil menjadi empat kelompok berdasarkan atribut harga, tahun, dan mesin_cc. Pemilihan " "k = 4 juga disesuaikan dengan konfigurasi sistem rekomendasi pada Flask API melalui parameter " "jumlah_cluster = 4." )) return items def append_section(): OUT_DOC.parent.mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory() as tmp: tmp_dir = Path(tmp) with ZipFile(SOURCE_DOC, "r") as archive: archive.extractall(tmp_dir) document_path = tmp_dir / "word" / "document.xml" root = ET.parse(document_path).getroot() body = root.find(qn("body")) sect_pr = None for child in list(body): if child.tag == qn("sectPr"): sect_pr = child body.remove(child) break for item in new_section(): body.append(item) if sect_pr is not None: body.append(sect_pr) ET.ElementTree(root).write(document_path, encoding="utf-8", xml_declaration=True) if OUT_DOC.exists(): OUT_DOC.unlink() with ZipFile(OUT_DOC, "w", ZIP_DEFLATED) as archive: for path in tmp_dir.rglob("*"): if path.is_file(): archive.write(path, path.relative_to(tmp_dir).as_posix()) print(OUT_DOC.resolve()) if __name__ == "__main__": append_section()