From b789e0a0fae315e1bed2a4d29821123cce0c6f97 Mon Sep 17 00:00:00 2001 From: Yovan Date: Mon, 2 Nov 2026 10:51:23 +0700 Subject: [PATCH] tugas10 --- Lab1_Eksperimen_Chunking.pdf | 175 +++++++++++++++++++ Lab2_Indeks_dan_Pencarian.pdf | 156 +++++++++++++++++ Lab3_Hit_at_k.pdf | 195 ++++++++++++++++++++++ README.md | 145 ++++++++++++++++ __pycache__/laporan_utils.cpython-314.pyc | Bin 0 -> 8494 bytes __pycache__/rag_utils.cpython-314.pyc | Bin 0 -> 10758 bytes buat_dokumen_contoh.py | 144 ++++++++++++++++ dokumen/etika_keamanan_ai.pdf | 93 +++++++++++ dokumen/machine_learning_dasar.pdf | 93 +++++++++++ dokumen/pengantar_rag.pdf | 93 +++++++++++ lab1_chunking.py | 155 +++++++++++++++++ lab2_indeks_cari.py | 143 ++++++++++++++++ lab3_hit_at_k.py | 171 +++++++++++++++++++ laporan_utils.py | 76 +++++++++ rag_utils.py | 153 +++++++++++++++++ 15 files changed, 1792 insertions(+) create mode 100644 Lab1_Eksperimen_Chunking.pdf create mode 100644 Lab2_Indeks_dan_Pencarian.pdf create mode 100644 Lab3_Hit_at_k.pdf create mode 100644 README.md create mode 100644 __pycache__/laporan_utils.cpython-314.pyc create mode 100644 __pycache__/rag_utils.cpython-314.pyc create mode 100644 buat_dokumen_contoh.py create mode 100644 dokumen/etika_keamanan_ai.pdf create mode 100644 dokumen/machine_learning_dasar.pdf create mode 100644 dokumen/pengantar_rag.pdf create mode 100644 lab1_chunking.py create mode 100644 lab2_indeks_cari.py create mode 100644 lab3_hit_at_k.py create mode 100644 laporan_utils.py create mode 100644 rag_utils.py diff --git a/Lab1_Eksperimen_Chunking.pdf b/Lab1_Eksperimen_Chunking.pdf new file mode 100644 index 0000000..283d2c9 --- /dev/null +++ b/Lab1_Eksperimen_Chunking.pdf @@ -0,0 +1,175 @@ +%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 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 13 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/PageMode /UseNone /Pages 13 0 R /Type /Catalog +>> +endobj +12 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102103738+07'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102103738+07'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Lab 1 - Eksperimen Chunking) /Trapped /False +>> +endobj +13 0 obj +<< +/Count 6 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages +>> +endobj +14 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2437 +>> +stream +Gau`V>Ar7U(4Q"]35D8=1QsMemcn1%1>>B5Am6D4HJT&=N\+FH,ge552&i2QIR-T#jhqs$8\6WqGWYZCB@7J6E.FClb7`ab)-l&sTJ"Gh5^hY0li[/DRs^X2/$WDO8D)4O^nF=jiT[8aoRV`L"ZMTh1WeZCH<1kXFY@I0Vl/o(po@RV2_-=3F)S9"`gN_1>_rdgrqg$Z+k,nodOFY.VdZ;-(Rr?Fe+__QL/:SqQ$E56=dUd,r1haU6'O>u`nL_bd8ba0([E'g-gPspCCjrY<@#s(L%34i<tLhKY)H;OJ78gPt^b]6;T5:p)JEULbM"GK`Au(-7#Lu]O:5b,(l+q&`1CU=A,@c>@L(1l&*9[>*rJZi&Hgn`*>#'>:()N#]$Md!W)0?gN5QDbKjmni!U"[]6<7I/kt6qh439`e&X3gnq?;j+Jj=Bs\3=il>+.D0;F8Q=J)s3o6iSX67=J3M/Pi[Ea6c$R&XN"BBS"g.l;`>-Qr/K82c9Oo"nC@,H'9tEa7;-<,J-EhRO[%UW?;Pn<@`X6.j.[1'"RQ8>cb[HLj&IhSO.2FM[N%&#`-lNn(g07UNibK6pTUa`@2`=tV=,f"P-#>u&@\7CNB-AS'THCh)Qb2o)-XM1l8B[a>+ck`W-MN#FkMn7mL/)Fj%Fp8adLAQ%g13>VtSd.1WY!_2Sh+HCIp#K%`';)lb*`6E(6g'^47o?p/Z=6./`F)upH?IL6UR9!VRfdd4S5?^[$=l?6T+E(cG#b5Pl[fugnCam&G1tW=CX4h9h1gOr_U-S`Tq1kaLUqaekZ0glc?l.R279HR2hP^r7LmhC.GOkRCMeaBjc]JK>TV\YLLIBX*l4`A5m/u"?&D2UTGC4%I/UKjS%>6QHf9>Y\h?@OlLh"<$am'YTA$$X9]?Bp9^(X9)-%,=<4[q"h#h*7)(o"l[)G0ZYEZugQ5\'ns3Z;XkCH(g`'U2KhAeC;QbN9,ES=du!PVCmEalV][mV$?^f3,T[_W-"@h07oP/B18K`@:6[%bGX%tQVck6mj2ea10NOYI\HX"k/cLf@u^U6@o\.UXlFn^U/gBKmaRkh"0D#=/5)/g)8mL"a<,h8$5(J>AHQucCq8jIXUl0P5'Ls$=&2C>eD1+5)m4AF)b">1)_&6NcX%d,AAOg9g=drFjAb=ogumu`DC3'SO\kScK9T&4ca0AD%`p&__.[6_(RTNsa=,s@Td`fb5FgG&P#OTsNnqd+-d:^d&i@%gc&3X9R..D-.U&GV&A(X5+'Ke0'gX'!McXr84n[$/bSp2mpq@ZuM[?-/Q<"5o"T6n\[k*e>3TYZ2@'QlA$8l8e>-i(L!c9>PgJ5RT([N<4I)EY^W'3lZ?iac/dFRX[&r\-oO.kTF1\"!K"Qt=Kea#^bh)o)=YL&NRlY@G83>T9_h;g`&XDpW@c[-i(I*LaYk$C_Q6>8R5\fY'h^+Y#b3W!=&RuNBYa$DU)7Zre8W<;a+m(>?@W7LKK_.UY23Q]3#GU*skqYpO3X_5BNm'o/WV\g$`6fRb@)P4+q<8%[KD$q_^psdlB79m=!;A_u[.pW4hu3hi&%$L!*YH-NbL%@NZHr)?F*bp2$M9Ad)Qe`XdVIZfckF))*%([fK?d-:~>endstream +endobj +15 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2104 +>> +stream +Gb"/'95iTT'#*[5'G)f<#;ZdXo?Je*f(L_*A$sJGc'RFo7$bo3df#7M@"8*KRZA?>P3$NH6\8QTK]u"?^@/+;.7bTc'gE3Ii8l#/Gca5BYeLCM9U;o4$a98:+3oV+XXrh%p$d=69%@g]i8\`[b(9Ou"kdLWA@p)%(+N0cV-pLcr#aRa7K_qdGgR1:-Z%N_D8^*iJ6"mfH`2?D=@,m'm[uEMp;/DPa[Q/!oB"^Mk4,Tn'A0ihRn===C?arTQTV#p;+U/RM%HFZG\kmZ:4p:R0-V^1WFB?Zus`+THI1-!SnO@5`QR?KUa%9"Q%,T4;V*eL#sH:V.^3PL-&'!=9UNXU#Tar];#Qn=(l4(a^`A1'?YQg/8KpAokh&s3Y=N`eBfccE0Ojh%!?>JeIF]Vn]QGMoTK_Yjp(9.LQ8GPkKkL'i[("EC@l1i%7@rPfpYV'GM>ptDs<9&'PO/Z4#HR(#osce&s@esbu,r,?K6V-[]SNJT@ePEqj%rdq9I-QaL@(:tI>,C?\!@2J1M63DuF_/d)l&I)KI2f'ms(H7s?[qF6h7uWs/i,W5H7g<9hXk?CS,Ck(Bli;`.3(o"'B0_g<$dD4oN\Q3Hsk+`krS'A>!s!-UiP`TXDVXl?3dr.[sH[MEGX)sKj^)e@Z);g'2k7boi'8ROa@hj`_6k"CI&IUH:\:L(u(']V9pC1R9NXG.Mgu6^lZ'e2&&#d*J-OVrrA?eMkD9"LnW`V;/\@T(EP@i!D0+fF4a;i8JE%)O]_+N'?NuAXG;%Qj+l"%`+mZ9`2BaRdQLR-]OMMI`f@G$mB!a8)ASa9_$-Lm_(\85Ud*"/].pONfRo@0HM+QVb6!?.$-:ItVS!t9MGb0dJ`N!t4;oGF5gMl"C.BIN$NXL)onRFiIjj"41h3`s9uV]V)K(ZJrmpToMtM^@Wkp4>gA3AOmSYms[O\dHdREOjgud!U5Y-CZnU`Q*,,lCjYt;Y'R)V!)#Bs%a`VK>KdtQfV]RXNf(,J3)52F>!8O.faue^N!IaUs%XH""I6kp'DbLN!']2IbBZAq5XY=VBkIFc%G0RfON-Kp2&FPgU@&JY<:q[n&!>b91cXloT$Z'c-FH`OSL-KobD-,t,D-,V2N;GpJ=,ZFG9)'J1$o/:b2\1(,4=X+u?'Dg3a'INE;b[Ad459&G6SL^R%oiQGqBCJlUVDrG^PnrJ[,ne]kanWrP*9>,8[W=]PRedp?KU:$CJ"$D7o6E:'DHk?L41qHGfPQr^5E;Sh*kjq8g>mqbOPAlYWN%F9S;j*e^],?USH;F*3\+u\)Sb@m)A4oBNQ4,(c5X5p[m)LdO8p!V`EmX9%Pc":+8FE]4f#9;3&'pXV2.dXLUX?rdHc]Cq)e!00tu+=:98Yo.+OV3B@t)?r3#Er^Z3L_Di02+$B$JM`O\F%B0iUp06.F?3@[Oi%]bKq?;E+fc!4lKseMgqNmf5pMbZK$W:k_<@_)i"%7=;S)YRWT#%1+l&&W78$FYF%ALUj1BQY8d_jQ6ahdOih<4=#^&k@A(;#_43j=ViEE_rV3M@liT8Ir+kVA,~>endstream +endobj +16 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2372 +>> +stream +Gb"/'bAQ+T'&Dk($FmTrHnf)b*6l[-8F[A%!_&R=DS.D.94Y$T9tIqc@/te):e_GAOX80\Z:d)#JaBO#?&s',)8d/c&;T/F"utMU:N']u'A2)*HlAQmjs.Oq*.:$9hQ5eW3a-XOU\Q`Q04K2$fB0B4[2VK=>Xcb\\Ums(`N(tQNJ?J;=JA7eZPTjVL!BY9gi#]TO7o%Y8[MSIlPPrX.U'"lCY7pIs'Am?WN8SX.D&!+8$[C9^u#jg1\("aRi*cPE0aK3q03dn4"M>k"s$'c#-QXSj#r4Yd9Fi!_u^X<%9bT]X,K['Tuk-7c,esZ6.6;Y)heJYH)qlGB;cs5XAOu!iF%mm6PK-rYM"3Lm86dBi!jhRV&[B13[G._fqq4mioYtg$GeK_Y,m-`WH2%Ca`E-(9V+0I#QIo$#>LX<.u/)%pR%\3i-YOkaeD:LLiLj3fob3UF)*O>2l'R2%j'AV3cII!4m>H*B5"iq/F:S,EI%2(M4O>dp?7+IC=t98r'\d]U3Tsb$,j+HIfo3j']&4MZ46>O3X(3QPp^nYl#E?efsnC,?e6oWdSD'FT!g.64aI>_Ha,1(*Ao+@hW(kYGnNnlR_^TKpP/DnDkZ4VuO8*LGP>H.YiW2*r>S-8!Ys#`-+<$S@\iK;skXjhRW,>Qu1B+*1N[9*Y#>^aLERtaL&IXOp^tTpZqsH.(i&k&q2"2WuT5H1/4WDH>daqBiQTAHU#Z3,IH2LJk^=@['\J^iDE2VIPbb17SD,L%gfdh1ad,0[q0Is]*>)P"Oac4rd[*AA.;j,L\SJf>,Wm#T'ZaM82ea4TGmehlHt@kN8m0.\T0S6E!uX2OFWfcY2;HS[<]OMi)HG#_V@NW:<+*DR4Q5t+Q[/qNr=B9j)C+Hd&?pkc$7`#E&dgL^CiTf(]XQ\l)'R-l/Zm.]fQ#^R[A*)r[B4Q6p#1L'"Sr\&$cJHQURJn^[[!&bENH]=bC2D7/K6=)>9aD$EQCR2)OO(=PUGAj#)65hd687BH>^SZS^2Z5H[J*lpC$thg<[cRT?QKIPLJdomhG[cl%AKC%\Hi",b@K3+'&423dOEe,qi0q/@Aa5g^bCF#LaE8kX;Lgd)!+&C,4O/pBK'gRAP*uRDKL@I=[2AGunhOM9o^@$eb+T9<*A`I`@1!NLWl=ffKrm2G3Zh2l4pJA$#F,(eEj.##a[O?2/Ug&J/Ie6^n0Y,pZgdPSGg!Zu"jK/QDWNS-4`Y<1T=LGu7gIC5j+[PGQH-LJ2q:>5LgSr_]QR2Eeg-`S^SE+j>;N/\sak<3hYNi&"?,R%ja6&FAk+c`@8A>BJ[kmi&[FVQCfMRHWQ5H=hQ.s>rOldT=_.+/Hk%;RKuY(CA3QMVV7n)PIJe?`7P.4'mpH]LT;/o0jJ6P)^NqG@;j0)mf^PVV#iU-+kHs8C=6O87/lSG">Z7B_9'/r5ic^Yl;0s63Y_B<,QlE]RLEhphWJm8")hIPQ.?qnMa.+4$)lrcXVZT2bi?Cl-"&J'3_5iH+7Y$eF9@f4t;eC?pa/$"\8^8J8o?J`J^:(Z5OdLSI'7htAG^~>endstream +endobj +17 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 780 +>> +stream +GarW8bAQ&g'L_]pMV/,OMGtAiMO!N8Zj;OA;SYqD7M[5r&h/Nq(A[iZU+N9/2A3g+HY2]-PX(6K@XK4Sn846*%frrRrWQc%Xg.n[n,(f(ls/5_6BQ-2le*U$`E(pWgX`VQE3Xh$QSj+FV]o2#C[.+[LkQHc]e+eE;hD2msA!^@#u-U/R'k[]W0UN92eL$%OF7Y>'c_q4Dr/*0ND'#8cRJ0r')fpE@:2M`knSt0rXPB8]i?f0'N6Gt6<=M#R[VKcECBsW9@et)V0'((Kpns)%NQ9629C:14Nnb52kD<"Bg"?D7T7?%@Mbc?;pF8Z_m?^F)G__j8(r&,CbR7^UP0rkg&@dKWBo7cdUU.[dYWp*_G$GDqR/T(Fh>jk)jrq(,VTuD;Y$_H;pDNPc8GmUfA5Z3+"M?*#hkal1Sumj%rqE+^Us&6t5^D*Xf"BM$+ohl26P@M;L,5rW-bjHO9~>endstream +endobj +18 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1784 +>> +stream +Gat%#CN%rc'`FV1EQ4]&]K3(Re9Mn[L3/8,TNRblcFu\tE+Mm).u@.QBgEEq(,=V=p3S?.E4n7-Iah!8[G\ncD:e0Sj'J7efRHsQD:&'#0f_>)-PpFY3,rg0gi]mC@/WYGAL&uN@IXeb?aRG[O1l]Y7]-psoO;k"ibWpQ],.@]=Ct(\O0m9eoC$?p);`UcE8PG]PDR:lh,%,X7<4,.O#U#Eh-ddrAKp3X/tR4f%u*82d?2bQ3qSnG`T(37<)l&l,t5"&3B_TQ(=!63Cr>=Z6^Yh0]:fcgNcCVS"]lo.&fi7mQ9.OTV5X>L_/I<_'2/]V[Lq$]P6qBrXMCA0MItWV'QFrpp;o_0jMNWiI'O4Y&;=Dl#fXT2,>cp+@L&#>.A3?W?"=gZ#e4Hc5_RHD&1PY4_kL=M*=S]^:V,Y6QN[N,ANW0Y22e#Jr:7A;9QB\$T<4!]ZW46So+qgh2MDZ#=77J#jU)Fc#4T2s;aVa>.Mlb!"KtQ2J;'_o!gKuP)H:>/F5*^:We_1eEjJqc&B8jK/MM..WESp$s-nP&'DZrEX;B>u?8H8?;@[1RsWQ5grTWU[dU()Iu/FI7&>4$(L5Mo^OIgN8AJ%Y[EqU;t#k85sUfso:#pDu0e$AuJ1V:aJOLWcmQnG_T?H>pHi2TFWp+an,J)CnMNMH$6^AJ\4fr"4IOTiXZa9A7pmN2ZK!p[R[pjX5;_hlEK,?KLcCf3`DJDmbU8]?V7Rm8!=qEi9]-/M@(7KP,CQiRdD].sQ;'fd=[/+s,cAV"@(@^_E&P4N[u^+\.ViIDGk_ks0d>1'h#W!t.5`.`h(V+4.18-I3,#hn7bi#3i@;\DGX2Y/=07QrMQ6BT\CpE#@!IY&qt6YYe;WtBaXGoorZg!++[tPq78e6+!C.^6A!lTrBA[U&(@9S^h[Re[+I@S1l1S-I6XmO;%h(4;W^"p_u3*KCIW4i,5Yr*stfpPcBnmUfTO8+-PJ&_$KnM+oPXqFpUh%AqPd,!@33,Yq^HUT*1FfjpG*m-P8XedUA8rGQpRtW26WS),8a,XS+;^s9Frgs]Zb2so0Ct9\(+dK]#g^Jf)TtqqV..,cVc*f"NJGRendstream +endobj +19 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2510 +>> +stream +Gau0D>BAQ-&q9SYfO6-@.\R_WMB,n=4ueNbS_!&q8`]IeC,eiu&n)Cq$NbGkfo%b,M^(k&o58!b;M@-kG\L$-9OJGfjf-r@eV!2^8\I7*#nHh,3KHg#kQRC7B&?J=kclo^ii8;6Eej@,+p^/O&_6I!WaZ9L=^Nb5tnSZj[/0I7hN;K0gI=_YmZO"noFkld#h5(8f/tMUI&RUZSE8.JU.(3&$h)pSh"-Pjdn?%GGHj4"U;JiqqphV?N!&J;H`'!I!X'!.Z?s1%#_dZ-*NGO^c:hUs':,f]t+U?VcR6.%PXS@#sMln#'Q-pSn/$H8g=BV+#+.5*nFYNN>J]@&I^.9K`e"N+^*VSn,$-,_=G6(ed)[)?nVl:X[(9ag#gF%cQ'6gPpCf(Bq#nk('TnA@*ac6dXe7&`m)-'gd#8N&`/)/\AMC;?sluMhm[CYO4JE\B$G\P'g1L_)4rG(ikSSMG"WN<`WBA<>d;)1_8$)fARLQ>OMQJHO6![)4Yq0K@2@K8hLapPP;>Cp%HlK,hX9E;ro4E93T=95_;J[VH9'V^qu2SVhDk6i95%=fk>f_4meLi:'ULkbQW=YghtM>*1]^O;!iujMG$IrIhu_A8!5@o+4g?,H19]0P!FBdOnBh81*RGC$YnsM%HLBEQbZLqDBeAocbgp5Z=gQAcN_9hW^W+;gKC8G)\nO0u6HEO"B[^V\SiWU)m=t#*L7`s*,o;9W:dV=+5t(i0.Lm1m>r(P__3$.bq?WDaOAkQgUL^dZWnD@*R8b6TQdL1J?kHbKeSIq:7#)uNnb`.%Wdn+QkdG/;hW1o`d[f>\o[mU.28W3W_%`]D0\&jJjtW_UQs;A&)+:F.Cj?s&R_m/"*Z':AP%RUsbUCrI%1)=u:SoluTVKs#!Ff?Nhr/6n"5[u6$oY;@!M%$<_0"_SY8D=/0LdIYk#aGXl@?[J>]DO)2BZJ/7PX%2':K\YCl-\g(ScYH7C]'G7]\u]%q^M,F($iba*e?t4$i[qNTu*EN<8&FjbdH^b]HR8Qn!@o%,=#sm[,Jle6"Jh=GK0]AL'[>,D?b-[tP^kJuQo2O0uQT.2-+k(aJ_CLV6_pq:27E(0`62$XT6.+)kXl-(G4/WOeTBcWC5pMg:ag;UV"i2*MKt#[qgPcQYq@8BrLCIe[K_#jN%o1^pk\F>@9)%C5riJpH52Q3sOA;!Ca%.%@U3S'V\0rl[Rg#Qs?K>kZ]Z9T=FO.J:_^hcJn?Fs2Jo+a8io/XI;fdq:(c["&#kS*WLe6kbIl#P$:8V6+8bg,/VN%WoUR0+8ma(&>2^p^+>d@-5JJISeW)U]fjfV5$48J=@H)QTC2ah]C#WU[@'&i#6CQPI88-%Qslb"9i&/NFj"L@-8pZ@kg7)@]>M/E.+-:q:^+est/u7a+pN)7cbM(_149[;rmG`ABZf^'eATVCWXDQVA[&8B:@X5cq>^6j$A%)r]Mfk:mmn]e*r_Q\0<[T4!CA=TZY/e#'bHs[H.Sjc4Yk)T!=B+fT;"Rk&TQf7P@OjXC0s9L,!C*mX8=S1lja`!&Z"E"F`%qm#ON!*57([Uh1[&i;6o9Tg16Jd->3i4l"XrrD!VGrrendstream +endobj +xref +0 20 +0000000000 65535 f +0000000061 00000 n +0000000112 00000 n +0000000219 00000 n +0000000331 00000 n +0000000436 00000 n +0000000641 00000 n +0000000846 00000 n +0000001051 00000 n +0000001256 00000 n +0000001461 00000 n +0000001667 00000 n +0000001737 00000 n +0000002032 00000 n +0000002123 00000 n +0000004652 00000 n +0000006848 00000 n +0000009312 00000 n +0000010183 00000 n +0000012059 00000 n +trailer +<< +/ID +[] +% ReportLab generated PDF document -- digest (opensource) + +/Info 12 0 R +/Root 11 0 R +/Size 20 +>> +startxref +14661 +%%EOF diff --git a/Lab2_Indeks_dan_Pencarian.pdf b/Lab2_Indeks_dan_Pencarian.pdf new file mode 100644 index 0000000..c2004f3 --- /dev/null +++ b/Lab2_Indeks_dan_Pencarian.pdf @@ -0,0 +1,156 @@ +%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 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 12 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/PageMode /UseNone /Pages 12 0 R /Type /Catalog +>> +endobj +11 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102104028+07'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102104028+07'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Lab 2 - Bangun Indeks & Cari) /Trapped /False +>> +endobj +12 0 obj +<< +/Count 5 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R ] /Type /Pages +>> +endobj +13 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2175 +>> +stream +Gb"/(D/\Gm')nJ0nGpNLg`k^7OsNa%(5H^Q]7%d&Bk$^5JRE:K%O6g..4Rc:rqHRE=`tG4o'1b;mU.kpT/7s>dM\0pJA'Qe^0_piO.S,,?MtmJ;hcY*:T)g`[;cL@FTW_rO_V';u1%t%/VY&-.FY0<<@gSc7J1?uA'GjqiK0#4%dYd9T"*_VZl>B8/&N@'A@IA8Q:cgsn/4pmiZ"WkB<`IaDD;!C"2RPnbBdrE0>0A&%,*Ll4]`iY@eps^58#'=lWC6d1035[+@/D:q5(W0l)\9$=O*aX$d+uTpWPIXp1Y($.r<9D%oQB=K`EED;%cq)?].>3#jE$L=;$?n6A-q$g1WU8&fVTA#9%Ha()U@Vet_^&d?AnRW`mgE!2@=WEGoLDV*f$_7XNra0.*60;X[1Od7[&'(^LGq3S*j+YkV5-1BkdP$R'kVa+Jpli98af=uNaHt;T9b4`F7&*]s[(M_tgua3qP%o-/;GI!)01IR=a9S;q$qqtsMNf94X(c^8nY9.m%rK-2NUtRp4rEW`Wd1g9n'<@$%sue'ou@_(`LiWY:on_iCZW'G,&bs!4mVHH[9``eh+FDT,D`).HYW;P(V&%"'Ef@@&G4d2>P/'LC.3Rgg?;ZA]#WZX?hYnl39cXhmK$b;r,0<>.kU0]?'$cb]cp2J[V3[l>mfL2mgutP@fl!j,3P=W:KnK?s'a_@qUM9!8,-h\6Q^R"($WK[f'IUh>-EkW4!),P?gDJ*DQ?n.j#NgZq[eafbs'%A/-qNfL=?mi>"K'd."mmhD;2237VFQ!7,a`WZU((*]X)Y9LdeYQ,"O#ZeVYhiOc)_-=NI"/LnZ&FgAW6?6c^1RP^&icBgIVb:(*2Z&?8StJ*:-UP[<;Z(_ItA>.6kFXV"c@&9mEsCpjI12naZ?l_>E$NjpGs9Q^*S0YW:&Z=kZZ_Q:>D^dHbme1\[WK,t=S;lp.^BA@(EcM#%4()m.b&ZQL0<>f_sfm%8QiR=#YV,&jo!7])CZ_Jj7FNrn;jUY:h8e`=#JLho(0KO7,Rk2].k7FikS$^jXTFXt_,Zk%[Sd"5e?@C2i3WBa0-N@g81j`HAR(nl#]+@?J>WMYuNkn]CMbK1+7(_e`&25WAB6@V$GTYUU28#l5aR!=8V/3()pHo;-+5mWKmlo4tLj3ZBl=QcIO;pmq>eiQ)nV&^buonWnQf/hsjddQH0eV.b?Sfmt71hjX"nn?pA1_)X\0C&\@+.389V?[P!HDm4#3g/J_Of]=$GCp/80a!,RAFj0k2mM1_01YFi;cILnQ)\BALY]TMZrD?en^H1T;^IN3/9ld$mSmi?mJ.LVqb#uN6ARS'u&$XtUSWKrSA\%);\Q+[$:"Pr7im467Z])S&`s1d%],YJ&_FJ"&6FPiX$.8AGUi531/mJ@"Eo(T7Gb&D+GLh<)c?8!F38>;m0FK5mX:(anE!OG+<8%"Nn-DZIegHapL!VOoC']g""t_4h+6?`:`"MAK\jBdeY8A;KIJF>JVThPN&5*8B`(aXA&$(I%Fq\Q!(&:)-f1uR\gV](j+m?'0,.QBh4Z%c?kHql9IR7oru@'4fGeWk]>)IB%)7Xq9p[dsrSk;iK`6Hqk]nlLRIT\GhXH;VrIk0ehV:fb^endstream +endobj +14 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2206 +>> +stream +Gb"/)6$(eh'#*[5lq@q54#Tco,"8AacD5>ae"ed6U>@qp=UC/mPCsbQFN4?pZQM]ZYpHDjr:8Y+Mb%<.>qGJbpKJ4L!XO>Rhg#OtEXEVd3:IKV$fGMQL?Vq;ANKZEC*b;817#<7/.QojE;9!=Q8G8^b9Kc,Xkb#jfQ)9g:OV\.*U94(rKEhmDSPd)(F.;)clkMZQ&KZ+HH;AljJndYl\_6A!aZO35C/8f9@O4n[]J;5[&%k_1BOOELtGX0liE;Eo!R/M3_`buj5Y:6j\5*ncEimLj7m$joj/F))KiMc5OC9DN]E#Lj1Y9!XpUHgVX>9l+Nk8AbjT6-"+,Bjg43a32RKr-HKa)?2,N:[V6p/&82ON7j[Xk;%I<_^&e"Ie(u5-9h7LM>Xe)>82+P8X?]lMorYL$%S:nARPJA&`W%2SbQ'J(^IYXYed&HCoC_l1Bb-d<7b[c9+BL&4f+OD:eTO*5P"g5H,1MI2dab%@9?D<.d5cYYM8#mJ:HOuS6rS+l(28]G'*j%D0EL[N40]nFR5sC+@UYS!3Kk'Cp'Fkj_A"a$/FU"\J=Qg]W8I1S<DW$d';>6ZrC5"cd9p.#]ptC3#H*SC:47VI+P@sppOb-`koJq=r)+'>5;cm7]_3/WW_JcnlHn(9@&10.oobD-\[4,,7aXjWGceqg9<3"j-1#5(:*R]M97uEUi;]]3810I$)L1iB0pbk-U]-(,D->OY,-V4gkliFRuY2&-?L'j@P4+C&gUY_K4?rpAJ-g\h/asC>W9A("&&2#.b,H]'XSO,C*C0D;FLs)L-N)2Cs'JRs_tY/`ro&6KUgo2A]43l$dn=rcbFCd)pqMs_5O'VHB>X\V#_/63Db0YU-V=**hL?TmK`AeWpqnr@s6"m!oGr++DnNnm9:u/SU!rbSGR6,Y@(rX`:!%Y)&\]4,>FZO]4p5CMkN_$V@jn%,JqjK-_d_P!13ep/#@>,TO,T#J=C4^$Ij/S0c!.`f6>"uBt*d_BQeJWmVJZ-MZu4t0j=)/A,s>/,S[l8`Dq%9Ae5A9.J2['9>WpY@?GD.3;+r:T0P"'.7Xrl"f+P`q\iY.VA5_!54"h8]oiIW\oF6Y^j=Z/O+pQ]".eo,Bt++0Z=A1dEIb3ioo6,iSHR)/)ek%&+sB$BR5M?[#"X:-??Rl0!!c55_ed^om!h!n>-W30J/##*,mir#a:s+P\/G6$Ns;lakn>kdFIg;oZhtS,hS`=**,3M`@VO+Ba''--Hpe2'ROZOmHUf'7[endstream +endobj +15 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1692 +>> +stream +Gatm:gQ!4A&:NH>-j:d,a?/pNf=15L,S33d;@&#TQ(f1@H)A#'1[3(Prd.U-A$6o)!YR0CfpV9.3iD85mLj+cK/3Jp/AUj/4FL@!=i#7lj_\uFgK3n)p?MV,pgl$!2T9]a?S#gNAm*!kIsiO\L:AO/X*%9V7>G9mmARi:Co>?>N@fn_[R#tWJU@qYTK0We1#M3sIc)?Q1cg24@-mJ0418gq>X1R/,K"m54o'p%JES-31Dk.l`ZA(`$lUA-M$3kZS_T6dF;=9bSRU:3TVe/?p'@(fQomF^jf\6Sn2sPp3M8gV#/,D`8Kg-=5X<;c\B"rV!e*V&!]pg\OoEX'*m`]'<+n$YOH48(M'DbS`e1)iC&GM_ueeEs&5L&_:(*FVju5Z+L7kUK:*-6&u[1E>'E57ku/;32&A^O3>jALX4*a>+#Ns>%HZUk%4LJO_8n4>Et)7,[I!+J+nUe#o1i^Z5DZ"\'Z4rOs#4ChGidk!@L%W.1Wga=W-$"F.o(@]1b+l*!`/!&Jrn\rE+$VQKho#OCq>qV4RDEl8ZZ"=+'?RTEh=aX6fj+pZ;&ppRW47\aXCgZ&so#",1RqUMu]1tS6APuGb/NJ+EY"um7Vc[^*]'1Oca;>QN.o=7lqs=OW2\FmlT8o@!>C.RG;_Q81o+Z)XAPFO%.:#aYfOC1XKgf@Q[p8U)[TE*Xm0K.cVQ&JP4(D=.kFgA'KgnD/:K/oAXt-:0LG,PejD`h4niH%B#/]CB4W,_h9:b^R]RBIfRA@-]4jB2d-+=sOqZ[5iTfA&4\IHS`^fq^@[&#q:ALsJ^P>k4I:U>.rc^;8;k<">R!FuRAmZQuE)-\W0aBGucM&b-YDbP$GLs_,f]gFOj97D$oJcSi]J(=^b-a/I[:%[Y$S=el[2kW6tRh&fk/4L,t5L_(`GNl&u5@hpT~>endstream +endobj +16 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2086 +>> +stream +GatU3qet61&cUU9`Pb1R)K7]#.L\'VJN`)M]"^r[ZUc$7?]t96j])LTFTq55ZZpjp)V:h:4^3T)3.R44Z"SM(;#Mp!\p*N$9@PC/gk+?($M[tH(LQg92'e`hh(G!%Gh?g>KTU<8bJ/\M*OOoE%G!5;DXo-Q@AYb^cmR'd:MgJF%62Y-S8qacH07PHm29TfN_[(r"YtZ0)[i!=3I,ENoSpC"Iac_42(KiAkqPUlF..RNDL@JWF#D.8c>+QmO9C"8k[2G8hsGQ^e-(2PF4Zg1+E[V_YYh/BS#EB;X"D4%=Rm;.pbG1.C:Do9VdaU2(+*59'GHjNSEoH;5T(5-)GB9YX4O"K8EF#:[;(qObp9;[Lh7(7(IuJ@ip;bY&NM9j)8f@oR1_MA;:"mp`jOhdS+eZk$Z.&FI7[l@-0BJ'Rcu$:Zqsdcfh>/F;UH\N>"n>9fVGH^UHmtppa+UI09m.'`brgaYnA?\/':ML.(/j)$uF$7$:(u?c".15t9FeH'*mk^I]_iJ(mEV?C`,.h(K)_<[bZh4d4I&>@u-1;lM'%r/RZq23oSZk_uTJ*:s2!-)B\$e6E5Ae[B`W@336_*Z(,dQl7+ZsJM>f$8cPrCF1:N*iV+5N6;>Fh(9:>-BL/M]c:N(`mcfi\Um=SE74*/b956aqAY^NIdYnpdPUos8N&TZdT8T4;P0;+sg01rAmEWPU/#&;\%bgD:Wl>;M.s;aXO%hrZ8CK#in$*VRtg5JZf;h`8bgI29lkH4VG\Z_Ta[;0SX44Y=0B7h,!icnLPrA_ONMT6aa3lU,q,A;8B^1\-Xq`O(T)`i0K&fVP9O7;>rX%(Yr(\6YjBUU'V@Tab(qb#5/`G%9-K[A(^EO/9+i3LH\6\.8E3G_=Jodo5$7=*&qcYukb?D!X2['Q#^Rgea3S[47%XB91?6RN[86r6LZjjd4jB\tSXBm:-*oO53AbgS`+W13l494E"`=8h3Uc>+^kg,;LaDeN)l&6ISo4,]]ih`BM,>R'.L7j(d`&L&FVH-,(P6b/2_;Dd=\cj%Q70fkPDAPm@o'YlX#jO:UL&TYia$Sm3H;/"ki'Jh>7[j`.hK>I/(.=e5.3qaEI4>/)GPR7?1LgfELY@/WcGg>M:hd)/7Wm:=*b,^ol#HnnE*0S_PDmG?>/!N2m#s'n'57UT=G$*dH_a#Y6q/>\sYu?7J_mk2.uX+AeNRs`A=AKMVG?3Tr>4JaoE#IQ,g$Cfs!94@iKdm[n5U'ZgQP+e8oK=)I5j-O7c->@'@MRg230o&"E#7LO-pqr'7Ti0-U5)>bq8>S%X.'MM#,ngVAFO\>leqp->;V*A2]30"_e,`u'S(I\cpc_Pt9]j5f*nP/+p~>endstream +endobj +17 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2537 +>> +stream +Gat=,CNH8n&cM-)d5>,e6G!L2[._YrEi&Kkb0\O#b7rmp$nh!RE'9RWi4a?j_JBQdAf$LEc''kF\8e[@qp*a=]t&=Fn6<)06G9N0hRg&Qh7O]EaE?:ClbY1VlS"W5M@-N_0RJ?'CKg.u(QTC"Uhe;e^@?)GGW0Y\DQ_#]YY6^!\^r%JFGaWY_L'dW6%aptp4dO71F=?5G9_@qR*CS*bSE%->YA(;.q/M)1Am1Hi%c&tge=BbEYl)R`",hVce=p#(E9qGg>,0i`%CBIrW`@P(;F#K)/9#](Wf);(7SCmDjBu2)ohcG_$;sB8;7]r!V"A9n7hsLkT/=J:>4LWI0G8>?i[ru6Df$nTRm`_"B;E=3rfYNqSr"V'NN;Xg+;\[P\KW(qU6Q%NB>cr,3#5`R>CWGAn0;,BBo;3n^I2IVbE)]VAq;J]m]3,C5atb_M3ftkNqk&m-d4iPXqhlOHZuuBO(q]nC*glP\8]@g1a.=A3XClK9'"B`%idH&mb3e?/*4QgM8H]m[:i]7Nh.`]dYuDBm0p*oNn::PY!#CC$Ad1_RL'J,5PbOhM9kZU8"'N;#+gZ-D7/U/?("$["TC=9FEXdTH!Nm\UN4KKf.+HDX/M;QXBblTEDPLoi+bV[9_,@F]s!9(?gJrlk`Y;n3(mdQfnM%MC_[k60$b;n+-_n%MHmg$e.?M4WN)*Pb8bp_.RXY,6CN@9E*gB0nJ6V=hW6?P#V&G-1>U9)0("ec037E.q:om@G@^2.(n(TM+@S?S=29ai@.+A@j+%dD[]T%ld@)?#FM(B\BDZ!H),nY\jkSJ5mV%#F#[-W]A<%66`B;VJi;W=RB=02D[a2IK'i'eN\g^r-5CE?;".NcXq7iq+`.];-o'_99&oEksLCl>FT4.dA%,`A1S3pGLcg1emJGKGJ)An?r=D$+*1kDMs7*9G?`,"6iAJ-_+n'>uflkP1(l3iLo/XFHM-r6G^[=g*MZpbEq)krKeLcfaW-dSlqde!7V+:u3nU1\APOpcp:TS(<2Omg]Q0,A/`VG3GN@9)t%a<#a;ueHA:mo,-9!2[._/Y]dVrbp<-(I#3O'B"]3H:M?-bt-\.#8&ai_5j1MAd17IBC!QB=e(Ajb\pn)1J+#WqfoS,hI"l/)j7s()(5r;[p+l(=!f*.^lRtI66NgY/9JdGc!d]^b)o:Mg:pW-<-7nK*A`5-i.Z88]AX0rA,u^Rrb.d*EQNZ[/2Ru`qrR*L5haJk=M5n\`.D<1[8*BcS=EBd%_g3^(o>Wm2VW.N_">T4M\roa8'skgUV-i0,ou,_i\q\`[qirZe+CRMs3_mhBk4ntp\'0X-dm!m'%OC@?2tN;I2gP>JWAbY6GpH&Uiq1E4O.@;e\O?a.caWOi38oBZXnc3@DMT.YXc66?:!**dkg7;gV>+O+)bLUe3c:Zg@6igb1>6-F5B][UV1i**0m:9.5KYc@=GW-S%TNica.]'FMdAC:O0NC_G?^9F_)7'kNoaHpE"[>%HhPJpu2LhDf3XDSE\MkHM34>n"4pHdl+^AR55McV=#q7O41YN'4N>1ZD5XtH1d(Q5odTgNZ7ACD-;IXe7odMVdr9;j=]n\/:hX;Dgi;5D<[EL&&Dcp(0K/)D77gf`$[Vje+WPS2;ZOr-0pg;`o?]CM.A-BWM0GVendstream +endobj +xref +0 18 +0000000000 65535 f +0000000061 00000 n +0000000112 00000 n +0000000219 00000 n +0000000331 00000 n +0000000436 00000 n +0000000641 00000 n +0000000846 00000 n +0000001051 00000 n +0000001256 00000 n +0000001461 00000 n +0000001531 00000 n +0000001827 00000 n +0000001911 00000 n +0000004178 00000 n +0000006476 00000 n +0000008260 00000 n +0000010438 00000 n +trailer +<< +/ID +[<55fd2463e8f658fbb5e6ab0241aacf37><55fd2463e8f658fbb5e6ab0241aacf37>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 11 0 R +/Root 10 0 R +/Size 18 +>> +startxref +13067 +%%EOF diff --git a/Lab3_Hit_at_k.pdf b/Lab3_Hit_at_k.pdf new file mode 100644 index 0000000..e8a7dc1 --- /dev/null +++ b/Lab3_Hit_at_k.pdf @@ -0,0 +1,195 @@ +%PDF-1.4 +%東京 ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 0 R /F3 9 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 +<< +/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 480 /Length 37218 /SMask 6 0 R + /Subtype /Image /Type /XObject /Width 960 +>> +stream +Gb"-6H!Fg%iN[(OlC1:+U[[ai"%1.iR79ePbbq8Zq@hI\t*^APCms3,i=<(/_FY9i_q0;nTS2iBq3P6th0luK9Xp]h(#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F0)Jfk>N+:*QA#_F1\#B!E&>$>*4DcQi:r;gJZg9ti+Ks>OO3o*E,mJQS%5@%DP/hSb%,U>H5>IPYfBk[S:c9(K^q+Z)"f^[sZ0.&"mL4k3o1RMq3$8QV\7*!i`ZEcrY(f@,%'lS$ZY$JX\3HE(&IP,!A>ch2;hS"87RF=D#r3Mb.0o\ns2EC_F/^p3s8DEPf<7"!c`/lB']5O0p?^K4s0?:`*h?nm:/=XjJ0djSk`u@J5BlSQh5M#'ZYctPYtea&c[YrS\CqTl495FVRPa9]Z(n11p$:5EH1Ron$DU(Mo(DZ=ET0Bc2d#p_q>E<01UC8mC9:^L^?bME#_F1`;1H<-q=u,X%o3HA[gEPn'AskgW>X,h;-mKGOCunT7\lL&U3K"@3SMfUkh5#P?rjK=,LHG'A-P3HF3>_hT89b0%m'i.DPTe>NBJM\e$!dG=$;,ekj:;'nqKM;$O$6GX:S'ZT!0\d+2HFMG[V_QrF`-!EE@2\Sr>DFr+$4X>e$Q8`]9^F,$\BK,AUXJGTa,!Eh)dBRV`04Rg,gZY.U-g1)L+St%TL:7F.Ym_/@$%hF_9eZ'oB-Nj\[ftW5JPR*lJbUm`<;KLIpTBh"TWrGjd57A<`Tt9N#499?2X30%fp):qXs]U@Edui[;08^@sr`Pr;HR-',1-.hIU%YCO18Oq>'ioDVa%1p5\4skC<9cL[jp)!.&+_L(-Y6*e!V?8]SlCP*-Fq)H"eu`/g\[h\3f3cL8K-1GO@);it^OQ9>L^4"EfWh2k&>jGha++0mlup!NT.-#jf<7,Idc@!Hm^[B"8l>Q_g9k_dnaQT5B[@m"@lOi[SC#X0/mZ%fW)00\QS(FIoM>c0GNdDinV.]):=M,fJHm7[d\TU_SiuDiWs8Wi.TLNCg=<6*]=YZW5kF-_HhZr6Y?j@f:JZ+8WFLF$n_W]H5>R\&AbbcR:HnU8_M&AY*^/f2h6'qhb%@toRb"3%Mr)-o5Q(#gl:9u=ERc<)o())$\T=r\]rG,8$38K$d=;2p\$=6,V+Ui^:7XEV5Q#J@EcU3[N<-i4_rIf$^+)R$%L`cI:n^3nDQ_IZfs>;nJ@iF?LCP>&;GsMrY7:R,h07c@e#1_'5Q:F&!46Lkb3=Y-p[/38#9J-*=L*)8f#j7.X)7:PUIXY$Xkhghh;$dLrqKu5*E@bRGOA_LHM*r(TO,%1cMR3CO$,CIo]Vu9LX)DMF2s:]KuRiSU.+?V7RdR^]_'WFC7=S(pYHrSHK]R3RaVin7VJ7AXBDmd]mJA7?QT:Gr;8<*17WeSpYL78>[1N+8kRjHD*:!D$GM=;oB+;nUa,s0o^n*#MsK32GAM)s\$pSUmbNfdcRW)"o^_RQ>.(C#YM>O%B,WU?!!#;N6(L)]maS5"gAao$G,YD1S=Js3leCM:C:1d(q`:mgIJ\1p?XEfCs8MnBp[8">G-X;l**Oeu`/SUnkrLa*W/[(\Ncb:7V^lDnYh0$U5q'&-RR.qZdWO38ir]p@?B[)b\bc?f/5);nLj,&eZs6dNnL`;"^%>gU@:#X096@khVgpJ%g4"jo@)ar-0#":A*o&%im^,QCL*bpAXsX#6=G0U12lcD,/-8]`%QY!"]q_B.UT>okIlB+99(Qp=j=RIX_:7M%eFKE81c>Q-!c]36pGg)O0j0j0dVG/!0q!W_4laZHIT7=S$or@lb^]489l8AkW*)K9\?bY&bH:C:jW$?+an'A".`Pf\S/cYY0T5?KUYHRDU5Q7UIA7oPU6IiDT:Lgfd[;0QHk4J,[PUTOiI]2T/Pq,pfK7i"8gtmP\I:Ji8gUC7e>hR`\p[$S>Nr<+oW8[CHE5DP?i_S7H"Q"ERG+rWqSrU!'A7]P?4)N\.j+^3m/5`l.Bn(U0n](Zf6I9T2`LL&cTcV9U?:gqZYI[uqsHPD4(#[I2#oc3K-1GO:e&7f-s_=o2JlX:MA;*u0K3h#i9p%$l06lp6\eF-3r/RG1i([dZ(Ejjgc.J]^>Rh=3,q:`$%)3](L;MM`a_oMpYMT2LGCYAp[@!(CY#Qo'e04!JcuO>^K^@fBec*N$FY9/eR8.>h3t5:qYCtWe3W!+I0%.R*$DEVB6@n(^juTYQE,W-m'G#!/j+4;9SEfgCc:%QA&fSDUJi4KeZ,H[R^%l6a-U<9=1n+H.ff(]/$Cr+B=lu+n/N,p*rfL7VgKiuFKn'FpYL7j_Lahq'^0)!'YnL[2OcDr3_h&!*,7B,[g^AnF(^gCg4BeEL<`@n&@phH\9As7NRF3Bfg%PEVd,X7>(G5ofdn]Qs**9rh3jNoN>*]6PG-N)mGs%ncc]XZ<\:pXb.!-!7MgGl7'EOb@d!s]/SWo:>N<*(MnI'n9s_`6\]lHdl*d02J7d/NCS:YGCDQF]9sjQ+FHC7lPr?>@fF))Gn"nDU!enqej<#mgnM&J=<=foJ>n89I$Z5JP=(-Pq*!D=OPb2*.tjN6IkVgMu37c'r@kS2kEdsb6_83t,DhB/!%M'Q>]=t`7Z@D`3)"G3L(3G#N.Mm"(3f,t&HLC`=]=Nk:WK$ab.ZX(riGFsH>U.dZQC/KG"ac%[:d'omJ=#9rQBFdq:)BB!9!XEMc4CK%fFtcB2oOi;![=V34X2\8Gac@K+g?F<0p#:\q]_@Uc53_^(YBnd5.#_4g<.@Y78TCRk]D,5@2?>ENqa?G].0@T@LFGnA>cp?S+2qpT(L3)EDi6HFapn+sS>YT0H\1It%%;7udMi2Ob-M]G5\=teXr0[>6Y('4Rkg:.qE7(L4t=!cX`oMVH0d#dW0m::srOK`r!9!+>T6=C(IA9iF=r$mTGPjb-pCE_GT@FouhRn-NKF,nm2g9=a"+Y.:WYt7e:hOp"i.2-aX]p#3bSbOGo`"gd$iRJPrqq%er`Kd8U&=TH^q`4qH"6NA\aeN/`CD"uAFu)`r;:m6l6R]$'YN2P?@,EFR;h\jflf$(XIr:od^;",@YdN-)rB#HY?igtMbEH?2B\oW!`(LuWpkkGVI/X]B6KrK8=3k\%e:gE82cThGWTuV)MJ)B\`r6K$$S5K<6,pak:-0eda:<,#e,NfI6>*.d:"[N\jpZE\eL(o"SN]OQ*Z,[na?XLEgc/%43kp8mN:/sOSc[Yor!6^]^0=7%[qWb-H";(kL*d$r2]_U]ZNXOAn?+"Ft;AVRmi.HVW?6).Q:n:P.kX'4$oiAn1:31Jt6UO3]GCkbIG1ZW_fs>=>I!bqPq!mBj8Pjc/;lX5j'^`M;SFl#_NsN-t[(pM,P?LI]2DjQ#7Z*-ZjTG.)_si(lT9jN@?)c^[!/'c#hP=rqg4_q]f]%cCTAh1W`QBAS6DrfLJss;%AUe24*OH5g,j604NHX&6UO5#2]l>:F@k"Ra)?:sl;dMK/E?N^r;:s8`[/Zri8VH+8UoU&A]s8/J*sgrrqu@`p$PSq;[O5]/$HTpS>"-rE+7j]r_G`R`9DYb=0Gq12:sm<`L/MpCc5Lo8sP@eR^9<*P*;*[@k;>D<_l7%4TG"K5Q.ClbP(>XeWQGm*;(#Tj8&!Tn%JIe3X;sOO,op*T@:he>e5%ASpTm&t^TA85W0>IGUf3a"ZBn(=)VsI"Wn"`1?4aZnQ='p)$]%=)j;b%`/"U-oXZJ1L3*dK"n4P]J/VZ$Aadkkq)cCO5J?DBuLd*PVL>.%uM2U%o@?fuYX@PoBj:Q;BKlZhQ/FWqNtSZgnRGQ7X9+$"A[Q1NUu5(-TTkcTs,;'[D4W)HSU4l)ZkOl:E]]%crA]Y$@Lf[BEVduAKVJ,T%l>OG+5?Fk6Xe+jO)=^iFukTP*=O[gI4HCaJf:HhXj-O]oW;"$l-qt*R[REpIXM_;&+u1!j%aR`h?mi6N#=3bTfPZ^MA=[\(+i],YP"sPY!*Aj:S0hUZ*ESH(o!b!cC?mnr;HNgq:TSm`f1ob-RU8f-/Ee&I/jfi&7@>'j7?2!<2f<-,EF)r-)l+1[;=OEZ&GYPXk)aeX&l]DeZ)ZPlM)Pi.6`7NLECta>!?`Q/G231q"XX@]mHf@GE7]A1d4.Ci7bSSlI.$Z@.<*j"i&$bD;3PF_:5D\UJn'cIJWSMhnHc2_rmYA?IJi>qG5e`";XUUHFiXq=/VWG/ugl/)'./Wf/G<7Y"tZR#mgq'^H@!qs+:-sn\#.HUK6a<^"[/*q>'h4-75a:L;*Z`lc-?tWRok3CDXb%C/0Np+0K<]oB+:r;c?\T.oiUFR^qGVpYT%:WRe1UX#[K=D!DiJa.8ilS#_S";(&qK9hfKTW`AS]T5s5H.J8VbcN=\?OIDWlXBDl-It!9$)fP]Oom?E!R?E`-/p_eg)*bOt^AC\([X%AY#?+Q"e[pIagHl;Ak>n]gala0AJfrG-cg%@o-;Br-dh+ZbV]J!I<303L31T[pY9iKiSgB"Smc&9P:'.L3ltTKV?up&t'dn81C_a*@B[=0IXENqW2?p@InAhS"9PK@neW=jsZfbDI#bqYU01a2`],mC.f;r4K\TGMdhX9q+%iIXZIP4Zkhh3B>dF]nLoapW8UHeUN:md4&4$=c4%IMM[0e.B67?7bb@g?[hf^a!7-[m*Ft@1L7V9p%.eHnaVHrN_WAG0AYf8Y?J>Kd%E[%;,R>lrncMgpYC&N5Q:26IJ`^uhIPBLD=-)eY?\VY`JPC?@U_P"o[?,Ko$O4&2,,TG;EI/U"JHfSqXj$6OHAbXX7+cMX/L13o(!bbDsP\gj[Egu$8cMIW+sk_:<-Y)"3O+8]6J/!P\[_M>Y2\<)f6EqU3S,Z#mfe2osR3D52O#_GljfWo)3;boOk'c!PTCCCRY92/.Zm'G$fLJZbpUqI&D8-/r\&-P91"@3S%5nF/b&-P91"@3S%5nF/b&-P91"@3S%5nF/b&-P91"@3S%5nF/b&-P91"@3S%5nF/b&-P91"@3S%_%itMX6/a6>@.pE5nF/b+H,\?\p!Iu>qK4;lg&>qgTc7jJfk>N+:(N+AbQHSXIRU('Ho7"@8]@1cdFT]e'MA+:*Qa*GKV-,(!N*"@3S%JND6CbXD05+:*QA#Qf.Ed@#jXD*\^95nF1a8>@sSg"V*$#_F0)e<*45-F1)a5nF/b&?$h5c:hbe$\BK,Jfml[)P*;;hA-5[&-P:lN\idQOhE=PJfk>N5TOQ\Ag2S+&-P91"G&V^kfcHg2KQ$,]/FN[[#Q.g8VG57Ybq7,`(Bb*hdj,3C$^mki+H=Z?A8a9ecThF4JC)ZiG4"#>p30JB?=$Y@#%R`JOm1*&(Dq]6:=qJq/nUP9AX4A=?,A1cdFT^!3)6m-IZK:?f@(IJ``mrf10e@KcaZo#W9Ein:YNrVc[n;cAYjlIW5sY[KoZs8'j$-Yo0ZGOOBDILq*=$"TJ-iGKgfL9I6/fuTW2-5bC=T?GjlI@QSp96$Df>+2ltftZJ,fIk!*HO;M/hoiJ,KNN1M=uaj)#S8CE_.P#^6ZE-J+ALB-spn"kS5oeZN2i;l?P0YTbhsJoJ2\BlY5.FCl_D>B'ObPg*eZ5('m#Opr?g!5RciJ$/,6N#=DOi67TFN[qJSh7@%4L1RgDJm_#XRKVSe+k,L5dcenCp9pWUs7$9ODB[IS#^6ZE-J+ALB-spn"bdq+-4"f'R3k50>KAY3%[0AkA&jTG?[OR_0ejq!"E*2pD(f5.3,6sAd*PTb:X@D7d*\UY?b1?YFIU*WD[UdNG>tbP]q!s>4ClMEj#t]&rUsHhmr=pR@n%>S]-(2`R3k50>KAY3%[0B6]mFtu^USO+F6Cj!Y$DtHC=T>LS=CQ6'a4U3h07b1$PuF?),0b4:qGO]-Vp>h]6E_')`Q9cO$EWrnJn%]cCOS$pJ4@P1"NO4[r0*<%<(+9W@gr61.B[!5Q:Gq`5a#,prG8u_VIPUa`Wt-\sH`LC$O3IX&c>`Fm>4IDKicYp[-]O!.+;$@@RXOjN*G`d(b)1;JZQ$]e'r9il-ggcC?nBpi)M!RCB\Rq6I0hrVQPe3m:7j^uac[rI;,J&YB:k'5E*L[f8ju$b"n\INcs.DG\l*p)MW74'aQKIc>L7#TMLoeOcr3P*6#[#"TT[Z,(cJt"qX`^cMsJll1MP:5JtLrVZY*(oo=IMP)C;$5m6l,@p4N_cJqJ]h]Y'[lDn^]!qI3M)kbF1W2fPp]B9`kCU@X<@3=skmRX^EkIGOA^(6j*KX6q!iHUtgJ"r;[BH)0t2XKi2_VH/75gHhV]-G98b]gp1M7U8+L>h;(#.hH]95YJ'Xu3[A=ZOsSF&OkFHgi#`QZg$gl8TC.=4'=eeFq:J]:T2/8'g2ZBX$)#U.'nnD1VMUj1=X!se962PKNR"hobIp&fr-1.Z[jtO_(e\JeSdb\?NZE1Eo9Wg5!IuIhQ8gMhp%=<$M(.`8kK]V+!.4J.eP-$lp8KI)))=qQ'Zq%+-si?8\SQARV-NHUq"XW1p$+Oiq<"/b!+=?W?=a+](qJJBY6aNMa_QK7,fS3(r_fWeqYIJ-f?:f(7rQuVV@fn)Eb<#!I`R3m(aAVgg00?p=u2EK#Z0)'Ng<)ibgm2_Pkrql-anG;IS40gc.C"&r+p?^2O!Ia[@.$PT):$`S:&fE*(RS12-FI&b.md42cC3Agka`5Fr6\bYLrquQq=&)=YEHcW!4*U+d6\iDUrqhEZ)gZaR!5S-ba,P4:T0E5Hf3a$uaX=u\%?$7,hR79j.??iF6!1lg?t#ZVO#`b`HOJVbi]#5k39-b%7?S/ELnjNuL%'kmbqEB$Dgt%F,-fFQnsdVe7`^d*0SP!.[(Nk3+.B55S:napa[bFp_f;$ktA>V-Go>b>Ktep5_H;k]h^(m-O)m[;36bd6_,$HLtl*VtV&eCm`IZqt]EIe\uUmc5Gg%F?ID%L(,)D'0r1<.LKaA9G9LAZ:"nSQf_0u)kCOhYBr>[.EUE%IJim1bEa`EEb-t?e\(hkVa[F&+jg"VesIJ0Wg#e't#V,S-3=$R9G$-bmWSXIRU[P[3S:IRRZ/a+X[U*-rIH;I1H0%/t-7/"'c6(`QT$GpXG:FR"^_%k=L8j[T]7V='R[k/'clr-amD@:b_W@/%TJ0%Z&RYs#6Ekf*$-Dl^(Ld1c9A.3cVWJI0E.e3d,W)U\G.LF?gqQlS\J$uZXZVO$0TVn$3paN$`DpX,/7JrgAcs3A2Ru-cM.uQXh<+HD`8u[W=;C_5A4fD>u%k7n17Ja?Y8:S(Rpp3ab`h95oMYJWs,&bh\8k&ZNP0dI05F[N%B.Jm)qc%)7H:k/mT2I00#!Jna`"+=(bshK8R7Mh9ZM:b%7E2!uP`#5bV'9j)K1\T1TT[Z,Ud(?*oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$-bp8Z^F@6[^,\7:IW9rVj*)6.A\5,d"=qZr1K5">bjTRM=bF2Ma.)u%k7n17Ja?Y8:S(Rpp3ab`h95oMYJWs,&bh\8k&ZNP0dI05F[N%B.Jm)qc%)7H:k/mT2I00#!Jna`"+=(bshK8R7Mh9ZM:b%7E2!uP`#5bV'9j)K1\T1TT[Z,Ud(?*oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$-bp8Z^F@6[^,\7:IW9rVj*)6.A\5,d"=qZr1K5">bjTRM=bF2Ma.)u%k7n17Ja?Y8:S(Rpp3ab`h95oMYJWs,&bh\8k&ZNP0dI05F[N%B.Jm)qc%)7H:k/mT2I00#!Jna`"+=(bshK8R7Mh9ZM:b%7E2!uP`#5bV'9j)K1\T1TT[Z,Ud(?*oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$-bp8Z^F@6[^,\7:IW9rVj*)6.A\5,d"=qZr1K5">bjTRM=bF2Ma.)u%k7n17Ja?Y8:S(Rpp3ab`h95oMYJWs,&bh\8k&ZNP0dI05F[N%B.Jm)qc%)7H:k/mT2I00#!Jna`"+=(bshK8R7Mh9ZM:b%7E2!uP`#5bV'9j)K1\T1TT[Z,Ud(?*oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$-bp8Z^F@6[^,\7:IW9rVj*)6.A\5,d"=qZr1K5">bjTRM=bF2Ma.)u%k7n17Ja?Y8:S(Rpp3ab`h95oMYJWs,&bh\8k&ZNP0dJ/o]OHBbc+O=7Rfl$mIoJri.1t8SiC`,PoHl5^V4dPc;goGFa&$?#REsl8GMPXcUtYUd\!Wmrp3RG1$C'_,(<.`a;acE;@u-l,W$;arAcAYX(BJ?!"^+iS=H,)]QnPu#Rh,(FQcQk2k3*^4of8b()%@FP*-Fk';K7[!'L#2F/@Ltl-EBGJ+fk_)"a%j&O3*kj:-E3WJDWq&t02lr]GJsA,qFGNkT](RT_IbhE1dOgU:rR[lSm0XPIdgnF65HgM`(*hgP6ih/:h$mC(i_+OZma/H5!7OYQ&KV':@\WQ6%NTMc@dW$F29%fJC8-caQZ%fge@h!)>]6%]@AY[b_6U2IJeokFKjfWh4#no!9p!PjR-.n[B!d\!Wmrp3RG1$C'_,(<.`a;acE;@u-l,W$;IrH[GCN;sM%&`L*E`f(c8OHu25e.($5?XHKVroT\g9"H=sT<0[Dlr0$$kUE>PIS-T\)t]e$&O33>1`$Z#=WPDMPa(794?YhJ!"oqYMn7m/bEr2+r9a%!b2U`2'cDXq;C^\(P`!+>%k8=aK1\IOiR"4;N#;]tA&aII>+Uk=!<>i_1b%RqT76X93ncSP2Duj-,rRY^e$N/X=rfeS\.i-krRo?QcTW']K:Ysjihr#u]Km&X(%HoK6?D,4T8*gm!W\E3:NfojnDHS4Ll@9DroP.YDQI$L.f;L\F[nUjG#F-6H,I"._LGHT2&ld;KJSiqG'?ba'_r7RtlA'e.H7JrBJOZ2JiV':@b8SB^rqK&Xn5<*?'+97chF(V*nm[,hF;G">cqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]^.F5F;6EFUpY-&-+7b[)ctW_M&?u@-Gt2-/B3ucW1g2p8Q+MoTW5c5G_??%=1jML`]S/RL?1B:YE-k=,B+*jmgA5#Csi:J)"a`^OH+]i5HL>/ErHkJ$uZXZVO$0TVn$3paN$`DpX,/7JrgAcs0\$IWnIZbk:;RMu*A>`JYPaWDgk-W/T%dq[V5u\T4P6(Amgm/*UY:J$uZXZVO$0TVn$3paN$`DpX,/7JrgAcs2t1kH(bd(+i\i5]C[]EcSJ^3\W8Wd%NhH3b`;B!78K1Iq)'!IXY0mOudGce##j'P*27lD5o56RPh,[Rj%a`b2U`2'cDXq;C^\(P`!+>%k8=aK1\IO@FCcj?-ga=S2te-6pu^+Ve8h&d!u-!NZULR\3e%2X=EjHDnX\u9cJS3Is6,2W`?*CmbE*D\ojk(#3jH7(4Z%Wa;FQ6;@u-i<9+L7:oT6m<"bW-TDiX1Ud&'dr7RtlA'e.H7JrBJOZ2JiV':@b8SBa)T?Te#Z^&M%I4?uV4f),r:FR$(K-!uAiWka%js'cW9VamW=o?sN,%Jtu8k&ZB;NWp%6)>cS:e>=Qd\"cPqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]XKWfhA2+>?UmWV]oWt;bBLVPZ!W&kWu$hIS`UL0%-B:7/F6T`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\9DrRjl?5?h:gfr$$(cr3P*r01*A2s?&,pCJ2XD+-J2:S'U9r(!X=o?sN,%Jtu8k&ZB;NWp%6)>cS:eAa65Gt?S4^ShDRRhq:ZM:b%7E2!uP`#5bV'9j)K1\T1TTYCJIqF<)B.Jm)qc%)7H:k/mT2I00#!Jna`"+=(bshK8R7Mh9ZM:b%7E2!uP`#5bV'9j)K1\T1TT[Z,Ud(?*oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$-gGprV>[)UT_E.lKHWj#7VS.nGb7Wclel+d\!Wmrp3RG1$C'_,(<.`a;acE;@u-l,W$9J,JiWR\nW:=nBlshsB"#UWmCT^OH.IYJ2+p(T2!$fWer$rUsHd-ISNg/H5!7OYQ&KV':@\WQ6%NTMc@dW$Km'pZFGOg=Yfd,=ddX\isN;!68`A?[:`JGB`f+RlBXiOoB0.nRnJON'U5g(%G3p7Ga[BQ&>?&-)%kOYWr$^qo?[pWNQO:*,)\3o(2I4CSRVu3d0kgdmX&=Q,TAD.P_2kV,YB/.2@,\*`OWK#F0s'p\nEk6AT[Q=*Z^&M%I4?uV4f),r:FR$(K-!uAiWka%3qrVYX&c>4*BL.&?[1N5S'C)?^daeOD>a>0FIGaM[^,\7:IW9rVj*)6.A\5,d"=qZr1K3LkMDiCGOJkOH?JoR%mTubBj1E-mB9m3Y3YqA'e;+8Mi!;%k8=aK1\J$H[gM9br7LSgTY+=]%F`FHVMsO4?TWCWM3-r[r1"_SL2dWXXsJ<;irfu.A\5;8u]lNoUo(o5H#jW\bG:Rm-K/tp0G1I6!_-DjiYrD6`X4fk3;,Us8D\AnA,>j=en8d=o?sN,%Jtu8k&ZB;NWp%6)>cS:e@U,n'C)4l-EBGJ+fk_)"a%j&O3*kj:-E3WJDWq&t4aNB/>\LCh<-LT8JRn:FR%K;bBL8TEga>pd$4"\QHC,Dr&!IcC?niDD>eLS?6#)lGShaCh<-LT8JRn:FR%K;bBL8TEga>pd$4"\QH1&n)(mpH1U1`h1F?4eCVs72a;9>J5-mEcW1g2p8Q+MoTW5c5G_??%=1jML`]S/lJ2:.ace*)Jq>gR$OI,bqWXmo/6K,qiPIQp=*#:kqnP%Ch<-LT8JRn:FR%K;bBL8TEga>pd$4"cFlGtLPOK`>IVt[o(2J=eu]l4*j3s[qQlI+T7&1E#!Je^`(qfsH%R/;.cL\uKKAl,Cq&0ro]]<([=.oDZ.nY,9r(!X=o?sN,%Jtu8k&ZB;NWp%6)>cS:e@U,n'?Q9JZW.c0'_0AX]l#7#3n%-rbQ09B:k'?5u/pEnP`%KhOk+=MYKI`U!C>9GO*i)B!\>3V\d!\c'gLJS#q&3mQ+(<5?h:gfr$$(cr3P*r01*A2sISKmGB`fNnEAK-hqGJRs1$%ZFmC)ur,GM.:Nq$WD;2M`1m.cRAasE*MMshs.2DAO8k%U0#F12A5p`5M]g(horn`"jH$alWVgUHu"RYUDrSDnil"#qUCh<-LT8JRn:FR%K;bBL8TEga>pd$4"\QH1&lahQCq!i(IVR:@"^@!T,[r7n)Q?MWI0qC)JT<0[Dlr0$$kUE>PIS-T\)t]e$&O33>1`+@/kAlFOB.Jm)qc%)7H:k/mT2I00#!Jna`"+=(bshK8R7Mh9ZM:b%7E2!uP`#5bV'9j)K1\T1TTW.7hOb;u3:QBds82?&-)&Vt59E%71a!pVprr0_]mC]"rV"uaR\l&a3Duc3oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$$G\7=abO=>$BX9&L6.Yl`Ih44Ce26n%JJZUQ9PIS-T\)t]e$&O33>1`&@S3AWI4lI;eqOcah!4ZGEGi4smk:7Q:n[X\;PkKoni_o'AT4[#iFWRA(`bCBN_T;C0El-EBGJ+fk_)"a%j&O3*kj:-E3WJDWq&t02lrg]7=eZraZ!!j&E:/4Q%hRkiK"9o&O3csSo[p?UhXgbdKVJ-Q)pgERI]b;"kJqARu+sKXa):G.Z/H5!7OYQ&KV':@\WQ6%NTMc@dW$F29-N/5\jN.B5*?BRqg,69KhRn+6i:tFPKsGpk?8!t8^qd`;>IG@VoCDI\Dr4lYc&hp@AasE*MMshs.2DAO8k%U0#F12A5p`58E9PV.3B;P;IK6Tnk>'(Z%2q&'J/p^p'Q`#(SN6eeRPemKb*GSJ[^,\7:IW9rVj*)6.A\5,d"=qZr1K5">WDB=N>jeX5f"ZtB$?VC!,6.]7G[IcZ"#\]X]DQ-GABjI5?h:gfr$$(cr3P*r01*A2sYncDc\r_t8p'7fkKaB`Unf96jlu$o!0ES8=O[eH04,1hrVH31c-9P!@`>9CURll(ae_Xs?G(NfF:V=HZ^&M%I4?uV4f),r:FR$(K-!uAiWka%/GlcNBf;Gts8Mu:f36SSCpju4q3WmH!+=$<0i#nJ1,8j&8WtJih/Lf<8H:G\fJ*!T6\Bo>J;kL,QJu=FJ$uZXZVO$0TVn$3paN$`DpX,/7JrgAOC`ojpD-i6?8]GLSeBlTOHHh%0QeXB=Gk]NMRQ!:\cbcP?J.V*EEAJ+fk_)"a%j&O3*kj:-E3WJDWq&t02`r`naK2q/:bI=:_Yo,X14p=a9^&/uQmdOgUA2lnS0lr0$$kUE>PIS-T\)t]e$&O33>1`$f'3B&m@l-cD(55F840>1>Bp[4=CrIYIn/-(3Pqe,nk_ZXXsJ<;irfu.A\5;8u]lNoUo(o5H#jW\bG:Oe#1r:[dUs-:0l)ebDV@>F[nUjG#SU5jpg[0Fdp,+H,"U3GU;ocE#ZY.U7J:Ng>B3&&6M\[no_&1;9am/3-^q`5.I@@q0$)gtVm;euW#NG\GNtXXsJ<;irfu.A\5;8u]lNoUo(o5H#jW\bG9KOH=YQg8V9*s8@.9dtS>QC0[6sRd05)>Dg&;bflS9a2c5IG^/T1\Pq,Q_1N!ICpl"2pqjO!3]ahd-bcejd\!Wmrp3RG1$C'_,(<.`a;acE;@u-l,h0C!k;@&+BU\)Gk#S,5ldd\TTZRRhq:ZM:b%7E2!uP`#5bV'9j)K1\T1TT[Z,XP'5(+u;%Ho&\%RDr/uX,5p3;im0e')2"MY.cR@jMS)4c.M_K+8j](qIni[2*&1oU?657.Ch<-LT8JRn:FR%K;bBL8TEga>pd$4"cFlHsmIld/'YQUQrRo?QcTW']K:Ysjihr#u]Km&X(%HoK6CiIQY$8AQQS)S9nqcpN.uQXh<+HD`8u[W=;C_5A4fD>u%k7m.meqG-o][)@<]kNiP[rbQ09B:k'?5u/pEnP`%KhOk+=MYKI`U!C>9hmd[$F6CjCh07ahT7(nC`XTU$+oU*:im0e')2"MY.cR@jMS)4c.M_K+8j](qJ$)q\Alr0&m1sSWDSa0*2?,>;C=UaH,5p3;im0e')2"MY.cR@jMS)4c.M_K+8j](qJ(@jiVht%Y.uQXh<+HD`8u[W=;C_5A4fD>u%k7m.mes[9,Q6<PIS-T\)t]e$&O33>1`,E&Ag73,b2U`2'cDXq;C^\(P`!+>%k8=aK1\IOZ#"pH]mKJe:HVLX?G/t?W)p1Zd%NgNn(t0F7f'HV`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\:/c2P@`_#b,X&"Ci+oX7$r&!$l%(?f:krbQ09B:k'?5u/pEnP`%KhOk+=MYKI`U!C>9Y@#%Ndki-gTuT-&OW@1Nm0/R,Xt]P?c`XmYWFSN:oABd3N/b2U`2'cDXq;C^\(P`!+>%k8=aK1\J$H[cM2#`+P&0C%9dq"TAi!1bIbT<0[Dlr0$$kUE>PIS-T\)t]e$&O33>e@GDs6l-EBGJ+fk_)"a%j&O3*kj:-E3WJDWq''e9Lo=/f#1ei>`Z,\J&]&^C!b*=IZ"q7`hCu`Qsf4cH-fui=#4r"rfSl.&tVj*(O5n>F[nUjG#SU5k#E8a-c>.&0)>GXrWP*;(Wg`e$j`Quk+e$OpeLl3'&l84Y&Ch<-LT8JRn:FR%K;bBL8TEga>pd$4"cFlI.])L;gIs(#(`TcI0R\k5ldcI[IrmJn4rRo?QcTW']K:Ysjihr#u]Km&X(%HoK6?BDDIo[OQI!GJXjY,G!q=%k8=aK1\IOZ"AL2cd,t.E+*9fUs4JE[;.EtYTPSP:FD(mB.Jm)qc%)7H:k/mT2I00#!Jna`"+=(Fmf2g4$,MI+i.csBpoeK&CC1f?&-)&Vt5>Rb,:"-HS*dHVd0>DU6rmb(W2/(@PB!Y4$2GL*+oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$$G\7\Q5h9Pq,p"J,Xi3X3R7t=1NA5RPemG1m.cRAasE*MMshs.2DAO8k%U0#F12A5p`5M]g$jhH1gIQX/Kf\p?_&*je^EoqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uKKAl,*"`e1X]r9/Y3jfB>HM):FIGaM[^,\7:IW9rVj*)6.A\5,d"=qZr1K3LkMDi/9q1;*j)a;&Ob-XT3HE'Ya(NPZ.6:2!5?h:gfr$$(cr3P*r01*A2scS:e@U,n'>/DdHMePfui=#4r"rfSl.&tVj*(O5n>F[nUjG#SU5i=a%1]gZM:b%7E2!uP`#5bV'9j)K1\T1TT[Z,Ud(?*oOf\45PnDj%!kOFLn@Qpnch7U<5]:s$-bp8Z^F@6[^,\7:IW9rVj*)6.A\5,d"=qZr1K5">V+X%GOODe`"PqrX&lLQ/aCle2g"91h7ImsP`br]AasE*MMshs.2DAO8k%U0#F12A5p`58=L14F=KoTY_o$)m3PAG>_o$!#Jd"2L8AS&8cfb+::K7fAOPFFLL%KM'2JVO^UErAbe[;2[SRe5e6.fSd5OYQ&KV':@\WQ6%NTMc@dW$Km'pLeD_mpaRL=#q[(SdYVHdF$RK>uODYc'nq92f..9Tna]#Mg(PPPN1ih#66i0mW;^jTKrY[fWer,D=?HUl-knJ=&):WfX,@Vo]Pcprp3RG1$C'_,(<.`a;acE;@u-l,h0C!k=(:Eg$SLHHhZC!45@>c,61,"6oZcj0k1CfUnspDp)[+0;S9ZT%N[_\rOEo+5PnDj%!kOFLn@Qpnch7U<5]:s$$G\7=kE>?6\jN>cTSJEo#ee$#BOp+mdT)qc^t^J8P)KKbL=qh!?a*OZ1sfl=gJ;4B$?X9lal`OE\.4V;irfu.A\5;8u]lNoUo(o5H#jW\bBak/Mp2.'nMjD>D_ceba:*b0P8NGVr\d6)$&$Z8X!V$HimFm>nD0E&F$Ma.)pd$4"cFlI*kNRlIDrYQ7WIN$*i0"oB4FQh<0"&`5GoqI_D]+mpF[nUjG#SU5jh=0J3VO3ce&LC7F2q!_]6+.mlPlBV!*Za-l?ZtKI?_83t0aiWi6^qd`QLCP3fUnsmg5l^i^Mn'qocfJ:,fr$$(cr3P*r01*A2s`XH$O\#ibgP.1T+!=)A*S?l00'"'Js45JE+\240B_)RCW+!:IW9rVj*)6.A\5,d"=qZr1K3LkMDhHe>Ucq=0J@$mFegYp$1(MRRhq:ZM:b%7E2!uP`#5bV'9j)K1\T1TTW.7hOd;t)ANepE:Mr,Jlc6fE1q=6b"k1nPYMnVcW1g2p8Q+MoTW5c5G_??%=1jML`]S/lJ2:.eZ2c@2Jq0H;54R>2E3>H\8c#B-;BpeD0km?PIS-T\)t]e$&O33>e@G>:\@B&9Ql'tS\uJc25'W"$^d*#dka7a)3!\0HDm.VUqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uKKAl,XH`pXc)/rUqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uKKAl,I&)GZFHHKcS:e>=Qd\"cPqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]XKWfhA2+>?UmWV]oWt;bBLVPZ!W&kWu$hIS`UL0%-B:7/F6T`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\9DrRjl?5?h:gfr$$(cr3P*r01*A2scS:e>=Qd\"cPqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]XKWfhA2+>?UmWV]oWt;bBLVPZ!W&kWu$hIS`UL0%-B:7/F6T`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\9DrRjl?5?h:gfr$$(cr3P*r01*A2scS:e>=Qd\"cPqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]XKWfhA2+>?UmWV]oWt;bBLVPZ!W&kWu$hIS`UL0%-B:7/F6T`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\9DrRjl?5?h:gfr$$(cr3P*r01*A2scS:e>=Qd\"cPqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]XKWfhA2+>?UmWV]oWt;bBLVPZ!W&kWu$hIS`UL0%-B:7/F6T`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\9DrRjl?5?h:gfr$$(cr3P*r01*A2scS:e>=Qd\"cPqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]XKWfhA2+>?UmWV]oWt;bBLVPZ!W&kWu$hIS`UL0%-B:7/F6T`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\9DrRjl?5?h:gfr$$(cr3P*r01*A2scS:e>=Qd\"cPqQlI+T7&1E#!Je^`(qfsH%R/;.cL\uK]XKWfhA2+>?UmWV]oWt;bBLVPZ!W&kWu$hIS`UL/q!M:AnL2TWDb#d,OKZ$E,b<^JHJ]<3)TTJ/mZ'\_$;(SmbKre4P[il_q3MLH1U/+(+ct8j$r:A62IpQ`LhQ.1'Tn<<5_Q]'mYEQ<%?l4Q%\;:Ud&L'A[Q@-&cdN9K9ML_Rl>6q!.f]PLCP=Sk#RZn:Bgendi(/_S.+5(n%VN#):G.Z/H5!7OYQ&KV':@\WQ6%NTMc@dW$F29-N.()iTTLhCY,_g-1g9D"+[gsgD4k2fs>MTq7^Ch<-LT8JRn:FR%K;bBL8TEga>pd$4"\T$cY3B9*KIXV:$F6?<=jUE+b!.Z(OG*M4I55an;!!j)G0?'5JJ$uZXZVO$0TVn$3paN$`DpX,/7JrgAcs/R&kD^E>!^K.AK[]K01M-VP>$>*dmd9c+Ig5sL:X;pTM=bF2Ma.)9a%!b2U`2'cDXq;C^\(P`!+>%k8=aK1\IOiR"4;N#;]tA&aII>+Uk=!<>i_1b$hc`Q-"$^rVW_9PIS-T\)t]f[U#?[6,G-j2]]iG8gO$EOcd-7\T8*gm!W\E3:NfojnDHS4Ll@:7rN;G=>m^T2rr.EGp"+%^Y$V1@h(JZZZJtm1V'9Er;bBLVPZ!W&kWpf./SD4jq"a%_^=rGRqJ@SSa#>)r\+adHGOOBULCRm,AJLY?l-HO,kO61chUBqPe$RFbqok>uV*EEAs">f9)2"MY.cR@jMS)4cC?]OpF58on[qsj.oB0D+I`_B8"TR,f6bL*D_uBW3$a%bqqX[uQIcK$eJ#utd1&rk/[&DJY,0+V(=o?%1<+HD`8u[W=;C_5A4fG2uiTH[hDVP!1Du2;MSBEm8_$'@)r$("S]1l$qcJcZrs7kW869cuR6[uiApCA%r*O%T:#Vo='L12[3t"U\_Y:ZRA9c5:@\%D!BE/#l9LA]a$Ok2cZaX5\s"L;7RTWE_!'1@mr1pq5?h:g^SFh>%!kOFLn@Qpnch7UV(hJnoT.UBq^(:SN+3["5?l;d+>5BD*Q=/-QcJpn;g.Q^?Um=Uf:k5H:k/mT2I00#!Jo];#7M@/H_e5lai[UR5oETe;/nd:J]<0TV);f"rojVgi8%"rOIFXr_PGJfEUTdI.rX8d&%S4Sm^!`MV(QeW4Ab#e`;NW]t.A\5;8u]lNoUqL:l-C]dl'"%55&%QEe[pk-Cg$R/CpQSG]Y(6JD[4+"ht$,OHo?=$s_Rl=]hIXUH).fJ$E8:S(RpjYl7a;FQ6;@u-i<9+L7;"&:O2Ij$Pp#f2);r:e9e*u.r)iPMTIJ&_hQbDqfFdKi;guM=bm<6g2j'j"'e[p$DGB`e$j2NZT[OaI#g>1S5oB+;oLoBrYM]huOb2WFIV'9Er;bBLVPZ!W&kWpeSd\!)S3j^L-pl;ZDc^uccB2L1$C*+C"Xm21"hlD\8H?gt?I.c#k.T\k5oFh5;r:8%r@THN16psFbFEArU.fJ$E8:S(RpjYl7a;FQ6;@u-i<9+L7:s3j.m[qudXEq,2P-eknq#B6_.3IZXYBo?Rud/Vf;<^!sB;HLon[Z,a?D=iI@jn>J$?CX,mn@_,=R\jBej1>]6D&PM.Z2'LBjACOe9mbom;q+6;qK)W4C,LmKo1I^m00VJgn,I7X^@qM]nEQ4/)RsV/j;I!4LBQGn=3:3??bUQn8`T^651lObr9)*a5B`uL"g@'Jmk;C>qtbQPm\P9qb\naZ/BlIBrLg,Io:!s#D$^\7(Y/ug],"8EZLB=J6lTHN/8,H.:b,beCtP*21&;P9ulC#iRO'Z7R[R$a2&@!QHF>Z]EimFT[TDpg8Be]h%m<:3nXRRBOK[sq69PMbtOel2OArj$e:-X#[AAkmAjCLV4cbMTm+q<./Ii][R1EdoBUX?/i4&,[pRe5cEViRtNpQhB'Hf<4E&fCUEdB.pN-p`@jj@E;uK7:^D:G[q;Ersbke[!`A-L$N@1Gt_Zm8nB>N/ADs+3/*o+b3bpH&4chj?WX5-DXD2"YFP_u<1V8gME(8js'`.2=.L@m6h#\3DX9rt5naaSF.NjF58/Wgp2"et7rbo"Y-F[Bl',O)>Jr4qIF1s2URT\sknLpY"i@LoC1L*87*'A@MU4k7:TEXF?Auqt%#Ks7`QlN9Bd+DqiX;OljbL4\YT4hD<$\\VI0Us7spM7.eG7?RTu\S"$Imrc;dfC$X?P_o'AD(fBp<:7siC*;2m#;'IaW;^tDl:T5b))tQ@4_#=)G_&Z:2%KUn5[pGVsQ9=cE!@g_;D`ur]GC>+qZ->5-\kMlq*t,2/V=kT_Gk(5PL-#goo>IYK[p!C=2B44[7`L_;<_]q8cWe2a(R+Q>E*eC\n#rH[B%j*aRaT&n=,%U4iK0MA!HS+4LMMT8i%)\#k.XIG<=VD5/uns0\8uV4p@qbQkOnqm5rnX4f+CX8r3e'Bmb)L]*?7PXpc/P_p+I;^tE#`dCT7#l6?lYlaQ1:7siC*;2m#;'IaW;^tDl:T5b))tQA'+R%/L`X:(b3J%1RF4_R+7>\sM:T5aN5Gb)BDTi@9JtP@")_41'jokN:cXD;K'dXK&5,G!,rKN0P\M51*$\JB%Dl`s;R1nf14GWlt<0Tq4q36aLp/_cTI!]O]7Q9N;8]q')#6R9'"!>:b;tL1V^0roOgpl5P#lg7oE2.1$2Ca9%Zq=.$5J?6+%j!chtcid%<=LrlfH9+%\nX`0a?"-t,?:U-rnC#M"F!5P#gE5_%o!qD6H+Ij>aT&n=,%U4iK0MA!HS+&n[*1D*>PLh0q>d@[*:d%>Rnq]i'gOVm[='d`4E<0YI`q4Ln&o]LPAQ"0i]4#J`RZC>&3(F%"*nk/#a*;Smgd?37T*$*h'\;VEUn(7$;X%05']R8\TL&V)N3hstLf3a$0E^\\sM:T5aN5Gb)BDTi?nDYnL'%#c2)q,Hc/,&6B&("o^1Q2S7,mh[$>Ri)'OQ_l+SIqaL=':4^i"Pp_9dkk7MaiQr8R$bBueZ/WIq\.BRp$5=:/Li7@nF/+K$qlp>SNhVS"Mue!r\PMYWn;.&rA?8;Gk;[8k"TlQk-2#6%:f9NP]VWMt6/A5F7tcrOH=*nQc(Mc"jBHcf:UVENlo:rS?YZc(+IMM.l"nQ-*@&4aXV7UIGnD9MfV-<)c!Na1o=Umh2E!H2o4Aicjm.?.j("ZWDqh&A6Tr"DV#5N$LT>=-Sbp]a=d)@bnFErkZ+5,?l]OUsVi?OKhmTqVUI/O(VoO-Rm,,7f[(3Kaqo`>rD8pfBY@]hd[TE4-j8\oLT!.-krOJ=de!L0Leb91MkbFlHPdu,;mC.r9nDV9^8R&ZA*>.\q]j&½lsQl,KE"pG0u2HN7qjokN:cXD;K'dXK&5,G!,rKN0P\M51*$dp0po;p'/D/=#h/XifsX@[nj+=Nm]E18.&0.rVPl.UtWHT/,cq"lC6HuA&_=fKljq04cG,=dbND6AWmbR*oST#:Ou.S:u+HqHg6q^E7+Eaq;3(8Tp^k@nGcCi+#jVn[!C;,-n.JDU%sM/UmV>c?!.30h*IVphM<+/dtk?!s_&VuE[NZSXOrL^n>&^k2Qegpq$2Wqt#:ER,7pBkaQoB^FWkIm?8LHIAs%]1,31aiR#IEUVbt[l7[#7f@k5Ua4FOC?X!65_%oVm#Rs:6t4B<<0XVZ;GeVsk(>./Ii][R1EdoBUX?/i4&,[pRe5cEViRtNpQhB'Hf?;*GM;4I/WoX-3fu]A4Q7mL9CtkENB=#'sbDYqWUqG@6hp9TnNrED3LsG9[2S?@]r<>+kWSd?fqp/(JoOjc5r^\M2,fY4(71&i?'d[m15,\q#5:=Cu]d?37RHb'Hk0pE[3WICYX?OO4>r,@utB)VPCQ&F-&R$TY$Ldhgm\Gi$&>M$QZZ[R1=)acKP./g^!$Tlt^q9P(]N]@'L+[LV?3UDg&U-rJ7Vc8nbT2JN12s,bHJG\rf`"B.)cK]c9T'C7FT-ko&*m!$mf#/l9;t/nD]1kKsC#=)AFJO4e^D:/+pZIPI"I\+`-.2=tNVTKi#fp.&"X+u/4Y.SA>hW@3lJo0I6/rH[)oQ\aijc@7GjpJ_&lj_PC4rbO]`s6Z_bSXgaIFF2+$H,-?T5M"W=h=OjbcW7eL7+M%pcjjn;]D]3'J$Ii^[pXFl/CR58K?RpUX?V0[2?oOgpl5P#lg7oE2.1$2Ca9%Zq=.$5J?6#FF9q80rg:6#ebHJc2P[lCB)T:4AF?fZESer'*:3()LNWIee()ueEmO`dp[tCU:??@K4lF\I&,Z"_lKDX5`3o,k+ao:CkGUnO]Y$?Y/PWG#CUMmSi8D"Yf:'t@TgOV3o]bE"kh)\&P7]$/'GhQe3MP``S3VSd3UDg&U-rJ7Vc8nbT2JN12s,`r\,G!+WE!f$2cbJ.bR*oST#:Ou.S:u+HqHg6q^E7+Eaq;3('OT(hcKpU1FLPAGn0]rW@3lGo-kD#m&*gIaGp"g=qL==IU`QFc^[7MjuMdhUnU7dbbnF)Yb+fj+ueY:C-g4=q2H,^5B?o)RHLe=P)9m/0qV?@_L>*GM;4I/WWb\FG:!r=][Z%?2hDCPOpU8#Nk8*J:l$2p:TYWn;.&rA?8;Gk;[8k"TlQk-3.=nUI>Dt4AP'`NdbbZCn(b*=JINZ:'&lr`?kTg.-VoOjc5r^\M2,fY4(71&i?'d[m15,acYb'Q3/n(t\Fi",_bp$;T5-2!AjZhoi2Vll;NQ515O*]\9%kF[7,4MBt)oa1SbkHI16M@tb#T2JP'IS*%cgmAVPASF*ho;p2`[XYpX]7TMGm6=\`3H?5Q\Tb.8kg?/:AS!XkkNga'c^?R:0JH>$ds6:e5PNrSB@!0OabIS/p=nZMP?YJ3WRY`f[VRs8b*7f#03\&hd%>Rnq]i'gOVm[='d`4E<0YI`q4Lo]8(4ojg[OljD;Wo?NdR7SgcJPUqXj#ffFmIWGb*=L"4c:+0-DDPEH;qZV4p@qbQh.4YTHgf;SQc+cSoe!FEM^PUAXrhq0;%S%mENPH$O[Zg[M,5I^6AheS;M2`Pn]991qo@4[&3!m!eclQeFV:oOjc5IdN%+M(tW#;8RcS7>]fe&#q$d5H$oEI`]s/GO3sJ6jA:[m/GMa<$Fq+8\*T1n%JH:&Of,WRGC)Be0lN`b0$aO1I6NQ3UGA3.$5A<.2=/qVc8o%NVT6)')$r-7-^*IrAd#(SVU`a@a+8tZtU0c%3@Y,DAu?=151OIP*4&W?aabg&P\fW[C4BarL)iQ?i-Qc@a(`LPd$cX;'Is]K.9LG&#p2??S_9:WDf]q=a0W/hbQY6TqQ\bE:Df%7'q(#;R?6brqL!bV!-Mr'D,q/_g_!2&rA?8;Gk;[8k"TlQk-2F@6^\WUjgG4T+_kOM.kJA=_@CM5CT,Y!WrPn[UJX]]ntt/GMb9i5:7h6LjJg`?'E$-2-X9hSPJoErG(EM&ueGDn?bW!'T"Q:;Gk;[8k"TlQk-2F@6^\WUjhRrGiFQ-N"$RfrRoKAcIpm]T)TTobngHIB*$?j?o7u7+Y5$*3T&5gq_LJ"DZI82jhU^q!&Og\9`T;A$OU"pkOX>A/aT,1l3^Q*5TR^n!`o*,IN?A#6dq*YaPRW&/Gs6s8MGBLkr*5YMS[D]/)6!gU([YS=Ej[qn&JHl7(3VG>Femg#DJh#&[iS"]/:)RK2Ua()/HEj2R)Jn8KTBp@aN+NE,uEl02>Q=D3"S8kM_;@XS1QH%Wr+SGk6bkg9%jgFJWW-n-TRnN+HhY_*"U.p,LGFU/?^fXcP1qOBQe$jM>bX7c&S8k;K$.On=\TZ[7M1`B"Z8g[9$jQG^(eS>'\r$G42I.?48qL.c3zzzzzzzzzzzej=R[q4k$m/@-jO/,,j8UfdLs?+kQ,c'gL'-Vj$VV2JBTXe/r-clcE=JHH-(9UHA4*q&F_Xjq/l>[4j`m@'0"EU21-C]A."L:O,kZ_SND$rs'*5;rqkd;jNu7M%hB1tHi$jG>4q:;pTK)FX6KN=bnLj+R-g?hT7MK>Y,9Fe9HqpNoheR`_hJVYMMe^K[jRmk4aPnrH.13n$6\Sb;Ym`9@#4hiWMuk_^/+fCi&dl1QS/7+_[kqVT[3*+a2_iak091#3cX/tkg?/,XB;`GhnIUi<:jf7!hZNmn.,+S`Pog`gUAQ)bfiaR?RpZ&$VK[0Q+pN@!2NMn*E6V#n">[_5MitoCQ1e>C!rf@-VpalFQ:B#m-JOQ5JMOd`"st,JGosDi.!VPr9\EP,GN#,#M8%Q!)2mmT[9;3i'.0lXNGV":7XGBr:JF697_YF9q+'>lDq,Vp=h6-^>->EDd-H#N>jh5m4mhN+:A*LPKAOg)i/1kd%RjsPlO]8..h-P\Bc0pI!iTC?m)3-k\/F*@&AE;91VV#l#[.?*2riC>ePG@1c2RBZY$uPrd0n@5^ru8n@tD+^]!k1=BHCPX]`!-p9,)ol34$VkD4qMjQ,C?`5J'"B7BjUkN1geFn(=i5kY_%*'8>fcHbj;2r67EZYeI*cCI&_F?1KcTB,(o;lILWUVh+gUU>U;S*BSEq5!D._0!8!B50hI!6o9)R=<-saB7)>1.^-V7TgOTFa,\^7Ws8X-dCiYjK:4lGDWA`rY?ec;gt]Vl]1/RCC:-o9;18?8F`MM#hYpDr:e%37,UDHU`u_;P)59?J.!T&m]6E^E5Ej3Q-Wg)r:hAIpGIMf%e[92E&L0c4DKbC!;5_lkpYC&9bo_H5dGA#>s(X#6.m&%IjPn.:?!LV9!s;8qK*$"Jrro5I.k>u-O[k3U/M4)b?9/'Wq@@,]Rl<93%'un2LklZW'@dUiY"E?\6-Yo[;kq`>q)$Hj5ns7#-BjE.2\OMg!<[*4&cq9Y5RV>UVG52jeSFsn!C0Ubg-IT2e7dsdQ-,`nm+G>5KZqb+qb4\U8k;Lgqp-BGr`>:T77Dp:Za3,r_,mQ8[ffl27un];U7e0P>/!B)&=&N(:nF8M$PtEpriCMHiq:o1=$L*kqXp^+<(05E]6Kdm/Be;=M>he69:s8OUP:`O1)Y!;/aQ"dPRH910pH+glfb!$L;3.PWZ?LiRNVbdQoo\T;"\SFYhn=0?3nR`NQ@o`Gg>&kkBc%mKc]RS.@g5D(BZ?VMl$KnVEC2D-U7AS#G\J"Zl86(eCHPa"R(UIUBJrMp&$)0\n+.4J8'/8N7a)4mFkVLKL*5(#=%9q+'.m+J`.c_#^Ybr>%Vm"f>sOsQNaR6$nAB@!/n4-$(#K.-M"9c4fd5QCYfqEOc8O$AASLD/(RM%f4=V$"fET[M<8Wl/p?d6SDf\D"cTg%2oq0L[lak5!D0Qbs?sd=m'])TOFuM]j>VmCE!m!&ebrMo[?TkKn\46g(Mf.O$7mOaq^uUAH4R>"_D=Z(D[c%)]S\8P>AW%eV]0!D/CeQdl.7`@/%3a1)c/*g"E'mTahPBb(%QU7B@Y"L,G]rfIfm`nr&Wr'49rCaW"qMdQbVRR57Rf:RgmEnLK7i^??2uj9eHA^(Yb1uHF*mWIR5f1qE,`%^X/f!ne*H\Q4S-ce++7*c;8N+.!=/n1$(qc:5lgrczzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(p38aRNM'A~>endstream +endobj +6 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceGray /Decode [ 0 1 ] /Filter [ /ASCII85Decode /FlateDecode ] /Height 480 /Length 149 + /Subtype /Image /Type /XObject /Width 960 +>> +stream +Gb"0;0`_7S!5bE.WTRn$TE"rlzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz!!!"l)g#n)&-~>endstream +endobj +7 0 obj +<< +/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.c7f64c81112aecbfd584bc06b36e526e 5 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +10 0 obj +<< +/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +13 0 obj +<< +/PageMode /UseNone /Pages 15 0 R /Type /Catalog +>> +endobj +14 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102104126+07'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102104126+07'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Lab 3 - Ukur Hit@k) /Trapped /False +>> +endobj +15 0 obj +<< +/Count 6 /Kids [ 4 0 R 7 0 R 8 0 R 10 0 R 11 0 R 12 0 R ] /Type /Pages +>> +endobj +16 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2382 +>> +stream +Gau11:QqP3'8Q&U'Rb8-S5b6.peNNaVCJ'`2`%FL`jt3Z$sHj;#ZcON9e%5KeH05m1)Q@`F0WQ@IqngG;QBs+"q8$?]W2'^fYO&$*4QNk:V]=NK\\N`\D)DfN0]\:R2:q!HP,:@bi/B,hUn2=HH@D0U&9*].SAVLamm[g)/!o'S"+P9M"(sNnK6&U;Z7O0gkAn3cKdsfjarpTkc(@=(fE.-5#Cf+o<2)2^qQe_O+CY`dMB$-mF:1n:cJ/I/:]GkHe/:2Lei>s'p7qLfG^.V#4O>"/k$4DLoe_]q5D\8guXcP,Lr1q^GpgZtF=7i1-&WBP5]VfH.>SL,#gYn_lC&[nM>E#b@j8&:]#D&j[#Zu&+i!@\be;LC4U[N!NrmE@S9FuM9j=L3p-tsI)1[ZuAm9cc(I+e<@oHO#FI`;l65%)R8_+tBf\ig"Z4C8E+]CPK+?\uFB"1JnIKL]!glCY3*V,19ZW8P.o5CL#\_nFlAnSmW9MSW)UPHMZT_GU&*&@+k;#a;of%ZBrTTV+Y(!a8m^DVj)O]<8AfL^U;.@eYQi-NGoH_7pc,a%/+S6g/duLYqRRt0SQ(;*!3-MdZq]/N4XrOu*75YRW(L%T[%)R)$k1]=9(3^0$q;e/UbVX1im^T17aFs+d]V[[s(cO6/.jGFT$d\M&KO3A]"WhuW&rMW1@&c%!hI\l.'Zt]k314)\,X4V4U9'+.p\C72+5a4(O%Rr(-]eP.2fFZ)39NrXqTN$K[AH.O7HaA'TrJY1uD,=7q>?IB_@2IbaeWAhk]G&Z-Z'"?6eU2HYKBuPTT'=/JN=G:pNCoGE%_WJ<@7@:s89HSVCYG=+",DSH6UV`"@*sK#CsjdS@`EZNCU08T+VB6g9:VrW/^*VKV^Iuqe_Y#qC#TuG1Hl]drm\6J6>7W\dq."!a%8,\u,Pq6@X=LPUl,BDR,]b"bIocfRrDddN5W*\ZNu?r5:$o4O270*@Wp&hbXdIW_\#krV(35t8EtJ6PG'N3cprm(t=2^1[0*g*d+O8,rp9H[Q')B^ALk\%@M0`8A87aj[L_dS2`$+$8dQEJc8?R#SeXRWV^C]X+_=h(dcg]g8Uf!WC>Y:e0-,T?;7-K[R0]m&$uXpmSk$(1[J)2mJR2T@-P#R>024(ah+YN,:FBW`c:gNK12pD=T1ns#1,;$L0g6J"16\JsaUlb1UQ`e24Rm;KSW%qKa%pa?9J&Da`O!A!VmloH/=s[Lr<^Ua!^EM66s@LU*)@JM%j9VG1q*X.9$\h%a2=,6j5T4&T1k`\04*RLoA*W;4aGq;pU9G`jLp<0rF.u^k:\T\h_,IRE""c$=Q7&%*4tTr%W=N^h@Sk<(V5K&XHo1c4CatT7/o]'3@jZ1md^=QI:dd2I#n*KiZJ$.3kcs2)O\V0-'f:Gc7L_'$\DB]Y9Z>qI3nP2>A&4GqVmpcEH[tfNO722h(g/BXR`Wc^4Q'`QbJ(De4IF1HL(,9`t%>+1o/mT-Q13CGb3oJV[H:e/EdKunn<;W8ffRj\Wud%UGq]-nRs8ie'LFi`pn:cp?A-REB50OB?%`JD5?Qh4W[D]`AuZ`r+Uc\\:MmhQ_md[~>endstream +endobj +17 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1792 +>> +stream +Gau0F9lo&I&A@C2lqtm3#SnOJnBePeUhM0tFHT[ql+n0,VI`mSX\e(ho'\YjpgmYDQo0#k#sRkGnc?[dHpHW*!WCnU#4Q4*YMLFqJAht*`$/<2Jj1$WYHo-%%S2ticSNNpd0Pg5(Me62OV^=%L5@06+H`?`KLH+:pNC1*RtGqdM`Y%RFECl%?Y@VPLlt?)%&bF\8JEJTb%>!n;B)LC9THe[`.SbGqQj8lmYgGHq^#Ic2`b+[6khOb/T7eWM3]ZpQVs_RkS2n1cb;MtV?PG*8._#GHdNhS+5IA2c'5ho=7687]&O_pitRorhitl)aO7^NWY4aJHujhU^"ki]a4#%!g21)\e9X=T?`[m#E/5.(5cTX^=M`>IngRJHH2/Ri5EOYJ-e`!Fj+>eDDG`5LB^5KTmf+Z%IgFpF@_s;TC^"QM@bs?>5(dZ)t8b(RAaNoOdJ8PA)o_=)gBCkeib1?[aao$-TGqq645!nrNd;+ghf"E[%9jnQHBXX,W9mE7nGW@Rb$IA35FoF'^\/Qt)J,_N=&U`nFYH2$EHufPmG`;@srnqU@h=4IrAUm:n*[50BhQN1bigWH^eXOVn*fkVh^G?FQ2t8E-'gO?Nq9oA;A66Y9NmX-2O,IRs_oW-*2!bHjfhY-uEST]?SrhV-.A&\?k)ZdcNbSE+GAoF?c`0\O8%`oY26SdaP!$oAEkU;k_tK6E(fOh%/@9`#0B;T_??NG`<;mnb=!oh&O&lH/=PYP7q^C(_c5D#g#bP*\B3/6B+iX]a?*^B@Xt].P7q^cME5iATJN!][*\I#6V:^9=bBI_PbGQR,4F0!8521An`7>$Pb8Ljkgbd=4F/d3Et8S*!ToN9c3_Y?M]n4,[*7%46R#=Oq[DWlK5ZI1o7ZBA85;b_I]Hb8SdhsC965,(,[q@I7I;)l$H$EQ>dle'Gb-W=gOl)!%f$d=YqE,A$rF*Pm8[/1:`D,ET"jf<,$f,NmMf@D#^\=3;Jk-*P;:I!i'YURDJ?Ih;u$$&rlIs%>EB7WVM@mSYBE^5GF8=>5T>c[~>endstream +endobj +18 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 692 +>> +stream +Gar&<:N+u1*51N6]OO"!"Hpca;g"P;Y3Mf(>04Db]*Em?RJP_HpQ];m/"lO'_2[qo($DQssNCkY.)@fAb)[:HPV6M6B(99adoIEY/YZ!p/4.Kt^3@%o2E*8?un]YUl/7OaHtVVF;RM^X@]1&l(p7i$\t%RZ7M:_&IGkS"H%bmmN1HkX]rEeA]E`qaXY+SC;ee@#Qoja&p=kXcDAXM!M^NVV`j&MWsK"a]83fe\c0R;@kOC2.pa8@VErq,c4/iV%/B;UPjZ>*8nX@X&aTW7EJGL5\%rM3nb[,i2RCH7aX*dqfc_cqkS&Ya3n*!Z3lC;+6anf>~>endstream +endobj +19 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2455 +>> +stream +Gat%$D/\1M&cV3*1#S`*?-j!/=agsU[LOqS[C28U[en$#KV>oMf#q@Iq\#Mrn:**GUaT,t0=;3edZQ]+6RE-q(rd,$dX)s?0O4!%.kq"uClrHFg]KZ7m\<%,D_D;i+POVr%"!Hf;PPhb)AW;l$0r%G)g_u_J246rZUpUR#oW@"T=Z?ROq/ad9;ia6&i'E3B<#7QHg=B_qL;eNPg.io<'d$0c\$#E#O2cp)a>KIl%2J'Xu1m\0XTDIN1[:JVn0"C5o1-4`X)]T`G]q(T/#g*q:+H<03g9OHm[SVE.BL."puf_("[Q#admt^c6iN<1"h>%RH<_'8Alo<)-9,BiM<=3@,eZ3b92sClUlmA;8:M'rYMfF:83Y0%!G''.qZ^EY7s.Y>ODC@u`tXREK3mY+!2cP8a&O;]e52rVGL=r7B8]GLmp#"d%^Fk>esd:F^R'c;hl;$"-U_8eH.`#XFYYeoBXu(MG6F#0VCDEUs3CDd6Gcp@EbuI(d,d!],YLGg[&Y?/C"Cp]NBDr(.S+&4]T^*('DrAK\in.2boYAMqT#1G3T\)m+L3\Fn+9cPKY:K6oBJ"LF]n!YE[3lR,C9YsRgde1&1;XgcCd^`ue5^^LQL\T'*SG>P!QV+/?`3^eDV4aBuqDE(\LR$C?G9FI`%&?+OQ`BXCj"lP5MD[Q6q'mo)_HO.Ef+_^9Z_g;]=AGSILoXf11@Ub$u/`C`^n@cj_^L*;>/djWC<3LJnLN,'3+Gf.eEe/Y<,g)5[\^*8C4Vi*X=UA68#erp'579-5$n0Qk%9-*JGP[\rlkVp/m0"GGQOb\JoGJl1aSEe+k1M*)edV%<(;_P&Z&NDm.6'c1L`-cnUJ0HQ%rt/Q4/WeaC.paXr&!.H%DF=FW^'#L("g8Dc7hAa<`=3h.hr#Ol?MMXK&$\"pHLoj_I:gAEja_%L0$;UsV@.Eleo!L5TB3QDNU#D47s];QU"k7>\sa&VLab.nZ'6%;0D_Q=hU2Z@Y67X]L%-C4J`CPk,VkF]bn6/be=`KlR&[A;mnAlWjo&[r5d4L+T5/-(s1U)s_(2*"D"VK`ZNEW,Gf-$k3HZ.8G:0PD4K*p$i9C5::NqK]3(0-&f,;$qi=QHqMShZs?MoTS"_P6"*,jb!4"JT5hA>)63VUeSh*Q=mBkL[%(*UenM'b_,]X_lB@?b.a:o9Gb<4D'Q.pP'TAd6kYIB-%AIc,.0RXGfq^i6`bJ!nGf1?NXq3YSW[:$?/_Q0?!9i:'9QGb9HFgHV2CVq_)QoH>6G#u]P%gf!`c/nqh^6rh-7_THj4&[EGdT?;U2CAiI$NcH`';C'f':kr`+GcJT[*Bg]B4:A@R]gaZ?/qe#Zc/?H,!j+)prmC73GF%YY=f-dScF8Do:'k%+)FWG2"QM9Nfb+nBc)@lB.#WahtY=%6'-iZepC]Rg;8)LVr3F4R\T4.(X$#l&ifd"_r_=Q4bc.D0o'!:].RP^65IAU1#gRNAjWC_g_VZeofFHm1@65F$t)(,9dbpk+/c=t08"MWcE*'/6/+rT@.P#Hk2e\9NprJN`qP-:c';$$V3`HB2OhNoHcBab_oBC*Cc:UO$+D]ppj;`8Zf.l5nXJ2f(I7eUklRngHgn"1@Y1Rqk+T-CO6kGuBrl3jZoa[%rXLW\]jDS6SOY>O?=$jlB'I^H#oiEo9gP+@AVpF=JFRf/!8kl7(-1HA8OI4]ZH'-p:M6Mi9]Ob%ki!\$DgiKMlM4$C)`NKJ]jfGtPA09'np8q!?amgWAf_s5*J)lAgorAd_n<%X7Ai#2AdE8]M8*?`rT'Z;&:GGj^VgY0lc0!'R'g`757t`JrrD@_bNT~>endstream +endobj +20 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2544 +>> +stream +Gat=,D0+Gi')q<+Z%JN>WK_!_a`;3?P\GG8-G=Y%?fPBUO;cGC"=]]`a8fUWT<&!@ZN*S_/U*c7#E^3]h.Lr(I=X^%_\b`;]rD']\UbRc63nrXPW15#68/A8l-5+!!JEMdE7`14cDo#WrJ4",N,E\ODM$i[T?kOiaXQcDhSpu<RG,`a6Y+f;M+U[EGLNc@\]s@?B"H$4mh6I#:EUJje0WSaS\=:PhET_SI2X+bM>dOha4Ed2P:nY\BE!7O``#>^)aAiIr;5f@RiOYGgr.c9@2OeGXU3Xgt-N@^_9D_K=_?VH/gU,#Oi4iChni9YOt3"UEqH6'q!0J$QXR_?1L;i)k9^=]tT@,S3jaX@CF4c7L$%'-#MT9KVOK!_ZCaJa"0&2Q&q^aI7(5U./.Um$oa@C;2b&HOg!>`KHRt->2+<8G>1Kq2/b,/ON(SI@q$!UV@,AN]gF96.]ZP3@l3lop1\bJ`d!\ZEq=5O>El`;D+V>hL6PiIrVA;2]DM.JQN";":PSe$V]j;c*f&/kECG9tMl4?ON:bIh@PjW;Z<[.++]P%nPKo[<%*j@9^E^"K$')6#.I+[VDlAZIB&T-dH(Xb7io^64=8t"XIbXG@8KPoiJ:gC`q\J>J[HA>$;u6oMCDpp;T:L5re\s&dOUXWbuRJKcQ]9n\mtbP1.A3o"q8(39s):^(rn9pAO]g\%c:`^4te^NlU['o?;sm4E`ZQGdQjuE1]+7+-\msFh+ST$Wbj9\,muI[J=`YbX'+!.6er[GqKn!cat^3Q6%`e*A^rH;5J?`@h7>GQm>%a8Onli3/+Psc=.Y72p7/Yk*%Xgg.VhcH')Bps/$Z(17b;S8m;2_'(,3;O:R3M5^m3XdHUiWo@5>NE5G1b-7Z=%]0SNGue#k37bl;QBc<(mPq,-'s,=*@ol3re/H1(Y!dun?;YX%9'r0/gtK[fqa=[_77(LA*?:$ZT4Y$dNils9LD'V2J58:AES^A2J^at)ThA@HL_BTY8(2&$OKDsMYl:tHI]s^rFoa7%W[(h@8tFpW66<^Wmntl6H$%n0K;\?1ND`\"]Ye/OgsP>%[:@rW(V'm39CaK?+)DX22o-!,1`@)u~>endstream +endobj +21 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1060 +>> +stream +Gasanmr-oN&H0m]nRUl0e%/a!!eEnA/uQ.8)Uur9M>G&(=Tba6Z+Y>3G"to!s8B;_&:T;>82+^ipnN:CK-\o4],6^npt_0V[F4+&mC3/hHX2`/#ah=DfUNmrD)Mj7CbY.73;T0e)iY%HbgP&a[miiT8Z=9&r0@c!CiSX2?O,E'co1^p[>/g(+(j/EFoc!p8t*mj\A%FeA;Q6QjTKf=5BG(K__2nWqIc,]Q'Vhuul)GeoT#5)\%KhD7mnDiGYoqGOW'<-[)2e;h9@!/).["GrqZB]bGmr%Rm^%rdpYHp4+48e`tAc5PWH8Yo#]siL8(6hS9#e,X3GjhSAfbJD,;]i!gQ)RBWUfIYVN"YXGNdHou1lSG-`HK@G2,_#6`?#=cdl@kK.iG&l6U21gs+JWD]UIE(0YJ`U(:;?nK^,1eKE1.[t^gh+C9GO/*R_AQhrf(&^X$d[j1)cn6-;F(K@,0pRJeWRcTr1fRA(U70OD;lRkm1o6hkh\IkY5m_S#'5GS?>Q0#rC1"%jNe_<2&Am8udjXL*JnNL1<=uEmhQTe$."%q9_$N4@dW+5a3s<:7#OoV6h^07K/]c?,OLe&d6;ku(XLmY2IjZ5iX!6V&5KYEL094E1hLAS5crmUpou?RB#N<[hIF17Ni6[7k>"7gHj2e>C*rSXSg(s[pZAlG\B=5"b?gHf8pK$EZURQ4>JS9Ct2>(IEq9i*aQH%W;Rh_uH;&TbEunPsf3>iXBSZ'=l`(5!W%"!?Ls&FN@@35+@Hd&RU0ZP#endstream +endobj +xref +0 22 +0000000000 65535 f +0000000061 00000 n +0000000112 00000 n +0000000219 00000 n +0000000331 00000 n +0000000536 00000 n +0000037959 00000 n +0000038315 00000 n +0000038583 00000 n +0000038788 00000 n +0000038893 00000 n +0000039099 00000 n +0000039305 00000 n +0000039511 00000 n +0000039581 00000 n +0000039867 00000 n +0000039960 00000 n +0000042434 00000 n +0000044318 00000 n +0000045101 00000 n +0000047648 00000 n +0000050284 00000 n +trailer +<< +/ID +[<37d39ad66933a680d84b75d7bbad124d><37d39ad66933a680d84b75d7bbad124d>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 14 0 R +/Root 13 0 R +/Size 22 +>> +startxref +51436 +%%EOF diff --git a/README.md b/README.md new file mode 100644 index 0000000..40e8cd0 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# Penjelasan Output Praktikum RAG (Lab 1 窶� Lab 3) + +Dokumen ini menjelaskan hasil eksekusi `lab1_chunking.py`, `lab2_indeks_cari.py`, dan `lab3_hit_at_k.py`. Alurnya berurutan: **memotong dokumen (Lab 1) 竊� membangun indeks dan mencari (Lab 2) 竊� mengukur kualitas pencarian (Lab 3)**. + +--- + +## Lab 1 窶� Eksperimen Chunking + +### Tujuan +Memahami bagaimana dokumen PDF dipotong menjadi potongan kecil (*chunk*), dan bagaimana **ukuran chunk** serta **overlap** memengaruhi jumlah dan isi chunk. + +### Langkah 1 窶� Membaca 500 karakter pertama +Output menampilkan awal teks `pengantar_rag.pdf` (judul "Pengantar Retrieval-Augmented Generation (RAG)" dan definisi RAG). Baris ringkasan kualitas teks: + +``` +{'File': 'pengantar_rag.pdf', 'Total karakter': 4061, + 'Karakter rusak (U+FFFD)': 0, 'Persentase huruf': '83%'} +``` + +| Metrik | Nilai | Arti | +|---|---|---| +| Total karakter | 4061 | Panjang seluruh teks hasil ekstraksi PDF | +| Karakter rusak (U+FFFD) | 0 | Tidak ada karakter korup, jadi ekstraksi PDF berhasil bersih | +| Persentase huruf | 83% | Sebagian besar isi adalah huruf; sisanya spasi, angka, tanda baca. Ini wajar untuk teks normal | + +**Kesimpulan:** PDF dapat dibaca dengan baik sehingga aman dipakai sebagai bahan chunking. + +### Langkah 2 窶� Jumlah chunk untuk setiap kombinasi ukuran dan overlap + +| Ukuran | Overlap 10% | Overlap 15% | Overlap 20% | +|---|---|---|---| +| 500 | 9 chunk | 10 chunk | 10 chunk | +| 1000 | 5 chunk | 5 chunk | 5 chunk | +| 1500 | 3 chunk | 4 chunk | 4 chunk | + +Pola yang terlihat: + +1. **Ukuran chunk makin besar 竊� jumlah chunk makin sedikit.** Pada ukuran 500 ada 9窶�10 chunk, pada 1500 hanya 3窶�4 chunk. +2. **Overlap makin besar 竊� jumlah chunk cenderung naik atau tetap.** Overlap membuat jarak geser antar chunk lebih pendek sehingga dibutuhkan lebih banyak chunk untuk menutup teks yang sama. Pada ukuran 1000 jumlahnya tetap 5 karena teks hanya 4061 karakter, jadi perubahan overlap kecil belum menambah chunk. +3. Ukuran chunk lebih berpengaruh daripada overlap terhadap jumlah chunk. + +### Langkah 3 窶� Memeriksa 3 chunk acak (ukuran 500) +Untuk tiap chunk ditampilkan penanda seperti: + +``` +chunk #1 [0:500] awal_tengah=False akhir_tengah=True potong_tabel=False +``` + +| Penanda | Arti | +|---|---| +| `[0:500]` | Chunk mengambil karakter ke-0 sampai ke-500 dari teks | +| `awal_tengah` | `True` jika chunk **dimulai di tengah kata/kalimat** | +| `akhir_tengah` | `True` jika chunk **berakhir di tengah kata/kalimat** | +| `potong_tabel` | `True` jika chunk **memotong tabel** | + +Pada chunk #1, `akhir_tengah=True` terlihat dari teks yang berhenti di "...memungkinkan j" (kata terpotong). Ini menunjukkan **kelemahan chunking berbasis jumlah karakter**: batas chunk tidak mempedulikan batas kata atau kalimat, sehingga makna bisa terpotong. Overlap berguna untuk mengurangi dampak ini karena potongan yang terpotong muncul lagi di awal chunk berikutnya. + +--- + +## Lab 2 窶� Membangun Indeks dan Mencari + +### Tujuan +Membangun indeks dari seluruh dokumen, lalu menguji apakah pertanyaan menemukan chunk yang benar. + +### Langkah 1 窶� Membangun indeks +``` +etika_keamanan_ai.pdf: 4 chunk +machine_learning_dasar.pdf: 4 chunk +pengantar_rag.pdf: 5 chunk +Total 13 chunk | ukuran=1000 overlap=15% | backend=lsa +``` + +- Ada 3 dokumen, total **13 chunk**, dengan konfigurasi **ukuran 1000, overlap 15%**. +- `backend=lsa` berarti pencarian memakai **LSA (Latent Semantic Analysis)**, yaitu representasi vektor berbasis statistik kata. Ini bukan model embedding neural seperti MiniLM. + +### Langkah 2 窶� 5 pertanyaan uji +Setiap pertanyaan menampilkan 3 hasil teratas dengan format `skor`, `dokumen#nomor_chunk`, dan `relevan`. + +- **skor** adalah kemiripan (cosine similarity) antara pertanyaan dan chunk. Makin dekat ke 1, makin mirip. +- **relevan=True** berarti chunk tersebut memuat **kata kunci jawaban** yang sudah ditentukan. + +| Pertanyaan | Kata kunci | Hasil peringkat 1 | Penilaian | +|---|---|---|---| +| Siapa yang memperkenalkan RAG dan tahun berapa? | `Lewis`, `2020` | `pengantar_rag.pdf#0`, skor 0.741, **relevan** | Benar. Chunk berisi info Lewis dan 2020 | +| Berapa dimensi embedding yang dihasilkan model MiniLM? | `384` | `pengantar_rag.pdf#1`, skor 0.969, **relevan** | Benar dan skornya sangat tinggi | +| Apa itu overfitting dan apa cirinya? | `overfitting`, `data latih` | `machine_learning_dasar.pdf#0`, skor 0.939, **relevan** | Benar. Peringkat 2 juga relevan | +| Undang-undang apa yang mengatur pelindungan data pribadi di Indonesia? | `27 Tahun 2022` | (terpotong di screenshot) | Cek hasilnya di terminal | + +Hal yang bisa dibaca dari hasil: + +- Jawaban yang benar umumnya berada di **peringkat 1** dengan selisih skor yang jelas dari hasil lainnya. Contoh: pertanyaan MiniLM, 0.969 dibanding 0.351. +- Pada pertanyaan pertama, peringkat 2 (0.736, dokumen etika AI) skornya hampir sama dengan peringkat 1 (0.741) walaupun **tidak relevan**. Ini tanda LSA kadang bingung pada pertanyaan yang kosakatanya umum. +- Hasil yang `relevan=False` di peringkat bawah itu normal. Yang penting jawaban benar ada di posisi atas. + +--- + +## Lab 3 窶� Mengukur Kualitas dengan Hit@k + +### Tujuan +Mengukur secara kuantitatif apakah chunk jawaban benar muncul di **k hasil teratas**, lalu membandingkan dua ukuran chunk. + +### Definisi +**Hit@k** = proporsi pertanyaan yang chunk jawabannya muncul dalam k peringkat teratas. + +``` +Hit@k = (jumlah pertanyaan yang jawabannya ada di top-k) / (total pertanyaan) +``` + +Contoh: Hit@5 = 0.933 berarti 93,3% pertanyaan berhasil. Karena nilainya kelipatan 1/15, kemungkinan ada **15 pertanyaan uji**, dan **14 di antaranya berhasil**, sedangkan 1 gagal. + +### Hasil perbandingan + +| Koleksi | Total chunk | Hit@1 | Hit@3 | Hit@5 | +|---|---|---|---|---| +| Ukuran 500, overlap 15% | 25 | 0.933 | 0.933 | 0.933 | +| Ukuran 1000, overlap 15% | 13 | **1.0** | **1.0** | **1.0** | + +### Diagnosis kegagalan (k=5) +``` +ukuran 500, no. 7 (Bagaimana menyatukan ketepatan dan kelengkapan menjadi satu angka?): +chunk jawaban ada tetapi baru muncul di peringkat 18 (> 5) +``` + +- Pertanyaan no. 7 gagal pada ukuran 500. Chunk jawabannya **ada** di indeks, tetapi **berada di peringkat 18**, jauh di luar top-5. +- Pada ukuran 1000, tidak ada pertanyaan yang gagal. +- Perhatikan bahwa Hit@1, Hit@3, dan Hit@5 pada ukuran 500 sama-sama 0.933. Artinya pertanyaan yang gagal tidak sekadar "hampir masuk", tetapi benar-benar jauh dari top-5. Memperbesar k tidak menolong. + +### Dugaan penyebab (sesuai kesimpulan program) +1. **Ketidakcocokan kosakata.** Pertanyaan memakai istilah berbeda dari dokumen. Contoh: pertanyaan memakai "ketepatan dan kelengkapan", sedangkan dokumen memakai istilah Inggris seperti *precision* dan *recall* atau *F1*. LSA berbasis kata sehingga sulit menjembatani parafrase ini. +2. **Chunk kecil memuat sedikit konteks.** Chunk 500 karakter hanya memuat sedikit kata konteks, sehingga skor kemiripannya kalah dari chunk lain yang topiknya lebih umum. + +### Kesimpulan dan catatan penting +- **Ukuran 1000 karakter lebih baik** daripada 500 karakter pada eksperimen ini (Hit@k 1.00 dibanding 0.93). +- Skor sempurna **jangan dianggap bukti sistem sudah sangat baik**. Korpusnya hanya 3 dokumen kecil (13窶�25 chunk) dengan topik yang jelas berbeda, sehingga pencarian relatif mudah. Pada PDF yang lebih besar dan topiknya lebih mirip antar dokumen, Hit@k diperkirakan turun dan perbedaan antar ukuran chunk akan lebih terlihat. +- Laporan akhir tersimpan ke `Lab3_Hit_at_k.pdf`. + +--- + +## Ringkasan Keseluruhan + +| Lab | Pertanyaan yang dijawab | Temuan utama | +|---|---|---| +| Lab 1 | Bagaimana ukuran dan overlap memengaruhi chunk? | Ukuran besar menghasilkan chunk lebih sedikit. Chunking karakter bisa memotong kata atau kalimat | +| Lab 2 | Apakah pertanyaan menemukan chunk yang tepat? | Umumnya ya, jawaban benar di peringkat teratas dengan skor tinggi (backend LSA) | +| Lab 3 | Ukuran chunk mana yang lebih baik? | Ukuran 1000 lebih baik (Hit@k = 1.0) daripada 500 (0.933), tetapi korpus kecil sehingga hasil perlu ditafsirkan hati-hati | diff --git a/__pycache__/laporan_utils.cpython-314.pyc b/__pycache__/laporan_utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..249f1cab04beb0d265232d2ef54ee0268a928890 GIT binary patch literal 8494 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 literal 0 HcmV?d00001 diff --git a/__pycache__/rag_utils.cpython-314.pyc b/__pycache__/rag_utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8e5703a14c5f3f75844591b76634e390766aecf GIT binary patch literal 10758 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 +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 +<< +/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/PageMode /UseNone /Pages 8 0 R /Type /Catalog +>> +endobj +7 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102015146+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102015146+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Etika dan Keamanan Kecerdasan Buatan) /Trapped /False +>> +endobj +8 0 obj +<< +/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages +>> +endobj +9 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2190 +>> +stream +GatU3gN)%,&:O:Si6fg%X@V.hSL.([1`[rE56)=)&HW9E,`mu^ofW\5V!Q0uXc18c8-!usHhN&Y:%4"qs'T9PW/C5eoJ\$05P];RlB^1d8:bQ"eWh)-1Rp2o?O;6o5@&e?,dJ'.s]#^SoXN7>$Ii$e1BbRESoAFcibUAoGbC(:=nYa'ff4+Rp5&&/&ZX#o%[_$FY7I-<17*mS'`>^DHXWntKVk,MYLi9fC6hNnPN'!s*A3[?Ti4fmS1+RV;P(Ed6%4N37:j$D@U.#,2j!CMB;s7)Cig0;niM?:)XUS5,nS.T:.He_ZB1Nmn/Po?l!H7aR\ZZj2tD]gJf)(E?i[BXOVZuD!aQ$C*9Gd\HA`G>T8H9?5?P?Xsc7mQS.+9K;=-[!tO>1DVP+R#$XrC"YGP4aDCt=p/Z7-46ZNgI4k8XLu$YQ)u33bGUmg6;sU/GR6Z[7T#s@\a\QKd439\O[+(h4SFGnTE&=Q,!<@]j>;IIH_>XJso`!QoZ+nJVXBDi>Rm>UOktj!fFD4j;HqF;SNf%m8%A*WfmIr9uijK?<4!-$E;s??f2n2"Ku(@0HbjT!MpFJQKJP&-^!+X.coPo1+JZk]OF>I?M.L7G(/LH81i*PC\Jo;E`)?r[4FBLgF14N`L4h+NS"H$2\hU9OE$nG5rhEr4::d2XjcrfQ4:bjoUXE\L)D1cY$c>1X)toZ#UN_I,4\UUnXpcb;an[)c;iF,-!_2<#c5B,7XtqaIc(JY66#a)d?0S/Gq,7=W'UgMsofET6JTT6RHTc_jufNPp@qcUp?#F#5t8o:kF@pA#L)WfJ#11j>SNuYIYRMkSio-aPDbNE5k?Vmj"9p7[AZq,>"&j.2kL;CfPUtCMAN6ZUead:fCR:0kl"9U^t,/-o$=rb*Q7Ggo0dE*HecmfH;q-lq'jG,dithChU=2^'E!sq2B;k=c\'N^:J]i\GO-07-YJ>&bt\:LG6WSi`cLN&WeZ2O@hi,TlTe!$AHb8g1#'B8(p;B)K-!:(dEH71=TuYd*Bl=NM!D$R"JJCC\]+U*^+J7b%R>`*-Lb\.bEjbcr>Tg(sO:Sno/(KR-tY!&MO6Sd#'>KFYjVB%jJ6&_&sTf`-r-1L1B4S0u]>g$EEQgP.T*i^b^39Duj&n]`)T0/8+RA4oL+()CO*LjGR`:/E[Ca*A:PIf_q:07f+;g$^2ZN_/,AFq3eTu?981mF3uf:=Vp):,ap%Nj;cR]_EKJrMQ?Yi5+.rdqXZ--\.l\].c%VTOdOH!dqO,#nm/cnQDR'2P3sr(,.G08k\aX=)g"?PN2D$8$=<=^7u^H`@TW]$[?D&d*dpinC0'aB?(C)C)>7aNY_-F(ZP878!6R0*]at3PEcBDQFhpVr5F*:`W+`#0XL~>endstream +endobj +10 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 536 +>> +stream +GasIc997OU&AIm?I)dih+`=ocjTYc=^]BSX?7(Y'>X)\,>DRdTfI=H!9"U?7/'<+Nf8Mu>8!Nm]JIa?hHkm8tZp,`-eK4*ZHtl`t;lbkI[SA'Wp(.d)M*$aD40LVhYBBIOS.82D,Mc\1b0EfiCo<2B!rCXY,lSK=E2>'T_WqhNsWNg,9`mR0*];!M$`LS;i:L*&lF)qAqlK-ck9b&RsESBsNe"`m+*T46g8rWuqQ20,(o>E=X@A_0(h/%sYT/gCI2Uh/G;;pQ2'.`&b'^X=BF.qsq?deZg9BeicO'[*t/0,VQE0HD/^8Z>qV(mZsdr091b7.61+ju_t[^A(=A^Hs_=W.YD58,cB'=k2&d\8`,Ok1i!0%%1bHZ;QEh=\GqcVNlSf]TM,+o^lo=09^KmoC0"-t@.b=;2@(E~>endstream +endobj +xref +0 11 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000321 00000 n +0000000524 00000 n +0000000728 00000 n +0000000796 00000 n +0000001099 00000 n +0000001164 00000 n +0000003445 00000 n +trailer +<< +/ID +[<7636af93ea22ee58f59714add6924af0><7636af93ea22ee58f59714add6924af0>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 7 0 R +/Root 6 0 R +/Size 11 +>> +startxref +4072 +%%EOF diff --git a/dokumen/machine_learning_dasar.pdf b/dokumen/machine_learning_dasar.pdf new file mode 100644 index 0000000..1eb0d39 --- /dev/null +++ b/dokumen/machine_learning_dasar.pdf @@ -0,0 +1,93 @@ +%PDF-1.4 +%東京 ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 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 +<< +/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/PageMode /UseNone /Pages 8 0 R /Type /Catalog +>> +endobj +7 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102015146+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102015146+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Dasar-Dasar Machine Learning) /Trapped /False +>> +endobj +8 0 obj +<< +/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages +>> +endobj +9 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2005 +>> +stream +GatU3bBDVu&Dd46Y]O]>&LgV=A^f-lVg26]Pch8MP-#]EdfbGn);LXj1\B?;5Mof91WJ:Rf\9W&q#O]:QlgVt>CC%3X=jLcn=)A7;`9O\hL*&)@g<-H/'uBApA1=@Cs8P%@/cZq"T^&U/A,XNT0p8Kg#[QeEBUU/p5.[FAM2V*_0%C%j:,bbad5YGN$1;-"n?ampTZe(OMlX\%_6joFG/mU7fT#+%I7XQiN[`oITPMHD:8(5b.V&$=0/'1[qMkn]4b%8OlrkqqdEh:1"PAiY`-7<7m`WGqF(n0XKk%iCo`bGo]h68ZsmG(t22F?@,2)%YHR4)L-'I17V%TdX)u"R$hVG4e9]QB*1ckfimBcFRE7ofN*%;1G&2.64C7n3`Ne0I!,<:?5o\t,)r-BSo1CmjpG[$#ph)nj[\CSo,9'/&/\-PA(;(SFg9Rj_Y1Aqf*60=K$I2VT'V[4?PI#dEDu>gcHrk:OWlsodr!cjaU!=srA^VP\[<+1Cq?RFd6AD"=r-og6^-afeVc6VfU;#,W2UPPDkRk\"K/9q=t88_/PnU*Q!G?5^bFMle'Y$hEJNr[Su%17pS^2c$Hd.b#n]E8nZmLp(5KELdKQGEr\e&.$NKH2VX*N)s!Q3;?+i_1rrRqMW(p*K3r\a,lC0*l'LP@O>]A1u#mm+MnIh%,3rmbK?u.)X'F,GlS=j"VH)YMbYUV3X6R96a>/kS?QQ/fBanYKRGrQrl:lhP4q!u>KK?tc?6$.FI30KH>22kol:853D4W`EIA5QX-.eQM/gU>WU-OEl8.=V1@(U,J)5AR2*8+h)nqcaMe&A3l8>t5V:,5Z%KV3K`ebQe=LuhG3'9F:pOI96:9<_Xm0#W)UrXp'K6AZd:OsTT03Cqh>*=[-l!$2uFdtlO0'"lR/[]%?U<]bgatfKh\HNF,P]`m(P4Wl;13(4;a%1,JoBt#1_.jZr4\T)h(\F_rYUo-Xh1:;dtjNfqtoJiH+j(PrQX*ZmeV*PI>dc4Pg6N,_=DEcYX_NYb[.E`A*MH%SY:J>F9(,L?NSIi#2Vr2-]I;TBUJfs0VXrq82[_]r;%>9<-fIY1K8Q@]dSBU0JEVY(Du@C"OcA]G1uriJlI'(mbhi*FU+G&f68l?V)+$GGF1Buk:c`r>^4ZK?VJ<5HiNR6KS]S?Rm>Q6!N["4C'EYVoC:,dEW=nRNPfM.o:Z/bendstream +endobj +10 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 909 +>> +stream +Gat=hc#28i&;9M$MEN;?X'5KuO%#NXl9h$*R*3XJ.7OrL;IYNrI>=%=a),;gJH/ms`VjsmB:70N:E"_l2:7bL52E*#:+=pp3`"n&D?S7\P>#(7RqIY^#fU0SdVa`Y8%cQQlK!Mru2@8gX[g9gQIel*lmP?k?<66^2C8CBbDNekYSgaIIGX(\f12#'obWTrPt)q/_B5Ud-U/b#*e=K`.BKlLb!&-mV4*'FLpU8PO>7,:@ID#NM9V-NH@0BYGksN[DXtgP42ct'#&s<(&qkJeV#adJ#Wb%c5VJ1V0$$X2@44G3s1HK&D8kNE4YV)bEcEVk+4ZU?QkD\bs2m;=ZRkRQs(pu[)ngB.-4K2rrsTf;dlBZ3p5pI5F:u\7@DVo2eB^YXAiu156A2D%OqmmNsAsD/p%>88?gRA-g)Vu^&l+kO"&\sL+`s@S1hRnp1udGJPSDV7;Z^7Kjl(f$^qdm$VXXj2!c]C:UdpcekkM)7$`QKfEb^1X`^p4lef\@Z@D%U$TjH^5Rnfh?;[VnNdsqY\7#V:h8+]>$WbhPGA%9H](&mITdUn,iG>8&]!mAV3padZ@7"]qgB@/h\qU"]k[<9.;"uXYNeE?hSGV0,H0AF'9aDaKqT?Y%P*dNiAD]E(i`d+Z_pe7&Ku+N0O4P)0#a&P"R%j6j\WC&<4q'`N".#Ne+Z?GD0TD8_QP_fSp;m36,8Sb]3utS\$rZm.pm.!9s$>eJbP1(clg$E_2X._iieH4=N^JrG3f=IWA!Ga'`i_F4g7VT$kr6%38dbjt;(%@endstream +endobj +xref +0 11 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000321 00000 n +0000000524 00000 n +0000000728 00000 n +0000000796 00000 n +0000001091 00000 n +0000001156 00000 n +0000003252 00000 n +trailer +<< +/ID +[] +% ReportLab generated PDF document -- digest (opensource) + +/Info 7 0 R +/Root 6 0 R +/Size 11 +>> +startxref +4252 +%%EOF diff --git a/dokumen/pengantar_rag.pdf b/dokumen/pengantar_rag.pdf new file mode 100644 index 0000000..49f2d7d --- /dev/null +++ b/dokumen/pengantar_rag.pdf @@ -0,0 +1,93 @@ +%PDF-1.4 +%東京 ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 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 +<< +/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/PageMode /UseNone /Pages 8 0 R /Type /Catalog +>> +endobj +7 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102015146+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102015146+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Pengantar Retrieval-Augmented Generation \(RAG\)) /Trapped /False +>> +endobj +8 0 obj +<< +/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages +>> +endobj +9 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2164 +>> +stream +GatU4bAs(C']&X:ma=\K$m6_89.,q!At7?r[Utk#,MK(*1e^7aQ+rr4Rtm(IaH=Z+ACQ$7kM2C:OH6$L]l"4PG8pD=@mE=NRl^rmhL[YXl,2ZM^U'TF\QmQ4RX=AnjVeaknD/XecQF/8l6'1oBQHt=[rcV_#0qY7c_5u"E7D^;E,b5S?*f1HJOf'm1lW-:@(&#jPaQ(s*QR-elRW':TDsYJaOtlBe0GZhM]E+&X%$]\2=sQ*PTZ'b,.9PWi+XM1U4,^isVXd#s-$/heOVp$d;(9)cWKWW;'nWHk.@o#G$0SRU3MP&'b?1Q,;-"n%XJSH$lk^.bBEfCe],*\#$o^ktc3`l4P2S'SOtg<+9*SKoA0t]@PZF"sR*DFEOXP:]UX/M/W[Ybo#/BeaH_h)A&;-c-oKs^PoMKF9>a?75rA?ua,ASaPG3@8mh?-RY]-EUr[fug]TUab?Bjg>p'XD+mQ"D*X(jM+VYLUh2O7r0GZ;*8jK)T2=rl>aQ>*p*:#.L:mk+2)Qf;Q(/jAD,QuQlOnfX:Xae$@$Oso@3)n0OtVt:'K?Q,*#4@tc4B9XW6E3!#.`?b^A$JaA+XVrj5P0q86F_,S)AV\?C<22)@e8"MI*!bkE#\q/j@9\XVGgs"`U31m`LfI1f3LEt_.KSm7EeW7`md,lp8k*(uMLGT:.Vh[2P9#+;.>t2LWuINKoZCl%(T3S>gnPC.n1NIuAV%Lrp9$SjDgqM^3n[a(>*"f26"@b?7`TM^(hmj72`_KN\8\)Z.InP<6if]"+.^.c+p5PdcoaOBEI>@LRHEg(00[8j5hp7%Fa.ujWmP;5ICRs8i(l@FD6Iib>k^k;:"ebpVaQ9HF"ibrHH0K#8:C#Oc$ki4H$K.Rq*%VIm?TPYMR;C(6\/ckW/,:nF6>hh"*C<,?aIRMNDENS6t#n3q%=+-TAN7/Y$AIh.RaB5gO@4R#>AcnRpkI\RNuLLD;;;CXpsg5DH,38$5:tK/B0*\7KXZ`6eKQP/8R^]6i3$V/jXF1L,lMNi2%2LLV:G\k\iEa&M"&<,ZoUH+YD+"RMLTo\]$i$1N8_(X]SFE;5]iS:L+5B*kg`k1MTf`)$hJe23mudhpXiNCO)?'Ek@OI^';2lBjbiC'jK=bD]YF]n0.B$_$Cs'KhMr'ha_.[TH*O7r4sFT32Gs\[\\j;EOlF.p$mSj;2"skAf22K_JQ.<8lK]H_k_$H$XG]R)p&DAfOJH7h3G)ER7jC?\(\Pp%<6$J6Nn_YAMKF.8<(-S>fKmW%'R]V#B!JAp0%ZN$B#652X(_&QJ.*E6G';M?kVK2h+tt&S,gqg"T0l-=/OZrel_iX!\5gr0oFS!^-##d[U4'9G7^E2GPcNG$b)D5[u#fDR?n**A"'I[$bdI(qa*B?Wf>#H!s?9+-O.4'j"Y+l]jfHkAS^%)LT+^U".1b>VAI9:'E-g;Ea/>L$=9e$M3_dg>^=B^RIHjm!QQmp0^QS(I90dN\U+`R1Ul*J]%a9GucVnCYTZCeopi+mu0Ti>m[:IVLbek1O`D:,..PIG7PJ@`_FLn+a6WgOtf=St(^#P7$\bl3'%[o*qq&%lfEl=TL`4X00Wr"fol!l#pns,X(^4HQW],o+GoEB6l\p!7NV_R(l)j79:$_U.38GCSaDIW2hc?[_Emo*IqZZZ!.g^8p^>WM)'@)5r00UEuL?&)=eO%I"_aAYGc2H#Bs!Z1g0)~>endstream +endobj +10 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1431 +>> +stream +GatU2gN):C&:N^lqU%kH77&+>:V2Y]P&*$V#*M)-BFNp`7`n9-bC?/5,9_G%Y&]E*OlghucT0fK6m4aVs1ca6/iWFokD5i=D/Lbj##j7R6(^'1In*AF1.c.CSHk(lEe,6u#lTG2O,eQS^N]6h-OtO7-0c_jNng82oERd)^dU'f6Se#\eB7_TRp4VE.*#ZSF#l@N7):'N/Dl*LXg#2g'kIhUB)s@L4@nK-K&WKqU\H]s[4*(E]2O_f[^7Vkd0!\(Ki.OpCcmW.kq&7;-a0Sf1qBo&6!g[3W+:7uG5hp#n+aL8"g]md-4rE@?@BX(h2eXmb6;cL)6GVI:#]P9T-\s!J*ElKVrM2MN"rOuO.odXU29.&qWUQk[2R,.s3a]`E2D_iVYc]tqYD)7io3OM`l0<7VoZNG2@AO`m*8d3Rs_9l0BBLr)m5iLWGId_.QAq*0U(VX:!=)einXaMKu]YdoXEQ)E7,[)e0%:EGrr:mfuuLE8mho/HI+[R6ONXd.C/Un`d$4)#&d%Za%g-G<]0f_B0<\')6O)FG_0Ol;U$t^0d=MZV;**6*V!/Q8io3"MV7Q;-V!8&qodH"M@V?@1natQ:;M6V`U4h)?\%:\oQLB)L/RP1X@ug-"m[B)49lQ1*C1qg)X:`=cii.3.XOWX2ct\?jWAm.=SZuG"WT]"#*l29g#==2AR51`4o[#=Llm-#c`;CB@koM5m0*3RaV[`!nD-'raq,5h@G>+@ipXREQ`X6V)Z4CPDd-2bN5H_`Kn1e.r2OarrV16u1q.6i;2-_?43.+JLVPHu1UEPV]3*Oj-+8%UuI*":sD,kKhW8WoQEOgg4D+CA!oas[FHpBJ%E\`9mJdE$V;k4B(JfFZ:2P16H'eZ\sp!ng_ef*i=6+$.KX'j$5-bR_*7\S+\R_20kel(IrKbZ(XKq>'QB$s:DF;1:?$`1GH/8jr5NWWI5r:7@GmY1#@Q&gj\Sj$k14i;;C$2Em;fV8A\i`j:(&uaaa.7+5P.5m0;RUE`><^i7&@B,"S8\!h"d)Y,nOiuT'p3jfOlLL4C%NJkno^#~>endstream +endobj +xref +0 11 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000321 00000 n +0000000524 00000 n +0000000728 00000 n +0000000796 00000 n +0000001111 00000 n +0000001176 00000 n +0000003431 00000 n +trailer +<< +/ID +[<94e0708a6e608b44ad056752ec8119b5><94e0708a6e608b44ad056752ec8119b5>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 7 0 R +/Root 6 0 R +/Size 11 +>> +startxref +4954 +%%EOF diff --git a/lab1_chunking.py b/lab1_chunking.py new file mode 100644 index 0000000..c4d57ab --- /dev/null +++ b/lab1_chunking.py @@ -0,0 +1,155 @@ +"""Lab 1 - Eksperimen chunking (15 menit). + +1. Baca satu PDF dengan baca_pdf() dan cetak 500 karakter pertama. +2. Potong dengan potong() memakai tiga ukuran (500, 1000, 1500) dan overlap 10-20%. +3. Lihat 3 chunk acak tiap ukuran: terpotong di tengah kalimat atau tabel? +""" +import os +import random +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from laporan_utils import Laporan +from rag_utils import FOLDER_DOKUMEN, baca_pdf, norm, potong, potong_dengan_posisi + +BASE = os.path.dirname(os.path.abspath(__file__)) +PDF = os.path.join(FOLDER_DOKUMEN, "pengantar_rag.pdf") # ganti dengan PDF Anda +UKURAN = [500, 1000, 1500] +OVERLAP = [0.10, 0.15, 0.20] +OVERLAP_UTAMA = 0.15 +SEED = 42 + + +def wilayah_tabel(teks): + """Cari rentang karakter tabel ("Tabel N." sampai baris "Sumber:").""" + hasil, pos = [], 0 + while True: + a = teks.find("Tabel ", pos) + if a < 0: + return hasil + b = teks.find("Sumber:", a) + b = len(teks) if b < 0 else teks.find("\n", b) + hasil.append((a, b)) + pos = b + + +def awal_di_tengah_kalimat(teks, mulai): + """Chunk dimulai di tengah kalimat bila karakter bermakna sebelumnya bukan akhir kalimat/baris.""" + sebelum = teks[:mulai].rstrip(" ") + return bool(sebelum) and sebelum[-1] not in ".!?:\n" + + +def akhir_di_tengah_kalimat(teks, akhir): + ujung = teks[:akhir].rstrip() + return bool(ujung) and ujung[-1] not in ".!?" + + +def memotong_tabel(wil, mulai, akhir): + """True bila chunk beririsan dengan tabel tetapi tidak memuat seluruh tabel.""" + for a, b in wil: + beririsan = mulai < b and akhir > a + utuh = mulai <= a and akhir >= b + if beririsan and not utuh: + return True + return False + + +def main(): + # ---- Langkah 1 + teks = baca_pdf(PDF) + n_huruf = sum(c.isalpha() for c in teks) + kualitas = { + "File": os.path.basename(PDF), + "Total karakter": len(teks), + "Karakter rusak (U+FFFD)": teks.count("\ufffd"), + "Persentase huruf": f"{n_huruf / len(teks):.0%}", + } + print("=== Langkah 1: 500 karakter pertama ===") + print(teks[:500]) + print(kualitas) + + # ---- Langkah 2 + print("\n=== Langkah 2: jumlah chunk ===") + tabel_jumlah = [["Ukuran (karakter)"] + [f"overlap {int(o * 100)}%" for o in OVERLAP]] + for u in UKURAN: + baris = [u] + for o in OVERLAP: + n = len(potong(teks, u, o)) + baris.append(n) + print(f"ukuran={u:5d} overlap={o:.0%} -> {n} chunk") + tabel_jumlah.append(baris) + + # ---- Langkah 3 + wil = wilayah_tabel(teks) + rnd = random.Random(SEED) + cuplikan, statistik = {}, [["Ukuran", "Jumlah chunk", "Awal di tengah kalimat", + "Akhir di tengah kalimat", "Memotong tabel"]] + for u in UKURAN: + pot = potong_dengan_posisi(teks, u, OVERLAP_UTAMA) + n = len(pot) + a = sum(awal_di_tengah_kalimat(teks, m) for m, _, _ in pot) + z = sum(akhir_di_tengah_kalimat(teks, e) for _, e, _ in pot) + t = sum(memotong_tabel(wil, m, e) for m, e, _ in pot) + statistik.append([u, n, f"{a}/{n} ({a / n:.0%})", f"{z}/{n} ({z / n:.0%})", f"{t}/{n}"]) + pilih = sorted(rnd.sample(range(n), min(3, n))) + cuplikan[u] = [(i + 1, pot[i][0], pot[i][1], pot[i][2], + awal_di_tengah_kalimat(teks, pot[i][0]), + akhir_di_tengah_kalimat(teks, pot[i][1]), + memotong_tabel(wil, pot[i][0], pot[i][1])) for i in pilih] + print(f"\n=== Langkah 3: ukuran {u} (3 chunk acak) ===") + for no, m, e, isi, aw, ak, tb in cuplikan[u]: + print(f"--- chunk #{no} [{m}:{e}] awal_tengah={aw} akhir_tengah={ak} potong_tabel={tb}") + print(isi) + + # ---- Kesimpulan otomatis dari angka + rincian = "; ".join( + f"ukuran {r[0]}: awal {r[2]}, akhir {r[3]}, memotong tabel {r[4]}" for r in statistik[1:]) + kesimpulan = ( + "Pemotongan per karakter tidak mengenal batas kalimat maupun tabel. Hasil seluruh chunk (overlap " + f"{int(OVERLAP_UTAMA * 100)}%) - {rincian}. Jadi pada setiap ukuran, mayoritas chunk berakhir di tengah " + "kalimat dan sebagian besar juga dimulai di tengah kalimat (hanya chunk pertama yang selalu bersih). " + "Persentasenya tidak turun banyak saat ukuran diperbesar; yang berubah adalah jumlah chunk dan " + "banyaknya topik per chunk. Tabel 1 terpotong oleh sedikitnya satu chunk pada tiap ukuran, dan teks " + "tabel hasil ekstraksi sudah bercampur antar kolom, sehingga baris tabel yang terpisah dari baris " + "judulnya sulit dipahami. Perbaikan yang layak dicoba: pemotongan berbasis kalimat/paragraf dan " + "memperlakukan tabel sebagai satu chunk utuh." + ) + print("\nKesimpulan:", kesimpulan) + + # ---- Laporan PDF + L = Laporan(os.path.join(BASE, "Lab1_Eksperimen_Chunking.pdf"), + "Lab 1 - Eksperimen Chunking", "Dikerjakan dengan Python (pdfplumber + fungsi potong)") + L.h1("Langkah 1. Membaca PDF dan memeriksa teks") + L.p(f"File yang dibaca: {os.path.basename(PDF)}. Berikut 500 karakter pertama hasil baca_pdf():") + L.kode(teks[:500]) + L.spasi() + L.tabel([["Pemeriksaan", "Hasil"]] + [[k, v] for k, v in kualitas.items()], [6 * 28.35, 8 * 28.35]) + L.p("Teks terbaca bersih: tidak ada karakter rusak, kata utuh, dan urutan paragraf benar. " + "Satu catatan: teks Tabel 1 hasil ekstraksi tercampur antar kolom karena sel yang lebih dari satu baris " + "digabung per baris tampilan, bukan per sel.") + L.h1("Langkah 2. Jumlah chunk tiap pengaturan") + L.p("Fungsi potong(teks, ukuran, overlap) memakai jendela geser per karakter; langkah = ukuran x (1 - overlap).") + L.tabel(tabel_jumlah) + L.cap("Tabel 1. Jumlah chunk untuk 3 ukuran x 3 overlap. Makin besar ukuran, makin sedikit chunk; " + "makin besar overlap, makin banyak chunk.") + L.h1(f"Langkah 3. Tiga chunk acak per ukuran (overlap {int(OVERLAP_UTAMA * 100)}%, seed {SEED})") + L.tabel(statistik) + L.cap("Tabel 2. Statistik seluruh chunk pada tiap ukuran (bukan hanya yang dicuplik).") + for u in UKURAN: + L.h2(f"Ukuran {u} karakter") + for no, m, e, isi, aw, ak, tb in cuplikan[u]: + L.p(f"Chunk #{no} (karakter {m}-{e}) | awal di tengah kalimat: {'ya' if aw else 'tidak'} | " + f"akhir di tengah kalimat: {'ya' if ak else 'tidak'} | memotong tabel: {'ya' if tb else 'tidak'}") + L.kode(isi) + L.spasi(4) + L.h1("Kesimpulan") + L.p(kesimpulan) + L.halaman_baru() + L.h1("Lampiran: kode lab1_chunking.py") + L.kode(open(__file__, encoding="utf-8").read()) + L.simpan() + print("\nLaporan: Lab1_Eksperimen_Chunking.pdf") + + +if __name__ == "__main__": + main() diff --git a/lab2_indeks_cari.py b/lab2_indeks_cari.py new file mode 100644 index 0000000..4529d40 --- /dev/null +++ b/lab2_indeks_cari.py @@ -0,0 +1,143 @@ +"""Lab 2 - Bangun indeks & cari (25 menit). + +1. Letakkan 2-3 PDF di folder dokumen/, jalankan indeks, pastikan tiap file melaporkan jumlah chunk. +2. Ajukan 5 pertanyaan dengan query(); periksa apakah chunk teratas benar-benar relevan. +3. Ajukan 2 pertanyaan berparafrase; apakah pencarian semantik tetap menemukannya? +""" +import io +import os +import sys +from contextlib import redirect_stdout + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from laporan_utils import Laporan +from rag_utils import bangun_indeks, cocok, query + +BASE = os.path.dirname(os.path.abspath(__file__)) +UKURAN, OVERLAP = 1000, 0.15 + +# kw = kata kunci yang HARUS ada di chunk teratas agar dianggap relevan +PERTANYAAN = [ + {"q": "Siapa yang memperkenalkan RAG dan pada tahun berapa?", "kw": ["Lewis", "2020"]}, + {"q": "Berapa dimensi embedding yang dihasilkan model MiniLM?", "kw": ["384"]}, + {"q": "Apa itu overfitting dan apa cirinya?", "kw": ["overfitting", "data latih"]}, + {"q": "Undang-undang apa yang mengatur pelindungan data pribadi di Indonesia?", "kw": ["27 Tahun 2022"]}, + {"q": "Bagaimana cara memitigasi prompt injection?", "kw": ["memisahkan instruksi"]}, +] +PARAFRASE = [ + {"q": "Bagaimana mencegah model bahasa mengarang jawaban yang keliru?", "kw": ["halusinasi"], + "catatan": "Teks memakai kata 'halusinasi'; pertanyaan memakai 'mengarang jawaban yang keliru'."}, + {"q": "Kapan sebuah model dianggap terlalu menghafal data latihan?", "kw": ["overfitting"], + "catatan": "Teks memakai 'overfitting' dan 'data latih'; pertanyaan memakai 'menghafal' dan 'data latihan'."}, +] + + +def ringkas(t, n=150): + t = " ".join(t.split()) + return t if len(t) <= n else t[:n] + "..." + + +def jalankan(indeks, daftar, k=3): + hasil = [] + for s in daftar: + atas = query(indeks, s["q"], k) + rel = [cocok(a["teks"], s["kw"]) for a in atas] + hasil.append((s, atas, rel)) + return hasil + + +def main(): + # ---- Langkah 1 + print("=== Langkah 1: membangun indeks ===") + buf = io.StringIO() + with redirect_stdout(buf): + idx = bangun_indeks(ukuran=UKURAN, overlap=OVERLAP, backend="auto") + log_indeks = buf.getvalue() + print(log_indeks) + idx_leksikal = bangun_indeks(ukuran=UKURAN, overlap=OVERLAP, backend="tfidf", verbose=False) + per_file = {} + for m in idx.meta: + per_file[m["file"]] = per_file.get(m["file"], 0) + 1 + + # ---- Langkah 2 + print("=== Langkah 2: 5 pertanyaan ===") + h2 = jalankan(idx, PERTANYAAN) + for s, atas, rel in h2: + print(f"\nQ: {s['q']} (kata kunci: {s['kw']})") + for r, (a, ok) in enumerate(zip(atas, rel), 1): + print(f" #{r} skor={a['skor']:.3f} {a['file']}#{a['no']} relevan={ok} | {ringkas(a['teks'])}") + n_top1 = sum(rel[0] for _, _, rel in h2) + n_top3 = sum(any(rel) for _, _, rel in h2) + + # ---- Langkah 3 + print("\n=== Langkah 3: parafrase ===") + sem = jalankan(idx, PARAFRASE) + lek = jalankan(idx_leksikal, PARAFRASE) + for (s, a1, r1), (_, a2, r2) in zip(sem, lek): + print(f"\nQ: {s['q']}") + print(f" {idx.backend}: top1 relevan={r1[0]} top3 relevan={any(r1)} | {a1[0]['file']}#{a1[0]['no']}") + print(f" tfidf : top1 relevan={r2[0]} top3 relevan={any(r2)} | {a2[0]['file']}#{a2[0]['no']}") + + # ---- Kesimpulan dari angka + sem_top3 = sum(any(r) for _, _, r in sem) + lek_top3 = sum(any(r) for _, _, r in lek) + sem_top1 = sum(r[0] for _, _, r in sem) + lek_top1 = sum(r[0] for _, _, r in lek) + kes = (f"Pada 5 pertanyaan langsung, chunk peringkat 1 relevan pada {n_top1}/5 pertanyaan dan salah satu dari " + f"3 chunk teratas relevan pada {n_top3}/5 pertanyaan. Pada 2 pertanyaan parafrase, backend " + f"'{idx.backend}' menemukan chunk relevan di peringkat 1 pada {sem_top1}/2 dan di 3 teratas pada " + f"{sem_top3}/2, sedangkan pembanding TF-IDF (kecocokan bentuk kata) {lek_top1}/2 dan {lek_top3}/2. ") + gagal_top1 = [s["q"] for s, atas, rel in h2 if not rel[0]] + if gagal_top1: + kes += ("Pertanyaan yang chunk peringkat 1-nya belum relevan: " + "; ".join(f'"{q}"' for q in gagal_top1) + + ". Penyebab yang paling mungkin: kalimat jawaban terbelah di batas chunk sehingga kata kuncinya " + "terpisah, sementara chunk di sebelahnya memuat topik yang mirip. ") + if sem_top1 == lek_top1 and sem_top3 == lek_top3: + kes += ("Pada dua parafrase ini, semantik dan TF-IDF memberi hasil yang sama, jadi tes ini belum bisa " + "membedakan keduanya: pertanyaan masih berbagi akar kata dengan teks (model, jawaban, data latih) " + "dan TF-IDF n-gram karakter menangkap variasi bentuk kata. ") + if idx.backend == "lsa": + kes += ("Catatan: backend LSA hanya semantik ringan (hubungan antar kata dipelajari dari korpus yang kecil), " + "sehingga parafrase yang benar-benar tidak berbagi kata akan sulit. Untuk hasil lebih kuat, pasang " + "sentence-transformers; kode ini otomatis memakainya.") + print("\nKesimpulan:", kes) + + # ---- Laporan PDF + L = Laporan(os.path.join(BASE, "Lab2_Indeks_dan_Pencarian.pdf"), "Lab 2 - Bangun Indeks & Cari", + f"ukuran chunk {UKURAN}, overlap {int(OVERLAP * 100)}%, backend: {idx.backend}") + L.h1("Langkah 1. Membangun indeks") + L.p("Tiga PDF diletakkan di folder dokumen/. Setiap file melaporkan jumlah chunk-nya:") + L.tabel([["File", "Jumlah chunk"]] + [[f, n] for f, n in per_file.items()] + [["Total", len(idx.chunks)]], + [9 * 28.35, 4 * 28.35]) + L.kode(log_indeks.rstrip()) + L.h1("Langkah 2. Lima pertanyaan dan pemeriksaan chunk teratas") + L.p("Sebuah chunk dianggap relevan bila memuat semua kata kunci jawaban. Tabel menunjukkan 3 chunk teratas.") + for s, atas, rel in h2: + L.h2(s["q"]) + L.tabel([["Peringkat", "Skor", "Sumber", "Relevan?", "Cuplikan"]] + + [[r, f"{a['skor']:.3f}", f"{a['file']} #{a['no']}", "ya" if ok else "tidak", ringkas(a["teks"], 140)] + for r, (a, ok) in enumerate(zip(atas, rel), 1)], + [1.6 * 28.35, 1.3 * 28.35, 3.8 * 28.35, 1.6 * 28.35, 8.7 * 28.35]) + L.p(f"Ringkasan: chunk peringkat 1 relevan pada {n_top1}/5 pertanyaan; ada chunk relevan di 3 teratas pada {n_top3}/5.") + L.h1("Langkah 3. Pertanyaan berparafrase") + L.p("Dua pertanyaan memakai kata yang berbeda dari teks dokumen. Dibandingkan: pencarian semantik " + f"({idx.backend}) vs TF-IDF.") + baris = [["Pertanyaan", "Catatan", f"{idx.backend}: top1 / top3", "TF-IDF: top1 / top3"]] + for (s, a1, r1), (_, a2, r2) in zip(sem, lek): + baris.append([s["q"], s["catatan"], f"{'ya' if r1[0] else 'tidak'} / {'ya' if any(r1) else 'tidak'}", + f"{'ya' if r2[0] else 'tidak'} / {'ya' if any(r2) else 'tidak'}"]) + L.tabel(baris, [5 * 28.35, 6 * 28.35, 3 * 28.35, 3 * 28.35]) + for (s, a1, r1), _ in zip(sem, lek): + L.p(f"Chunk teratas untuk \"{s['q']}\": {a1[0]['file']} #{a1[0]['no']} (skor {a1[0]['skor']:.3f})") + L.kode(ringkas(a1[0]["teks"], 400)) + L.h1("Kesimpulan") + L.p(kes) + L.halaman_baru() + L.h1("Lampiran: kode lab2_indeks_cari.py") + L.kode(open(__file__, encoding="utf-8").read()) + L.simpan() + print("\nLaporan: Lab2_Indeks_dan_Pencarian.pdf") + + +if __name__ == "__main__": + main() diff --git a/lab3_hit_at_k.py b/lab3_hit_at_k.py new file mode 100644 index 0000000..270cb6b --- /dev/null +++ b/lab3_hit_at_k.py @@ -0,0 +1,171 @@ +"""Lab 3 - Ukur Hit@k (25 menit). + +1. 15 pertanyaan uji beserta kata kunci jawaban dari dokumen. +2. hit_at_k untuk k = 1, 3, 5 pada dua koleksi dengan ukuran chunk berbeda. +3. Kesimpulan 3-4 kalimat: ukuran terbaik, pertanyaan yang gagal, dugaan penyebab. +""" +import os +import sys + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from laporan_utils import Laporan +from rag_utils import FOLDER_DOKUMEN, baca_pdf, bangun_indeks, cocok, hit_at_k, norm, peringkat_hit + +BASE = os.path.dirname(os.path.abspath(__file__)) +OVERLAP = 0.15 +UKURAN = (500, 1000) # dua koleksi yang dibandingkan +KS = (1, 3, 5) + +SOAL = [ # q = pertanyaan, kw = kata kunci yang harus ada di chunk jawaban, jenis = langsung / parafrase + {"q": "Siapa yang memperkenalkan RAG?", "kw": ["Lewis"], "jenis": "langsung"}, + {"q": "Berapa dimensi embedding yang dihasilkan model MiniLM?", "kw": ["384"], "jenis": "langsung"}, + {"q": "Seberapa besar tumpang tindih antar potongan teks yang disarankan?", "kw": ["10 sampai 20 persen"], "jenis": "parafrase"}, + {"q": "Pustaka apa buatan Meta untuk pencarian tetangga terdekat?", "kw": ["FAISS"], "jenis": "langsung"}, + {"q": "Jika 12 dari 15 soal terjawab pada lima hasil teratas, berapa skornya?", "kw": ["0,8"], "jenis": "parafrase"}, + {"q": "Berapa rentang nilai cosine similarity?", "kw": ["-1 dan 1"], "jenis": "langsung"}, + {"q": "Bagaimana menyatukan ketepatan dan kelengkapan menjadi satu angka?", "kw": ["rata-rata harmonik"], "jenis": "parafrase"}, + {"q": "Mana yang diutamakan untuk skrining pasien: tidak melewatkan kasus atau menghindari alarm palsu?", "kw": ["deteksi penyakit"], "jenis": "parafrase"}, + {"q": "Berapa banyak lipatan yang biasa dipakai pada validasi silang?", "kw": ["5 atau 10"], "jenis": "parafrase"}, + {"q": "Teknik apa yang membuat sebagian bobot menjadi tepat nol?", "kw": ["Lasso"], "jenis": "parafrase"}, + {"q": "Undang-undang nomor berapa yang mengatur pelindungan data pribadi?", "kw": ["27 Tahun 2022"], "jenis": "langsung"}, + {"q": "Bagaimana melindungi sistem dari instruksi jahat yang disusupkan ke dokumen?", "kw": ["memisahkan instruksi"], "jenis": "parafrase"}, + {"q": "Kasus perekrutan yang merendahkan pelamar perempuan dialami perusahaan apa?", "kw": ["Amazon"], "jenis": "parafrase"}, + {"q": "Siapa yang memperkenalkan model card?", "kw": ["Mitchell"], "jenis": "langsung"}, + {"q": "Apa kekurangan chunk berukuran 1500 karakter?", "kw": ["boros"], "jenis": "langsung"}, # jawaban ada di tabel +] + + +def diagnosis(idx, soal, k): + """Tebak penyebab gagal secara otomatis dari data (bukan menebak sembarang).""" + r = peringkat_hit(idx, soal) + if r is None: + return "kata kunci terbelah di batas chunk (tidak ada satu chunk pun yang memuatnya utuh)" + return f"chunk jawaban ada tetapi baru muncul di peringkat {r} (> {k})" + + +def main(): + # sanity check: setiap kata kunci memang ada di dokumen asli + korpus = norm(" ".join(baca_pdf(os.path.join(FOLDER_DOKUMEN, f)) + for f in sorted(os.listdir(FOLDER_DOKUMEN)) if f.endswith(".pdf"))) + for s in SOAL: + assert all(norm(k) in korpus for k in s["kw"]), f"kata kunci tidak ada di dokumen: {s}" + + koleksi, hasil = {}, {} + for u in UKURAN: + print(f"\n=== Koleksi ukuran {u} ===") + koleksi[u] = bangun_indeks(ukuran=u, overlap=OVERLAP, backend="auto") + hasil[u] = {k: hit_at_k(koleksi[u], SOAL, k) for k in KS} + print({f"Hit@{k}": round(v, 3) for k, v in hasil[u].items()}) + backend = koleksi[UKURAN[0]].backend + + ranks = {u: [peringkat_hit(koleksi[u], s) for s in SOAL] for u in UKURAN} + + # ---- kesimpulan otomatis + kunci = lambda u: (hasil[u][5], hasil[u][3], hasil[u][1]) + a, b = UKURAN + terbaik = a if kunci(a) > kunci(b) else b if kunci(b) > kunci(a) else None + fmt = lambda u: "/".join(f"{hasil[u][k]:.2f}" for k in KS) + if terbaik: + lain = b if terbaik == a else a + s1 = (f"Ukuran {terbaik} karakter lebih baik: Hit@1/3/5 = {fmt(terbaik)} dibanding {fmt(lain)} pada " + f"ukuran {lain}.") + else: + s1 = f"Kedua ukuran seimbang: Hit@1/3/5 = {fmt(a)} (ukuran {a}) dan {fmt(b)} (ukuran {b})." + gagal = {u: [i for i, r in enumerate(ranks[u], 1) if r is None or r > 5] for u in UKURAN} + s2 = "Pada k=5, pertanyaan yang gagal: " + "; ".join( + f"ukuran {u} -> " + (", ".join(f"no. {i}" for i in gagal[u]) if gagal[u] else "tidak ada") for u in UKURAN) + "." + penyebab = {} + for u in UKURAN: + for i in gagal[u]: + penyebab[(u, i)] = diagnosis(koleksi[u], SOAL[i - 1], 5) + print("\nDiagnosis gagal (k=5):") + for (u, i), c in penyebab.items(): + print(f" ukuran {u}, no. {i} ({SOAL[i-1]['q']}): {c}") + + # hit@k per jenis soal (langsung vs parafrase) + per_jenis = {} + for jenis in ("langsung", "parafrase"): + sub = [s for s in SOAL if s["jenis"] == jenis] + per_jenis[jenis] = (len(sub), {u: hit_at_k(koleksi[u], sub, 5) for u in UKURAN}) + + # kalimat 3: dugaan penyebab berdasarkan jenis diagnosis + if penyebab: + terbelah = [x for x in penyebab.values() if x.startswith("kata kunci terbelah")] + peringkat = [x for x in penyebab.values() if x.startswith("chunk jawaban ada")] + dugaan = [] + if peringkat: + dugaan.append("kosakata pertanyaan berbeda dari kosakata dokumen (parafrase, mis. istilah Indonesia " + "vs istilah Inggris di teks), dan chunk kecil memuat lebih sedikit kata konteks sehingga " + "skor kemiripannya kalah dari chunk lain yang topiknya lebih umum") + if terbelah: + dugaan.append("kata kunci jawaban terbelah di batas chunk dan overlap belum cukup menyelamatkannya") + s3 = "Dugaan penyebab: " + "; serta ".join(dugaan) + "." + else: + s3 = "Tidak ada kegagalan pada k=5, sehingga tidak ada penyebab yang perlu dianalisis." + s4 = ("Catatan: skor mendekati sempurna karena korpus hanya 3 dokumen kecil (13-25 chunk) dengan topik yang " + "jelas berbeda; pada PDF yang lebih besar dan lebih mirip antar dokumen, Hit@k diperkirakan turun dan " + "perbedaan antar ukuran chunk akan lebih terlihat.") + kesimpulan = " ".join([s1, s2, s3, s4]) + print("\nKesimpulan:", kesimpulan) + + # grafik + fig, ax = plt.subplots(figsize=(6.4, 3.2), dpi=150) + lebar = 0.35 + for j, u in enumerate(UKURAN): + xs = [i + (j - 0.5) * lebar for i in range(len(KS))] + bars = ax.bar(xs, [hasil[u][k] for k in KS], lebar, label=f"ukuran {u}") + ax.bar_label(bars, fmt="%.2f", fontsize=8) + ax.set_xticks(range(len(KS)), [f"Hit@{k}" for k in KS]) + ax.set_ylim(0, 1.12) + ax.set_ylabel("proporsi soal") + ax.set_title(f"Hit@k pada 15 soal (overlap {int(OVERLAP * 100)}%, backend {backend})", fontsize=10) + ax.legend(loc="lower right", fontsize=8) + fig.tight_layout() + gambar = os.path.join(BASE, "_grafik_hit_at_k.png") + fig.savefig(gambar) + plt.close(fig) + + # laporan PDF + W = 28.35 + L = Laporan(os.path.join(BASE, "Lab3_Hit_at_k.pdf"), "Lab 3 - Ukur Hit@k", + f"backend: {backend} | overlap {int(OVERLAP * 100)}% | koleksi ukuran {UKURAN[0]} dan {UKURAN[1]}") + L.h1("Langkah 1. Lima belas pertanyaan uji") + L.p("Soal dibuat dari tiga dokumen di folder dokumen/. Sebuah chunk dianggap benar bila memuat semua kata kunci " + "jawaban. Sebanyak 7 soal bernada langsung dan 8 soal diparafrase (kata-katanya sengaja berbeda dari teks).") + L.tabel([["No", "Jenis", "Pertanyaan", "Kata kunci"]] + + [[i, s["jenis"], s["q"], ", ".join(s["kw"])] for i, s in enumerate(SOAL, 1)], + [1 * W, 2 * W, 10 * W, 4 * W]) + L.h1("Langkah 2. Hasil Hit@k") + L.tabel([["Koleksi"] + [f"Hit@{k}" for k in KS] + ["Jumlah chunk"]] + + [[f"ukuran {u}"] + [f"{hasil[u][k]:.3f}" for k in KS] + [len(koleksi[u].chunks)] for u in UKURAN], + [4 * W, 2.5 * W, 2.5 * W, 2.5 * W, 3 * W]) + L.gambar(gambar, 13) + L.h2("Hit@5 menurut jenis soal") + L.tabel([["Jenis soal", "Jumlah"] + [f"ukuran {u}" for u in UKURAN]] + + [[j, n] + [f"{v[u]:.3f}" for u in UKURAN] for j, (n, v) in per_jenis.items()], + [4 * W, 2.5 * W, 3 * W, 3 * W]) + L.h2("Peringkat chunk pertama yang memuat jawaban, per soal") + L.p("Angka 1 berarti chunk teratas sudah benar. Angka > 5 berarti gagal pada Hit@5.") + L.tabel([["No", "Jenis", f"Peringkat (ukuran {UKURAN[0]})", f"Peringkat (ukuran {UKURAN[1]})"]] + + [[i, s["jenis"], ranks[UKURAN[0]][i - 1], ranks[UKURAN[1]][i - 1]] for i, s in enumerate(SOAL, 1)], + [1.2 * W, 3 * W, 5 * W, 5 * W]) + if penyebab: + L.h2("Diagnosis soal yang gagal (k=5)") + L.tabel([["Ukuran", "No", "Pertanyaan", "Diagnosis"]] + + [[u, i, SOAL[i - 1]["q"], c] for (u, i), c in penyebab.items()], [1.5 * W, 1 * W, 7 * W, 8 * W]) + L.h1("Langkah 3. Kesimpulan") + L.p(kesimpulan) + L.halaman_baru() + L.h1("Lampiran: kode lab3_hit_at_k.py") + L.kode(open(__file__, encoding="utf-8").read()) + L.simpan() + os.remove(gambar) + print("\nLaporan: Lab3_Hit_at_k.pdf") + + +if __name__ == "__main__": + main() diff --git a/laporan_utils.py b/laporan_utils.py new file mode 100644 index 0000000..42b40ad --- /dev/null +++ b/laporan_utils.py @@ -0,0 +1,76 @@ +"""Pembuat laporan PDF sederhana (reportlab) untuk ketiga lab.""" +import textwrap +from xml.sax.saxutils import escape + +from reportlab.lib import colors +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import cm +from reportlab.platypus import (Image, PageBreak, Paragraph, Preformatted, + SimpleDocTemplate, Spacer, Table, TableStyle) + + +class Laporan: + def __init__(self, path, judul, subjudul=""): + st = getSampleStyleSheet() + self.s = { + "judul": ParagraphStyle("j", parent=st["Title"], fontSize=19, spaceAfter=4), + "sub": ParagraphStyle("sj", parent=st["BodyText"], alignment=1, textColor=colors.grey, spaceAfter=14), + "h1": ParagraphStyle("h1", parent=st["Heading1"], fontSize=14, spaceBefore=12, spaceAfter=6), + "h2": ParagraphStyle("h2", parent=st["Heading2"], fontSize=11.5, spaceBefore=9, spaceAfter=4), + "p": ParagraphStyle("p", parent=st["BodyText"], fontSize=10, leading=14, spaceAfter=5), + "sel": ParagraphStyle("sel", parent=st["BodyText"], fontSize=8.5, leading=10.5), + "selb": ParagraphStyle("selb", parent=st["BodyText"], fontSize=8.5, leading=10.5, fontName="Helvetica-Bold"), + "kode": ParagraphStyle("k", fontName="Courier", fontSize=7.2, leading=8.6, + backColor=colors.whitesmoke, borderPadding=4), + "cap": ParagraphStyle("cap", parent=st["BodyText"], fontSize=8.5, textColor=colors.grey, spaceAfter=8), + } + self.doc = SimpleDocTemplate(path, pagesize=A4, leftMargin=2 * cm, rightMargin=2 * cm, + topMargin=2 * cm, bottomMargin=2 * cm, title=judul) + self.c = [Paragraph(escape(judul), self.s["judul"])] + if subjudul: + self.c.append(Paragraph(escape(subjudul), self.s["sub"])) + + def h1(self, t): self.c.append(Paragraph(escape(t), self.s["h1"])) + def h2(self, t): self.c.append(Paragraph(escape(t), self.s["h2"])) + def p(self, t): self.c.append(Paragraph(escape(t), self.s["p"])) + def spasi(self, h=6): self.c.append(Spacer(1, h)) + def halaman_baru(self): self.c.append(PageBreak()) + + def cap(self, t): self.c.append(Paragraph(escape(t), self.s["cap"])) + + def kode(self, teks, lebar=105): + baris = [] + for b in teks.expandtabs(4).split("\n"): + baris += textwrap.wrap(b, lebar, subsequent_indent=" ", drop_whitespace=False) or [""] + self.c.append(Preformatted("\n".join(baris), self.s["kode"])) + + def teks_chunk(self, teks, maks=700): + t = teks if len(teks) <= maks else teks[:maks] + " ..." + self.c.append(Preformatted(t, self.s["kode"], maxLineLength=100, newLineChars="竊オ")) + self.c.append(Spacer(1, 4)) + + def tabel(self, data, lebar=None, header=True): + d = [[Paragraph(escape(str(x)), self.s["selb"] if (header and i == 0) else self.s["sel"]) + for x in baris] for i, baris in enumerate(data)] + tb = Table(d, colWidths=lebar, repeatRows=1 if header else 0, hAlign="LEFT") + gaya = [("GRID", (0, 0), (-1, -1), 0.4, colors.grey), ("VALIGN", (0, 0), (-1, -1), "TOP")] + if header: + gaya.append(("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e6e6e6"))) + tb.setStyle(TableStyle(gaya)) + self.c.append(tb) + self.c.append(Spacer(1, 6)) + + def gambar(self, path, lebar_cm=15): + from PIL import Image as PI + w, h = PI.open(path).size + self.c.append(Image(path, width=lebar_cm * cm, height=lebar_cm * cm * h / w)) + + def simpan(self): + def kaki(canvas, doc): + canvas.saveState() + canvas.setFont("Helvetica", 8) + canvas.setFillColor(colors.grey) + canvas.drawCentredString(A4[0] / 2, 1.1 * cm, f"Halaman {doc.page}") + canvas.restoreState() + self.doc.build(self.c, onFirstPage=kaki, onLaterPages=kaki) diff --git a/rag_utils.py b/rag_utils.py new file mode 100644 index 0000000..4f3e460 --- /dev/null +++ b/rag_utils.py @@ -0,0 +1,153 @@ +"""Fungsi inti RAG sederhana: baca_pdf, potong, bangun_indeks, query, hit_at_k. + +Backend embedding: + - "neural": sentence-transformers (kalau terpasang & modelnya bisa diunduh) + - "lsa" : TF-IDF n-gram karakter + TruncatedSVD (semantik ringan, offline) + - "tfidf" : TF-IDF n-gram karakter saja (pembanding berbasis kata/leksikal) + - "auto" : coba "neural", kalau gagal pakai "lsa" +""" +import glob +import os +import re + +import numpy as np +import pdfplumber +from sklearn.decomposition import TruncatedSVD +from sklearn.feature_extraction.text import TfidfVectorizer +from sklearn.preprocessing import normalize + +FOLDER_DOKUMEN = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dokumen") +MODEL_NEURAL = "paraphrase-multilingual-MiniLM-L12-v2" + + +# ---------------------------------------------------------------- baca & potong +def baca_pdf(path): + """Baca seluruh teks sebuah PDF (semua halaman digabung).""" + halaman = [] + with pdfplumber.open(path) as pdf: + for p in pdf.pages: + halaman.append(p.extract_text() or "") + return "\n".join(halaman) + + +def potong_dengan_posisi(teks, ukuran=1000, overlap=0.15): + """Sliding window per karakter. Mengembalikan list (mulai, akhir, teks).""" + if not 0 <= overlap < 1: + raise ValueError("overlap harus 0 <= overlap < 1 (mis. 0.15 = 15%)") + langkah = max(1, int(ukuran * (1 - overlap))) + hasil, mulai = [], 0 + while mulai < len(teks): + akhir = min(mulai + ukuran, len(teks)) + potongan = teks[mulai:akhir].strip() + if potongan: + hasil.append((mulai, akhir, potongan)) + if akhir == len(teks): + break + mulai += langkah + return hasil + + +def potong(teks, ukuran=1000, overlap=0.15): + """Potong teks menjadi chunk berukuran `ukuran` karakter dengan overlap pecahan.""" + return [t for _, _, t in potong_dengan_posisi(teks, ukuran, overlap)] + + +def norm(s): + """Normalisasi untuk pencocokan kata kunci: huruf kecil, spasi/baris baru jadi satu spasi.""" + return re.sub(r"\s+", " ", s).lower().strip() + + +# ---------------------------------------------------------------------- indeks +class Indeks: + def __init__(self, chunks, meta, backend="auto"): + self.chunks, self.meta = chunks, meta + self.backend = self._pilih_backend(backend) + self._fit() + + def _pilih_backend(self, backend): + if backend in ("auto", "neural"): + try: + from sentence_transformers import SentenceTransformer + self._st = SentenceTransformer(MODEL_NEURAL) + return "neural" + except Exception: + if backend == "neural": + raise + return "lsa" + return backend + + def _fit(self): + if self.backend == "neural": + self.Z = np.asarray(self._st.encode(self.chunks, normalize_embeddings=True)) + return + self.vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5), sublinear_tf=True) + X = self.vec.fit_transform(self.chunks) + if self.backend == "tfidf": + self.Z = normalize(X) + else: # lsa + nc = max(2, min(64, X.shape[0] - 1, X.shape[1] - 1)) + self.svd = TruncatedSVD(n_components=nc, random_state=0) + self.Z = normalize(self.svd.fit_transform(X)) + + def skor(self, pertanyaan): + if self.backend == "neural": + q = np.asarray(self._st.encode([pertanyaan], normalize_embeddings=True)) + return np.asarray(self.Z @ q.T).ravel() + q = self.vec.transform([pertanyaan]) + if self.backend == "tfidf": + q = normalize(q) + return np.asarray((self.Z @ q.T).todense()).ravel() + q = normalize(self.svd.transform(q)) + return (self.Z @ q.T).ravel() + + +def bangun_indeks(folder=FOLDER_DOKUMEN, ukuran=1000, overlap=0.15, backend="auto", verbose=True): + """Baca semua PDF di `folder`, potong, lalu bangun indeks. Mencetak jumlah chunk per file.""" + chunks, meta = [], [] + for path in sorted(glob.glob(os.path.join(folder, "*.pdf"))): + nama = os.path.basename(path) + bagian = potong(baca_pdf(path), ukuran, overlap) + if verbose: + print(f" {nama}: {len(bagian)} chunk") + for i, c in enumerate(bagian): + chunks.append(c) + meta.append({"file": nama, "no": i}) + if not chunks: + raise FileNotFoundError(f"Tidak ada PDF di {folder}") + idx = Indeks(chunks, meta, backend) + if verbose: + print(f" Total {len(chunks)} chunk | ukuran={ukuran} overlap={overlap:.0%} | backend={idx.backend}") + return idx + + +def query(indeks, pertanyaan, k=3): + """Kembalikan k chunk teratas: list dict(skor, file, no, teks).""" + s = indeks.skor(pertanyaan) + urut = np.argsort(-s)[:k] + return [{"skor": float(s[i]), "file": indeks.meta[i]["file"], "no": indeks.meta[i]["no"], + "teks": indeks.chunks[i]} for i in urut] + + +# ------------------------------------------------------------------- evaluasi +def cocok(teks, kata_kunci): + """True bila SEMUA kata kunci muncul dalam teks (tanpa peduli huruf besar/kecil).""" + t = norm(teks) + return all(norm(k) in t for k in kata_kunci) + + +def peringkat_hit(indeks, soal): + """Peringkat (mulai dari 1) chunk pertama yang memuat kata kunci; None bila tidak ada.""" + s = indeks.skor(soal["q"]) + for rank, i in enumerate(np.argsort(-s), start=1): + if cocok(indeks.chunks[i], soal["kw"]): + return rank + return None + + +def hit_at_k(indeks, daftar_soal, k): + """Proporsi soal yang chunk berisi kata kunci jawabannya masuk k teratas.""" + hit = 0 + for soal in daftar_soal: + atas = query(indeks, soal["q"], k) + hit += any(cocok(a["teks"], soal["kw"]) for a in atas) + return hit / len(daftar_soal)