commit cbc21d9155f962f38c29e824620475da179f25ce Author: farrel Date: Mon Nov 2 11:26:54 2026 +0700 tugas diff --git a/Checklist_Pengembangan_Aplikasi.pdf b/Checklist_Pengembangan_Aplikasi.pdf new file mode 100644 index 0000000..30ca7e5 Binary files /dev/null and b/Checklist_Pengembangan_Aplikasi.pdf differ diff --git a/LAPORAN STATISTIKA MODUL 1 REVISI kel 3.pdf b/LAPORAN STATISTIKA MODUL 1 REVISI kel 3.pdf new file mode 100644 index 0000000..0fb0c14 Binary files /dev/null and b/LAPORAN STATISTIKA MODUL 1 REVISI kel 3.pdf differ diff --git a/Panduan_Python_Dasar.pdf b/Panduan_Python_Dasar.pdf new file mode 100644 index 0000000..b6f0e67 --- /dev/null +++ b/Panduan_Python_Dasar.pdf @@ -0,0 +1,99 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 0 R /F3 4 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +5 0 obj +<< +/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/PageMode /UseNone /Pages 9 0 R /Type /Catalog +>> +endobj +8 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102034842+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102034842+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False +>> +endobj +9 0 obj +<< +/Count 2 /Kids [ 5 0 R 6 0 R ] /Type /Pages +>> +endobj +10 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1516 +>> +stream +Gatm;gMZ%0&:Ml+%/cp.%Th.c"^3%,1qO:1Bptt($M]=\BSV^<\WhaiJ);Cq>OjAnU4E?uiaq8rR574!JrLjeT$%c)ErqtO.8U84'i9s9OF2]:4=T:d5*Bq-Il!5^^m;;es'tp)&![>Y8Zt.3p3Ahc[j^Jo$PI_hZjMb3*Z'[i7@CsPU[-SLidit5CJ!doS6WN+Vm7ucK81#HaIY=u)B'=i[`1PO^_$e_bsQEL]2<^7m1:pq71b='L=N#[L^)ojDFof4X-\!#k:W(Y`3;c2Q8)M_XD3es`78lgj10Q\aY9Ph]e2M[eU\]RUcdhYI7*%.Q%hS!g_=_q"Bt#C.fupZY6WjK>eZ)9KC;\P9G%!C;m+c-%G3oZWjZq%89J=R&$'SI5IU0!_89r3?8!qoa6QS>Vq.d0hBY't;(fA"9?DGIWi4S)]/*cOOql]W-MYZ/>4n_??m&;u#7S%p>?97$EB-<&?5T5+M&5NV`'$kP`);1o@%r%Jkt=M7rk=]^_!Hoo/IT&*K8aHID*1Q#"Dq$MtqC\(`%YbpKH*lWl*@6Yn(V-!Jtr)Q>P59Wp2S6U89;B5@M[!%%K>VA92945Lf+'l%:rl>u!ACX[7r+^GZGbEad5,[7-#0P]rtF6p=g[%Mj/b5A1W"=1#SD:r1^lC[l7^B_5_Zoo*53@\R+uaSd3OG4;<2)(=:;4g3T[:Tl;I+NLck<@@Y.Z`XE&5dKBo,nV'Ep&1fG`9Qe@p'U^uRj4^5'i3np:>.j$W2h`hq?7ir?scZ@DhSp&"+odduD/'a0`/Lmm7W8`V3_mu3i_?I!bjZ]Q22<(I%!qegYM%e`UU*.r&X$QERNdm8#hk8&66ZG]8"D&]gKgqcO2A#\G-RE[Dr(~>endstream +endobj +11 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1002 +>> +stream +Gau0B95iQ=%)1n+&34;hTN[@_.^2Rn[6&JcXgr:1d#%$9[Ui7#2[%%tq^CQ'[nG*?:]qO:8UPpH7ku%cqJj8&C4N?F>hj;V+j;$Y7kB8f*IOBWob)K;VuSn0Iu_Y!B"K)#@ErnD04o@XAOPnKRVQWDr9@`48@W8Fb<](f?AtZ-B%Wt(,ZIb!:n;3A&]%Ia%a,u('JF"\W/W,IUfcJ]c5>:Vh=7?7)NNnc!GR@rpSu4JG,kG!,k,HJ.Ic@j(gef\T6IT,*MntF_S7b9SVU\32@Dpg77%ug2'AX5TnnNFcVkNpo+M2_VFUFRU^ZCcmIiY'_g!@8`_R>oEK5^DG2IS6SmYe=*3jL@ER?5PII=S_U>fi\;]+Lq.2T"Q6A8DMH><:b#F^)tn;\hI._(u@\7+p=I+4tfqpa\d5iNUkeE^8u`Mc`\b[O(=gaJKdN^oUKbEo3TatK&(A!X5HsZmoXDO-i*Caop6,rN-*2s\SW?%hBW=HNof7h\jR!Na0=TPsM!r]j'i[o$8qa&5rQqCTVs[\sY;=hI1?kK/:R2p$uNXendstream +endobj +xref +0 12 +0000000000 65535 f +0000000061 00000 n +0000000112 00000 n +0000000219 00000 n +0000000331 00000 n +0000000436 00000 n +0000000640 00000 n +0000000844 00000 n +0000000912 00000 n +0000001192 00000 n +0000001257 00000 n +0000002865 00000 n +trailer +<< +/ID +[<8f92dc9b38dcbce41e380e8ab68ab56d><8f92dc9b38dcbce41e380e8ab68ab56d>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 8 0 R +/Root 7 0 R +/Size 12 +>> +startxref +3959 +%%EOF diff --git a/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/data_level0.bin b/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/data_level0.bin new file mode 100644 index 0000000..4ba5e3f Binary files /dev/null and b/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/data_level0.bin differ diff --git a/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/header.bin b/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/header.bin differ diff --git a/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/length.bin b/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/length.bin new file mode 100644 index 0000000..796705d --- /dev/null +++ b/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/length.bin @@ -0,0 +1 @@ +INSERT INTO "embedding_metadata" ("id", "key", "string_value", "int_value", "float_value", "bool_value") VALUES (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) ON CONFLICT ("id", "key") DO UPDATE SET "string_value" = "excluded"."string_value", "int_value" = "excluded"."int_value", "float_value" = "excluded"."float_value", "bool_value" = "excluded"."bool_value"ÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/link_lists.bin b/chroma_db/07794b5c-0c35-4e2c-844f-9a4c5e14d754/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/data_level0.bin b/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/data_level0.bin new file mode 100644 index 0000000..376d97f Binary files /dev/null and b/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/data_level0.bin differ diff --git a/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/header.bin b/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/header.bin differ diff --git a/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/length.bin b/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/length.bin new file mode 100644 index 0000000..c513b27 --- /dev/null +++ b/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/length.bin @@ -0,0 +1 @@ +invalid type: string "1. Histogram Data Berat Air Mineral Vit \n \n \nGambar 22. Tabel Data Kelompok Berat Air Mineral Aqua \n \n \nGambar 23. Histogram Data Berat Air Mineral Aqua \n \n\n \nGambar 24. Pengolahan Data Hasil Kuisioner \n \nGambar 25. Tabel Urutan Data Hasil Kuisioner \n \n \n \n \n \n\nLAMPIRAN 2 \n \n \n \n \n\n", expected i64mart \n \n \nadan Mahasiswa UKWMS Angkatan 2025 \n \n \ No newline at end of file diff --git a/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/link_lists.bin b/chroma_db/15de88b1-252d-4baa-aa02-c1b460ac7986/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/data_level0.bin b/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/data_level0.bin new file mode 100644 index 0000000..8092723 Binary files /dev/null and b/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/data_level0.bin differ diff --git a/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/header.bin b/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/header.bin differ diff --git a/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/length.bin b/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/length.bin new file mode 100644 index 0000000..4b8cd38 Binary files /dev/null and b/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/length.bin differ diff --git a/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/link_lists.bin b/chroma_db/520f237e-d1bc-467a-88ec-dc8708a7a800/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/data_level0.bin b/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/data_level0.bin new file mode 100644 index 0000000..d74a1c9 Binary files /dev/null and b/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/data_level0.bin differ diff --git a/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/header.bin b/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/header.bin differ diff --git a/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/length.bin b/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/length.bin new file mode 100644 index 0000000..60efc98 Binary files /dev/null and b/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/length.bin differ diff --git a/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/link_lists.bin b/chroma_db/8e59f3b0-8d78-4272-ab21-2e1314f12941/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/data_level0.bin b/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/data_level0.bin new file mode 100644 index 0000000..1fadffa Binary files /dev/null and b/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/data_level0.bin differ diff --git a/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/header.bin b/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/header.bin differ diff --git a/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/length.bin b/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/length.bin new file mode 100644 index 0000000..93b0993 Binary files /dev/null and b/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/length.bin differ diff --git a/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/link_lists.bin b/chroma_db/9697631e-70e0-4260-9d7b-582c5d3d5d21/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/data_level0.bin b/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/data_level0.bin new file mode 100644 index 0000000..80a048e Binary files /dev/null and b/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/data_level0.bin differ diff --git a/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/header.bin b/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/header.bin differ diff --git a/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/length.bin b/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/length.bin new file mode 100644 index 0000000..cb3e162 Binary files /dev/null and b/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/length.bin differ diff --git a/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/link_lists.bin b/chroma_db/99bf254c-f4f3-4783-8e48-2fd747241958/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/data_level0.bin b/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/data_level0.bin new file mode 100644 index 0000000..841da97 Binary files /dev/null and b/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/data_level0.bin differ diff --git a/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/header.bin b/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/header.bin differ diff --git a/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/length.bin b/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/length.bin new file mode 100644 index 0000000..796705d --- /dev/null +++ b/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/length.bin @@ -0,0 +1 @@ +INSERT INTO "embedding_metadata" ("id", "key", "string_value", "int_value", "float_value", "bool_value") VALUES (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) ON CONFLICT ("id", "key") DO UPDATE SET "string_value" = "excluded"."string_value", "int_value" = "excluded"."int_value", "float_value" = "excluded"."float_value", "bool_value" = "excluded"."bool_value"ÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/link_lists.bin b/chroma_db/b252a69a-efaf-478b-ace0-33cba9ada47c/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/chroma.sqlite3 b/chroma_db/chroma.sqlite3 new file mode 100644 index 0000000..8c3d72a Binary files /dev/null and b/chroma_db/chroma.sqlite3 differ diff --git a/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/data_level0.bin b/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/data_level0.bin new file mode 100644 index 0000000..83d5c0e Binary files /dev/null and b/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/data_level0.bin differ diff --git a/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/header.bin b/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/header.bin differ diff --git a/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/length.bin b/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/length.bin new file mode 100644 index 0000000..0c130ea Binary files /dev/null and b/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/length.bin differ diff --git a/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/link_lists.bin b/chroma_db/e46dc327-e5c2-45f3-9363-a80f3c5f804a/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/data_level0.bin b/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/data_level0.bin new file mode 100644 index 0000000..758f44d Binary files /dev/null and b/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/data_level0.bin differ diff --git a/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/header.bin b/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/header.bin differ diff --git a/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/length.bin b/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/length.bin new file mode 100644 index 0000000..796705d --- /dev/null +++ b/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/length.bin @@ -0,0 +1 @@ +INSERT INTO "embedding_metadata" ("id", "key", "string_value", "int_value", "float_value", "bool_value") VALUES (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) ON CONFLICT ("id", "key") DO UPDATE SET "string_value" = "excluded"."string_value", "int_value" = "excluded"."int_value", "float_value" = "excluded"."float_value", "bool_value" = "excluded"."bool_value"ÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/link_lists.bin b/chroma_db/f85eac34-ef66-4593-8616-0518f33ed14b/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/sesi 11 AI.py b/sesi 11 AI.py new file mode 100644 index 0000000..b9a1e91 --- /dev/null +++ b/sesi 11 AI.py @@ -0,0 +1,548 @@ +import os +from pathlib import Path + +import chromadb +from chromadb.utils import embedding_functions +from pypdf import PdfReader + + +# ===================================================== +# KONFIGURASI PROGRAM +# ===================================================== + +BASE_DIR = Path(__file__).resolve().parent +DB_PATH = str(BASE_DIR / "chroma_db") + +UKURAN_CHUNK = [500, 1000, 1500] +OVERLAP_PERSEN = 0.10 + +MODEL_EMBEDDING = "paraphrase-multilingual-MiniLM-L12-v2" + +print("Memuat model embedding...") +embedding = embedding_functions.SentenceTransformerEmbeddingFunction( + model_name=MODEL_EMBEDDING +) + +client = chromadb.PersistentClient(path=DB_PATH) + + +# ===================================================== +# FUNGSI MEMBACA PDF +# ===================================================== + +def ambil_file_pdf(): + return list(BASE_DIR.glob("*.pdf")) + + +def baca_pdf(path): + reader = PdfReader(str(path)) + teks = "" + + for halaman in reader.pages: + teks += (halaman.extract_text() or "") + "\n" + + return teks + + +def pilih_pdf(files): + """Memilih satu PDF agar eksperimen konsisten.""" + if not files: + print("Belum ada file PDF!") + print("Letakkan PDF di folder yang sama dengan file Python ini.") + return None + + if len(files) == 1: + return files[0] + + print("\nDaftar dokumen PDF:") + for i, file in enumerate(files, 1): + print(f"{i}. {file.name}") + + while True: + pilihan = input("Pilih nomor PDF: ").strip() + if pilihan.isdigit() and 1 <= int(pilihan) <= len(files): + return files[int(pilihan) - 1] + print("Pilihan tidak valid.") + + +# ===================================================== +# FUNGSI CHUNKING +# ===================================================== + +def chunking(teks, ukuran=1000, overlap=100): + if ukuran <= 0 or overlap < 0 or overlap >= ukuran: + raise ValueError("Ukuran atau overlap tidak valid.") + + chunks = [] + start = 0 + + while start < len(teks): + akhir = min(start + ukuran, len(teks)) + potongan = teks[start:akhir] + + if potongan.strip(): + chunks.append(potongan) + + start += ukuran - overlap + + return chunks + + +# ===================================================== +# LAB 1 - EKSPERIMEN CHUNKING +# ===================================================== + +def lab_chunking(): + print("\n" + "=" * 60) + print("LAB 1 - EKSPERIMEN CHUNKING") + print("=" * 60) + + file = pilih_pdf(ambil_file_pdf()) + if file is None: + return + + teks = baca_pdf(file) + print(f"\nDokumen : {file.name}") + print(f"Panjang teks : {len(teks)} karakter") + + if not teks.strip(): + print("Dokumen tidak memiliki teks yang dapat dibaca.") + return + + for ukuran in UKURAN_CHUNK: + for persentase in [0.10, 0.20]: + overlap = int(ukuran * persentase) + chunks = chunking(teks, ukuran, overlap) + + print("\n" + "-" * 55) + print(f"Ukuran chunk : {ukuran}") + print(f"Overlap : {overlap} ({persentase:.0%})") + print(f"Jumlah chunk : {len(chunks)}") + print("Tiga contoh chunk:") + + # Tampilkan maksimal tiga contoh chunk. + # Pemilihan posisi dibuat tersebar agar tidak selalu awal dokumen. + if len(chunks) <= 3: + indeks = list(range(len(chunks))) + else: + indeks = [0, len(chunks) // 2, len(chunks) - 1] + + for nomor, idx in enumerate(indeks, 1): + print(f"\nContoh {nomor} (Chunk {idx + 1}):") + print(chunks[idx][:350]) + + print("\nEksperimen chunking selesai!") + + +# ===================================================== +# FUNGSI MEMBANGUN KOLEKSI +# ===================================================== + +def bangun_koleksi(nama_koleksi, ukuran_chunk, file_pdf, reset=True): + """Membangun koleksi ChromaDB dari satu dokumen PDF.""" + teks = baca_pdf(file_pdf) + + if not teks.strip(): + print("PDF kosong atau tidak dapat dibaca.") + return None + + overlap = int(ukuran_chunk * OVERLAP_PERSEN) + chunks = chunking(teks, ukuran_chunk, overlap) + + if reset: + try: + client.delete_collection(nama_koleksi) + except Exception: + pass + + collection = client.create_collection( + name=nama_koleksi, + embedding_function=embedding, + metadata={"hnsw:space": "cosine"} + ) + + ids = [f"chunk_{i}" for i in range(len(chunks))] + metadatas = [ + { + "sumber": file_pdf.name, + "nomor_chunk": i, + "ukuran_chunk": ukuran_chunk + } + for i in range(len(chunks)) + ] + + batch_size = 100 + for start in range(0, len(chunks), batch_size): + end = start + batch_size + collection.upsert( + ids=ids[start:end], + documents=chunks[start:end], + metadatas=metadatas[start:end] + ) + + return collection + + +# ===================================================== +# LAB 2 - MEMBANGUN INDEKS CHROMADB +# ===================================================== + +def lab_indexing(): + print("\n" + "=" * 60) + print("LAB 2 - INDEXING CHROMADB") + print("=" * 60) + + file = pilih_pdf(ambil_file_pdf()) + if file is None: + return + + print(f"\nMembaca: {file.name}") + collection = bangun_koleksi( + nama_koleksi="rag_part1", + ukuran_chunk=1000, + file_pdf=file, + reset=True + ) + + if collection is None: + return + + print("\nIndexing berhasil!") + print("Ukuran chunk :", 1000) + print("Total chunk :", collection.count()) + print("Database :", DB_PATH) + + +# ===================================================== +# MENGAMBIL KOLEKSI CHROMADB +# ===================================================== + +def ambil_koleksi(): + try: + return client.get_collection( + name="rag_part1", + embedding_function=embedding + ) + except Exception: + print("\nDatabase belum tersedia!") + print("Silakan jalankan Lab 2 terlebih dahulu.") + return None + + +# ===================================================== +# FUNGSI PENCARIAN DOKUMEN +# ===================================================== + +def cari_dokumen(pertanyaan, k=3, tampilkan=True, collection=None): + if collection is None: + collection = ambil_koleksi() + + if collection is None: + return [] + + total = collection.count() + if total == 0: + print("Database masih kosong!") + return [] + + k = min(k, total) + + hasil = collection.query( + query_texts=[pertanyaan], + n_results=k + ) + + dokumen = hasil["documents"][0] + metadata = hasil["metadatas"][0] + jarak = hasil["distances"][0] + + if tampilkan: + print("\nPertanyaan:", pertanyaan) + print("\nHasil pencarian:") + + for i, doc in enumerate(dokumen): + print("\n" + "-" * 45) + print(f"HASIL KE-{i + 1}") + print("-" * 45) + print("Sumber:", metadata[i].get("sumber", "-")) + print("Chunk:", metadata[i].get("nomor_chunk", "-")) + print("Jarak:", round(jarak[i], 4)) + print("Isi:") + print(doc[:500]) + + return dokumen + + +# ===================================================== +# LAB 2 - PENCARIAN DOKUMEN +# ===================================================== + +def lab_pencarian(): + print("\n" + "=" * 60) + print("LAB 2 - PENCARIAN DOKUMEN") + print("=" * 60) + + collection = ambil_koleksi() + if collection is None or collection.count() == 0: + print("Silakan lakukan indexing terlebih dahulu.") + return + + print("\nMasukkan pertanyaan untuk mencari informasi.") + print("Ketik '0' untuk kembali ke menu.") + + while True: + pertanyaan = input("\nPertanyaan: ").strip() + + if pertanyaan == "0": + break + + if not pertanyaan: + print("Pertanyaan tidak boleh kosong.") + continue + + cari_dokumen(pertanyaan, k=3, collection=collection) + + +# ===================================================== +# LAB 2 - PENGUJIAN PARAFRASE +# ===================================================== + +def lab_parafrase(): + print("\n" + "=" * 60) + print("LAB 2 - PENGUJIAN PARAFRASE") + print("=" * 60) + + collection = ambil_koleksi() + if collection is None or collection.count() == 0: + print("Silakan lakukan indexing terlebih dahulu.") + return + + pertanyaan_asli = input("\nPertanyaan asli: ").strip() + if not pertanyaan_asli: + print("Pertanyaan tidak boleh kosong.") + return + + pertanyaan_parafrase = input("Pertanyaan parafrase: ").strip() + if not pertanyaan_parafrase: + print("Parafrase tidak boleh kosong.") + return + + print("\n--- HASIL PERTANYAAN ASLI ---") + cari_dokumen(pertanyaan_asli, k=3, collection=collection) + + print("\n--- HASIL PERTANYAAN PARAFRASE ---") + cari_dokumen(pertanyaan_parafrase, k=3, collection=collection) + + +# ===================================================== +# LAB 3 - EVALUASI HIT@K +# ===================================================== + +def buat_koleksi_evaluasi(ukuran_chunk, file_pdf): + nama_koleksi = f"rag_eval_{ukuran_chunk}" + + print(f"\nMembangun koleksi {nama_koleksi}...") + + collection = bangun_koleksi( + nama_koleksi=nama_koleksi, + ukuran_chunk=ukuran_chunk, + file_pdf=file_pdf, + reset=True + ) + + if collection is not None: + print("Indexing selesai.") + print("Jumlah chunk:", collection.count()) + + return collection + + +def hitung_hit_at_k(collection, data_uji, k): + jumlah_hit = 0 + total = len(data_uji) + + for nomor, (pertanyaan, keyword) in enumerate(data_uji, 1): + jumlah_dokumen = min(k, collection.count()) + + hasil = collection.query( + query_texts=[pertanyaan], + n_results=jumlah_dokumen + ) + + dokumen = hasil["documents"][0] + + # Hit jika minimal satu dari K dokumen teratas + # memuat kata/frasa kunci jawaban. + ditemukan = any( + keyword.casefold() in teks.casefold() + for teks in dokumen + ) + + if ditemukan: + jumlah_hit += 1 + + status = "HIT" if ditemukan else "MISS" + print(f"{nomor:02d}. {status} | {pertanyaan}") + + skor = jumlah_hit / total if total else 0 + return jumlah_hit, total, skor + + +def lab_evaluasi(): + print("\n" + "=" * 60) + print("LAB 3 - EVALUASI HIT@K") + print("=" * 60) + + print(""" +Evaluasi menggunakan: +- 15 pertanyaan uji +- Dua koleksi: chunk 500 dan 1000 karakter +- Overlap 10% +- Hit@1, Hit@3, dan Hit@5 + +Masukkan kata/frasa yang benar-benar terdapat +pada bagian dokumen yang menjawab pertanyaan. +""") + + file_pdf = pilih_pdf(ambil_file_pdf()) + if file_pdf is None: + return + + data_uji = [] + + for i in range(15): + print("\n" + "-" * 50) + print(f"PERTANYAAN KE-{i + 1}") + pertanyaan = input("Pertanyaan: ").strip() + keyword = input("Kata kunci jawaban: ").strip() + + if not pertanyaan or not keyword: + print("Pertanyaan dan kata kunci wajib diisi.") + print("Evaluasi dibatalkan. Jalankan kembali menu 5.") + return + + data_uji.append((pertanyaan, keyword)) + + # Bangun dua koleksi dari PDF yang sama. + koleksi_500 = buat_koleksi_evaluasi(500, file_pdf) + if koleksi_500 is None: + return + + koleksi_1000 = buat_koleksi_evaluasi(1000, file_pdf) + if koleksi_1000 is None: + return + + koleksi = { + "Chunk 500": koleksi_500, + "Chunk 1000": koleksi_1000 + } + + hasil_evaluasi = {} + + for nama, collection in koleksi.items(): + print("\n" + "=" * 60) + print(f"EVALUASI {nama}") + print("=" * 60) + + hasil_evaluasi[nama] = {} + + for k in [1, 3, 5]: + print(f"\n--- PENGUJIAN HIT@{k} ---") + + hit, total, skor = hitung_hit_at_k( + collection, + data_uji, + k + ) + + hasil_evaluasi[nama][k] = { + "hit": hit, + "total": total, + "skor": skor + } + + print(f"\nHit@{k} = {hit}/{total} = {skor:.2%}") + + # Tampilkan perbandingan. + print("\n" + "=" * 68) + print("TABEL PERBANDINGAN HASIL HIT@K") + print("=" * 68) + + print( + f"{'Ukuran Chunk':<18}" + f"{'Hit@1':<16}" + f"{'Hit@3':<16}" + f"{'Hit@5':<16}" + ) + print("-" * 68) + + for nama in koleksi: + h1 = hasil_evaluasi[nama][1]["skor"] + h3 = hasil_evaluasi[nama][3]["skor"] + h5 = hasil_evaluasi[nama][5]["skor"] + + print( + f"{nama:<18}" + f"{h1:<16.2%}" + f"{h3:<16.2%}" + f"{h5:<16.2%}" + ) + + print("-" * 68) + print(""" +Catatan: +Hit@K adalah proporsi pertanyaan yang memiliki +minimal satu dokumen relevan pada K hasil teratas. + +Evaluasi ini menggunakan pencocokan kata/frasa +kunci sederhana, bukan penilaian relevansi manual. +Periksa kembali hasil MISS dan kecocokan keyword +agar kesimpulan praktikum lebih akurat. +""") + + print("Evaluasi Hit@K selesai!") + + +# ===================================================== +# MENU UTAMA +# ===================================================== + +def main(): + while True: + print("\n") + print("=" * 60) + print(" RAG PART 1 SYSTEM") + print("=" * 60) + print("1. Lab 1 - Eksperimen Chunking") + print("2. Lab 2 - Indexing ChromaDB") + print("3. Lab 2 - Pencarian Dokumen") + print("4. Lab 2 - Pengujian Parafrase") + print("5. Lab 3 - Evaluasi Hit@K") + print("6. Keluar") + print("=" * 60) + + pilihan = input("Pilih menu (1-6): ").strip() + + if pilihan == "1": + lab_chunking() + elif pilihan == "2": + lab_indexing() + elif pilihan == "3": + lab_pencarian() + elif pilihan == "4": + lab_parafrase() + elif pilihan == "5": + lab_evaluasi() + elif pilihan == "6": + print("\nProgram selesai. Terima kasih!") + break + else: + print("\nPilihan tidak valid!") + + +# ===================================================== +# MENJALANKAN PROGRAM +# ===================================================== + +if __name__ == "__main__": + main()