From b3b0c8fe373c9dffb555bf8871ac2e006dbe3ef7 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 31 Jul 2023 17:24:56 -0700 Subject: [PATCH 01/14] added pdf transform and tests --- src/autolabel/transforms/__init__.py | 3 +- src/autolabel/transforms/base.py | 3 +- src/autolabel/transforms/pdf.py | 46 +++++++++++++++ tests/assets/data_loading/Resume.pdf | Bin 0 -> 98553 bytes tests/unit/data_loaders/test_transform.py | 67 ++++++++++++++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/autolabel/transforms/pdf.py create mode 100644 tests/assets/data_loading/Resume.pdf create mode 100644 tests/unit/data_loaders/test_transform.py diff --git a/src/autolabel/transforms/__init__.py b/src/autolabel/transforms/__init__.py index 855fc0df..10bab44e 100644 --- a/src/autolabel/transforms/__init__.py +++ b/src/autolabel/transforms/__init__.py @@ -1,12 +1,13 @@ import logging from .base import BaseTransform +from .pdf import PDFTransform from typing import Dict, List from autolabel.schema import TransformType logger = logging.getLogger(__name__) -TRANSFORM_REGISTRY = {} +TRANSFORM_REGISTRY = {PDFTransform.name(): PDFTransform} class TransformFactory: diff --git a/src/autolabel/transforms/base.py b/src/autolabel/transforms/base.py index da9835e2..f49bae24 100644 --- a/src/autolabel/transforms/base.py +++ b/src/autolabel/transforms/base.py @@ -7,8 +7,9 @@ def __init__(self, output_columns: List[str]) -> None: super().__init__() self.output_columns = output_columns + @staticmethod @abstractmethod - def name(self) -> str: + def name() -> str: pass @abstractmethod diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py new file mode 100644 index 00000000..7e116632 --- /dev/null +++ b/src/autolabel/transforms/pdf.py @@ -0,0 +1,46 @@ +from typing import List, Dict, Any + +from langchain.document_loaders import PyPDFLoader + +from autolabel.transforms import BaseTransform + + +class PDFTransform(BaseTransform): + def __init__( + self, + output_columns: List[str], + file_path_column: str, + page_header: str = "Page {page_num}: ", + page_sep: str = "\n\n", + ) -> None: + """The output columns for this class should be in the order: [content_column, num_pages_column]""" + super().__init__(output_columns) + self.file_path_column = file_path_column + self.page_header = page_header + self.page_sep = page_sep + + @staticmethod + def name() -> str: + return "pdf" + + def transform(self, row: Dict[str, any]) -> Dict[str, any]: + """This function transforms a PDF file into a string of text. It uses the PyPDFLoader to load and split the PDF into pages. + Each page is then converted into text and appended to the output string. + + Args: + row (Dict[str, any]): The row of data to be transformed. + + Returns: + Dict[str, any]: The transformed row of data. + """ + loader = PyPDFLoader(row[self.file_path_column]) + page_contents = [] + for idx, page in enumerate(loader.load_and_split()): + page_contents.append( + self.page_header.format(page_num=idx + 1) + page.page_content + ) + output = self.page_sep.join(page_contents) + return { + self.output_columns[0]: output, + self.output_columns[1]: len(page_contents), + } diff --git a/tests/assets/data_loading/Resume.pdf b/tests/assets/data_loading/Resume.pdf new file mode 100644 index 0000000000000000000000000000000000000000..90bdb4d6f45ae113815f5d0ec984309de5ef6231 GIT binary patch literal 98553 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
$B%17cB?_gT!+)?ItD!c|w&9l31?>zeRp z4M1W{t+M5WD^bq6&#@$-n-u4Cv5it_mdUUJ#W|iKu$#=at;ST=+8A_6s2MrjZhz%2 zsMb0$`}j}EL1J6`*XEL~lHum&2 zS=?3VknEQ#zj=B;M9uIGNI!(kyl)tLt5&LHlQwn;-WV=$r}60-`p4*8b1{|AZx_yb z!4~bn5Z17vCAcLJQ{)Lpdl9%dKDnpvu^t-{#kFZN)@xEAQB*-I!I(2E;&+t1wo5*@cs6d z8ma$NmLuCyN^dPF=8`Pt+!4%aJ>lAxFfbL1Q}S)5l19uYxg^#$3OY@VfY-vg_UZ zZ{|6UWeL9hP{yYS`%sLR0d!$oHc(Rxbkr$RH&Itxu=DKnzi5ym?4IpeX7Yl zQM8J2>SOZqA@-_{e+nJwiAea?$9SUS}JyRB(A z2fs5K<52Vlt1Qsp*EXi;o~TVu#bgR$jzx#pb!j(qXG95)J6XUPuYc?UevP)tH|LmL zIyBZ23u?pEpKJ<4N*1QpR>l*>*_PsM7Y&_XLj2Cy36pdDCHinLbEu$8+qLq8h(9!tzYX_FNiQ1OjTUS|vjOH$jjg?B1+c1ud-4?S zB6q`tBE=vF29z!HlO9!ETBoTHu)uWKzMLDb^J~b#?LAs|36`X-+}T8{Qnx@WIJkwg$rpB>p~M1#w) zA+?potzq>jgqveav8J-fZdiw#CbJ*s1cU;@Q>2#+0u8anSW^=pby{|HKh_st*IhMF zM??g~yyOFfSiVb+UVRB5mJA%bo++D?Jp;ga6Pw=<^Uff(pYjHfGwO#@SE)zk7nwu} ztD);=DXAbkWm9cmheFi)Bk`Ih-{-85~9^(uLZ zbF2C6(6#(%^`_?vXL6^VYi#(6ar9KjiJWVkpR|KCL8kJY@FNFmh(e;sc(Z8s+ZzIf zOgg|O*E_}`;uEM9x8?0x0Cb3g-a7$&3<7CWtX#A=h4ub7JuAe}`fK~3v2z8IaY~Bc z82hM_@dB^|lIwIoC(mJUHCQ- zFIM3^!`TNB#30ASS0U%URcP8~%)Qb*try{L6&A!2Fo=-w6knamn{86Z6 zdr|9Aa!?mzd)fSQyHPJk3v4J?U-mPi_&3!CH`PLOnlK4ar#~WH_P+0x;UmS8W^h5} zPB^wjY*6Gmj(`!zL$Uk7i6y1sk!s<8w#{V&&ZiYAgo-Ab#aRkvT`e%`X%TU9#o0Z? zDLDOUV!4YaijO$TOqOLV`G7IpHS7rZ>lB7lauPb_G-%4P9v2eti?u^f7>$66qi}6r z(P=*KfR$Dsw$u<-V6m29q1$S{Z-sSWkvU@?`V};fcTsJ#5_3&5tgN(~^fPU=-K4S7 zID}mH_M1SRRTu5JW5Ob2G$93oLWp~gBG3>WOXKY2l9OW;)o01EEAs&^l)Di9mLoPg z?NrIOqeb@?$EG&wLB~t2W?k*Lf(oP@FwW8U^2!$S$n7q06A;?j*UoeXO88sgOa2&r zDuYfhw?IMZkTJYLoRRJMz`Ly`N}nfp!bM$)Fq`pTx#qdb;J!+QhJBQF%9nYKoC?Ty z^fTm)cf#_d?3EuLXy59iDwW?%u>o6A(Q9pH8V!hRkUr{_-{+|>z3v;@68)4nM*II zm6lo)L}WYq8#t@nq^Ypz6fg6 z1>JN{HXklIbj;`!R1$6^m9gQKgqaksd*pS^SZ= z`OQcd1!sseL@`=JEy5fgvqE@)UGsdlyFD{2Dtjsi zu2r{bmCZNyXxld~#a#Sp#ZnXY3+bk86TH;IIVB%ay{MBwg^K0jB{FjM-;pCeLnQr5 z#_@uG%?`J@@Tv_QT5M3y8dLMN$oTcDh+#de%w7mdvczy@_{-BKCL}3w3-9aA(awX3 zvuM5DsWi#;!H`G+5A0RSD11Va?PnaQltm)Z@f4S6B&S$vLMP@`W=9lVvQtOy*+pSm zw_K$28063!%B#FpoZCHvXjq1navuEQW`sLGEt~zW~`{`agQEf zlxlw6c3F=0f4GSDcbwUTkY~#q$QsiO@LGj9pF)))a8Nw5Ns>cuQeRS# zF-Bxeh%OM!O-e>^!%G+nex~5m=UdtX&pfk2jH+=7cxN6kHM3%me(6q>$$6?Nr<+nh zK)5*0mcgG@bfTj6YV*)YnuP4=I@2|T8_>0n{9)5nOg!b4i1^1>-qLx!32IVkUC4qV z92PqQMP_U6bF`c*Jo(6P4PLkeYmh$hfZ6crCx%-PAgiKlpvU96ndlio)~hE;v8tvc+w-+{YB;u@@OS3Q!nShDhe;DdzNcVdA!?WX? z+mSKH7DO^0^E{FF$0m+Yr-8dPk8y zaF7ylwKmTewZ9OqO#qvx)Q@ref?W%iJxl1u@6;Xtkz9+(*eZ(X(Wh&=W?z2gKGidr{t8RnsN9*p%kq5bM2OcmowXc|HNgA?or#)C!FSItWJ(L z+K7d1Y8V~1ZB|qfeR!eUr>)aHKt_1atZ%-Zln4>}fr*p8msQWX5soNr9u!IUoS$rP zuvmdq`et~GtzH&!de)Q0WxJz(l#=dBEyY?aWJNw4-hUtQ45VGuFXYcJEC64o{{5!^ ze|_CV3;b3&On}p^BJ$ex9evzw4@oUD7+6soiEgo zw}_sT!oQS{w`I&A4xWuP36xU}Ga54*U>?E@$O|*}$tY7zs6~lW#ty8eNSf^RT+mXA zIZwHmtnaAXI;1~$wy${Nu8-njDS&rp}e=cdmNg4-nd>F$=aG9;k0CKkrds)iM4FXajcIf=VR>XNm}@R|0w<9g1P z-a-GZF8?Phij&5atL|jC)lH?^S_^JV2gSwEjHnGBA>2=&d+r%$6Z!WZUp74}dbY>p zsZ*8TL^cUy3z@vQqnK?WX{t^A_~@~s>`fz|iB3X`s7VG_)96D>L-@StQx>y-AUCLX zFg5;4=gTa75VC7Q%uJMpW1i?z@pf8JwXZlGUy)uoy_(aDw8tH=QpwR~mhvP#+4f40 zHd|;H&Qc9O(IQn{eB;#v%ogmQ9pppTZ2TGsgkZ2Ercuteb1gXnlpc5e=+!(qwQ)H5= zni0S2d+U#A%u&5sNMDMVR1b0e!9%@O|32(SG+Nt6f%5C!U`^rMPOLC9moiXxYjCNq zSjbUf)j04;VaY!82|?BgbWW9oB-0{@NjAT7MPmFAl3ZO@Md0bq8VW&%pp66L zOAHvH!ek%2jbhy1R~H{DrqwVV-5TpzCv7H1DOYM|3XBUjc6HiNIz+kB$TBW*R^Pph z?5fJ_?1yMRu^7GJH}Uw+QVzXTtfX7(8S@D}rLnAK4`$ zn!IE7D9xR!K1n_2s>W1JDLPb)s+!0zHQB9r*&dARC7?J3Ytsgevcl=s`r)X@`JMK{ zLVf}{hc>*&ORR#=sEAdsa_u5XU6@t7dpXErFJ$r42FyQ4zk<$>GLhN=z8^$kb9<|H8g#n3E_j3nZl)2TKOwA6HAwslugP@i!zr|H1op7&HXxM zSiLXVGy{nr-X@0Ceds}_)a$G%w-G&g5xxQwOMAJI2jHJrDT=up>~zd~uY_`D9E`9* zbXhzoj$c3|QT|~rp34Og?4_u`Kat z*edZNnLDyq;|dDthk0AGn#>aic2EbCau?+4d7LgS4+YIS0_ZOU%V|4U4BusJ^=)d| zMki_))7E@s9~|3EFo-jrnVjRQd|j@cB~aa^jMEI%39hnlcT(Fjre(36a}l zPrnhdD7aHLVCU*ZTuF;8mfn>VgBBVRFER1wj@#N>E$gV}%&R=mR;vliE0U176}i~N zgfNf_%q@AEW+GgRk)2(?*({%|+(XkshW=hQm_XZr`t1GuSWJY(FcBvS)s?jFJ*UCz zgpcn-jic+y8YZ^L-^aPZ5$&QgX=9aVz51%Z3X-~}&YJPsCepqJ<<-gaIPMI_3{$B5 zXVn14smTBgHhh92+x*OnNOey&#SA`f!-E#84kFA3s|1x6{Ib!ZW6UD!pus6LHRUSL z97gP-RTe5jZyCudNMso^@$?i z?y}$F4T-r5o|GT3F|EDEo_O|rihbf^|EMN9{5@ox3#sKd)EhDZ9b+=6vF(74`Yp=z z_d9|7$>Q`ia37eC|#*MN7o}c=rW)(^-vq&~VB^8|`SBy%`g}PwNfNvg_n6kAoD- z9f>z-^o{=)dEEO0n}3X)Y%B5sRfDWFS}gqRiQLj1hIz|$Ts9}pPr(~Iml(Tw zERf|K`rOdQCZWELJ_TlnTSfScr8f55_H_h7k;a!3+b>Qgk~iF6t`m|@9>-8Tg}Bf6 zkT$H?U#Ff?<*t>6VzK(K*;8#P2ju2>a1?sHljpV=v^F9|V43Hgp$ZTh#kP)>(55{{ES9|f84J!@KfH7&g}8NKoBnqE1&2rZXP#zCWEonI3X$_ zPc-@GeEE|-$HPYE&@crfg@Lr;rmgvndD|A(AzSO-Sp3)C4l$r{1}@UD>4DSTrr|pB z!?VS*b``4hvQy0nGfbv7Cj^LcHP?$34jE5aAn`OEUiUbn}yrTIz4u`I>& zFq*Ei+tE`>V@wrH9SGrf4Olk>Yj9%&Zx_OPIajTGgYUO=v<1V(hLYo8H<;RO zlu;t*Is|zC3PLvr+i;*%w{NyL9;YWd83)0uj~emOH(KB2G^S;z8Dp%VChp^C=%dzR zbSX0f6egxxrb`uKjqcnCU2#Hb0Pf_eD?L79$Ev;pPAwWsyIv zesU2cMlX#Cs7n|k^Q?bi>ZR8q4Eok_ovhY=emFZbn_dcclJ4YrmsOy-EbBD6+y)QN zt~vH|v8FFYUv0VW=O*TmUdIx#B9(-~4F*uUNRmLq6eW+_NNwRfSc8s1=||9PB*P$I zJTD+e$ylN#j2dyqP7!#`Z^O5R%yy4*w$SiRcjCM>;=x|;4epRI!=Vs;B^_CUTDh+W zTfrf*9FZzWVGnmO_vr0s5vokCM1Afr{ts%qYn32t6kjz#XP_Hk-8rq$l|iq)vw0Xx zyW^``MHj(XycwaLU*iSg*dOJ5DW2-vnib&f6$C&X@Li~@=Ecd-zm(q$ETbRMc&^rb zhmNWr~}G`YfuH5x(<&<6mW3jd4M{RMfD!j5Ff>lxC?y_~pGiJoSR z$Ph2s>55oT7ma#>VLp$yl!PTL%-w;umi6w$y%X)ZBCTBIo}abS*6H?;m(7>e=W6QN zTRpOU^7vImQ6o8dyrvSU*9IjB+cXYulgZI>%9~s37QqbiQewj&?5}6GU4r6maX%2Z zE`26h8OyDNul{t2Lh0v`cn`u-9f#J>ohwTjF>2MU7+M5c4{`MYCS}~`t`v})e(;`Oc7b?b>rETRLvJf~ToB_nou)hFn1*ML3C3KsSL%*>%{HX) zjN|zobKA;?5570bHSL?)+OnX7O(<`)aIud$S%t0q%DwK)P(TvAd|Q!QskA5}+Unn; z4|D9aCrxY*>w3^Im!d84H+@NgJ#LV;dUImn5zI0PR^ep_XF1R4#NYCveFfcobM_@_Rf70_ zc#CtHWf4&+%8N3GhsqoYjjEH55m)+vhVD^U*+-Vr#l6WC!J5?uw1>hL%cp8N73NUy&T zCE-vXjdUegME;uFN??L+$tg~>OJl=__8u{pW1HE8``K1~9*C(YUce4x!h0OCK zWY<2WNNNxbTpM%fh;C~OB&ruX$M#4Vm34%KXBf^05^Bx*18R|YR^?u%U|WX82+5h= zvxJ(jHPED=m$$8K38c>6zcV~Llhjy@7sDfPFkhKZ={rJktvg_DNIWChVi}4`-eT!t zT_fpVzxQy#G<4!$Um5uD(P_%o8}QP{JuO8jGN8y8AJ;+Eb)In&r)qjOGft&xHXw4Y zu<6vp)UZ4!|J*BO;9imv)&3kbo7H}N;_ZTiYZK`{iwTgvpJ{4bNRdwP#<=}l%l_H@f|rYM9b%N}PHRlDk1tK`NPUpsKYpDegCoVv)9ztp87 zU=EWdPJ7$=DRnc5<#@g)(24TOKB*u+h-Dj=b_;iX!^tYNy9;0D<6c)%CVrX(vT20Q zF!u{ST&HTyA)G9nkrx@{ePhs63%XbKo^J`TsbUS3fq?)o znIgROZ1?#XaJ?ZoTlIqCzPv0uxel&iPbbDOW#FEwZdpe0{ms z({v?v|N6oS+QV$m23huWU`oJUe~$7d!%eZ-$UewLlMio$IWGbiucrl+YcL1+YA~)J zGA`fvn#uL~!I8}8Q`2P@ozf*t1w;G*CEiE+Wtz=zFHXb*4v<4{P@Sw^+8znrnFo_n z%YEL1+TQXmDB%qUee7(cmPtDWy)fotBXm^m>ADU5rt4{V?81Sdye@n>(U!&a7~L3i zt7bnpoD~ss-D7%OuY(goRwfu!*aG7Gf_0J3@6W#UV@5$@q@ zcc*3BTqSa(-rq9sxtT0g`SqjQ^s)`t>fV#7=6FEWIh5I_rtiP?0I4)~s=-{!NIj2c%widrl4NwZ@Hv@U=NZaww7b;E74$d1J1`|2`6S`QJfS;0|FJZu630E= zg@eylPRp%Le(kUq~bL}4$ns3Tx5Yom*WYvF<@OVG}5%^ zMO~_o$Lo27L%vW_s7K7_>H7i8jLSEz1$lQM*5G7!P6b8D9C3XVGR)109#SBEcHC?F zBLyRmOJ3hvuVg$BjUXDm&KpR45Ar+-798$BE+4fuLq9-TA?bfyIU_ya^1e$)0DA;a zh2-0LC~iBNMNEvCUw?huvVpOP`$(3%ghlvaW1boK9n$I<)~hd33y(7_ck#6^3ha9u zKQ1PnUMB8@ogsGLSc^m07<1c*rVVE5Q&F55CUP67G|gGz>UC46v{NOv^v|nomS|lx z4>Sm1mJJmUZ^eZX$Q>#wiZsK<>U+6H6nYPnC0EI~MAq)K1(n&siz3lge`bNyY9?v3 zn6K;E2|?CfR??#8dt-2-;O> z?W~Wu#AIGkOk9FGAij5aGWNOHfw<++>i$p<1f4LQ&m`*spUWk9ZX=Yl^~>*=-!~{% zVIi#~Rq8lzVueMq-op)UA>5;!Ag82(QA4(XYk+gLX%Z(TZB@k2=>q?G)1&riD%ifPhARH@i|NG+qWZuD5qdmIdA{d<1G_NSk=7+H@esaY z)qm4Oz+oGv;St0wzS+75+PJxMsL>qe2XZ479nVjX6osGKxg@TmVmBBS?Jk;hAIl?z zeze6Q*)KiS;}})9ymh^%Q=GcHf=gO+rB981gdrnM**E%5kbIRVHdpy%0QhTCc~zKQ z0E?v3?-)bVTAH_&o)WOR(lO#+CebmSq;2+v#(*|-F#gz4=8VdX?;h58t0V@z6rmnY zoeRIzM<@;{k0$(7=ti>8FBi&GNaps#hE-qU0SZxezj?00Ivu9V4G2=jvvcE9UeiUl z%%TA9^as)Qq!u2g>liB(?VaYhMHq&8ysJ+MJvVD6L=K-(w;w3QMd$D5@=yE}Q-AJGLoPOHN+{{k)GSdnZThe$XZuhH zp)ZH<$N}Fc*v!lEN!RBiIOf1C++;o*!F&gE9(N<}_Jds+X_Ew*BaG1PabX#B$cB|^ z+?tKdvTWA?{nBd$RohJrHhA5XTd|2qzbTyvld^}ZYxMkyV66CDaH0EW7J0^4SrFTx z_0O=FM!g!m!x(y16jnDWPSO-tG0KoU8(!O>Xpy$WFo3vvt+lEEq~fYUU^lcwM?e!n z&|P5LE_sr8U|vGid!f)kug7p{ytmF@e(fI$w$S=gtHWPj2PZC%VnxFy)9YnQr*eRb zkS?L1?wfAiYGsWy@u{l_^RUmUWi^XT)W;I-W#+n19!UtyF$Cpns%gYNE_~k z{zIY3EA9&X3Iq!Cp;USFCLfKl!oG5j=Zw8H|Ah6qD6!@QeW}Bo4dDbb8A2_jVgbYP zv~Yo_N8tXgW2i_e=Q4f|>j6Tl>3!D*>iPE;k6WP6#_jZyi`>|$8i?FL~tCdqyGR%7nBOpK&M9rZ(V>h`<*9d@iIp?=#Os!J%u@ zcA4(8h2N`%xc8fUlyG|pW6I_IaEmJf%f!NR=@bc@O&iU!3M#9WS8tUF z$wo-lgwoc?;AYX4W=bQa&I;`WGFs_kvi{z?X(_6cx}PSqzBi9IzPz^|-@UH4+g;TC zG)sT*_mIq5;P&#;IpA*@**!pa-4}e)S3P0hnORU0%XCE(^m(I78Jg(IM>T&l)ecO3 z5!hW2jzX}z_}VZBxS`2W3zT`H8}%i?{5(y338)X&PIOkQ)V_*IW2w&Ygd9=&;3Uviz-qk$u_dpQ=79=6U^}(yBz|OVSyi=wTD@PJKvcBE4>J;zMMwR39 zpjR?{Z9I2b^f&FGhQ3UjCd?i)D`iTWb4<;#dm>urS!{}5SVW1j6iYr@O5%g}FH|j! zAuwKwm#thviAYh7WrPeeh4#dxBIx8syauJJQ0B5$Un#dr+6_0Kc0}Ng@W~x?G&zC~ zf$>iC;^N0rd%#hG5Jn?;WB;=t${|oMM|niQl0Uk~Jab$qT_+6l48BJs(RV<&=6F+P zi0HH~ZCJoJ+UPEfq4->x&`{Zsl#%${OxAQG-APi|btOpwS5$W-IhaVtm9aC*CM( z1X^|xA z1VIF5ujem#v77q^__hi3NuTqKVtY4fgs;Yl%mhh%2onF`FX_Mgod=(BHI5-Q2okQ6 z4}%fDQJOUh%b8gAA5Gv+Aq^It5BCNL3xj-tr&KdwsK!Dn%GVM><)SK=1LrqAQc&br9k;eTb;Wz>1wJ)AYpH1(y7Jz25ska2X_S&QNkUMIBtWio zHM@}$8b~EehRuvxqda@!?yjYFcg-=(&p;hC-^S!d3h@J5e18WaNk5V)Wmua9Vx${k z!2>x`z+OY5D3Eg$v(h~qTNoBgEDl67DV882pJF^qD2Il7a5~W_R@{bx~>NEwf;l_q3mY6Ljs)Ph7PTmsnpxi3xi4H!~~E$e}cOC<>Qiu#P%}@Hn_8>~rqTYSiLZnnB~F!!eVMv`g1H zovufjNnL0kBT!%|c&}xx);4nB(uu3a%%J0?hPDkQSbMkUn&OzEsNEazxxG)xu$4GJ;keVhc%98Vg2jd$pxkFnQub!$r zzOgaeU)R(Vs3|!x-Dp$2vk)qo(AZY8DYtb=@)V_ajiYQr z&lvU>;@74H{pNeO`42`OTWnvh;|#Hljn%A`MfDWgTxX1^Ujd0Ki)y+y6{+J8#jW(P z+Nv}W&p`Y->AKh_)MtuA`?>U}EmmF!jW}!=qOHj>lKX<=#IxYB@z*lNq>5u0pWQU7 zu~wN>JyN!YZB_3`44i^})I$d=u<}j4ddC#Q^~!q4#PoYw4Xp{ahzRYb*XXGoFhLYU zGI)9fZC6P1`0=sp@r-g!r=T_a+EnRH=@D0>FlUNk=ihcng>G+Gbb4It%9t0K6Rfi_ zlgf}PRuo$V9KH=(24#^n%^?k+adq(EW%EaT$Kd`@{gweV7>R`^R@tb6C5+V_iW-7w z!KQ?5Y>t(+xdkorAYFMuX4W-p3@lvD#QpOr`LD-;c;DXYhSyPW3Nf-*TrDszJNn9ifK?l%s#aaLR4Mw@DVu()OcY8bp?+eTMe2NfqpQL*K3-Clj`49+z0nR;y$7Lw;i49MTIGe(1 zz<_`NLng-=PZ%J*94!Gpa(Lu^hd(zQqah`nOgcQdAg4_GI<4y%*)rxa!Kc$_B!94= zZ((s`5E~Zi2B7wXEeWDrPmM=7-eHE-E4N#&aHh9BFsv@Wi$JC|{)Ve&p58O+<|t>Y z4&i^ZF`mx>tfhBNvqR2ulT$f$hyK&Y3B?slsn-&*7pD)bw%#4AL?Os3M z@GW2bzFlqfK6G|cITDVnk9B_p)U%Pan4#s*wvN&76`g)Voo0t;euY{*DtihZ+@5gv z(ZjDH&Y9=ss$urjPuGxwXTCbeedgn8p5oBpIpP%VyYa~Cz3hfh^G-nRZS$- zQ+!_V?u~7H9d(s7qmbFPWR{C*)wLFX0Y zSUFJciT%(hxoqC@r&F};JXM*xq*34Qoz!*Vo@w~(cHLTGdEg3aVUL^yOwM-(L|wHI7h18Hi>02|VHzf>!Ol`sQgrIB_JWjz&V z*hY+QcGxCRsg~Iu^Hr!BPb?{yBIAa@(fNs$9LEeL7*>okQAClf(D%m{0QNCy1$yv; zUERcEc%DaS3=<25gU`Wugqfg``A>oj`VVDsaSRID&L!^R)fqMyTOP5WV(a@} zIL#tLS2Ze*xHO!qjb(nc%%kF;ox}g~n|D{n%dw7(9=~$8{Q`W(nTbKszy{+J|=&{uvrqMO_GqOP*1vq>?b3h1PbXW zPz@7Xe)*+9_f^M?F z`fohbf&dW)i^jNTXDFU@!4_m{I#E~mWC=9Ws@3v7n*XS%~o=b z)}Rf}!mcqzLu7$=@ZzI-&=#1rc(bSn-VpI+GzdXr1?mAACRWXAfsRpf?AC;`lbZ>; zo^-Q8;Ngi{1+B4IoraAYm|K5>T6f;vXMkBhkEE)#dkX|F3hS zg({Vdr5TZT&%LG5Z3#LodwZATsewFbCl?gDmDD*Jlbbo19^{9{;*$%6%@$1~iFuGz zlx^#Ar^EIM(WtM1-_2Q&_pJ+*c9nLhb^CO{vXy%$rZdh}O6kf{Q)47_W|J>Un_DbyqCB?D~AUBynFyukJ0*PtEDHb8y=cpiGp zfA+woK9=3KAiRCuG!PoCNhMPVrT0IXqABydT;THP+on|0&ZMh7k-@GevsSxoI)m?e zcm$_C1+;C}a<$aLNqjzR>%JHUcGt0&3K^=OV0wNkW(wRDRp(#j< z%KYZ-_4Da7#)rODs0kbZPj`oPqrvMr#!miE$LSafyqeG*4r{Z6=1>}$2=b!CT%TQoGJ@oB()>*2&GMptf7TRhg0#P4{$Dx zKFBX1qtPwPPutMTweIg?*x+9J7sMU;JtP=#v#BH!G<8avo4g<78JKS3MIPu}okxj!JS69Y8rRgNu}* zStoD9#*TqP2@P<>U{_^eTnv^N8vtgEAe0i~fkN;2+Z|clECeJL`Om0JOTVbS_Pm?D zi<|i?XJ=z0LpLkq$NiPPOV5kHAx+m6{QJNNTZhzJrYnSHgm8XXuc`nv6M-BMt8#HM zm7|9EAwj5602I^`2Z2cym`X`Srliugilw}dBQng}n_CK@Fbk<7O z+qTy%tD*`EnmGLkXw`_dei0wNurk>~GaKAtgF8%(=)h$9bgF6JwK1eQ;}S{|A_#uN zG7KHEFA-Q&uvmC~^y&3?3gc>qNzcVJhxexrfQ#P|L4&%g`dlTS-CVYO_LIli@CvT> zCcl_TiN>&cL--2?(J)USUgqtcg^B|q<857we@g`JTi{;~;XrX(e)pXp*3jP)-YObA z)t6?cMBn|dFG(epCF~&jq3>wwi#VX7;#a+4rAno$VYwDyIs1SpDabs$0n$T^9F4#5 z{jiKxB_izDMGIMC(QR;(t@7`}=Xeow92`|#Y72kq!0kzess68zm64|fCf;SdJL5+k zys-5ZmQkA!8^tQVw`=tF>%Gg+k)2y63MkP*hu1A9gkQT6Y^8X=45+)o*mhLC(NW{4 z1{76#STEBbiKan=l2;j*^p zWK#zVN%w3Xm>q2{K%125=92cOX@HKP@b1VE&~MsX$oCGAgqb-ACM%*Ua%5&WDl<8A z8dEW06UkDN2jSDtbi(u;a~gBsjCXp*ga%kbB31hZKu?HoE}KB7fZI~n`Pw=0Gvp`x zGrq;B(Aq)}%Z2Mgu{rb<)u&-Kzp4h{60rzrN?;+Haub{)8hZrrA>u?Mj)EFkX$j+Nig98o|O{?lM+@NB+*E62JbY8(LFIRWaxjL3~+Q7)oPv< zAT5ihNHje|z`byJbeq_;b87)yDdq;+`qG=_A<5VN54nhJn5;&14owUC&vX{LQ3Si) zZKa>fxfz$%sz0ir7rh%&CJ@ROW$m?vx6-l&XW?RI4fT0hIi^-At`c|i*&aD^PuDJ) zw3|_)AQB8Jz!x4>?GnKvCGzAe&R2(@@foDBa3+cz`5J>C^}3Ey!7FnT1Q=Q}tr8&G zc1ix6;h}26fQON@jb` zQTfod;Y%X46144)$8KJ2F+th+qG_W(F9#_hkN?FWa<{mt)@N+9>g1&C?Cy5VcQwO7 zx+k6k63kYu`91EdwzLeRbcpDy7EWVtrz6$5s-_N!UF@qYD#U}4OUWyjYJRuxd%V}GM6nPQRXZ};fkn&qatF>p@{FG9FQ za-ZOZq3;+ij?AlGgdUOr%pn%{_$Ic8G+H2S1I+_$OMruN=siciW%qx;Gg=n_QNy5x z;jXLt$J^80&>B^(FwRy3`xouQY#wmeY>SpVE>FGG^U2b7huvtX%*Yk?BH9^LztWu8 znDdr5)kHS?z?11Lj}rF1$-rMEb1R!fK>*4H2lJHb0UiLP5d(>umsg(SiC3>HEwy)k zxttCo-k)2hEgzexgVXI??(w>u8;e^NgfT(}Zlg@|H8hq~c?|hHaB~OTZ4MVK@o%Qv z{rh=oT+6!Pe7yV#+zhXp%>1wr20!G7tk}}kI)~ljZhf;x=K)Kr3Lzs=3k=LQHh*ZR z(S_xtNY-2CXm`THVV^L6Jb`ZhtXz@Oj(6WE_6W%dsLvA0BeKLn8RNx6IqC$y85f4D z2kU1B0Qd*B!?l$%bgE+@iKaah^SzRlDKjvzynrGzVSuoz@$EmRtvNqzX-&^Pfu&(* zbXET@x@nLHM@ZtQr9E?gqlOuQN}^@94$cfe5ELo3sVT#rUs$HZg3;yxDU_O(y3qM@C0>P0y+4B!n{}l~-${d|tdOpvm8GzILN2NOx5CcJYZ; zffvAjYTr-%uQ-<(dq68~iTKxJTzh;kNS zvB16daqd3#G2fTX)}A|=+?*G{3RrvxHs9f0f$ zo#ubk?>G<=vm6i*JXltp;S~cC3j6pf0)?qjt(ajz=Yn^|jk%;0a%!ii>Css7sDDi0&ZxBo0Jx!_J#C!&o)M>1)LhD-`8xWL53&)HhvS1{=K_Wt}4T5Y2jybLJ>jt<4 z?O+>xaDUaDK?z`-7*I^GjkGA0cJaVR_wt*{1?EB75FrA%n^NIFU{n|o>o;u7w9Nhy zU7WP_&C?dRQ%%{eBx4pf=ackeQKhCVB8BD(N#+o2`GScH{>A&Sa2y=_XSVk7Cjem+ zt1cyo<1uDf2ABN7X@1t&W|(7lyjx~tZm&6WY3pbBZhU(mbP-Mt+qp@ml2=Y!q_=U! zfKnImqEo9a;Vt>i)ZuI};1v9;Xg0m~Ao*1{4o0?zqAf+YbO ztdV5FR3Md8PrYsyo2U^Koo793XY3tkK6!U%Uof-qi$S-oSiT8tG@?Y+Vnj3&MAagP z{R9!VII~*>Clj`Qb)u#F_+R5fs8%alO+fCIypQ3+h9#Gx?`7jfx*jtP#$KCAaNZaK z45MZCV^>dz<}l}cE}h*E%)VjcklAqY$y}+HOt=V#8Zr_lR`={8t+fe#gbptp6d7)5 z*AWA{RB-4SVbC1E9JOAum34J~5Y$MSys$_BGjvk%jpY3ysjX~0hi+Z-lm;Q~cC(cQ zRmxFuc;2OTZdPO4BvFQ(TU~Dc^06+{jGP8G&^J9#zvQkwA2%nnPvn*cLl9OOM(5&Y zqsyXJA{lGL<=?R!n0CPv?T~BNJ`F@1FEJdTB@hXCMKegYRR3AAVI1p zp@YX|>Psf~x@C5AhOM7lV6M=S$O-`5;+A>EozZMI>siM^9--NpF+<`yZ}$g#Ft_Ky zZIp}quk(^j2c6L+lv}$&V~Gc}5PV8uZK@%{F5OKQIU9m+^(DXtWLE zS2t)X4w^cMz^Dy$bP-jDu#6;!XH~I5N2Ff!lJOu1z`p{a5{|3Lz)$A|&duzL^>6(D zI^P}x+VcA8;qCg;3e)L)~tqKYYw9+p4yDPek%!d`m^sis9wqRX^)`mpAG@y&}nR+kW z|3mf1fDJ8!F!QZiNlutAfr79&nDW)2;8u2Q>sZvX1<9JL9P!jWvh3w02(}tmE7DJ> zA6q&0r=%Hc^;V#Mf!n-~li_cuUt78Mx1?Eb_DVhwQ)&GD-oSP{lKpc&#bU>Lb~Gw! z*o08oZ(N07Ii5H*`Bb)nje>IS)Zh*;M2KVeHRGL)P3lD-OEP(?kf-a_l@B2 zE3ctwIrNxl4$;L6?)(q>kZm&$>Zl_Qci*kFm(k}vo9*d)`bd6jwN=iSg<9&w`y)R` zf|ZMN>a;|vRlRC?pE=~*be|mh%k7%q-u-mm?ccfgO))SRW@BSq5%k>k^5u)%vqouf zl|Y>@lCF7&SX*ZqGOw0dcxG-fXkjT{lXyPeq%#8ZR}lO90v*`KJB`hTa#n*PguAXG zkY8~fxNjb$^MBn{9{XIM%Bg4{93_e-taRo=A4Gm3arxGAOe(uaZIIKeQHuMV2X25* zUE1K4X)e0dDDHfCDRE&y1NLY#9$v89uP0r0?cj^7&S=*($fP27x2X(^q#R$}!BC5= z5vdHjqyk;R3v?YV2JHX|q&nD0q20kBbOycB>34y_Brq;|E$Y^thI7Ij01GyG=Olwf zt@5s0Gi|OTwK8>5u(ji;xQ; zU-59vxewZXL5?WirGHJ{Q1N`eQ$t_EI`r;3^wmx|WB+SDV_fr?cKJQhse@B2YoHHa z4F)oKk$?K*7>Cc>sG525iu2Y)+LzF3NhU~T26xqNi9NxeYg$SgdyVeh{`i#)d`a|1 zq2=eTrFCAPvw`Hdhat@98rbNAJYx{EFz?Iqr4FKji^z)@p1~*_q-5SkHF&bM?-7RX zG~GA|qDWupfr|;2q+gaakF#~LcZNN8QGa30DRoxQTY6wPzNEiso{g%Mh`Pm!(%36V zf{;FPhm7W=MmLBVS*zJhRo39_nR)W$-1FNBt;y^Z^}uHFjT%3YD~2TPET#etF)cQB zOEkq`&YC(t*6}|uDg?F5P*;#oD_6L%-xY$Svn3=&P*-5eR4Uh@Fj4Fsjjyf6M?oI0 z-;5Glplg=qmSUqro^qV*;e! z_c`#k`?)$*{&vY-&cpfgTPHs)cbgiejW4`uRpZURD_GUU4FMlmQJ2qLOQS}_BOFo@ zJPBbyI6y)@PvoBgVZ=OO=r{1K^oE<6wQ}=RVJ`&2KAJ3(VlwDZ0RPgl5v}p^iXQg5 zt_Cjd22p8W+No9&R%z=}%JmOcO>{4HSR_qYID2ZwzG@o_{qVTJVx1EZKs++!X;XXF z&W^LMXCy`7o_zXHmo!eTN4)B{Xyps92bLTlD^WYwY6uh)oq^s52W>i_nJ8`2bhlXw z_<|2Q14`$Uxi!Soi8QM4yN1>)9-Ddlv$*I;ntDe`%hZa%UCeUg>7(xaNv-Nz_CoUq z^aA_>Jq10ApT(D^N6UMro#bZ_!OfL;dv#{eGfYY$pEv8SPN4=n6hu1lKlN+eX9Z{R zIzO^JKb!*}VI{OzVY}S~R}c|Tl#8=ZOEFlv;eo+;OAI}P26towBJRe@uHkoxpQSA_+R@V6yL zHK+0JgWxyqwlU7;sQT#rDci>_cJ9sa>)Y|{J$Km6^_%Hz@%8bP^XY|@(#9gQkqNC_ zTehUxc*#6+eqF3!-g2Y4F-1uFU+^SXgloAWje(6;=(va~8>2-En+2AT4Jabg5>Sv> z&8s+)@7xBh1cbPU@7sExmv`?M?|+GbHnw7 zrEvk!L^YU_{sRr31c40Z;d=#Ehm>MomH$Lp{z@`i(3HtqdEjBo>*}%Yvavz{^P*r! zY+%}h>+#qYJ>F^E1G@+Q@{x(U04S%R|8A6NsiGa`94yXQR0SJ-rzOelwDEl|j2I^A zknyhu&TqH6Hmq6*P-b4;+ze$CY$iA>MT)t@y`IDTrvGZiM2o*hNtff+{&y@GmVJXp z;H`|j)kV0Ut%_i%>P}6JeKK0zL(3UEI5P^GI^$KSb!tM7noQ)>^$8 zy&0c!?_%%Ddr(sCYh}+!O}(^&8w3NF-!1K6=%RVkz!mB@20R5c0CY9k*k{>#kGiuW3c;F*;7@PsIc z8J}x0L%3fhkQVBj=(R$qz-ty$%+1Ws9D-Gm!9+Ulbyqgdt(-c}JqJA(4ss%J34h3T zd!ZwWBJ{^+?E@IjxTjp%z~-|SRF$QQ;~>>+GSdBE|EktCpp|q; zVIFAZ_UBRuCGhqh^x{4bLoe)a@AlO!Tj=WfUf9`Ebv{sNcf{3uA{ybNsXz^1otG>-99K(59m>A!5CnqUsEgh^{^hzq}Y50sc8wfs)d!H}ZTv5Do_ z@3&;A*1!vG!5_$Ds8%uY+O2?^#C*GFCjLZbU~n}wDogCym9T&A<}Y}{=5cM*8ISUz zonVz+^2zGBKfh*crGp9Xi*KigS@hSpElOGkZ*%-C-qytwG-Jfz@Vo!6v9!2X-#T#P zdNNZ?7|4^gJ4bMy=m}tP-Ad3y(1oMfAqb0QkD%|}zbh}JaP=A1$ebZ-_mN*)uaiMW z*<(oAXQ0r9JfXgRBupWv>PO6ZUW+TmblX1^wdA3mO);xQtQ()h_-}mc*0^aM|wZ0R(HeZ+;L!y9iTEUEq$EyM1-0`@L5_ z`RYIPUz*`yFjf&@BKKAgkNUedpBJ* z4OH0mFV!)qZ^PnVAO|mKr$8ZdTJ1-^&(unz{u5D>jO7`?Ns^=U!rm(f12_bd5|aE( ziU5%Jf*Opw&-3?t%szEKYm5w`okDi&FnvkK>Cyfw(z-F8JHab$ioR(U6yc^tY#=IP zsvYmfAm~nAjWA7V8osP}PRyOcIc1P;Cuw(BB*8Rv-GaNgWg$5}>t0xv+d8u`t@0W7 z9eUlkz@Iv!wo~!erVgWWGu}f#hWv0INyAH|uB1_)l&uVVPyK??nEW^SKJfVa`0?mv z;)f|d>@DJ=fitApjgCKj?R0oIj(fNz1Q(B==rsJ-_)h-NdhUgn*WWkV3qP>Z8X)Szf}=5)gfqjrf94E1ocO2o z*R^M(R*zoDk=@3N5)#}1)+pkQoo-JTi-zIoyn2Z4sK%M5cW$;_m&h-{uPlt==B7YU zP|tIfR^VX?g7PQFL zoi32g6y$#l_VS6&NzRzao_J1ZKY_L%^M&PKsZWSqpe$@watC#m#K}M(%D(%(K zB`=4EaVkHyDB^A+>h2%wxuds{MVk7j%{qk{SbDgRQ~k9K_pz%+2@x_8+Gq5IbztA> z!x!_OcT69ZKf}v|SwF2LbzLLw{M-WeyrW2A3;71U3Fw!4C${=(C;vi}oa#gRV*(971h#C{NmNwh zwgf;n4;h+#6kQl`Zg9D|l50Ml*c78wfY`z@B5yI}B2(u}wyG7A3ooUG@*L+b1;0iL zJd`HKrBRADT;GdYXFGl3&JTeK74A`_F6r*Qr9dU7HEubzeVRiy1zBvsT%PG7%*5E#`P0vbcZ`765_4R0Bw|d(F-2IQ)0r*OYf2z;; z07s}P>69tFzcmq80EamX>AQcJl6ET7&EzAn#j`f?dLY;My97bEyjNKE3~=KGysMn3 zUmpF8z#Q~|wH2_%r2FSUK?C{>7%*V}$1?f{=x!fRiy3(BU=a+sm5IQ*_2wl6S6zQL z5HO^bUmq?pAVJuC($;~AXXx7>KccaUQd%M6H1((CrsSshgT!=?L zA1U`tcx?bsX}3Uu@*AR;wE;jX^P zl4Sku4f?(S(^q9-U4+Y`$T76M{0IeFKTwK8=t&P|NRpFSWt=Y(ltF+LUdj6!|MhMZ zu46G2TO%|K2&}Ri^1~cFdZ`#W;8aD$7R)o8OH_;N8)spTeDT%gtEK0}xSXIwHuBL# zFDhiPebr=fbfs3j%nOO9s!&v=5q}nab@Hgmt!b=J_g|MupoWBU$K7O#9|Qvc95UGH z|AQ3u|J(k>%*@8d^1n$@tgQcS{NJP~g=BHGU%j`#If8KXK?EKQ3_6o|;_`5@2!`;( z0B|TWMX&_v_=j-CH4&HxihH`i&XRH~2r>-jbo1qQZ0DjbJ;9>G2C?SChjnueBbv71 z3!i#%Qtp30%5`1;-2CoduIdBKh zd(d*33G%o*f0l13=P#hzf*zyJ=&SK>og%kt{QqevPQY|U{}vP5r=NXkM%_T;{+AS- zdy?MRVahlHhr2qtW?bdjjB@EN!H`}O8WB_V5qFe6*Mm}#}Nh=I}q-UWKl5$!Fr(N zGC<4;1%pnk#~wI_4=_%b7(>$;xk``XZjWFnJ_K_)a|flxMELAA9R!i7?LkubOdcNV2cH3aS~KqynO(gsV2(b5un zL-eOnF>lc7$P5`+kCRTGT*aVStaDzEBg$=wl3C8rT+#iOB%VG_*&U%W?S_Xf%#&Gm z>yJk04s57@I={0|>88*kD_3CEu{qe);z2t&3}#`9vyUEjt{#w^6+^Z?LVwKvZH~Fb zIJ-#Lb?vNhM~>>KZFP9HYyc0V$Se$zOy|%hdlux1s}Y91`@@`7q%u(g#!X@MOtwnD ztnFUWEtyBhoVMqVeZ-Tei1cv>$D}y3>S_7#oQ^WVF@S45#F=G|!_I#DcOU)9WE z1I1V3!!d4tNYRy;Q50U(z!LCij;p#L*eRtRA_x-Bc^Ke4x%Ak{Ca|W;Q+FJ++g%5$ z96v{X4`=2@Jl2Ih=8)J;8BD}cNS6Dj0)r+Qty`$L9Y1x#Ripavp!4Fh>#Bn)ETPI8 zpvY1+5YEO+k@!w%j7E=O!59I0Q2AFhc@GqG+zhY&puANK!kw*CeK6 z1&xWy6!2punCwzA-jKus^|RKHg;6@=NcxRy>B~V>FFn)0ZTo?fGtU{-6Ryk7DzYlk zX?_gE^D!B9rYTyTAlB%(4kY+}klB4l?zJ%ziL=NzQfT#{6nPtrnEZrjMwZ-!*>oXg zOxMX$?@=YH61R%z-6Ga!Uv4%hYu0B(TzzlBFwC^_4Bve4XAOHq=!q2^8X?hQjNt^b zNJPWz^!{;VQkbxGYP5UEmB_ha!Ud8Zaz ztL0fM_2#qAKHH_I7t5dYf-u6>cv}hm#Hi(IQ7^)0R)@u^#-s zUr{V9i`-pZZHmkfy?3dw-o!;tBQQouw278SZQeAk;*46w4x0F;Wx5QS+zF&in%+Wz zi@LEXo$Am+E(d8IL^oEuOp;sW^wxl01X3nNj7_!$dh=ba3}1MJG(`H{QZNUd)Cr~+ z@!_kLCafWK=Zchl7&Lzyi{fPMFHR7*-3y- zik*aw(FCDj_L2y+1iCnk*@<6~YNS0wM#;#-&5ygekPUBODp3ln%$Sv9ppky`ERDOo z_Wz1qTYW|A_PMqwYtsEGx(VfobMHoR#lX}Ez`{Df)Cr}mN0C&eIIF*mH26W`ln99? zf>suXw^JXz4v2v?q063X=QRH08!pQhiMvHjdo^JpmNv!Itk~2_|Fh#miNWpeuc}$I zT(8mV<75ELTvwh_^~G9zpL)-IeHlPJWku#>rf6nqW}Mwe*VLkrFsSXbKz7e$hduJj zFDYqwRH+b(YxLVKoBJZm7Yf)25#{ZGsFqSO=-#N*5yK;|*skt@TH=H0m*i5CT^(rA zSY1ngX6_aBDS}?PLe)0lfhN)8F}yPU6?~>qtkE^O!YNm+kh(kZ0dWd%d`IqzG2%Jw zDj5UoxyGQrP-$|#hggoft5$ta;C@twr@TQ}Msr!oQ+i&p>U2q9AX&iL}nh5UP! zdCwzvVT2!uhp5^xW#pbC)0_skxO|6cT_e$Cnwbr|c};iq^69#na1{WMXV~(7N}86N zWZKG}fPS2^(Uk1g=?U{HBy}kD3Pydo#k2OTKVi|tc`?S4x(Q|2IJ^Sg=E+E2noBfx z>NS?MG}=!dVDpM)w($<7tq>OwFb!Ujj9{kOkkbg|<_>G?`i^?342U^wAG9-j^A)o8 z{yms88^@c%{D6N__O*u6slPL1kTtZP&beYSV?f+zD>x)Cj0sd6mPa#}-V_5t3|2rQ z+zxG#)XI_?FOO9CkIY#KBPFzz9NgLrDFUG-NMhH=?&XzD-#lNL+ULc5HT}sYl!gbhWB|)cf-A&aEq> zr+~hfLJ3=G>io@>(^c9b-4@F-!dyc5D@(!~Z5xAVBInp+%d1brwwigB#F1{eRCuB->@zZw0QezG^&4C~ltqpG5Da)Lw9%au)GtwZtER7KVH3_Z1uFfX{z=ZVvMBeASD^ihC-f0z3U zQ6n^VX&cGylcNUuMWLjdP%n~58dPgM4VCKLmDudLD1g%^`t8Fw`j}Sw_XK1FU1ViN zYjW*}Q0H(QGb3iCtgRE5Iq@G@UR&%L=cBusWhRE{U?Y;jNz^DpOzj~__G5rx+jG!b^z&QOKI zIZx{6)vTIy0=H#3w|X-&;pc<+lX!ZHQ5B;)C2};CZ3%}}2yMv3B$H7%E<>`ZXzy0f zSZTKsg z=rS=1xks5G3jVx3k5ru@En|D3<3F-w^*ORHCm#m_;bp3lER(UHOP^5UnVSbx%`G|{ z{r)3}d*)D-1m53+tl}EdTr^;YMg-LdKtH;rIfoQA@bgFM!J_}M@vn!hU5wHo?Ew(I zbxW?R`8ya$?=7z!>@L>zVb(H;CNu-=_s=FY%IJI9f*sfny`==GALTu!0wfDCS3OiX z@Ra|VKUs8(|J^+f-LlW}?H87d`>ay`l2lSai8=PBxJs=}F~TP7_LyhWa)e6ktMT}x zH!|aEwNJ(g2D@ggRmiT`WvDmdCp-{nuMY~y3^3FH{V31ay6L>w3(Vb0Z|E!9dIQMK zoS;#C3!XFPdt5;ez<9`PnrKW9){S>FdO!Pz`5A!X`HmrO^jt_xPCR-6y*th73@GfQ z#%|_y=&Q+KgM6S3s+;u}kOzmJVOJhQC8_6xy)kSQp09q82*L4 zCEq|h``4I!m-{bO{*64tST3+rOn_MdKb+M%Q~di5>Y5>53@^||vw)cJ1jv4l@wah| zP`E3joG=gAGaL#6BZU2RVAD>CoVX}(U5P*ZGz{oy;8URP+o}FJfAIkPdyHvrv?KC? z*fUO34z~YJ37hdZajx<7Bd2Y!9mi;Pwudf{XjcGHJ`z4Dw=%_;Mh3litTpo)(A-SJ z2;-5`xO7gtdhG)rXDL(pK}E9F6wdMbWWEW`m&{7|wr~bUF(*z_Y@IHZO}HbUqg?xP zOW2e0DMb0EXvj4;pIcgIuf!5DN~m?l8Ly{EmLTIX@g1`Y$Jq(|zWv@TbN# z!2x!$R5t0^Ew$z|zz}e1`GCNKC#i{ zc=Au%_aEqCcUf4j(3f2<;nr&CIq7wZx1V|`aHsUZ9dKuWqhE+)|6a-Smzn~AgA=g3 zSk(R(tB_NRpjL5Af(JU7ALwcAYNt8hvx;{`PkC*A(JtO+B3H1}u+||@PyhahAbar< z1@~z$hIC@*gD3hVXZ)2?hBE6bzOmlQx?eJuHZ$?$qu+sp8@qaunar=0q9!@jcW^G=1En6!kuH3I5$ai$}SFe(cE4Q+1%M3Fz zJ0>Hxv?lx>vx<_Pv7r|Y%RXqQo|U-5ux^*;1=V?e1rNDbkG5HqHzz#DIMt&)?5Hl^ z_^&oK{<&)ZNR$%Nrp2RK8f2c9Fka-_ozfP`Uh03iUHZC%3NIN*m!XasoDZ5F@B~S7 zJuQRbCO+I@D4y1R>q6g;yYc~jvQxb1pEkVgl<62&oKW8ssYh|rykMuiz5!$n_$78` z%Bj<*)CEB^^COqj>z6;kcA*#X+ku(A6Dnt*J>>2jF`GP;%u;=}{s#bDK%>83Ks5}{ z8#D*&hvixgRO_#yQctUWRlX|<=u!17O;;P}A+(ES-0wn-Dnq?&Kz#(%kAPvsX29vo zS}m!4OV;~svYzL7s%fQsPq55Ole}d#jiDKkC8V3lR1R?X~x6u0qRdJ574YJ;_JqHnQ@6tnD6MaYF!%>`riUNO^r0QS+`U3R38Tej` zSK?45B;KP|DQlq*#c6RuoVL689jLxfPpYrs{7z6vZA1Nj2eolNYWPz4>NgZue~tEJ z@DBr9@b=q8d0HNIs+TZsCS)6+cMfwLg`BYyIu}9Tqy_3PX`MQV_rWE8BTz`&4Eh96 zPj$+Vs7u_UF7F)F1p`>PU1Q8zE8bW@PFy11LoTk*ELi@nm;Ogq`eyV%Eb z@VrtFV>FHOi0q3%*rCHsVjuMYN5uOW`xV-%9J5a;hbX381`eh0V``AJ7idC`%~qZR zrfIY3r@(f!D?v{KC!9VV1U>+EVD2-ZC%yPoV2H(tXHf7C0|$U#xw1^~T6g1T!V|Pl zjp)CPS_3FIu!kEXWpd5!GzaU=(H7XJ80r<&5Wu^QihNtBNSzA)Bf)_@6vSXLkE%j+2j3!bC>J$|7*Vc1*cJ9oaGZ)E20@?-mE{xaJHzq(Ga+JejPS}I_<8`fuX&h^0-~;LuOnUp^6Uc2NyK7c)V~;aJ}_oo9}w%x4k!Kg_p!fA>i5C?4D}LF zhkW)DDFHtLj@Uf9zpeY?u08*Fa=(x2;x741N}W4r;?CK?S-DrA|4+c#c^7yGc>8}n z7A4rLfESQC>N2uITa4UuHNkUr_&q{~GQiUj7jW{122lPD%Ikq5l;48w5O^;bSIy9E z*hdgY82G4~iTs_Ay#V^S-H>cMd=q#k*~jo%LjL`G3%io-hh6|4fe)G4&`ID;;O9U& z+SvX482S$Iub{6?_BDj^@HOE3KoxK>$wSWr!tcTuzZ3b$C%i*HSH9Q#+UiPW@t~ZDM{U>7kE%6B~*TI^MJ}6${8{9W zqb%NSk4m0y^<%R~#6Xcdi~Kch5pw3_JFjTWdvJvvH^$zr^kZ@q@B&#DZ$+J3c0{U8D^`Sz1i0va$!0-e=(KQ{nX^N`VAqr`l214cSIwDO(sRXJzT7H2uq0WC z&tDL_8kxebkbeLQyTYzw(PgW6!Tq7ZhzZpatjw zRN{f;Krv7wEUc4tVxApn?}01^>VXzOC9BxaVBabvdy{P;bJ6w%c7V(P|A6h6d>ot! zJ_h?7@axb8w^{lTu<#$Y?5{wd2|i@;LD;jvFGCmnlBItG+gh~SWpO*(Y0lx^$Q<}{ zfD(YAbO5CTSh@qy2qb0SVcX=3l(3x`Z>LBWAiY2iz_9K+vF^eigX{zPrTx2*tw6ib z*fUtiLd z3z1r6(wz9%0un}rLLLp%(six|*KQZ{x^i44F2hypN~>qhtc5Yoidh+}VNsTr80j+| zOBaERmDEtdO=B($+$1 zgv%l!V=jcx=ZpHIfoMUrL#_AM2kHyzJJcF~O`xWrrb8|Bmj%iS$~x3ye{rC=ptwWj zejeZjyhClN?x{XseYskVR@YZISF=)ly0}?X5|TVOAh_3@l^H7aM3yN%_&ZU^HsC72 z2!|{N%7A)6ReB(~(hX}jtlgvrhyrP7(JihQNN)Lry-oT=N%)n!{0x7*%UrssCQ^wj zHwv@?41IUO-z94&OFhyagB+0d8moT0v~x(i4Z{UjC$G6W9KSkJ21J1dAdOsRYsggq zJwtM!0q6l#R)^mjwnpj3uS@A-K|^0SgOiyv2_e2|PH)7kOvN715nP2lB;`IS%cRUR z(ku0wmHIC$_3u^c`6ws_Bm&0)DRT{XL_ZnPYa)7KL`QG;kzArHGo;jnq<@q0F)4#a zMy`GtA8<9w405yt1B5&x`m`KOIa!9Jj2cPx4FKC>!n+6!EK-xGax3o#SG8qk!f@@ z$x}q#4J4q5nN(XNK8l{VfmbFQuP}q6Rk;`-K_52#3qko}mglIS)XL$w>6d>poA_lH zvx#3$I*SA(r&EudyiVpycAEY@=tVmjxX%9>J}dq>xj{YVQU2$1_aKE$iTo&1qNg2HWtF_2zvN)u}j%Gb1rFU=$IY!VJwTNK=JWW!b9;2_bHMg*p|So!E3%rBpq! zoW^`rq-xdjSZPsJ!ZGrt*pi~En6vgPYfn)6qbH%ol)VW`R;|Sb6{hF1V!nsho+gz3 zhxMwDZR)z>_ul9C>=!%nS8TtRFYM>oj64YWvP05W@{-uJU_vQDlbzzm`Ql?}1Y?_N z6qy!?FwvUB-6IE8%VK9OG7F(jDgnnpH%8PsT0%CmPu%9)M{&C3}SY!9YL${ z&!kDAemr-|U2W6g=6LX5DQ9vhQyZ zS}beLP3r;=VX-AHKWKvd0gyc(si_RuRIb;Ki28o7QRU0WH1=GNcK_@ z317qqNIbZOj^hJ?$bA@+$X!jTyge%z&^lXUu3oIb&rMl<{}~veH8#nMMx) zGM>hq`s@R>+mjC`f;>4t9vsQ}!T15@Yk3S*!4ih(03-iD+d$j@0LD{2*ZTW{1MXnV z9UK6PrN6&_r;r}*bviTs*QmmoX1uZ9zMWL|45Y8Q2LkDScfgtH>3fK}kE(mzfeh*k zZVhMp)PcbH9PuCWhKWU-5P1zOVM7hZq~8#@eZ`iO@sTA*!CBzCzSji29mP zCvv_*TiS4Fb2y_zZISkEc{xS&1+X7trLIVu-L!XYZgV{15}qxc(I7as^l~I^aJQuk zfv9R#>#A0&g560~MKEk!QVGvK;wqiNuP>=gFl=_WAsGq5U4i>gB9TZ^N~L7r$&`>A zOTwmgb%xUGp6m*zm9!E}tFb@?({!YkT6hSBX-*>D z(x5aXjYwnCq{LChw(td28IwMhm=shKPC^R?az0Za;h{g1sRV5Z93+79?&Or*9&VLT zAKZ2^+{{-YE07zg9;g$DMZW?5A<$od{t2W(-v#~((9eLT=m@ilSrrs^1*pe}OkX+z zQ#V!Pt6MV-vS)iAQ&eV{)8{SD~fKrBLs2poEMed;~v|lqKXOmZ5{`awikAj406phYSEz#$T;_gcB(QO92T10ifJyg8nT< zc+4G3iOp|xsU{HaUcHYED)N$-A=m}!*3dG~x z_-Q1_rkl#5vdQ$h(y^$Z7U-Mq0;{cYnO$ZNFkGfVcb&|oyDE$BAgAUMti;yr26B*n z5m`~Q>ONt9%}ky*95IuEQ@k0S!d5sP3i#KHq|F@WhKsj8O`Sv+78S(=9u`^_YHLsw zN3qSj+)I2W)M(>4Vk>r35OVP3!0}OBck`Jun_MLu4rK>D8@IoNkNyt(@x47&flsn0 zuKs@V=xaZQ7^)$LujgWDQY%+zs&tRD3^+GK5LP%%_yPzZ|2@NS!?y6x|2qgAwfgK1 zyTxWgTA$x<@ii>3Ce^0~Mn>rJ;<#dGct=e9KxBA~v>J_3XF(S%szI-5HrkfihHZ>(8dt&5 zZtOM^qbQ6*P_XjRj>YH##58Yd!e)!3iAILwRtO0q^13B0=a#&_27i5>-B!%)*%9Zp zTD`|oT-zM-x9u3oo?R)8Y_=Bi#e8#p?Yczwjto7ZJMl0H!$p-rEvl2S!{z<{Ar{l) zJIyeNnDAyC!^iPB{s41$8aKR&hBaHe=qfEn=^CwG08T@ZtuC9Zldy|-h~p%+{L;O7 zJcuqMJ(ATjq;h&j<<({%ueSKQc|68X@`OKZ_{IThE51*b>D+2-Jh|xeGr?%J+FD)R z+Il(1t5>T~%V@*B8_ZkmPGmxrXy$y6&Ixz1^DJwlqOh=J8XHv$UxK`>2gwuM; zO3OB-UVL_JRV09?0VYQLEjB&{1-CfUC0h2 z2>(NcyReb)1hc%(Ko~TGZvlM0YAGr*s%Bq}amaYwIBwJ!MaK-O#PdswD=QtQg?YN6 zlorKI%eM)Av2YiEAB<5!W=8`PW3xlLEq#M${uYHRA3@y#tOTA zg;vq7*Zxw=sm`Y~UAm_nUBYf%(wwx6>VH!7Bl9`^xuW;j_Z;sEA3Hu4t~+mQZaZx7 zU7!)!Qk!TO9p!?ScjyIud4u?vc*HT{)CwXYjuO!za)pdYSWa+2^=hq}!f6PD=hb3E z%P^1mX{KH^nAnmL5s!(JA`xepdRVw)Q}|PC6^gXQxh@eA#WTKkBOj<6e%y5 z*h5!}uE0Kz?~6jl=&>SW#^poE*8r*Fv~GX?#iQl4MC+>a`x}|F-FL6!9_&1`r+>`r z5$8_-;*bBKy6mHxy3aWNd+z&9a>-2s2?-?T5|WTa0-8j{a^&i)FNKOdu2Q-(DXxe@ zr_lNW%7_$)FF2U1&dp6@eb>`d5ygNWZFj0$H_y7Av(37lt<15ubcdb&!A{psaL)~C zb)3V!Kkm)RIXvI*^Lf5c@l&TiUjTJYRZASePW{&li{S9Nou@Z958eJ<-@0{gT$%dU zJb9Kj##dpNuSPv*L&Xr$Jsi1YC=rtn-&RWvB@4wTbxZW+dCdDF1OoE{YP2@`esm=I znT{t>SSWUQ+QR4ahw_KR?}b0~eCYjC_|v>GuVJ|mNJ7s*L4iz?)abx(ilI13N3YPV zYz1o)j$Lu8p`xf0*Tex?3KU#}29ilc>TjxkC2BzRr8Gb#2QC`Gkc2*bUKlT=`U(?; zR3SdUvWD-#|2;|lUDc^kNJQU`Qc;qG1y@zeO$%kQM>ODk@|8d-b;fI&C$!5bPF6%u zXuPFtyk^{7QZ&t=IpHFgPiJJfJjJ8rD_(`+SZ{{Ur$e|DF@<&DGU7?m`|Cg_hPmQ8 zm?1ee+Q_Bpyb#=pKdByTA)V1hnli{!~FiP z6IF|b+vA%KO#Nfe+9Jj7Fm1DWgY`f3IA(+nt#DT!Teh?INCWfKo`V}IpMU-6tikOU zcb;ABcg_^pGERTAsj_^YvtXf1_sy=#bseWQhP$y_hfrM|k;q-Oz$)Pi^Q5F2Y1Ifp z17z_CT%bjk1ID122xE{K&LCk|<*H_0$mDrJpc$Ss2xNu?(lz)ArWrjPQzaHSQQ$a% zWef)98g5xMlB{0Ui=vT+WAs}zMJM4iH59bAXA zhuQDsV^mCemVVY)lRIDl7C)9mK!9+X^3rG$uy%OgWnvdb%=wlD4 zo>~v(!3Oz!P^J{G9}ydhT$NLr7AXBgP50VwCbd+J&Mr5GtSeLp^^F; zWx9oMh{bXd?T=~Ds8S2GP%Z9G=%hN`91=$Z4RQvT0#)D0Y&3t%wTa#2WNWH1^6(1J zXb$GM#}ZH4yy#s&(5)+`8md7&va71=nO9odHikV8e^L3KA#bz(*A*L!fN4Uz$oEX0IH^uYSueQPz_RQ zur`Xm;khaw*Q_m3&^&gM^TC>WGqor+l5S4sy-zI+AC=3;8mmXwXO@ z1F|w`{!6ZF8O0Pe=g)}%mzDWd#S#AWp{_G08uRz|-R-_Aw!te^zVY)tYez1g?>Ya%05}I>+0=8Bi@R6V{&i8&$-@`EaAOx#JRGG@ zVHfDhJ41wdI5J?#S->VAj;J9#*acwepty*rQmvGbehKeUAHWCHh=e5tJrGHiXo_W+ zB>X^i&{QT(Qw%M!>e3i{A2_^m_W^C5gdYti^q{vJ*kS4uLQ@~B2ExdU%B*A(49i@n zJ|c#6-L+j{G==L4ZGwj6@laW5HyaLhXSBbYHm~?9`zqJPc5zJFxHutOu#T9A!2=4$ zINtwj>h4tOCO9~?uetc^5hq*W`{D+3D<@p5*LJcU=(oM-H#_l>2(+og)wq~MW<-Mi zjghvzxIS)(JK{N=-negX*+4eHdTO0-gTEoN9d`J)%-j*_WqR2-9~U}J zo#x)mUh84zknpA9`Nw7lcuDlnCnYd%HoI6L28W+X zU1VD-i*szDcC$#%g(7lYMCWV2#dwyH#WIPhF-ncq#uBlS7!&hc#}_pAKoEOCS8P*l zeKy)=j}F5>(_qo!`9umTMgCo+V#qp4}u#Oa&3a71}_PRD7HP|$3s`(x*P`uSPy@~j>XWlt!uWQb?aM~?B zEn&~`nmgxC{-Wu^#gd9Y_P^7#x)o+^Q@7OD$78eRtXjGM<)%*GQtCoiVs+QeODz>g z8+Wg$Z|?n7>x-{E-~WfE_R95Jw^T+Oil#nZa;kRck+!O3B^!`I&tNB9}H?_ zyZ;`0H}9UGX<%B}cA-t&YS<>VTDH3P39ngnqR<@A(>RM$h*;t$(gt`c`{gJRL^CbjY!!VfgE7GO3bp^ZKlmhcnN6~Py_J*tp;iX ziNHvJ34q2h1wW?hZt5tVz2N`y%=mPmnVhD6SsG6A1WJ|Sekxj~^a|$W3nqh4_U8IL zz8R8IN1R4&Z4&V0*1K?bGT|jp^gfOxO{mmNu;IdMo;ywcx#=)NA)P=IXj-8ZnwmOC zejE5{NAI2b?YGZtJ@A*?$8S&(^P<+LtG`>la839-xn62BESy`AYsWq$q(V+lZ=fG7#k8fhTsB)6ozytHV%)Zv`!|84Q&b~ z9aT z50dbw0H%ZxI;&Vow2kky9<(CsyHFy!@n;AM>@lTzZ!J>7o-lkQ+?G8WbbV^PB_k3| zrPWFuFC^Z|RIV&t_>@+aX+^$;iRgX+(%=dBiL&+xIFh=Zq6l%gu+!@5bY@%|Z4AU9 zbQ64*y4-cQneOg9Vm)H}po{i)dKY>ZEpyO5;DyA=cF=3U(`dDO5YSt|cJLrYoiL=* zX*mr#7FDHF2++keH@zbzBf5zPSt_j<@hmuN4QPT*WN$ppwgf<$alJ#O$?x;joU!>Y z)Cev1EVWRnVOlEVHARb7({)u5bekynKvTc#7w`>g(9Hfi~3kR$0>PkNFjU zr=RvaGhJDDY1H@n*Bn_``*JwEKm}E+WIH>iRP|>#;F{+sk!U;T4UoR{-P(> zb4`O+0cGSMe0C6<8Z@NMoIS7hFAY)B$~Bw&=R0k341#6XCM;_S!T z`oX-~HaE~mX*V#;DDHKH7U#`Z(-5XLH=RSy~dpKbc|0u8BKT^-3}koqslSQ2q0 zR`_XTEw|9J7nI2e`1~SDU<;6|X$yh>!_}ZKR7U;L-l%l6)Y4;ZAz(e|hL%+vc}hel#+B&bs$r z-|$q^V(dFTuf4Z(=A1}!;QQXXHtJNq_wr`Z8`%Y0M~*@NzAy6ed)M-Jg z7y#XI-7~-iJjihZX$otB9svn%Kzs~vdmwQ$<)^5tz)l83MiF&Yu^W&}pjd;;<_4bi zZ~!6*4Jt7&x9QSPeT0L|DOl()-w%E19@v<;H#Aa`$!7#X}GpqeF*~WS|~$X zVMkTuVniLy{d1{*=GXI?j8mQkKdFEh>w6y0-C9^5T6g_6{KH2bK~I#CB-Rmojb8fT zCqoN$l0;YfBFkVY5_5l3J((W>`Z{r<0tkaTv{i{M0ZW8!U`W^|@8R|bhq>Y4E$*}6 zjc7?7crLg_-dnv#J}mxRzQ|n+UJMn{$t=1t%C4?Ss;F@J<7!p@Znei{#TOxyQ;1h9 z6(KV5?)YqRwzQ4A1V0i#maj<+EkdcZ8dFw1&y@={(H64CDyy5tx$!4qw{uBwH!@>@ zlNq=~T%KH?97rBa8n_r&-3chnaH0@$Mrl2w1iR274~culOETk4D#^~|O0<$%u3N5O z&Mc2@)NkN6@au(saYOLA&`bJP_*aA-@<8%j^yBE~;+LXxv4QpQMxP&hc$=?676C;^ zKqBlBDgU&(h)h-bLy3gZHZ5eg+fZdlMaoW+eN@;d6Iw~DWni>16<2phn_9H0l#~Aa zWiD7G#9YXgp~IfKNR|2pZcbPf;=zgb9HcMP6s{55q()1%0x<7 zR-H>r$?PXsW--dJUUio}P@jAkUI0F@0y>Ba6CnwQ8`>ucc5*$v;hx^6?g>yu&A7#D zWpXj0;|3L>eUlo%`U(by3{`O^HBlj>5VFEyO;}RVctyw|K!)R;Jksj}A~9BSAmj+h zFv?WPutEsPR2){xRFI!0!)t*ZY-0 z1;p~ef)&+Q_!4q8A#yXZofQ=cAMrkFU$SWoulZBP{4kcOPKcpKUTs-1@aeVOfSi%+ z?qIu&=DxXd_knNco|l%@?|N;)>9<#P_VN>q+RyU7+Eu@#e{PaXds;TIszWE(r zL~@AZo4)Z=mSGxSY~S>j?ZLPBLz_ExJwfXLNo#ZeeMQF-t`;Fu(ncW)qbN#6J-gXG z!XfsM_9DK-e?-@~oc^VZF_SE=o`{nT!5n0kjYmQW@dxm&5llrCz(W|4Rds2f8( zgl`kKG4G^zuP)@GU>1EkW)2_65ND~>*pAXSN>OQ+iYn1^BN_$}!fHJHD>L+%8_mef zDFlGV2CoCM4vzylGnYbSQ1=WbswF%-Roo^riYJyX{kCvQ&?%26}M zS?%$9t-QX3k+?u{z$WQ=;|x$@C6nPlY?pm(6ZaX%?|0|iot@Y|J9h5uvwe5IoPDGP;*a zjBaCsG4(}j{s0DrY^zU9~en= zD=M#qB;Dh2lOesXj$k7#?S51l0t~Bp>Pm3QJ>U>#M)5TO!;^1!bxd!xu@{3Fl0zY978Kfs5$|?5_F*!y6Wp9SOGdR z(7ZQC-n6#8*WYyw<*h&7TDe7=t!$w-_R9_0kahZkb>;NMZ$!{S#G?>;G>V_~H2A@5 zQ1JpPnQdlqU1WRY2gXrh#5iS)7ul{*H{H$jv;EEidVtx>?sYorB{8bRHA&1UDr@3` zu!_ecIYpMirBv_=qmZOTi%f(QNu)ylh!%*KGmS)+%aYkK@&Z^zLk1qGm!smfNF>TS zM_85~X=C~sl3+NdouS}fSIrLlUcHSY4H+q$gP(2;kI0Z{K7h2nw4>NvJY1xT1ZSh- zY$}|Mii>OYHmZ7ivED{if2e8v1RAu*7mUef1)3>O+%8|90$k;Z7S0BC6C53OmTm3O zDqE&n@Q}w%+$6Y9GH7~_aY73#BcFm5Q1GdVBnU1PwVlBZD8bRr; z$tGIUJRbiO%dAVBKIiI|^+xM_ecR*>!^p^dxW3bH1ii^%A+JAGMNVB)8@F2e>WHda z^H(N$IoG<=8qs*pTut@tis_p5*9{#(FD9x0Q3;42SJ6bJT&% z$;?}scc=@Q>yGQLNynti*coGw4?gVsJETBs?}rj}6UZlk~)6QJZAcUX0o#qlX&*M|8JAb1jh4dZsP| zLZJ7L@@MdTc!a_>2NJB%o0{1JLg;Ydp2&xh0mMd9Reb>-oT*R_`!Abbj)iJxXsVNZJJQo>~uL6Hj<%aSSLAw3z2fkEPcJSgY6c&Lp##L!f$NiX^+GP9BG24I8F-U+* z%muSvYTyMSPU{VnM~^#EMvB$o%E#;?$7o#SSls99@DKY*ufN?-;@;WszwdYW9hin6 z8tCQM>;^hU{@bi}wR9wqHz6fR2tv0^4&Nrh;p>UT#Wh3Ite_!+&T<(Wl}!y;O|(s5 zO5BW7O?SyAIm2+QrYBR$bdsWLAc%Ms-&`b1oR7)4vV>cO4JUi%63!%@Mb(-oOFT4$ zrYjbfu{DaL2R1UGDC3A-aWWr{6GbHmZIllBAcxqt2Ew)HO#3U2>wvwp@3b{F*z@AY z14~|*FU@~~bcena4Qv_TV{L!?VAtAT>^t{(-=;aWwIT%;*LnEM+y3y%KYwkV*_Y7J z?zL@7BGIHhW36g^urD|0$swya$Cs>_y@>yWal{6JO}Lbq?@mWVCvav56~&_SCsP*I-x zuh14u&`?BBj@|uAju(}!lUEB}TF^G9wHueqTuuJltAF^d^=La|4Qq4ct<(l=Od3j? zefLI|O2I(mHmC|rDD+2A6$70$uXzRJAz0oMa_iodH|;PO|2#A=w?f#2)(g+%`h@-H zrQA8;UEvzKA-LTF#2DIGXiz1gWPCxO>Wrk2FeqB6GI*ZK5GgpDOFY6i3&l{;Xv??f z*AveYy+U7Tt1(3E5x#32B=#FeiJuyW^P~B<`Ln{A{QLYn!uk9J|Do_{kE zXp7KdeqnAUTETbap5}Li-V%OiToNuBSA;8ur!rs2k{DLvjgqJ zp@0Ah6d;5UfkJ|SC)QRY7cd0g$O(`*;lK0YP>3g;EK3lEkK&-r*4Z{hhgak!v<&Y`fRHQ%Y-Elf0SgU8zTt+T?o<`}E$>XBUPhpU3$U_u3uMYikIR zc-5S>-+zQT>N?6fwxd2~5bb6h3s|>KP(ht`3M~?qqevJ%DVZ`Tld7T?M{&Xlw-x26 z86~5>7S1UFt7M zJ&(F*olZT7bR!>H94r)5#d$MW%oqAGX9H^%$qT{8791{i1{06TvT{ zA2fUz{UClz{4{FyYmtCRIP?t>20V+0B51qRg(6rW*q986t&#Q!vS7R}iIRIAByhrw zF*Ktx8q>#Em=*GTUdLph$*;@sq&y|xludFYFfa!UpaBDOAnbG)l$%c!2IU+trQ{0u z?nFqB5YnUno!iTgEGb>Z1XJo0*660!&IO8TlpmBbGON6Mj)QuZk0u)Y-k2yP8iWjp z`cV`2B{Lw#BEJ7S%daSI*Jv&r;v);qVJ^53Eg_Eq!>2IGsI#{8Ba1>tCH;?mqQkWb z%|)#>5*Fh1x-I2&;|k;8v|o56JM&{i*;*D+7JPpBw=drQdAk4M+_wVVN0;v@W>&+Y z>egL6qHGI4VCvCFZR0!6+;l9fsd;%<>1dm0zUt~mD=%VM2(|I3SD&t!F=-=V;C@!P z5xJ2vt%#mYK`-ARZ-|;mK*ogFGOPr8VQ+K@48dpkXM%&_ZLxh|BzP=!9-NO|5w53Z zqjlEc9UCw?OODmV?#L@%Z$Q@7N6so9j>jnxANx<$0Q>4IEPH$*ooHAw>c{h*F# zouM!m6IqPlSw|>R&$IqeM1zg7XfzB#ElQiw3or=rWa3q$@MzHgE-D^YB?VIg6EOaph+YwS>=n~xwG?b`rHk+EW zgrO+HmJG4%9^1Hl)u7r65bQYVcf(UdeoTZVs{R=v93QfV&2$5Ig`1JWF7N7t8#`Y4eJMgyY=fIymB6R z*X`^5Vv)yBzJ255?!tPw0|HPT{7-4NzP5F^B2$Dp&1>*M>BK6R9MjvBF#IQBS4q%reYhIZRC_N+Y@$ON*195P^-G6%UJpGc%K zi@{>pYF2$Jr}AoOQFc*oxqW%ENUgCyPOnL(Du&^+p7W&=&p3&$r{FriF9aE;nx03HlQ-#k8wT}`lEY=Izi{O0# z!A^h@J61t?-JUy<_OpRrp5?e}0sTtWccM0lG-h+u+H4NVmUNf}(&(%aFoW`wm^MYk zS`uCW8W+w2zzaaAfQUvbu1OcX;{rj~Mb{md$yI?PSNIE@M57(8-@u=g!K8doo<&I2 zmgO}0eRN{NiT!Mrd_963Lhw z;gwLVy5{HNzR}EiB~m}qetAhrBiB`&3_C3AUg86dV}TQDQT*eI_vZ&sc8jH+v2 z&nQOW7e94RT^>7b8!8-SFhkwL%BzfE|GjZWGu9Hw89v zgc52EwTF&bj92K}$lYbyZA%(wy!*j6*-EvS!K+DFRcJ zRwUxqR(j?p`b+^Ot+JYrwAyhSQA6T2HL5j_yB%In-i8hqz4HV?W>LRO@BOIPK1PFP zGN-I0*P2V@S<)(4C8CiMZK6kH5^Pe8r<<*LCaW&9egki#ma|1`2`>?AYf82fQ&NlR zjn*gd9;%z^W;bO9&4boKe6VI4HDnuNwz0$hZNaU|v*!K8KL1{2uR5AOh##j8c@BAx zGsoD&;t}PrI!2r$&r#=?G4?h8xxiUv0-qqR)Kr)^)t{*khzHacfd|2r-O7_{cY2gG zx3E22FTXiueiHu|-&GsiMpA}ncJ1A@cb%-)AKBPzd)Ll7PByOX+D^QVd)~XW*QQNr zpr7GnCDI)&I#imtqT;Kqp8a z+z}L{wnALf@Xk6M{l(fl@6331*R%7y&pgkkxKF$2ZLXo%mejV4Vy@gLP%MO=A@ z9BVXTnqjNQr6?R&Qw7*A8s!j!!^ju%hkdC`S2!gJaF@<#GTdqAN?~($b^J=0cdN6j zhb(`VV^x9Y<6#&b4k=31<#rQ46bLKOp=2T)7bos2o5?XOOMX$ZIcVm5QfDd!k`7V4 z+s$#TtN%O98#!3BDXW+x@GaZHN~W`gY_5@;%+Wh?qq(u%xZPODE$6u0J?^irPw?TZ zA^yjdibD9`7H`Ch9jHpH6}A znD2OeFfI5u_2Y+fvDAb4Kc`gCPQLbbUl%2IlnQC=z*J8pnB3qT(vyY6$q%p7&p*20 z*ng}osq1<+o;bF`QfKPVtxx(q630MSSN`})jQZfq*)}i}I`J=qt4s7*`XXA7Hqe93 zytdS;MJTFj5rzwd*>yU{^F-T{UL6T}&_EV1N!0FqkPLzLAEybmLFK*`AdHx|$L3Ae zj%?aCAgT*4S(isqM^{g_AXqNAsbEDSr1Zj1!tCm8D@q0lg5$W1WxH3lN64ZcWh*@n zl!1K`RPIhUuToxNC6V`iiJ z1J{h3mJI1AI+{O?&hzJsOhgKn#cHGKa4AF1A!d`hDLz!Ts%ImdyM zZG8jf!3}%)1Nqa(;ImE9GEuJ^+d+94 z;)+2$AM8wP=_fv{RZ#W*%3@7iny4<-LDd*ulZ-KpOq1NEXaS?z2(g1?xoq{ax!A8H zoW8zNZ;4`DoSUNlPh#H<;p4hcM+gP8&%7p6oz%(R$&D$IR$ z^&V(`2n!a^o3hHAZEz|C?IGTD!&3I8NOVJVLv%rO*;44qV+7`@0|9^;C>Hv9d;6MF zp9!@2n|-ztY6}Luequ}ediw%R;{ewH9Y7bg`K6BD4WnO*cK!D6dv{j#B$Y|(nM<#I z@nijp0CgWmS9zBrKE`1w5@XZOeBJ>_-l3+X+RU9o43 zQ;&arqTkPYDy463d1)oHQ4n?Dcbl1iIqD=CERtMy`UNDr zN%qPB;P<9Uq6-9Oq|2_5JQv7#4;e2AavO3`E+3HtUccpnVg3LL>n>g!0ar=%ZS}6v z6u5St-ZkdLUqaG?GFaOIlW+?fZUNVkYsj@xr!%P|8EAyrEmH)&vVUN!kS`iSE+ea7)7{T9b= zW^tMGcY2=oMg1G)HUT-pK_qJUqkoc%IVYXen6u%$=%k$=2tkD84nYt-!=7=^q{q>K z;*y6(9?_$EvQVA#-14v<$o{u0MbDW2i)~G_3yD$LE)w@DuGH#HfyD2 z{{H?q8s##5_0=sQF)_Z9TKU8J{I7m?>8D{oXu@X!LtA0@(C~vP&Vm2Ce^4PtAedlm zb+UKWsL*+L!WJihQXrOqYZ=+Vdv19{f{suva zlpr*ExI0}y39@=^p1n>=@$GW#bnaw!vHQaN6!w7gh_itj+I09G^_IGT{^E4?;mvrD zyjvMfjLBol5xK6MlV0{sdMD*K@$1yZ#1;H=e3QM|{x@z>d0)McWro@+?Ul~Q&Z~{Y za)R}$_$R9iNQE;7gn=T65O5Z7F{U*%3TdLIYQq|t*QEAc?UFX9-O?7cWzD1QkNl|> zx84luE*7SDXUcDqV3i~jCdq5RiwSs#@HK%FG9t<%3yq<1G>I;uIkbRWq=P~~JRCX^ zqJ~5GwGa-?V!@J@8H^Z_QJE}bF-~S6K5&itdec_cC$=xu>JuvywZ#b=R)$esTAHw_ zdr?|TG55~M^O3_5`jrUq@x%z^MyXW7B|JgALmky^g)@yrnP~Fm8K3DCMH3TEClY3y zyDT;d4;uin3CsYzsA2)xSZmt9&L-RWo7_fk)$g2m^&ZC4XMU9H>5qE(MB=FzHvEU` z>W^*Wy5sNNJKvpu`>3TmrX zWTOm)R!W3a+Ef~WjkO#5gDefhAhmcZrG+(Gv`vgMrLd{mq_IrdhN?;^W#9K4hMLIt zeRucX-Fxpo-_Pgs-Fk9t_qW$&@snpi#KO(%SRU_=rlZ>aP_VfBW;vpgUfB7q?sf!C^md{BX9 z(EDFsQ8EG_!IQ@gnQlOuH^Xx3n3E385Wx!j{NcD=##FI!`PF&8x_AEAsAOD};W%** z{-nx62VG612uT^x$OrrI2m~NDEyQSahZw|(3yOY#odI{_Ht2Wgb8RF;l;E}}0H)=` zw(W+FrIh{eID_khe)GH4>RMj!dN6cHl`h!M#Obukln?_er-Xpw^wpmyK zG1X?~tar68T}7c61%Lv{EAqPx>OeVUXH@XQ9r&?C$oyl&`&J_sNfo?`g!K$r6~Ue2058uh2}U`)i6;tn*+{r~ z+2mBZyvwy?{i3E&OeObhJ(mJP8{FVwJZli5R&Il29!{JW@}TB4)*y$fkP8tJ4B?|p z){d^G%B$pVH9xlG&VZgeF(oU7rKZjJnTr|7{eYEpj*x34G+DE#Q zu9cRRS|Xj7Ic+}DKe*f`97c{4jzyveJrb}=6Rx*_t=E~8qiSz)ljm4CRa!HZz?y*8 zlUGPePjUIq1|Flr^CUed)11!TYkm3gRw8^y4lq*e zMKj~uqM2#8lEVoz5UYeUQ*9*zoi&%;nMEB$5KA)?WZKn8#Z`@kNTnE4&!nsB(i{oY z)g5T%0iEgKlr|W(L+ikK^$G-;1PM^^9Qw!rYrEBeYGIB9xX9te;l!b7i=Hm#>@m>Z z%|L=VY~9JIU{CR!4o?!m?HX&RIVO#3V0{r)vejj)K*kZ6de$>)UG0u0Rk<3MrmHR| zldCyo?vA@csno#uL_v1!&$!&KouQrH*8c1va|yC#y#SnFX^HTaXXLr6VhSJtik(5qzTH;gOhMI=+pdo%=+t`!f9 z*WnVFB{c^6Fje3eIQ!ODC_gww{q7i(D;8z-6loWJq?&HK>7-5)l41nt7onLUVs44c zAsU4T0jKyUy;-t`T^6Pr5_G~IuqO2;92p(-0#Q!^botoCe5hnb#Ppa*o5#q{@Nr_N zwuKJk2ku$p7RG?~APfYv6HBW`QVwd)id7K&%Rn>1_Ota8&4T9V!fvhQtKi3=t6^n9 z=s?@^D;X5y4!R*#8K-0_q~e9M0RPnP+T6ql^=a+(WcN+oL8?x6s3WQ7&>sC=h`^#y z-@(G3Y-lqOqB(wOUR~`kEdLl-gTPP$hZE>2B3OcEv#FP%6IEWSk* zsugha7Z6pcwIT>HYRP|`ZW#(e$!Aj?x^pLnyVcV*i@b4~lX1q!^=bVRYF+1R!+;!F zD*eMZhBR>#&6{j)9qn>?>}q3+_o1&R=IB$$^6Assd6Z#=#Rl{_OQh$P$QMNBht3o0 zn#eYVSK)b+H8;i@m?#;svD4+p6_y#2TzAD1G*f#)ai!Br$El2YGC~7(p=X0`$8&$$ zCv%|XyZ9jbg$0wugYw;F(&F2)C;2>{YH88qT-l_2U(MLmELmAbqukI%g|czaTb$Hl zKNr>ovQ)x;#2L0_+T5{)Tgi;sXIboc^Pqv^OfJY5Hj+>_*HroZseCAXXc#O^`TDx{ z{Ys9kHM>+*-pPlLG24lc z?esUxcrre*&x`3Gqa1pSS>9)g-CWXG(}{v{J?!Ij{TyGbz`;fJu5S3Gu?dPfRay&& zSW%A{d4M6b?_Fi>4*fa;W~ie64IMAZLaxolEVY!I`P-x-ncnatTCvnPRkk-*OVau8 z&h=>O;hj-Vn^Hj#rxr90*Au~)8~<~P*AIh^bM|%qFu$48BGu+csTX710}aN!RGV%f zf&#eMsj4TC2I^jUa8K^K@jAQMig@mpN{7Fmf5-H5Wqqjor&K0t?@<)dMAaQ%O(k1P zOk?zpSIf-Z(Pd}j!r|x3^?oDW^O9@gyWReB8uM94u}mYY#k#<{H4Z*ZadE<*a{SZ) zcyInn15EL&CeciOB=0jxb0z>nWrZQI`4l=7ymVDj6^3dA6lWC7>WPQ0mR3vI>+X}> zkB{vy-QaY0%I{vUSu|Htwc2_Wdb9bG_Vr=CCBOk&tN$rJUrEQ zCWj6S3(EikLc+|*CP%h$tp#=Qj%&muP--_LoJ?3`!erWRWb2m)NvJ^O)%D7^{&`FT z-9Lzob{Zn zldI=__w@61_mPz2*yqD&r!pEFfi`@<0G5llv-E0L<98aeQ~PD9_Px0?rhLA1z_;0? zlU+tO*xB|aMU~c>m_n=J#Dzl}=XQ2`ro@+lMNKJbxacH_BY!})isyYWiRDx@VMfc% z^?rM#GlKXvoSXK1(%G4V{~nDNx!{+dMb%)YwT@1+A+8;f=0O?UWw_gd*bf7e$ubTB zZoAerpyUm%ND^ULZb(>u0%aO{Dl4FG=Hdy%Q?$#%coD~^F+dFrw`?y~I>i8fy-tW? z;@J%yKx-WgR{k`Q=wAYdnlu)48ws)jbb=ZgO(EvSx%KBl8`eueeyh_BAi3H%K2z1= zVLiXef`v(I7T=b44hwhUBOwulvma-QTN{~7q!hVCn~+oH zg1LI33AV9atP%$JM|S`Rp1UjXgE7OgykuRuN$U`efX-qc`oB z94`ujpj=vv>^Jv%FX#cGjdYIfSqV zmKH%)RJ#w{!7X@GA=wO{2IX{NVv;*x-=sje2$u*(p|Vp;QwOlVbc*1lw3@&eOlrs# zkyF&s{;rcqNovnO?xq#X%XKyA_r!F8=k8!AW};_g?$#xRurwQ+9rvnJ%kB9+`8r3p z{?Gc;R#u5H)PS^vg7J9)sr|D+^iWupCHjvZ@A>N1B`!wYkqY`|Z<=grnoq}Ax*IxB zFn*-{vlqo?cSoT3GKz2ut~@rbJiO)vL7PaxO&=}1n~?n%Bkt85=r-t*P+AW|q9oio@ccjSo<*rBF#uVUq7O{Nb zMQJM}hGo-0wIb}9~<_ zFABUa>i%u1H-9FpdjWxcrEmUY$s|G1PY76MFKPGZmljK{hbBA=1p;-p(ZG7+#Tb=? z+`ai|i=bu)dU<*|E`XC}rX*{Gr!F*Vy za(e07UD(&Rjxcx1zkO-XsP-mN&4CA%fx9rCFhebrkjH{U1>QOY3y>P}slO_P6`M-h z<0Y>pY!o=R^{oj&_}v>aS&-vUN=cf=`AUI!{64wVK+9lSElAG9w-hxLRa2}rN@sg} z&K;beIlH~$MmO8ms|T?mgg(6UN1h;V$_r`9RP2qbb93RncStRBkehLkn?oy>SqLqS1+NNSX|nC@emujND+i!)oD7p&rXst?n-g zTTyh2+&6T2X$1nRMi(XDsv(8y1)_?PBi;th2?;9@5FbM3j^bT^02u*bCQtq>MArG7 zZ9j2rK58sgfZVFTEu5pp!kiY=eP-7fck&6%XmoJds9>!R(D^xgG2`MrcAOwO@tOKP zGD($z6Gl(c%L1J1j@^Z!`c}cG{NUrT01)6WB+Mc(hWp!M_hEe|83F@sSY`$nW~fL^ z;Idm39R4*JRs5Q9C2DK1$&0u72&Pt5?D_)?qg6-|FzmL8Y6vk28uD=t*#FQZZnxyt)%%uprVp*5r8+gj-Pp$3I)BNhUsN4yCEpT|H=sj0 zsY5+!8-dC)uA!(Nx0Ebk>xiB8#f>LEp+#95J(B2W1rDb0A)xySN@ zg0+^38aLfX4M$A`UISj_#{Iuf9G3Rd#l;5I%1flQx7sKi4ptfq2%3z_WC;z&n|RYP z0}D~7;Svr;vh4)kRBgG5kEP1o%1=IbwYV}Z#i9z%>DWl^GHTE+0s_im@88-Ch70a} zPTHwaCIDJ;(ISQ?@C)mdFKvZ_bkf8iHmKX!9vb*PtzMX1sLa4Wt+wr00UcQGXKv;= zVhhq2?7(@moVin~<9TnGmR7Sf)&YMMq!xr|WL9%ig!{+8`S~F$3e@SLREI8)G~}Yi z$_$62Ol^zdsbU#8N|DZE3(9pc4OmdR4EPK>!3`pY%`N@eWmFGIB=)4B7aC=+*TInY zW^!`4!A9XgQFddwV})K8tO^CN zS~P$N)uU(?US>;;In*I^q=lX2dJX0|^*rD>2pQu=7>j*Yj@CqO5K7RZ9c<04vY>q)>0oq!P83hcssNK!9T$c_CT6UNN)JTH^L%P#zB z(W|!NZQ%W3`JSnd2Y2H+;GNsud-9%{)g4IdH3-!Gkh9cKUJxq}UScErd-2;n_qC4b zdR{##YeV#`XJAFcHu6Fmn_XRLaUNg0EQ#6o$r2yV50?0FE|G@#vsF}Bxm;SmPFBDi zbz4gl^#3u+PPe#Weh3LKeBzqynv6)T92b2v6HY<()_MCg3)~;j{!{eVmG?z~*9j&= zW&$6A7Gwwt7jHyhW#Jt3e{pJO~ZNdY&jU@)P?m zF95sVW4e|t#pWnsUp~Hja#Ch@jZ;1|6Mww3n<@zkvOXEaL_%T8DNxOip~tMU3D-p& zfoNYGWtfvu7%WkN;)&ccP1D%Y*xkF^dD=znd!bbi{CKqI>zM*sT=szhER|*>#NhcD zEACcc%S2l)5?28%(_p8-Oshqj{-x+Qa~s$V?)F$-72SP7e^;Jpm)?;%27@;GG3XM; z5MwvxLZu^0?g1~1uZm9K%Oi>m3}rqF_)mW2(7qyy6*pz&7Bt`MBwcPlToMM#7Vh>C$J<-GAa8LSRxdBfKh}7) zv5Iv?4VS3&)Y`vhFgAWY;;K>Agj&Km+FUBj6-&l81;e(BtX9-YNtV_y_wQf++zMK{ zme%!U1*ST%HVG+g6eL&lil;*S?)T+BnY^K!$cfl-81I#HfT9#|UD~BdAD~y={;{Bn znWq<1#b7KB_@Z4s$!thK;_;<0S;>qtL4U|>;Qyla3YZM#9_91pui7(gSCa_&qsu!c zo3S@k9_xJm+;|U<^-J#b*hSOnLgsdBax}2wzjTo4najP%!I0b1ayFUwXqv3;2DG~t zXx_7fiN?ug)?PxPQC^j2)Poiktc2cvDCp{sCITtKF{(!=M| zp3*!vh?}d)9t%XQn1`4}sd_c9$KmF|SZX>|&dGi|Gf#tyfRm^q^54!DI7A%Xukg(+;G96UQ_ zcR?i~iJsx-;W)6}x+XKL)Co_Fn$oUUWx~065+}(f!x#g3qm+nt)U&_! z=vL!9xrVH%h(bWlJ+Ab?lMv>QfyqKhL4qT%0z10VywSm9M6>hy$wLQ<#sM41JYu+< zI!X0jWHh@Dfmw0ZaQx5|%PWHnAod@oH$TL-0T2SB7s|W@ ze+R|4H#y23Ws};nTSc#m9ZWZ;qjjqj9$o3dUhTfL%j3NPII5_HwDldH6zCCV8}nJW z?5^kG^_PviQtUA4EIk4;KL_?ek6}P_zw_5W_md-o8udvQ4EX`ca}mkYYdkrJcfHtq z^ltVDAeDG2*enOh(YpzZ(K;9wpT5#SXzprkvR?K$SA=aGkqjt%fXw@Vg~Axw!e>}w z9)BXS)8L1U0fC}Y3888Kiag5o*1P<*BNE?E*h%w~<2`Rj`L}U!{xK;7`+}V)2eL!V zcm5V|W1B3eX+)Zq9a7hsO57TRC!$Uz5evT6d3JDHgw^{KCq(XO40Zxyo1t~Y+Kzg% zj~k9kt+~%)?qG`4m=f0 z`f$O9I-pVNp8;VkL#Pp8Y`xcf_i3)wU)Y#wAOU!pM0dSmu2ECNpzCO>*&C?8(%Hhw zgfITc`vT`TDawd0y~vN`lt`}q#%%bVV979n-bjV9le8M&PL~`jA}=C0Lp@OU?X&Ia zv)24Xz*BKcNJ*b1#(p+BMFW=Cj%9*~*HvYV@;V3=kLImdWg+&X=+cN+p0EDkt0GIOEeMKYMkKu3frOmd2k3te?KyyGvGj= zQzcSANustj{MBamEh)TJ0)C4~HQL|#akE+@{8aU52@E)m7C#ncF?%Y6*yN^AP!{o6eAlX z^lF}o^GHFS*_v`uZ_q|m;6C7Lf?N(Vk(X31m6yO$adj%Lp(d;UteTXCcIpZ}xtJrl%0!m^1tTypjLr94jc>zA~0?n&eFAlZO za$)Vn)H+r)=H(NDgx0po3T)*BivD`df)rw7)yf;8_~R$hg4Hkdu{$GTpVI&-!B)R> zu66pD5u9|k_&JiV$d%-;(E1+F-vwmCp8e7`HdwUPzi{T-R zVz+SkbNb+a1nt=!aVQ;&zg7_X#aR#EQB`QV+rgLjm*%CQ#XBgH;b4ScRl-{_VoqLy zmH>?~AVFx{?$0LU5`g_P%=yQY5oeKEc0(F~IL$7$Vi@v0-?l)yU*i#)CUy4UgiCMt z$JWSZ6BcSbjyy#`W8NXO_5m62H088SdivcicdpWs_OsJi*i-p_Z@=JC?z~v_mwv+= zqRu|e`hH$3M>RI)Gy#fHLfiX+gwD#c%iz-!^2OL2P^Q1N<5J9K>8{8&L1;nt?nW5S zs3X3Zz*GVDkz9F2ML`8?!CW$_e_Y-IDoZD8;@28eaP$qw-Caq&L-YuXpih_X9UFG5 zM6*7GtTDDS`YBB~2|76K+V=gh%#w}9S?P6Q7%aBtQ%SC}qn13htE6QfLy?cGu^bwL zAV--aeXQ$<0(Z5@thh0=pqMZm3%y>szamOOSPft@n?C{Id6&J7K-=ILLLt~5JFkb0p zRnk=5TEe|~yQb^y5$1dq5`ThP8&%KSqW3Qg!A1E$Og3w_L^j1%=s0$L_Y>FLY+rcgkHMi^z;gN*M$Z%1Xn6YgLf^Va6$FC2 ztHI6)I3(%9VPNVR@;K2~1>ZokSIqN9WS&A~Wi&^O1y0O4v8Re@bI&x{3$z5$l=$b$ z>gu?Iz4uoeLxUSt+)dujME(?lM(}gK&Om)|XkEQP4|q~*+q7U$rpi#6TUhG0GvpHp z`|Ok8+V~e7gyS${N%tSuZpPT)_bX2$=};K>Zq?aMZMYOX!biLbV-=ZOuIviD{c56% z$UZXcIECqGo>0uipG~J#f&txYxg0zwybr#qRZ1av?>(iEq;clV(y|PT8M(HIU14KF zPJ}G$9|gyb1t?O|xMA{J4*QIKMKvic+qk^ie|@OikqIZE7pg zx*a9;#y@4y^P8n=;ZZ=mFnIi@W<{iD$V@6)?Hp7YW1=S^*DHw@ql!i}_4TN9{iuZ2 z1eu|v`(C^a?zprEGNQZsc(8Q02_%kna9}I^NngbZZ$B?VW=z7T<9UPLz_8TVkYQL| zB2BbsbVgS|XBXlKMWIiKWe}c(a}M?+!?ot;w{SJu@6Ty3B!LFB3E?>&jsq<^6F1JT zvN+dXZI@`mbWDxjZ;#tFIeMeUP+6`&kE6!fG5Iodem4wG1Je;p$1U}EVIFzV*r@7n zVBN#dT9lF3tQ^!^rV+gcHmIcJacaZoM=+A3vg`<-J+6d7uH<*Yj3Kls=J?RP9dprn%V}Ko4t2D9 zr@yWZOBl(h!5>mY7pG44nnmg4v61Vhtze4`GI7nb9_1+QfOo)`lCvV6I$(05^9643 z*)DO8ze$92p~pKWg99574d8i!3(XLaE-h6joadMw-Mt*<3R;71K>45<`)WZ7)hOg( zy6rG9!_5j-U29@&f6@DEm*u@hh!v9IiNI&s%w#l@HxjTG#LrPlG(c`?!WspRSicwO z?tuMQTO`yuf;gqA@S|iPGQhT=Qli;w?6S4rj`R?!&;wP`NP=QwwR~$EU>)s{HtoLM z*%DPx7h~t|{m+{(YB;CRWuVYxwr)4xt(|V$8xLETU~WB52V5g= z4Q)_UVKTqvB`iY%s(=KD0)m67^A(EJczl0xAdG|i<51&^#N+RQM#~NsEiaV)??-Hi z#TP{*+t8CS7*I1!=#Ipz&5Rhcs^>qpj=1> zmMOICm0Bo4Pr1ALQ7J>J0SPMR!_7sP#O)|3@6p-4Lr}oe?Nd63SXLE&iFqu(dg2C} z3(-_>Q1c!YW*%s~(eXwEq=l7~i$F318{f;25ln9df(^ht@&!wA4T-iI)j18EIs)rW zJV%*`bl>zf?OWlvFDWMR8fw)?2678mRl&R%tqKyB^>ybb&DE%8+b0W38qOe* z5F!j^k3j~JyWtl6!tGJ#I?01mf$W<87M_8KV8s2<4=wTu$S0v54a+av~)$G4KpXIBK3x*^3az@VD%e&J}|V2Ah2Rww}32yXH3U z`+cMQQ|#&lbs^eN6?kKOi+oq10mppV7O7tzX_#H{w1l83I0@lROp+GW2c|y96c+c4WM~?5BBDDdl)~)V69v6Bs!sGR6zTrE0@^88Ig!Uc`T9a6zDfh$Mkm)6NaxzBYA`4}!Cc4D0EraerYlE3BXbhx&>sU#A znptkS?$3`H$Uonq3s3(@j|UMZh)%7c4X`mN;w+<_hEQB41T0a^k2;3Vw#r3hPWFhBd@L>N{}Dr>60p^}d@&TYVM(ohRpHQ2nJhJNT1`?8*ZlF> z3Q(!89XV9VL0XlmOf4(Jmu>7>VD}f#+~4D=pl5o&spC406n8nJWBSe2l2%)_TNc54 zi8E0(&zhjf-zsn25g7L|${h1^3dNALGYenWuM<*%++Q!$5I`F;V3zQXleMq3H{dgR zaB^@^n0w^_E5aRHI|!zwF!dsWA0+sGw845Jbb2h%lpbhHa-bDXi#mr;EU4s! zvFpKqn6V7OCP z(m!QEl51cQW$rzEvBw!*%BK^@BVSRRSxMLGV`C@94z4pD>E{aAo9a?Jaud*b>BiKi zg5KnEn^d@zIEax6ym`1BxwA4^G%b4u-G}8ZakI*H$F;9v`Px#G1&?xULe*N8hVx~o z^55s211ied%b>G6ZzJmY&d6QPptXBu?PU7Frkv)-f12yTw)3;XIg_T8YM#Y&%A?Xd zXxo0f?1qbNLgPOGB~L*zzlWARp)Ji*(_<4iu5T(|KJV{=6|LCgDcO~hPwu4Jm4k(> zD?76>aH}8KQ^8XK7Jy9u_i8L0OjVp>Eq>B_nx3Q8*q#BA#ZY64zJb3&YPFsqW4cL` zk|sTU1W?aDC-{QXAJh^~pCF+A&$aLc1$-uc@G(>ffQr=LiepU=85x9KyZ|3{ftLVb z&<;O}C__dp32o#q2x9?>aZ{Njs5*(k6n%(zWgUT?ee$q5I!%T$5H>Ax_QE%E*JOSf zm%V#Rx5X(sqRn%Q5~>4C&Y}XgCbwq3fb*}v@&KM?wV4c&#H-GtBK4J$YVm^_+Hmw; zl_Hd3)Qb)4s=a_g&Kl|URIe`i9H3kx($fMqCq`tub(?T}F4MQt=<^4(mYv~FBrDEZ z*B)+5-DmgLd*3597GDMqj*<&2Y}W#FOiO#L(r5GldX+EEb&8kz#%9VZ8F`km^ieGH zZx0_AzBwS5FDm$ul>V)UfA~0*KK=S{|IyIxA#m?Q3?pX(m!zAL3>qg`gpiPQWLD9) z12arJ7q;el5)!Mwz8Y4@YMkDQqIr6s>Er2f5cb%wN|Sl7ts*8)qaOwJ#5xzT7VqtR z*Ho;JAvk+1itlEvAi3fk<+`!$IQ%rc$#r;?xw!*B5*r&gl*IVu>rKTJ7YaxQQBdTa zuDSd3Vlf_w`p;h*#eL(VZ?s`TN5i->&7>cR%XIA;$5+9^bQyvyEom4R6i zGQhp$-XmZ1WBSk1D2uc9*69mj$BOH@JND|rYGhCrd*jaKQh!9k7Sl2fQIG&4poE_s zHOgF29f~;sP(Y@+`nXC}~;pYtHBQ##VtM+(xrfdA*cSWufCrPOYhQ$u}NW zr7Clsbj_m{r@@szeQ@SOm**w><+U4+_oU~9dqj(znd>V;SZn)i>LdsG&3`xfA;NTM zE+q!+($uW8dbj4}9E%)r4H^mv#a_Ztg~Q0=NK2~imA@BN}JvV9}cVaN17lh-+x8})QrM%;;u zm{*fyi!`c2USym$>C|u(PfaeFw&Bd=CN`4OXLby*+%m#?fYK2o)lyP=@st}b?7EWc zltA^ERUoO@vU_Clk56iG980-iw{jIt$e^LWhDE<|xvVQnHA!5NIn-=E{^P^C;Ek!$zCU-x6f{ft`g2|#pKZcx$ILbJD z#gee2zi_-1t_<>4rm%RLx26sW)hkv1fMkIj)O?ll$pWW)UaIwjB@J-Ii&da6)4yfK zL~vf35q`PUwFQXFd$r=F&HJGMx~X=$&-^lioPVz-?MWoLDBp&a4<9CaP4Py{!ht2E zKGW;-vcpb-n^H7$q+vfbG+CGj+9T_$>t1HZVQdysLe-KwXIgx#v+Es?ved{sUs-5u zc3NT)<+`wkgToOKJ_35_^NRTn=VekSr7M|647F0J6HZPq5&^*!sEwWjBWZkobxF^W zP8LOPlqRLw<@K2v;W-r)OtQpn!s(o$f2@SNsTm|XC>fTDD@jX_omt#$OEToq7AKsM z8__1HC@M24mcc3eb;q0;M~-$29%r2sc?PbYjsu=QS>_*XJ!0PldD!Z{Ep0X?_EYzC z{t24D>B*e}FUH;T%T``FmRMx4{LoqF`c1RjL$z6k= z#ByUXh~%zk9XFgAt|zPBU-8B9%IQ>aic9FUd>-5YTwH9g*6&KE1JyvXuD9>{->3s%j z2RbAdkE5Rd15YDNGjDdInc2p6Q&9Wu@__zQ{{#SB-;9ik92u*E+{Lb?b)R`bLAz|* zx{2?n!#Z)&wpjHy_}p)-1bzsw&1IH{w@zJG-NmV7V=2A%eD&F+$HKBnMSEU?_wJ*s zB{S}s4Gc#%Z2&1b(T%mZm7kbWKES&cNW?GxiDAe^xPVEL{AweVt7$DEHJI6MjMD-)+Fao4ZJ5Vz zlvU{;zg-V~bGW*k?+IDlRTj8pJz&wV-X1%^gCG=XZb_%u1%|bDH!#ISBVODs9nemJCEGg((CK~C0)Z@^V!M8 z;<=;VOUe4TdWDIaUFsAJ8%3)XU(k9p@F$c$I>#M+;gwfb~*FmbbOJp__l0q9Zd{uV16w-LrWM& zHhg;g{|s(!d>A@q4|@}QI#D}YXJHd3BS#B+XFJE=ZUHAF6I*9|26}py-q*PY3JFM3DS6l!+F8pe|BgvG8(3Qy3D}xhoBZ!Zl$=d$RR6d2pI1=}YZFF% zCWhY*MSK`uUi|+T4Et}_{{%z#JImR`@i!W417{Oq6C*q0|CTbbHFGw{XJBGvr~kh# z27E>)Mpk!Wd!92)7rwH)akZx&7 z%&AdZy7~Ir{^qhoxJ%Oog zu=IdWn`)=#&+>fN{wLx1r#H$j!SnIi=b4FzMuhW=N63E z!p#_j8X*36%f7~P_hT!Pce1y@$He8Eq1t2AG#3eOKxZpp?zr>`kR>Sa_&J#=K{9^5 zG>1x;w&8PAC(O$lz%MxzqRy31x6ga1VODA~y5s{IVthor1LD#G^aF*UJ$HVz1aP4$ zT`{}CLJ;4>Ui8S+HOJSBr28WipDeq;dR(Ym@J0s!j#n(|Rem6Z;J#N(B4=LjDNyKs z!m7C+|E2JLGV@urCMbzlco^nv(uy}zXx)D8$pOauGI~tu2Y5ns$vcwswUZ3aCILuY zVa)l2I3Cck98yIEp%azt&E!}~&)D-P*i~Q^a6PzO98cpmsra_9l^Pl(ETShRAw z71@w5WS4F||Fa{u|Zym3Z8m#siHQ^w*|z!i-Z zma8(n!W{jaV8F@VtiSP_U2y|#zXbOQ(D(Vz&gmZyCPy}1cJEfFL)Fmye9?*KDTWNG z;n?vwBcr0-=J+YIAkW9H+&MeDmfr#@e>I=z4!Cg2TF;0OA2#axX%Pb<5|HdF`09V2**hCD;a}3UQ+CAJH(#5u^tn0!=_! z#Y=noEy^>>+$D7^z`F&aEGTHoSd>L}=4ZOXXKqzgN_bUdUHb8f=E<3{fE372%%egiNBMqe7%IB4Klsd5f!V|3PQx*t|rrI)rZi~0ocaGcOikc<$5>4~?- zL0jHNW#to1+8j9K{d;+S(>S{h!OLpjepeJ{Fj z%BeXyU3oI?R=HVZ>DbmOmu%b6`K@?^5Ya2oHtzDL2=v1=g&<2aB-ut-qvX9D#>GY% z&KY8Bi?kE5-A^@sj@>95F-OhgOvnKR*DJJOG*QAuSTp5D`@J!bR3u{)&cii5jn7`q4Q8D(27>5w2rR8D;F z&Ll*jP=Z=CZI=7e$x(wj@32f>MNcf^AFu_+5{mIQDsY&(Byq^JM9stt$8K@gfTUqb z-AYwT)l!XWPGcF%wA?b>GFeijU{NPgd9Tc)x5auoP6H_^BP+RP_n3U{%XTLBWZX#mBo%Aa9uOO-l04J+>Q$ui1};=vDb=5qrTC$sY{C&T zwkfvC3W(PWCq$g5rXb3C9VK0*CPlmua2ic-)Inj#&ZF2_ms8mm_;Ol{{wlEDElrf5 zFAl~q0MGAso1xD&cG4Dj4h>~f>JV6vbr0{0Ee3BVSd%FOI&*>-1FF4GkX_z?mcYO- z!IsQTBMiDl0C$;BXmqKl#GNaIRf~yft zEad9p(B|a``-};M8Gu@+-~J^Crav@nn2J2f53c-GvBRR-oM%$bP^@GqHX4J$dv%r| zs3BKSgm?I{^b8&I_a{CqmRIDD2F^5e9^KmPytXAsA(Q0E(U7Tyn#R028_6KGe$+U%wo{?9{ z5|;A1<-kQ6?Z!>e<0pBAsDz{^pPXi&#k1IOOQNqwite#r@NHMg}<`+gGlw9Sgk55zI~QMMNv<)lLE7*uOS6`vUIJ@T}Q8{A(ToJZwe_!p<_5 zER5y*?Ru9d7b4dq;LX=NE|@>wlv<)fO8jmMi||ff!_j#4}Qo4Ce2ZNC1Y^ z_Vta+OI&j?Or^^cc%G?E>$NITkCn5L>|m*;C41|=sc@{)K{&TkZ(zPx3s%A__o_JR zPezhRz?uenXdWSqZl9xfC|Tf(^dp$|T9w&Pe#Yf}8xR|K-qynNi@HpCi34$`Ej(KW zHiB=TZEH|@d0&B02HJGQYkc_KMg1Wdp|k(iG?)wt|P1{uknnhw&v zA_iw)#aWO2j*Ll!%b|=M4dl6R3-fyFHi&h30}X=PKd{FwsZTxr8+~ob!`9A9xtVN_ zHG%3GKZY5IjR#ZD`%<)zO%8y6E-fB!4Bd4+RECY3eYr?JgbJ;io`v$N^!3Dy>0CXp z`3cc_VTO(#qZnY6lrhKCFdd&s1Ab8*#3WlKO5(aEbR@Q&8U~<^N=6@WzuAcXC^&Fn z>*7<7x1j+3G;}T6d0G7U3x^ZX&w%;(D$0j8om_sNZTG(3e(Zl=B0`QonS=VjFePil z+*FvBbLN8OqUv1TizU?=>gT1-+XkJSo5}?ipF%%Dg{!Z+xAQGw-I66D&Fl7dF%Qgi z-Ler1CByEtzmgg-CGuZu1To39)5{jjT*YB7v?T*_nV!76|GJA~u|2743b(9p!aE~= zO0wr}pzcbInppj{I$|t6HImIKK1PXyEdvxtD(Z=lH0-dk%cW4Xf4YLor)K+Ar7}bhKA@{R!1`Lc9zF)aBCY zv5*D&#KQZ<(uND(E?LR%gufkxL6-I9f3bB=(V0YT+Kz2?l8$ZLwr!_l+eyc^ZQHi( zbZjSYY)rm`S!?G1=cHDh*Iv8sdTL+ywE*>i)#z{ZqR;F)p}f!KVJPj=C6bw+TV>{N zOk5`SjjU@=vglU7!%}6&ByVr~8~xYBa{F0aU{vqK^Zc=IgHzthFjr}|H`|O$62y1B z#s$;9IWvX@@NQ^WHNx7^t-+^rRy+QVPp;aUVxWhozZXAHPk;|CtZ)uwD7zp#-T}lg zGOt~=6O~ks(Y5H{8z0-unu3{r^ePdDfOFu z%e2fp+GB{9FUV7Ihf7PuU3MoLZ@Z@AH=Q?d1?a9_0?*|Ec}B6VU&l0~7SmfJn?^Hh z3;QF;-4FoxZSwnxg!Aj{LP9Et%j|aa_%^pGA5!{#xyDT?<+Mf~GZD5*Z-x*6agU9Z zh}P}Rr^q%(<)J85Qybq%%8DjV%$Um^?pg}x+X5Xi>L%hI0$e`dKDOhWX4YOJOERFo zK3Q)?U5WmLRBE&}E2O=}Enx$5M$Lxr*lNgw4U4u3*D-UxV#ln>vP6@Z9@|E%P_vN! z`Ldn{Yrdtn;#)t@&wORqF9Q3Oy-`)FFqzG`_OPo69&)oKs0X18BxI!6a$}#W zDX!69XLs5qZ{bF%oUYIWGhGMGOT6?>nRC?X}iX_%?=A_F?+T}S;o%Ss9%xtl>jm_To_^ppK1Npep zn|5Gwx{MHi>)4D&{-~P5wMIg+fHUra z;Pz$cga#E$ry40dh1CC$Vb}xe>r^rMVUw%@G zF=m8vCwaljJEG$g!ZMgO7aP>V7M%PR!~W$gZvuRH3i;n*Wd1@GG?f8G8cr)4fT>+O^*8`F$ZN=gDC*|TB*=V<$WKPXl`)D^hZfwR*cko z#Y~`&+T5rEpGSbGNLXvf5fYJ{aW(zO$Vr51PW$u2iO+G0$a z*bNsA7KH{4GJST`BfP9F&=g;&6CdBi8?V3}q~qH5r^&cG&Tu!aUw2GVguABT)97CQ zs!Lm&fvpk}J$c(O)ekO)W0v)BfXtispdBdi^B$E6_9-sx2{)Nxf7X}l11}sgI$9Ar zEPxKNpXup?ti3JRj2AWo75f~8FJuIw zNu|3U-aPyG(LN&=LCj?08<3}btS}B>a&`z(rARlQOi>ha7_BQ4L#dI2iau=cF3Xo* zc6oG6w;+XT2<9JAPQJxHzZaSD8_E%HysYM>PaTc^t?20~UxUu!7lIV6XY@pnCQISaW_z zusJ>W4_J42cbJ13;|%0JgNI&Yk~t@}obsU;l~W9$oA(d2x4^j>p-%8q`K6ysw%Etf zWCuLgnu!79e|^)>F>Yn5(Ls&bi^>uBR2j!S*zwdle{DJb*@t1k$)MA%&)qHkg?I;h zF~2n-=R#5FB>O7GbW&p?1K-g5ohn(`lC()!?Ns-Sj6UmE;lYUVVG#y#qBdifrOw&v zz<1=0694_D``kE_WA^2JJKHBURvKqi4oB!?op0x85NX4WOcFkPmJC6gmk(4~hi`yw zV!|BK21-k{ET48ELTrA?0^d5Ydc6p$@!0WrkPt48O!gc<%wj#1Of}FYN^XNE#) z{J4i8x8X2I1Ny*3(ci?d#8ru@QrX7QH>Xx{$#%ep})`AURzdB!2Z5fMA$U93dF=5zV?XRFu} zdaL{i^HHH!=d};6Qabw0WNEH9KSzze!mjME@D*q+UtTL|XCkQNc4>>yK%?%tKb6it z;v#A}3X^V8{9GitK7kHyI2J<%e=5}PCqhpm5EA+`{9mP_wws(+heBrl`c7mp!89iC$T zLPq@bGuS@1xkkvs$ch`*S&CJJ%S;=@TsAI)NEY;m)LK9$Gm1FIheTYrd8+|}=QWWrIC(uV2 z(}y0Lg*coAKa>YQy!Ge7iCbjC8vvCM3zQj6yUxSr=h$fLKwj4AN1U@Hxo2i*))V2V zttOR7zhBdW5oq{yBH0?@+)G9ZTm@DFL&k^`7s+#t+IRld2OtLbL%DI$z92OH2&6)@ zm}&YYPZ%w>2O#EG1MUrtsQSi^VE(uU@!e3%397SGlvv%h8(@b(3jl$jZrEAz?Efsn z^$RJ=ys<5loR-gHC^R(;SCF9%qHOslX%7k!`?EP&Dy>V!_$E4mg z4rpcy*#`8v4Uo0&&oN(&*&VcwY2T)f%QrKH&L-xXua2=c(*WHHWg+XYY9uiGGJt%W zc>jLQa82man)YFtlAs?j!I=JY2LflI)GqoYlz}_JY@*G&tQM@>U$Rlf4zmhcw_-5D ziY>b-qc+ShE!IS&iPb9>l4L_7+2@qgoHcAtk(4WE`>iCAJWhV)cSa*Au(M`ck?@A@1FzdEKlT5;*rYLXQP4D*Ht zlbBFpKn=-ojIJMloPoJSU;qkb@RdA$?|B z_0f=IIEz;UHTCRiW$9&=f6rrV%ZfGe=?r=POua44S_T!Q z#3qT*oD9kf4~L436d|jGACt|*=Y3MOiHaaX#b0Nf<`!%eVUV$Czq?5AWAr-DiNxfi zHJTbw-`c}!6&&~AsgD&CuCWXWw^VyWEY-KSAGlW%DR0Edaf>juE&UgTGr$vTDQbvU z$TohK7RoOFVP1aR%)ZyNseNlOjJ@hbt;Vkrpfu;Y1&JODp7~dniegP>?i0xGvFA(x zQ0Q-uzK!+$A_(3&r+Fx-*of!B0tOscdMKp_f?i+zPRIev4*U& zvPe^* zS$D3xdU-R*&J;2BH0D7@d)DG?O_UERqcL+cH@>qb=2Q>NfZ4!a6<=iA6(L6Xsowyk zj`@VA(|z;MIg1uj)Qo>wGAxx58>nh%b~pIW>R#5r-Eez%vVD5)i8*&(Klu1(_};yL zZG>M9wK-i~7$Yupor1bt#SD~9EHyW2uC0S)Gc{|QgZ|c1Rm;Iuh!&5OS|oGPuK1%B zR|bY60bXt_l%Z0OR`&*3?AMoY^Z9Qa)12fM@ZPzzb!4`6a{HM@GkV!2+#r4H9tel^ z>BvJQNn=F9MiWwGFNB8b@DDY15}J?j4%yyR0!wnZkOV9s8CnV^^vX3Q-=tM$LWGFU zGXaBdnU)Co#BQ`AVJ&Y(PmYKFcYuPVo?f7{b|%>kOnsLS%beC=Fr2MwIqK0oLxDmd zc7~iRnF*0=^17G}d7pgydx@|9D*ox)C;vrB=CkUi_b^x#=S#Kvt5gqkVt7o}49@X! z-kS@o%m>)2$mS&^@qH^|j5V&#@}Hv$Rc&-kkS12yM22uhmIw;1A58nFLP?JfW5VCD z9jA4^?)0%=NnanU$9NOv6K9dK3d@=1V|Y(HF`gqdTl?egwyRggh34AnA@;ho37-y6 zK)Xu*`(nvn0KK@bASk%zbgkX$v4KB@7(JLtUNZ!Po^BHyZ=f@u)DD+GWe8hT?WU5X zclce(l8LuGhma1HnNV9RxP_c>n_i+T^N6ICA1REXD%5aIM8<|-J#5r5%uV>GCIfn) zfDtR%^am#onkZ*XRyt|wbAd@9dSvKFg~QGquZxNuU;Ax-Ie_Ckriz>BjcZFXweT{W zR6Apkq{3fwz^uuE_pH7?f!no+vyT^ru-)J4&9sY{_WucejY;C5vZKn+XKq#`5-ioj%3Xk!x;L4Ar=-Uxuj zB_^CkmEDX?g4}c^PdA*(C&+!ZqByL<^DuR)M)PGUqbLeYb%ZRRsJTuGK_g9v_PP4p z;g5gt;!e!(84IA{m|Xw>h2sOEP(o#lMh`}%%BnYyh5=XW?}%NJ^@!kv;Z67keyKf` zuA}e3Uy03pLimh}Eg0IpXrvS@@bY4m?j~d*MCv+%@d8t(C7@F-8`q0~#fl^G5OhNY z@F1D)QzXuUGjl4a#mR7HBS7|#t(9~H{XIu+tEogvCdT}0P5Cz;N_<`QMp(2b{Zmxs zvfZ>Drp{BsP>aD`T-Us*esI|Un5X{~ZYd~b+g)dr9yDP9+Yh{U`Ubt-rR<7@Qecdue*8B3_U_IR(*gl3^bP_B7tUpV(Ev277mF?6q zT+zqNTKvYs{#vEBMeql)*TIp`EF(u)Pxk|z4_KO#Kqi4vGo>{+%x^Elp9Z>d&Z~~& zX6pvqsF-#=8I3fY#M&FgVp?kr`NS{?#!w?eN)Z!SlzAeU=>}xli}@yN8Hk-nclT{n z7>EEolTo?gtHaIlpybt;f{bOJtPk{UAoo`cLJ(FR_|(pY>E9nv@(w)veuD4;o5=aJ z({kXRtlowqw9OScHmZRDP1Ohs32qG&R(wFiZ(^zu@CCcII=nT zlE%^4{HOi+8+PHF`18p?zyrn0ptm5XsCs{RAgS z?@LKz?J?Z7g)VVM6hc2bH@Z?^GHiR?>p3xTAxdN-a@WYERc%=>Z%gG`_iP?XXGMB} zz&L-Bb|uMy)iq|DvusstIi9CGn&poy=LXCEgu!tEp)ctG=OX^`I?-yV=PHu6(#evUM}G?H1Baqn;JN#`e!11eFt` zDz>5pj6s)w*^W>_<#j`pEF9&77O%y8%$c}9QSRL%`_?Z?j| zB6@p2N23~ds`&}Yd@ZR=GP%Dr^tK21-rJ%DU9+m_>*np_MQ@K1hZgH>a*JBumR$L08 zngT?mi&dMPwf{9q<+0&T(JHH3m@AXZX0li;Hu1m7tH~BBeghX-4sQUurTtYG3$pZg ztadWl0ry@%^5HV03ZnY?IOn?!`ra+jIFFYm<2!te&_`{`GnwgUWU(P*}@3uV5 z;ukKQ1=H}RXGyhfKKTdgR7#)l*VaePCTtGA2VIzS4mIa?-$@st9bKex*Wz)VktTMGmfqLE}UD9SCxA`_>}(pZ!UJ;`2d>lDt;Y7ldoZ_; z4pmHoR?VOe7O`gDaA#~)g>Li4Y`N1YJ^hWp8_@sqSDK_V%(r((`Gvl26dQWGgZ8Rl zDsdTOVvEMaxIa{h6N^V9&lyg~?fxCzJo(x1hbs4%n-~}8Uc@TfYt~77wVY-kkTGpWn{_pXvRsNIt6hPObiL-h5O1%g-+fp~qdv`*I@=>2F}crN~) ze{j@!?sgdvlU#GbblsYSEF)MTN@vTIFs(`+q&jHppz76a&beA-6IqB~T|;cJidu8j zNaH*1u&625Dg(o(I=o0`c#Ck{9}ca2hMt^+My?ykjf;9@AWI|K$%eA&bf9J6VNtdI z*7oZ%-T3g)oR(nf7b#`4q|`1Oj^8!`465^QHdAIYD1)YJ$iU4m%SR&9gzaJYkcJu0WVvL(e4Ai}6tTT!a2B#Wl1Neh9%`>oHr zI6VGGb;=NZN_$k32E%z|Iff(B=TUs_;@WkfIWMwvw;KKHSn7<~d2x>=MdYVV>^GbQ zl6@}`+fmfokR5$!W&^HmjS#%V4rq?VtC|-UgQu#4;frxiM?BU6&fHw_)ml>1N2!qAnF3!@V1Xs!`O1 zAq-JUAW#rUL+=ts4=tDbDgW+z{~;I@g1d6Ituf#}quA~n4Rlwq4g|3ZCI}q97vr-M zLAt{%L1u(jg%uN8CO9#y+%%|N67_Lia!yIwcc9HoZTQD?CqOe8R3i#UiNY8M?3c!j z)FibwO<-M0f(}zma6+91{Aj%}R#yyyFQauXSsl6Ej-o%~T72~3wFJab|KkgHZ$Rnj zPMu2MtCyAqhBA~^?de_Cuy?8Jd8O-Z%$bGAByfEot<5Ldt! zF@vd8HG64Wr9~v#7QD$t-CbIxI@SxkPMPjL&ieL$x*y!lzPV7WqFxi3=5jeI0SdA} zRE=MCx!L2Bapi)Dzr<@bIi$>3n4i^%u-}W?M-Sz^OWS=G!7KC~ z_;89~>*Z-jGK3_dq3q-CwK0KMqQH^Oegq6C_nV%UGNe>Wo6l*zdz5(sdK^)XZxcHK zf@WA;61A6vcV7RakY%N|3$u&iESJP4MIiPSfSDgR;co`no%pZ(S^`&0oQixY7qd1K zkN`OT=fPt4BmQuj%Hm2bczS zok~}0W(2=#jS8Faz&*Plke+85wUClhNQgw+^af?vL z-^fi>2tSpmTBb_;N%y}UI~w>Y-C;8{^gP=-Xk5_3%`^!+_`yU_e( zHBnNG0K-0r!)Bcw&(tM@@ z2n2_c*AIrdYu1yM*pm~9M6cvtr}l}JjTs^iczK#|wAB6Ew(BpO)u5w#T4WY8JAJwF zeTNus2iQ*4)7fy?4TMi8=SS-3Wq$5VEbZ~}7m@oiH+4POT$ctFKJ>^L97v1Fh!3Xt zP}U~ft$`-;l)%@u)6-x2%_}IsE=RHRoY8y6dzc+N|GAH>cmoLnb`96_E1b*#9a%>P z3W@}io{%*0unoh}&__fD`EMi$ForWDhDktyMBG2rKhv8wCC8M4f&K#W;`9tPYh3N= z7SYn!>1vrKTIfVU0>Xn$cM)O+U~3MpF}g&o#FF8I?D?);3#2C z3Mw*4VuVg->M*yB^^T3jbj?`T7-(iZ6`gdEv@~{?czRqI`QiLQ^Q3WWdLIpOLGf!^0*6wNuO-z ztF&Sa0b7`0pzl=hYv4Y)axS5g{=K=`MeN}6*Y?bmPaYaTSI%aw+03Gx?^}3()Qx)) zI5X_{m|%HuIx^$y^_m_+E}pi^br`0)Tkw3e6lA@0X9N4Fp#H0&V&8+jHg@oO%#qb<-?6#42=kg?T`;nx!$i zj5}B`$xFW_aY4Vy-w4OvSocZlkBEL^6zEO8SaGy7;54ML`lY|)JiAYAo3^psNVjcQ zLL*}Jn3<(P2+SO&4bhsVP{62aYQu7^D@&X1+dkv8ZI7tj24Y$u->ya%PaDU_!gSEYCymuS@~GF0f~!uAHAcc#)b;vv(e4SIZ!)#4@OEQXEWapZoXllsOVdBh~{tmGQP z*&Y=RbSv!szl**1K(iRlY0RSvwT3SW+O8>aP+X^BYEgA$@4@eVD%tM0CoKMn*|rG-h<9Vii!7UPc0`k3=YOS!$&;`aQ z(GeeI8FmyITvV|}!tkMn=Q?;v1#CTbFD-}Uj1}c_zGhZ5n|L1~2hlL%cvhc-hY&`` z5$|2B>BJ$}cA;YlX*Opnkxlt>c>tQM?1EIah`0Drgj#!nDjo=yFhh-8nYzk)^|c~+ z37Lv)F%_b~{V4F*F-Y9e!f1HTh*_S^6MdGSE`XCIF!8zLd^q<+ZV(fT)V5W}lNjyQ zWRMv-E$UTDS)T9JGoMn_V?ZduOWxJ{B_fctcNlTVS zLOQGnA-Anwl{KHLEe-hBmLtDD!1p&&jjv}|rycwOENmRYms{PM69r#dpejh+Dt+H= zCXNoYe?vLWVJ`^9D#k7@_)Ry20`f$^pF8kn4+soJp8x;2@WBfJ6|7d%<#~&AK@oc! z6|zU$v}!3dGelkZpb+>h-n(i*<6`;Cood1Jb_4n7xBYqpUh%ox`O?XN3avMLL4LHN zr5W5=hxK8A`fbQ;9c+jh0J&FLXXX2aLCv-oQQPN=IbEIheQ?G1in-ZCcy#aAc?ARQ z_iMcN(NO;GI~>=3&0{M?@Qy4jyD@ixy_%1AIafM%+Dr-N|?Q+OU;!s zdi}4%W-+ElmfK6b1>UxHnumj<%jLk#D=5iD_*a!bf(8O(8>GcX$l+C=SEd2^0_Wd* zL6QaxCW&W)g&y>?tKm}N8>AVC`2E?ZnAv;ncJsN!8#nV{S_6TRt2V|r2AYyjiKJVGMLMqatV^P#6Lb zWFRJwYC2kOLde~O5$QUuP3Vuv96iW5i*6#Bi9Uv#?{M1HGk72YEDlJDq-Mt~Ziku* z_EVWBtSpb>WU>%N)Qej%hZdH4%be~L?_+U2Opbgm5LLVzyfDvor09~qyG@iI{3(?|K5WDjX5uK>%1ZSPJ)M6 zEGAj*5bM@bwe8zH!b_uIQHdg@yr@iq2xJf@t4%sXPb<1^23^Z5A4XIZ>Csxrs^Ge_N>a*K1|V)C>jjL@zTA7bn^8_Cc@|^Djt!Q|RIBb?txzpNxHMxZTYW2| z`kAiDv+%FBwHem}*RDa<(cJ;vwZZ}&EdVJs!S4iYKhP#8Kr-F1?(A)UY|cWzJuC0= zEf%U(XGA8N92qzk_SpK)K4^k~L5>sP9{PjSp^XW0 z&H?e3LDJ^oItSp@;aP(W8gOHRcJ}+)BSi(q_Cw!@+v9md*92%ApvVM<8bI)X*AhSx z3BrZJY6>F6Q6GZ83L8G1*pVfFbUDl!IVLshlb?i&e@(m&ncdQIU{s{ z`GCs`BIW_|QW-!N4e_iYJNtX;Fk=FgcX8aItNvQ;lC`1gK_d1S-tfIJc!TgE>IahV zh2GGBAYl)P;~*0fiIGTfDTO5wdKRIdi9y6Gn(#ZqaYpJ0lEsUU!6t<{D#D=`?3trv zL{7|OH=$pNhR*Ar8a}Xif@cev&8>n#^r!5KLJ>EK} zOOc?wiU2bPrHRlGs3B#BcMnw$fesxENe^`nX`+Oih~5#2#w|!PB?K&qt_!V;zePEx zv&eeNmBizYeI0^15^73c6Cfr$$MKWulkE`q7SvJHBr!;Fi3(a2Jt&S)j8gofTuf3| zVN(@|&O271F1uPHJoi1vI=4AT#g0A}fh{&&SZX?IB3uWU=Ps6*)1TsWkn0N37yLfE zIK^LxUF|ioZ zlTMeN5&oSFJ;4}IPe%1-gpz_84Civ$1E)LEXjS5~ciXQfjoWz)=v7z~S8UA2GkTkWLJxv2u`_)v{Y#Pzp zTlHeqyw%Lr7uBtrx%Iv#gN@Lw@f*$?I8E&qup6H1fBvy;_!@ZiyROV^)6L77wGLk= zZ4rC*_%!(hfBJuxJ|W_?A;cqCh60Dq;(SG<8#8L4vjt_3{K2uoO~i?g;l?4y(dUHD za?Iw+Lg3hPDB+6Cp3H*GlINOj<7g{tZ)&@AX+M*4K<`ZHv~}gZc>C$SP64z`*lb?) z^eGXbvp_$=*mg2+7H(Q?if`3=BJmF4CFA`-T@OLLfjpDE;ym=8{d)a+mbKM-sa??9 z`P*6mMZj;N4hAs|@=fZjyS?|KH|LkAcb+GgTiS=MU9XkrX8kz*)As&a2HuVn=3PKjPCfbLCxnG9;h9Px-m-7wugJg-nx-6EAd# zbi`#;cF1Yp=^*r=SI%Baf%H-~O%hJZT8<}2L_$h(J`-nT&6W+<#Nx*Eb&S9tdzk+-474Y-F#KA=a zT?5&{jgbex?6i1v(NvaHoqfU$BnK0mq^ncaXg`b6G-;^$RD>wT>HjI;1=8rUQDy!t z!5(!k;?yzrNqnjTbkGRM2`JOvJ3UPiO}~FWszE=_TR{(%SA$pLW)a@1uNI`*c1={Z#_~U6xdl6#u*-Diyhgiw z{K~(gu1Atb$~(q7;yX?rJsv}`*WlMfV54)Qt*`;Cq%5B$ST-r_xz-NW7Ix`c7>(BA z)Jt0Ho(UpvN7-)bWHp-`rYpsOj(RHZ z<)`IS%g6I@R$!}^Dvzz|)31WjTC=*7P(2U8FDp^2(3P*Y8tW|)efyo3$Dzmd$5nO_ zo3-WHYrFH_PDY!9Jz(oRuibaq;mjF$2n5?5nJw#09?qmUd7vU-?XR@9bdvPA^p6bl zjO9$B%-SrRtnjSoY?JJT9D$sgT-@Bq+_yZ7ytRDs{FVZ;f|NptLf69MBDJE?VvgeS z66})5lFw4x(%mwpvY~SJ^70Ctir7k!N|(yBDxIpiYLV);8rqutT8!GrI-ok2y7PL2 z`jrOhhJi+o#+oM5rmSYv=7<*H7LS&jR`b@~HqEyAcB%G(4(^WTPP)$0F2b(NZuIWh z9+;k>p6_1I-p4-szRP~I{(}MifsH|p!KER^p_yUn;jt01k-<^H(cUrMv958h@%9Oh ziPlNB$(AYBspe_c>E;=>nU-1h*|s^(xsG|B`JM&-h5kj6#gQe+rO9Qv<@puWm9MGFgD$D3!v)zE{AZswN1UL#icM>5o zWGEtSY={JC4JZ@f?0msL4RK)5(3Rn081cn}M0bRysKx$Z(0|6lG;hH_QTS10-3GR7cOfFMz#R7L_MUmt>jPlY9}2@C~z*A{qqF!kfn{ z*LFlp@GA|(-g~ZO5j197f1B?M&alnXvpv z6Caz)11cKe#g&}prW?L%N<2qoW;Udn#}Za$=qo9qaC<;_rf!L4UHDO6oys=i%SZw* znCNfa{&5K>ADc?sX75wC*!J85!@EaP-C}%2xs@kZZ;$Y?>_P3vM@N^9TGE8TAEe(~ zp)r(A>C1-yLG?6bK54`n)qIwm>i2an0K11dHp>5qtHy9F?KGsgQ;0rdnaBzz%2LJ1 z;}*V^vS9oT^?6@wp50fK{~53)*XYjeaYC*frK$Odptsn(Wo7%awJ4F&EywC&5UR68i0w2Y{9a7?W^oSYSc2sT zv|qpE!Wph&wOHj@g|^Si2NdocOqrP5CNA$Bp4$e~K z$Q)`xX&FZ5p|gnn5FcD|*`aT(DZp$@)JwF(X+VdBu4O0AT?j(x7e^(znqaS!?TZ4~zzePx6 zn*f!lB2A!}9Jcr@<5d39gSKTJ?$2f-w*cND^qqgWsOD) zM?So|?}bI*DVcgn&Ris~=A;&RBo)GY*R`*pg_ii9Ra2v=ul#!{`@GY#l6L(|2#hG^ zv+YQ2qMDg7C?#km$Tc)$>i%@h?R1yUSrozy-ym)3xjTgX+QwUAev=!rSb;2he@3Qg zoF&?_J&}IFa~)&G=NIlSosZBe#|h>W@=!*Td{sgiFY(8+aoBn%4xt>b(m}PAWHMX~r%p!Im?S$vdX%75gs0lu zB)VQ<9cew?;7p_W}I;ahjDy&+CY&7SwU>rD+Yse>&UoJRAu`Sj-^uWMX9 zbV27-A(0bxmuw^p6N9luUV7Y#bIvidcIrl z9*A(s?NgGw<5$G$ybaMMn`->uxbu0ZkBs=bO4^%iCfY~be3?rBm9Y>;{|0)1mB ztfsE|TV;gP^a z%?U%7s-&taY**Dwyl7(l8vZoRhn}JLkNfsui_ZxNW;?YG?rodp1P&I0k9xY4G8Q#n z!2XbZUIoYwDvy}nwCb|Tw8x{U~W7ipx`>uE4iv;j0~J(ur=)3nvG9L_+>#3 z%t^7#3{^VGf>!l507Ip~8T`=NVbxJeJ@Hb7vMKzS>|^`dPazp-rB?kHyU=sBzCQfq z_Myho5uFII&Buq=W%VEAoA&KdNoZCgmzv(CS;l|Tu2WOEIyFx{WwS;gTM<1rH~j4s z=xuu!Ep%@d{;)@+GaMgz*)3v6a05a*Qzy-!dPzAS5)b`0)qU)LQUX7q`+b`Z%9M}> zok_yz1q~!X=7lODSpZ5>P`F{7YypMOvYa|us<|bwL52hx_N=MJkcdE5iH0SA5W-lN zsRe{N!$>@x3%(`U_a#2vlB=4_T%5tF*l&bg*SI|?9Q&82w6<0^7{p+Enl1-?3 z8@}c0M3WwewJ5L9RZ?cvjLh3avYQezSCNq!oQ)IgZeBbPRgsELNWs5XMc4SI7Aq;; zF({S0=e;I9(_-J4T3h%xe)lWkkmO!Wlod0|s;;bw2^%+EB~+o2Ovf0NC`U^8%))~a zhKRXzZ`C44bE-@t8hp2b$JYO)QKcnqiD8*eeK)p{*-4HMp~IYopO}_k3w>+JLX6F< z=#{BJ_nh-l$9c4Zirk)S`uelH{sR>KW|_^DGC`;q{R&G;*TGAmoVpy&<@E+vK^>c` zn#xjdIU?ezybRW5@1B&PnaWCYTnHOhrN|(@RJGPRcRjc^KCB8|iSURv8%8pFb)K@R zSh?AmPrOu!oJLpR{3Uiat!qKS=wU3WYV5EQPG4>ka#b-Wwk8WZ>pC%Gh@aSUjmp<- zEv&qDK6O~`4%CV#@l4wjD>zB3LPgu+mvE*GutNf#z=qnGn}26J{W@d-Kj@@ClR-kY zHmo&>l!n*_-E}0R>kUVOn^LhG%vNn`I%paoZ(H~oNLSu*qfyTZFo4$x!D1Z_p8U5xtRr_`bBS0tMb0I zU;+5!HrOmgw0v0@cW{Nu2Zihf8ul* zEP>*HD*_olpKQ|gjz@d>so{!c2}uq6d?4R-txsMNR5LIO-xdtjfLM{bWPuz)wp^fF zG+Oc*?0NEq{hOGd;bTeyuJXYHps zXpFd}9q4)Fu8uRJn;qtymM&WdP6-aB3}(62rQh%6VHa`T5Dq!8j?!$LFuVsF%f5ke z8s0YqHUAmM0k#ssa;;M5zI-8?**L`9R_G(hf6m@!}IjzHM ziZ1_b+KzPNe7chxlW%K_-#b&iUY<3`wz)B*=Y`Jo6@I8_jnETa-I#K7R+_o6IWKJB z3EEPa(QuiH8tU*<|JKaYTiUSy(~=H2?=A^{;chZphR=xR(Bv(q?x z02?<$(uKJG1*}y8X&%oJ_<4JfB~B-RzqFHg((JiT?m4|fjT}+dt#WX?NaoESz1O}J zsWQEL+s}=%PeFOSS$|lET5EgEyc1JSX#o2A91-1W;|=B=#_?8fG+iGyYv#ef8wFum z<<43=ba2nZ3%(5)Dy5bita-sXijiiY>;ymg`72*%oZINjhyNONz??A4$!C`JWGi=y zud0na>W$DVlkBRuV_AsA)?;zPH)D9|)^36QdhAd*$dez}de{E^-Y%KkZF_lU>HzH|zatS-JU%Ya4cxJ#%E@ohWs}@ZF<) zFneN(t2;|8%2$ivFNf9aoZ2?E9^}TL7r-Y@%wDHf@Gal5+CfeEeiJf zH<^*XdrKT7V<&=*d)I$eb{0@^EZf2k?ry=t5Znd^9o*d|xclHTxF!S%ZovW}cz{44 zxVr}r?(P-{PWUG|=iYm+z4!ixW_4HZU9xLe)v&5(f7WvhVjB)Y6iKb|4`;+{*XAas zN?oc4TYkCOf%z!K6#U-~->6mLNsZj>A@+KsKfH3lxK&QP@<3RMxi9IO(+cMbx4hTM z(5d15v}2Ze5KR0sZhqHV7@jI@YhDNaAi8Ypeq0|S&=)Zt?4GtlfJI1D1$m2C=)1e` zk(rd{B?^tB4yuZ8x*H3M3~(aYUPckV8s4hIY{LVa2*D|Uo`0tln?MdMG7UVUw3|jR z#&-1&uY(JmRZOpEC+uEkYxl>{2>jLyhoP}kr7J(&Gcm~jo}ORgmgbmYm$%U z|9ttqy5_=h>-Ah2MUH;~4%mFdMhnnD$=FMoop%Vo9ZBI!JR9CW=(!${FTWQLUo!1uLRu z_ByO!JthQXBh9WXwWsC>J*otcZ89Eo=Wp70a6MQ*6tWPf;k`K{>^ zr&59}{SkVw^u^C4JZY*3V(~l^9MRXcQJqQcHvY_IX!*Q4)2unTktA$z{hH%r{zA2+ z1A)0qui9LFk~PP-;tCJ^EAbIH*Bkf|)Zm4|pLq}-$!*{V&s9IHh?$)+W|>zeBwtKw zB(+>4`g(#a54dH=M@UxXmp9I459_{8nD-p$8L&x&1?wZB=cCX*(5;uOZ(8}2H@X?ZwA((-2m^auD zpU;1^i8>{pS76mtTG%c>+@_ii%#O^wF#U8*memS7B+c{hH zuGjVH9e)}s9yiKqAw|!LZQ#)NJH1w_$DY7B6j|44*ZV6WzeS^9msGhVIbdCC9fDi( zDiJ1b#v+{;a{WA_9EgNQKA6!Ndsfl3kgIdOU+WVtvW=Q+q1n?u*F%maLx*ND8+-T@c6Ur|p+l{U}i-SL9M&bDwxqql^E^eMrl2q_E za1<@K8=uRlcDn32ecHvcGRc{vncv#wvgl;^z{y$nUCNCNSZrLdL#U$NP*T;R zRTn<;$@Iqy*X~O}XZ10kuve!hPjvYnJ+ORB8AFaOWFxM__kubfk^SqAn!h@N<^-nB zg>D*%?%W$hyVC}IPSbWpU%ax5$GGj#;d-pO4wG64TG#dH@hUIl3Fz{KQ<_ImDNY@q zX(zn~UYGy0)~S~oC~}QQMlX(k`vI>y#CQzlaFdg!sRND!b2*icnoiW`K0ytApR^d+ z^AKI3Zqj9;m3HYRdpFj@jYnAQC(wj886;cJ?ls8_sqfF{k~=9VJUKSjD6*YC3eQ^5 z!ACDG?^DxmNQ(As!87#L+s+#Z2h5jFPW}30C}ADa9N35V%p-L!tph8x zBtap};%v4k0j5u*5m15IV(a1YXH ztBH1y3;J$*to`X0s=q&GGdyq-9~WM^eZWLHH=VF8?P8{QO(^i?W5##T$DJGJ{J_vQ z+8@q0pYsDr=P`tPO%!}zppQ!MuaWUplQ-gTab2?`54uf3!QnU--dg<%Y8YhVg1bnb zB3~5~4Wsn#Ls4jR{g2^q?=A)D^6bZ zAHQ)9{@LKYLgkC2@PSD5=-B}^Q?t;LEUWn_C4>d6%d5a@ z`dtaCC{S*X_ot|{c^pY&M95xf{3Z)1QS^wY(egv?^eM?_%wz1&+j;dagh5@5El3X6 zAu?xAV{#MKwN_t!LOL=%hu&{w$ufx*vZ2S8c8Rz+-QbzW;2sigkex?-RYxT<&(ZRw`F2tgP ze#NLk-5|{?dS~Kdrpt%?$4s)oh=90eULg+$q#Hf{3pWM1AX1-933fW1AZ&rO3x{s2 z^8BC#-;`lI9jp>Q;UMjm^L+5vHt&(*po;ZPuV8{>-_)47GnktC$i6=JDGdLkyIR=~ z2Mrxky`gTNkbQh8wP_mB?x94SL-jWn7XMu4p4w1ieig7jZ0B=S;8m=2ta|O&Xl4oXb2%$2Wrp5R(dN)*dlEmPaRFF(o zYiC6PWalJbS&d4`{LTHqAS|LXQ&JW^)YxTLPDlmG$Il+a&fVTk+aKsgHEuT8ew4Qg zY?2ejTNulItY$1n2~7Y-_FbGfoz~P~R^rUwjrZd=0kxW~~l2b`mOblLqh*^~C0S=`?TU4_37zLqf7CVo5A{QVPax{by2&!XzYF&C+C z2QvMs=qy#9uXx#v=<>S_4q2(Zm$Bf4#a1HV_l;3H^NwHt(ND-5C&by)wLOp=Q>V#1 zoZLmg6mF%6-juZga{Kbx8g@QsICYIHZxjp0((0?LmTpqWqbwK)j^&6}c^SMccf7p0 zt?QP4sj72N`L51^ps`9ZF4a@|f~^40B*s673(av~ebY!50W zQ8RU$=Pz4q0${$y>(vctURtg7(zg?9kd18~22IbwwKWe;SR z+gJQPw>kr!u#f>F=rIA*S2emI+J8CqW;WzYA$eccTpO0UqTj$vL03&W$8JX(3(QAh zYXB8tKEYF6Qn1)d3^a4p;rL!!L`+(+7>W&3_ zf>G!5w~`qMpM1wHJk)NjXMH?|>P!wE3o$rcg4drAj+iFF#V8$v|HBU8uOQa{mx$H8 zJba+Py?q9P1<{6utp1lBz(pN2ElnHZK)sjw4$5i^IoAl%NQu|*$W~QhxTpNM9B5C= zJd`>^gob675%NFAe?--1^XPaRIS*Sr6kXHb7l+99g`)SA&DhH-hIJ|`qQXTtTY zcZc(LmcqprTzGHc6Z~vMI=&8Da!#<%Fs&s({6QQM4J4kmL~oe=W9POQ{0cMNn|2V9 zp}B{Z;T2-$%kNF}7(Ar_N^*>?KGe=b&V}e(YFEj1T+1}kR~&}K53GVoJ;g6?r*Lr& zu@^;V?f}&!vo8UxyCh~8ix&iV(5@ofy#_=awbWIS8JbT}|2-PjF!HK-x_aayAQvfS zG083ROp90Oz#3o|3S`e5GUYPNOULxAAybx>MzFr;LrT7;aTWF=GZ&NV4okzM$XR`= zxWec?_iVvN@2K-&xVe1GbtlO_yJrALr>S{G(&(!USR!3Q+^1_mn$He9h)%H4NnUSq zj9z)0;o}ZQIVNxZ=Dn5xVL)slso9E!a?W7MrXW%J5*#dTF+$X)`MzX7_)3jxzy4t3aeG1C`j`-vP!%$!A2F8 z|2(hNu(E~PqjR{aQv64{{WfwrUn;MPoH0+Hnc}xOAkNud*sg~XvC_Vd#w#SBp6hax zc2d86Yk8aTIY&y5P@~N9w`UC<3!SotH*&$Lq6BP~{z6t|IRkZt`4%g1YpqG?_STK$ z$+Elwf#TX<(HakNs}%Wps)Ui*NSKd>$&zo&7A8DIte3dnJG{`m31e}%yRG2AEkJQT zb)Rt(&Ty2AFll(sXzrnQlUt;Bd?Et=LAdSAMWL=Fy2-LqLqg~UFPH>9A9|NK$SiS; zUiA1#=b8ltlTFxzSFt#$b|EnjlLsEVhcY~z-s zgoc|jzCU-bH93O zFaS?!5%16|*<7R}kmBd%x5f#>bieDaIChgIO&<%_VdsSbiw)rtPh(yG4re`rgGO07 z`YXXHn}O`?%w(vw(soGuwq%!i4>c^@K=}j2@cWfx%I5t+%&zrGV#<2$Y{QhQc9?wE z!~16h%MI1%)?th9X^DrWjh-2-o|apKNR!{|Vaam<15%QZIpr5L^i0cFnznM*vw1qF@p`}<^ykk~|-oZYD8 z{GJFoy=qop^nG*xQz`~(WeEbmx9b?_=(IO#39lpZ^M*O`Jx$5w)p%vsDtv$zj@b6N$u3oO23oaaZ84>lFiu5rpRsL5<(;B;6wFP69M=U;>>2ySvk3!Y}vwP zCCfdgPHUtaJPnb3@dJ0Hub44f!M78ynu7V{RWnTY)Okz4q5RkvM(5*sZ&j&Rl@Iq( zRJaTO_QyLNu~=T&xVcu3pZ3#ha3MOS{XM5rtIdLwJ*EIGqM|Uz;oCd5Hpx*o>F?aj z1O~cJQI-+bic;UExC+|_8!P!1n43QwzJX)uk*bKbRhEqsm7 zKa93 zy5%v~d_<7#DG1-BAPSxUbsW^HP-V_Hvd|xttWHAuh;=oPk0QK6LYwD$8eTVocl>hC zV}pmK@RC*Ae5>YI>5IrbQpWK^j#$NaQmzSK{5;CfU8ue-0x^856#Z^Q9`hI~;pUo# zby|vxm7iWg8ohJa@e#M6?f$my5Vs{@#bEutF8s_GCdWTb4e^GLwO$~=u(PD z0@uQWflG+?T@IB7PM#l?#tOKZ;1fn#p8%h%jL1lUeT%aeYSd^vLFr2)%_lXR!MOO! zfQbX16KzX8gBB}ZL2Z}TL^bUPGS~2?mHh+x6JO@AtHzk#iBE6MF}M+2pZMYmo-PVy z=J&g@Z#ZB-dwmIK5?hQa)t^ga_Vh=ESpq{W;5o(x#^FF87>S<7LUjre@iW zj!8*slhu&kruha{2_l@C(DGJik7d$>Hy@knVm;4~GPlrK-besR>|O0fF^ez z#R@5NL05TWR^vGV79du`P%CO$U3?^t# z(7Ynxu^*JQebP&l+m&0^*(q6Y5NF5o=CwhPS_ZQ+(GQ&2O$pwXWCBVFmv)K#s28C- zm9Mb59^wvYZ2!*Bb{lo;V+_2Dh;b7J?k z>?XzChWsv(k5Kx|a;?`>*g>}!!t*MQMR%=I+0(NH@A|Tsl7wPE-UtjZK~&G?lkBB{ zVUiLVDSEAjrb!Kj`+H-gXl4Aew?+CpK=%Pr46g3T#)ipIR)ljom-n}wJxk2n@{;EQ zH5M0XnWD}ttyLRlMNf7{g(5)@l@ZdEr8o*1nX9%{HWt$d7s``tp9F@imFk3V4%i{q zU$~txr&kkDjmWM2 zEgklsI&!yQ74~~6E30z%^md1K)0bo4C}*dHDbiHrkAHBr+BsI*DodiF+SZONjZ$); zN}k2?LdSyii`1=o&vV{oenM#=*;&k}J1eb`kqrV0d^NtAB9>l$)3Ug{f9hT#Z6r~* zGkDY$H%jGw=A^wk{lWx$S=c&sL{%~dpR4aym|GQxn@b@gZ!y}uuA$c>DB*5i-#dEj zhOuABZs2uRkkKtORV>)H#25F*iT^479di9PCW4ZOF!gHJmzJb0V^{*6cU%iB@ z+OENpPb3Sev7$kY7nt{QPbgbOKJDy7$W7u0>UOYqcT#t-D zRFa`uytZ}w@#o!JvQDOlSK8s>c_xBvDx>##nli`rvXTh5h$Y$3?r_)n$A=K?4vv7w zu+%+>sIaBR{dD|bp>lfTrb%Zt3%l3qRKR+P$3iFxvfz)!TjZ%*EuV=-xyOFUrTm%b za#L;Ng4zn(=;s2a0%O$k>KDR_P8eB*Rm@T%u#iLk^vwb<)SUDPSJbl@Dblmt0`^9V z;xu0L+-^NYYw~7OWr_GajNK|;(e9NQC2`#oIGYeC4SH?P6L0wcubq$oXM2eTQ50d$o_L*rb9P-43v^=PrUn1s>toN}+9&%$q* zAsmLLXI83V9lMz5;DmMPrb*|DBZlE3`^k~lzalR;Ll}ui16}*U#kK*sPO;!XtX)f9 zV$|O{vsW27I*H0YAQ@dqToY4poQD9=RN9r7->+aI$D^R+Q0%Wgt9)LNA0>=m5=y`3 z{Lu=HvjREj6aLEzECrWk@p`f0*}x86Is8EOOa#s9R(SQ9F1^JA{sdeTe5oPb-0iox zbXou$gfZ*tXFb^C-eRm2?dfc%85ffBdHA_NfqU*7%P>Ph|3HqD+ZsKWy{PVmGN(Azs}g z3|goW*qci_ykR+`=eH!_7c-uCb0H8gj0_UyeREU!B<1V8ULElB<+n|HAcnC7yqkQy zJIHqe&K3B&pn^>6l!;c7SwA%VJuEjZ`vGKpF>M^i6?pKWWfH)_ zfK6}OTvD9vh(3%Kve$1#G1be$co^UGFUd$%V_X8f^e zdc!Tr#S&*zlPZhQbHYB@xytCh3XEhJpnYqZ1i#r3sg;*W?--#Gf*MdvWu&qJ;apNU zhfu@5Cq7Ah@w#1V_YL0H^oC05ui0!il@26#nE@S_73SJqDpkm_ZdG7*VAw+YmfKx@ zo*~W5*P1hp<*-qS;?2?Nmi`2MC7z1`yBvqTK=BpZ9&F>R4x7KaoHdtGT!(+V@l$i8rE$)`bpK?@*Ne z3Dr5+@iEbXRDoMuE)dFfbKr%hr1<*pNa^`Wn`FuM`^4!S_f%Z);=uln2K0~3Bep_&`CsnV$a(h1Z z>HDko*ZGo@A}aH9ZC5u}A}8mI=dE5Rek)yk!*Jf+FV@bJyRZ5|0j3`w?blkD-yfG} z3D$afI@qs}*-h#jym!V%uKoD#A@Zc5PK^_) zrUzr$m}v&OxQ92m+ma_F@++Bi2J=TU{grTLNa?#TtwpiIasCBGt&5r_NAj5pPdjsX zd(?<-U6o!$Zm!y*-<*S{w04D~R&35B#_&tjPVdGgK|-3K(8Ox=j=-dCOn&x)Jyx4-5+*`yA3&bdW zs>=K{<{gh_@>g@Y&?U5{(yhC9T3CfJVvlX8z(*g{t)GIzqu?2A!8-hGUMcVo7U z4VB6JMF_@IU%PpU_5pQ|n{SbgAiBt>h``)J)#AW`%!|O8};stbU#`!ta;<{zlWFy_mTHC74C5 z3hT4z9c*U3sVJ<+n$@_hQ&d?mEToK+1GKbal(_WL5iI% zHWuJuS7}yyd`Z5xE|kns>FN zVVGcTX*}K&_)f&=$W*hsS%j|pgR!VukQAz;a~@mpLz_XNv%7Es{{9u9Rjisi5fv#x zIxV6lf(jth@a&pro#HkuT{Q3BM-H@niSn_EGI&_dsME-n&`GjtH`Htm`Qt|n^ZTP( z1vvm!MOK(V@td3EpRc|fgMtHohVeJUi?0bl^PICCi5JuEoT@z*^i9=-qR*T{$op>I zUX$`iLgU!N1Q-K=PabV3{4meKZ=SB7guHBJM>?8+HB^d>vl#(jU@; zMUcsMF&A0`ul^cIopvGqC&HPU$P=~+qlk@b_A<3VK?<{)F3P}0!^x!GAjA!+>y`F# zw;0me3SG|jqE4)G*{}(pW-saHVD~^W3Eg+j3SI+o720SRB!Cbo6+-Mx%&x@6+j09Q zg+o-7`6R{@ysIv7$YzMj#ZqKt@9ezT0`J#J3I4~Ht$VgepIQqe8kV2y&upcsV!XuN zuuO@r&YE4R+T2+cfVrct7biOcr-HlOS3_1}$zE@l*NhewhX}PcgKX6==t#^oEotiO zt?!W<0AF$WL@3z}j%La{+wB(ghH6IZ?);^l4({)wtB3h&0CTy5PCT~)8Ah!xTuBE% zTAk~zos6AMmn8hh7We$PU?=5xEchko!Hj=H58tHFc~&}~^$5Drs^TMBc) zp)ZUWoD3m`I;>0IcrH(8-l)h(y{viFBgjAf1%+}2ks^WrzA=yd8cd>!J>kS=oUCvM zNhL0t=+Du58P88plk8U|XUh75maZ`4#}^|%I8>(L_g5*!kVLQ%MYy+i4o;F?!t9x} zl_m;4WBd408R9OSR9$6J*6e|7^k=FiF9oiK^{7HKC zcXic1g~JB=sst^#s)f5-^hutah2Ja_SrY7TnLM%ON_%OaE01QSeIMh}lV;kT8AN$) zDj+D*V{4sgtjS=ZrDLH$?Y2+4t@)Kg-2w@06AJA z!JBRozB=)+El!tKkJpkVDlN@Vrkmedr6R(eb)6zAZHz!$%0hr&Zk?w16=n#%lL&E3 zC;uV%1?2Mz1ynHZhr#*u&QUo>o%M!t^bVyQImS+Ss-^IgzePHq^GCr=VW>gUM`y z0@rVIuU(%V<~nBXH1wj&KV=vL)It5KU!MnhuWYz<-f%RJ-heR?*80rCVjX|#2RS|= zH(WTCTO*;`8#gh z3-V%uso{<+K~KjMsCzam>;>tG3BMDdYl2myMMe=}Jn<)wb1Pk#YgIbrD%X6{zM?qOR({{~O}mHroe z>ucd~W{z<96h8df$C81d8xz$gxb!CEn~tGZcQxj~$40F3gM zFsLg8>ca%9f{TlTmE$kQ>mQ6%b}5}-6xUzyR~;DK6$B%@8UVO?_yI<+pTFW*xw!tl zUDDoASxxA#s0y&gi36<79Nd3jDg!ux9KTworNQ|t;DWW8hXeGVa{J3p8B7%>^G{Fz z$%HB%j_$BJ!HBc4Q{!K2_3xEZGqbUBhn1}evHYd?iz545i(S&$1J-QNKb6G&SA9ts zOBV`pc9QzlB1S0zP7V%k*ybDujC9M(1!CjiW#HgoV8UWocDDTAMAY1zEj=t?#9Bsi z3pZzTGblg`);SOd2UrphkORmAFl5w$z^J`m?uJaTEX6&bwlF#`qmqZ2lM_I~%+1Nl z9hUsNM!(Y4G=Bqpd4QG<4ghT{H+NVyVHt4(Ibl*-5GV|-%cu;273J=14F%{pyIBI* z08-8t9>2Ppm+7w^$@7mU|A`1zwt|{jnnBF~|L{;r7(jIk1~g{i=>q;M9$aEO4C*g5Qz>VD1C8au;TRWyHZC zDk9`&X)T~3B?ItwbZ~MPfZYi**g~N$0_^NwUS2>iE}*lU4g0S+`Rp8=?3|ozFbOty zA1A1pH=C0?4a1*$5Z%A4Ik^MPe)&Am!r74>qRS2faQegN0n;=X?;IFPD%<#)`*gza?oE%ypJ^?OX0WOd}1K^k3pRV&i ziTurVgc<&u>-^1gnEuG~58n}H_^a>y@#IhU5oY-B?(>J>Zx0e?`0pOXz%GLRyWjqQ z?dfgi@<(_4*=w--|7_2{2%5S4VNa`HqwK$#3MT){nhEXN@7-;F55{>$?nAR>QU{LYE}FWLWo z{1*fN-%ak~;pXsr)L2@uTm3P=yTi;5`nwxjTKwJW|AVZhg@DDcIndedU%d(T1h#-U zzAPFht4>;C}^n>>90 literal 0 HcmV?d00001 diff --git a/tests/unit/data_loaders/test_transform.py b/tests/unit/data_loaders/test_transform.py new file mode 100644 index 00000000..8b97c5f7 --- /dev/null +++ b/tests/unit/data_loaders/test_transform.py @@ -0,0 +1,67 @@ +import pytest +from autolabel.transforms.pdf import PDFTransform + + +RESUME_PDF_CONTENT = """Page 1: Functional Resume Sample + +John W. Smith +2002 Front Range Way Fort Collins, CO 80525 +jwsmith@colostate.edu + +Career Summary + +Four years experience in early childhood development with a di verse background in the care of +special needs children and adults. + +Adult Care Experience + +• Determined work placement for 150 special needs adult clients. +• Maintained client databases and records. +• Coordinated client contact with local health care professionals on a monthly basis. +• Managed 25 volunteer workers. + +Childcare Experience + +• Coordinated service assignments for 20 part -time counselors and 100 client families. +• Oversaw daily activity and outing planning for 100 clients. +• Assisted families of special needs clients with researching financial assistance and +healthcare. +• Assisted teachers with managing daily classroom activities. +• Oversaw daily and special st udent activities. + +Employment History + 1999-2002 Counseling Supervisor, The Wesley Ce nter, Little Rock, Arkansas. +1997-1999 Client Specialist, Rainbow Special Ca re Center, Little Rock, Arkansas +1996-1997 Teacher’s Assistant, Cowell Elem entary, Conway, Arkansas + +Education + +University of Arkansas at Little Rock, Little Rock, AR + +• BS in Early Childhood Development (1999) +• BA in Elementary Education (1998) +• GPA (4.0 Scale): Early Childhood Developm ent – 3.8, Elementary Education – 3.5, +Overall 3.4. +• Dean’s List, Chancellor’s List""" + + +def test_pdf_transform(): + # Initialize the PDFTransform class + transform = PDFTransform( + output_columns=["content", "num_pages"], + file_path_column="file_path", + page_header="Page {page_num}: ", + page_sep="\n\n", + ) + + # Create a mock row of data + row = {"file_path": "tests/assets/data_loading/Resume.pdf"} + + # Transform the row + transformed_row = transform.transform(row) + + assert set(transformed_row.keys()) == set(["content", "num_pages"]) + assert isinstance(transformed_row["content"], str) + assert isinstance(transformed_row["num_pages"], int) + assert transformed_row["num_pages"] == 1 + assert transformed_row["content"] == RESUME_PDF_CONTENT From a4cdc2b23d0a112ba587d95d4024514c99479e58 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 1 Aug 2023 10:06:42 -0700 Subject: [PATCH 02/14] moved pdf transform to schema and dependencies --- pyproject.toml | 3 ++- src/autolabel/schema.py | 1 + src/autolabel/transforms/__init__.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bcc7d123..a69681cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,8 @@ dependencies = [ "jsonschema >= 4.17.3", "tabulate >= 0.9.0", "typer[all] >= 0.9.0", - "simple-term-menu >= 1.6.1" + "simple-term-menu >= 1.6.1", + "pypdf >= 3.14.0" ] requires-python = ">=3.6" diff --git a/src/autolabel/schema.py b/src/autolabel/schema.py index 1644b23d..e0ef63a9 100644 --- a/src/autolabel/schema.py +++ b/src/autolabel/schema.py @@ -218,3 +218,4 @@ class TransformType(str, Enum): """Enum containing all Transforms supported by autolabel""" WEBPAGE_TRANSFORM = "webpage_transform" + PDF = "pdf" diff --git a/src/autolabel/transforms/__init__.py b/src/autolabel/transforms/__init__.py index 10bab44e..616f7838 100644 --- a/src/autolabel/transforms/__init__.py +++ b/src/autolabel/transforms/__init__.py @@ -7,7 +7,7 @@ logger = logging.getLogger(__name__) -TRANSFORM_REGISTRY = {PDFTransform.name(): PDFTransform} +TRANSFORM_REGISTRY = {TransformType.PDF: PDFTransform} class TransformFactory: From 6adab26453272be89f09faa89c4471d2ee60b9ba Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 1 Aug 2023 10:20:05 -0700 Subject: [PATCH 03/14] removed usused pytest import --- tests/unit/data_loaders/test_transform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/data_loaders/test_transform.py b/tests/unit/data_loaders/test_transform.py index 8b97c5f7..6615a9e0 100644 --- a/tests/unit/data_loaders/test_transform.py +++ b/tests/unit/data_loaders/test_transform.py @@ -1,4 +1,3 @@ -import pytest from autolabel.transforms.pdf import PDFTransform From 6852cb1b2dbc0f2c544ebbc5994a8fbfb9ebe877 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 1 Aug 2023 15:42:58 -0700 Subject: [PATCH 04/14] removed the pypdf dependency --- pyproject.toml | 6 +++--- src/autolabel/transforms/pdf.py | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a69681cc..5a43f5d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,8 +39,7 @@ dependencies = [ "jsonschema >= 4.17.3", "tabulate >= 0.9.0", "typer[all] >= 0.9.0", - "simple-term-menu >= 1.6.1", - "pypdf >= 3.14.0" + "simple-term-menu >= 1.6.1" ] requires-python = ">=3.6" @@ -91,7 +90,8 @@ all = [ "transformers >= 4.25.0", "google-cloud-aiplatform>=1.25.0", "cohere>=4.11.2", - "sentence_transformers" + "sentence_transformers", + "pypdf >= 3.14.0" ] [project.urls] diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index 7e116632..7785bb27 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -1,7 +1,5 @@ from typing import List, Dict, Any -from langchain.document_loaders import PyPDFLoader - from autolabel.transforms import BaseTransform @@ -33,6 +31,12 @@ def transform(self, row: Dict[str, any]) -> Dict[str, any]: Returns: Dict[str, any]: The transformed row of data. """ + try: + from langchain.document_loaders import PyPDFLoader + except ImportError: + raise ImportError( + "pypdf is required to use the pdf transform. Please install pypdf with the following command: pip install pypdf" + ) loader = PyPDFLoader(row[self.file_path_column]) page_contents = [] for idx, page in enumerate(loader.load_and_split()): From 5e89c58a652c15a5c1f1650d2a3f162938c32765 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 1 Aug 2023 17:07:58 -0700 Subject: [PATCH 05/14] added extract text OCR method --- src/autolabel/transforms/pdf.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index 7785bb27..8506ed8a 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -1,5 +1,8 @@ from typing import List, Dict, Any +from pdf2image import convert_from_path +import pytesseract + from autolabel.transforms import BaseTransform @@ -21,6 +24,23 @@ def __init__( def name() -> str: return "pdf" + @staticmethod + def extract_text(path: str) -> List[str]: + """This function extracts text from a PDF file using the pdf2image and pytesseract libraries. + + Args: + path (str): The path to the PDF file. + + Returns: + List[str]: A list of strings, one for each page of the PDF file. + """ + pages = convert_from_path(path) + texts = [] + for page in pages: + text = pytesseract.image_to_string(page) + texts.append(text) + return texts + def transform(self, row: Dict[str, any]) -> Dict[str, any]: """This function transforms a PDF file into a string of text. It uses the PyPDFLoader to load and split the PDF into pages. Each page is then converted into text and appended to the output string. @@ -37,12 +57,10 @@ def transform(self, row: Dict[str, any]) -> Dict[str, any]: raise ImportError( "pypdf is required to use the pdf transform. Please install pypdf with the following command: pip install pypdf" ) - loader = PyPDFLoader(row[self.file_path_column]) + pages = self.extract_text(row[self.file_path_column]) page_contents = [] - for idx, page in enumerate(loader.load_and_split()): - page_contents.append( - self.page_header.format(page_num=idx + 1) + page.page_content - ) + for idx, page in enumerate(pages): + page_contents.append(self.page_header.format(page_num=idx + 1) + page) output = self.page_sep.join(page_contents) return { self.output_columns[0]: output, From e9dbcab3ab3c92dc11ef77812cc2964eacebb9c3 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 2 Aug 2023 11:49:25 -0700 Subject: [PATCH 06/14] default non-ocr, moved imports to optional --- pyproject.toml | 2 +- src/autolabel/transforms/pdf.py | 59 ++++++++++++++----- tests/unit/data_loaders/test_transform.py | 69 ++++++++++------------- 3 files changed, 74 insertions(+), 56 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5a43f5d3..e3c802d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ all = [ "google-cloud-aiplatform>=1.25.0", "cohere>=4.11.2", "sentence_transformers", - "pypdf >= 3.14.0" + "pdfplumber >= 0.10.2" ] [project.urls] diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index 8506ed8a..b8b94975 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -1,8 +1,5 @@ from typing import List, Dict, Any -from pdf2image import convert_from_path -import pytesseract - from autolabel.transforms import BaseTransform @@ -11,12 +8,14 @@ def __init__( self, output_columns: List[str], file_path_column: str, + ocr_enabled: bool = False, page_header: str = "Page {page_num}: ", page_sep: str = "\n\n", ) -> None: """The output columns for this class should be in the order: [content_column, num_pages_column]""" super().__init__(output_columns) self.file_path_column = file_path_column + self.ocr_enabled = ocr_enabled self.page_header = page_header self.page_sep = page_sep @@ -26,6 +25,26 @@ def name() -> str: @staticmethod def extract_text(path: str) -> List[str]: + """ + This function extracts text from a PDF file using the pdfplumber library. + + Args: + path (str): The path to the PDF file. + + Returns: + List[str]: A list of strings, each index containing the extracted text from each page of the PDF file. + """ + try: + from langchain.document_loaders import PDFPlumberLoader + except ImportError: + raise ImportError( + "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" + ) + loader = PDFPlumberLoader(path) + return [doc.page_content for doc in loader.load()] + + @staticmethod + def extract_text_ocr(path: str) -> List[str]: """This function extracts text from a PDF file using the pdf2image and pytesseract libraries. Args: @@ -34,12 +53,25 @@ def extract_text(path: str) -> List[str]: Returns: List[str]: A list of strings, one for each page of the PDF file. """ + try: + from pdf2image import convert_from_path + import pytesseract + from pytesseract import TesseractNotFoundError + except ImportError: + raise ImportError( + "pdf2image and pytesseract are required to use the pdf transform with ocr. Please install pdf2image and pytesseract with the following command: pip install pdf2image pytesseract" + ) pages = convert_from_path(path) - texts = [] - for page in pages: - text = pytesseract.image_to_string(page) - texts.append(text) - return texts + try: + texts = [] + for page in pages: + text = pytesseract.image_to_string(page) + texts.append(text) + return texts + except TesseractNotFoundError: + raise ImportError( + "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." + ) def transform(self, row: Dict[str, any]) -> Dict[str, any]: """This function transforms a PDF file into a string of text. It uses the PyPDFLoader to load and split the PDF into pages. @@ -51,13 +83,10 @@ def transform(self, row: Dict[str, any]) -> Dict[str, any]: Returns: Dict[str, any]: The transformed row of data. """ - try: - from langchain.document_loaders import PyPDFLoader - except ImportError: - raise ImportError( - "pypdf is required to use the pdf transform. Please install pypdf with the following command: pip install pypdf" - ) - pages = self.extract_text(row[self.file_path_column]) + if self.ocr_enabled: + pages = self.extract_text_ocr(row[self.file_path_column]) + else: + pages = self.extract_text(row[self.file_path_column]) page_contents = [] for idx, page in enumerate(pages): page_contents.append(self.page_header.format(page_num=idx + 1) + page) diff --git a/tests/unit/data_loaders/test_transform.py b/tests/unit/data_loaders/test_transform.py index 6615a9e0..a7b1b2ab 100644 --- a/tests/unit/data_loaders/test_transform.py +++ b/tests/unit/data_loaders/test_transform.py @@ -1,46 +1,35 @@ from autolabel.transforms.pdf import PDFTransform -RESUME_PDF_CONTENT = """Page 1: Functional Resume Sample - -John W. Smith -2002 Front Range Way Fort Collins, CO 80525 -jwsmith@colostate.edu - -Career Summary - -Four years experience in early childhood development with a di verse background in the care of -special needs children and adults. - -Adult Care Experience - -• Determined work placement for 150 special needs adult clients. -• Maintained client databases and records. -• Coordinated client contact with local health care professionals on a monthly basis. -• Managed 25 volunteer workers. - -Childcare Experience - -• Coordinated service assignments for 20 part -time counselors and 100 client families. -• Oversaw daily activity and outing planning for 100 clients. -• Assisted families of special needs clients with researching financial assistance and -healthcare. -• Assisted teachers with managing daily classroom activities. -• Oversaw daily and special st udent activities. - -Employment History - 1999-2002 Counseling Supervisor, The Wesley Ce nter, Little Rock, Arkansas. -1997-1999 Client Specialist, Rainbow Special Ca re Center, Little Rock, Arkansas -1996-1997 Teacher’s Assistant, Cowell Elem entary, Conway, Arkansas - -Education - -University of Arkansas at Little Rock, Little Rock, AR - -• BS in Early Childhood Development (1999) -• BA in Elementary Education (1998) -• GPA (4.0 Scale): Early Childhood Developm ent – 3.8, Elementary Education – 3.5, -Overall 3.4. +RESUME_PDF_CONTENT = """Page 1: Functional Resume Sample +John W. Smith +2002 Front Range Way Fort Collins, CO 80525 +jwsmith@colostate.edu +Career Summary +Four years experience in early childhood development with a diverse background in the care of +special needs children and adults. +Adult Care Experience +• Determined work placement for 150 special needs adult clients. +• Maintained client databases and records. +• Coordinated client contact with local health care professionals on a monthly basis. +• Managed 25 volunteer workers. +Childcare Experience +• Coordinated service assignments for 20 part-time counselors and 100 client families. +• Oversaw daily activity and outing planning for 100 clients. +• Assisted families of special needs clients with researching financial assistance and +healthcare. +• Assisted teachers with managing daily classroom activities. +• Oversaw daily and special student activities. +Employment History +1999-2002 Counseling Supervisor, The Wesley Center, Little Rock, Arkansas. +1997-1999 Client Specialist, Rainbow Special Care Center, Little Rock, Arkansas +1996-1997 Teacher’s Assistant, Cowell Elementary, Conway, Arkansas +Education +University of Arkansas at Little Rock, Little Rock, AR +• BS in Early Childhood Development (1999) +• BA in Elementary Education (1998) +• GPA (4.0 Scale): Early Childhood Development – 3.8, Elementary Education – 3.5, +Overall 3.4. • Dean’s List, Chancellor’s List""" From 19d9c7e456b3c7ccc310bd09e3b6890b73895758 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 2 Aug 2023 12:07:10 -0700 Subject: [PATCH 07/14] moved imports to constructor --- src/autolabel/transforms/pdf.py | 49 ++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index b8b94975..158d5511 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -19,12 +19,32 @@ def __init__( self.page_header = page_header self.page_sep = page_sep + if self.ocr_enabled: + try: + from pdf2image import convert_from_path + import pytesseract + + self.convert_from_path = convert_from_path + self.pytesseract = pytesseract + except ImportError: + raise ImportError( + "pdf2image and pytesseract are required to use the pdf transform with ocr. Please install pdf2image and pytesseract with the following command: pip install pdf2image pytesseract" + ) + else: + try: + from langchain.document_loaders import PDFPlumberLoader + + self.PDFPlumberLoader = PDFPlumberLoader + except ImportError: + raise ImportError( + "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" + ) + @staticmethod def name() -> str: return "pdf" - @staticmethod - def extract_text(path: str) -> List[str]: + def extract_text(self, path: str) -> List[str]: """ This function extracts text from a PDF file using the pdfplumber library. @@ -34,17 +54,10 @@ def extract_text(path: str) -> List[str]: Returns: List[str]: A list of strings, each index containing the extracted text from each page of the PDF file. """ - try: - from langchain.document_loaders import PDFPlumberLoader - except ImportError: - raise ImportError( - "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" - ) - loader = PDFPlumberLoader(path) + loader = self.PDFPlumberLoader(path) return [doc.page_content for doc in loader.load()] - @staticmethod - def extract_text_ocr(path: str) -> List[str]: + def extract_text_ocr(self, path: str) -> List[str]: """This function extracts text from a PDF file using the pdf2image and pytesseract libraries. Args: @@ -53,22 +66,14 @@ def extract_text_ocr(path: str) -> List[str]: Returns: List[str]: A list of strings, one for each page of the PDF file. """ - try: - from pdf2image import convert_from_path - import pytesseract - from pytesseract import TesseractNotFoundError - except ImportError: - raise ImportError( - "pdf2image and pytesseract are required to use the pdf transform with ocr. Please install pdf2image and pytesseract with the following command: pip install pdf2image pytesseract" - ) - pages = convert_from_path(path) + pages = self.convert_from_path(path) try: texts = [] for page in pages: - text = pytesseract.image_to_string(page) + text = self.pytesseract.image_to_string(page) texts.append(text) return texts - except TesseractNotFoundError: + except Exception as e: raise ImportError( "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." ) From 20f3d726e0318579e8fda86fa2204b536e19920a Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 4 Aug 2023 10:39:58 -0700 Subject: [PATCH 08/14] split pdf transform into pdf and pdf_ocr --- src/autolabel/schema.py | 1 + src/autolabel/transforms/__init__.py | 6 +- src/autolabel/transforms/pdf.py | 92 +++++++--------------------- src/autolabel/transforms/pdf_ocr.py | 61 ++++++++++++++++++ 4 files changed, 89 insertions(+), 71 deletions(-) create mode 100644 src/autolabel/transforms/pdf_ocr.py diff --git a/src/autolabel/schema.py b/src/autolabel/schema.py index e0ef63a9..756594d0 100644 --- a/src/autolabel/schema.py +++ b/src/autolabel/schema.py @@ -219,3 +219,4 @@ class TransformType(str, Enum): WEBPAGE_TRANSFORM = "webpage_transform" PDF = "pdf" + PDF_OCR = "pdf_ocr" diff --git a/src/autolabel/transforms/__init__.py b/src/autolabel/transforms/__init__.py index 616f7838..c9b88842 100644 --- a/src/autolabel/transforms/__init__.py +++ b/src/autolabel/transforms/__init__.py @@ -2,12 +2,16 @@ from .base import BaseTransform from .pdf import PDFTransform +from .pdf_ocr import PDFOCRTransform from typing import Dict, List from autolabel.schema import TransformType logger = logging.getLogger(__name__) -TRANSFORM_REGISTRY = {TransformType.PDF: PDFTransform} +TRANSFORM_REGISTRY = { + TransformType.PDF: PDFTransform, + TransformType.PDF_OCR: PDFOCRTransform, +} class TransformFactory: diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index 158d5511..ab8a62e5 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -8,95 +8,47 @@ def __init__( self, output_columns: List[str], file_path_column: str, - ocr_enabled: bool = False, - page_header: str = "Page {page_num}: ", + page_header: str = "Page {page_num}: {page_content}", page_sep: str = "\n\n", ) -> None: """The output columns for this class should be in the order: [content_column, num_pages_column]""" super().__init__(output_columns) self.file_path_column = file_path_column - self.ocr_enabled = ocr_enabled - self.page_header = page_header + self.page_format = page_header self.page_sep = page_sep - if self.ocr_enabled: - try: - from pdf2image import convert_from_path - import pytesseract - - self.convert_from_path = convert_from_path - self.pytesseract = pytesseract - except ImportError: - raise ImportError( - "pdf2image and pytesseract are required to use the pdf transform with ocr. Please install pdf2image and pytesseract with the following command: pip install pdf2image pytesseract" - ) - else: - try: - from langchain.document_loaders import PDFPlumberLoader + try: + from langchain.document_loaders import PDFPlumberLoader - self.PDFPlumberLoader = PDFPlumberLoader - except ImportError: - raise ImportError( - "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" - ) + self.PDFPlumberLoader = PDFPlumberLoader + except ImportError: + raise ImportError( + "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" + ) @staticmethod def name() -> str: return "pdf" - def extract_text(self, path: str) -> List[str]: - """ - This function extracts text from a PDF file using the pdfplumber library. - - Args: - path (str): The path to the PDF file. - - Returns: - List[str]: A list of strings, each index containing the extracted text from each page of the PDF file. - """ - loader = self.PDFPlumberLoader(path) - return [doc.page_content for doc in loader.load()] - - def extract_text_ocr(self, path: str) -> List[str]: - """This function extracts text from a PDF file using the pdf2image and pytesseract libraries. - - Args: - path (str): The path to the PDF file. - - Returns: - List[str]: A list of strings, one for each page of the PDF file. - """ - pages = self.convert_from_path(path) - try: - texts = [] - for page in pages: - text = self.pytesseract.image_to_string(page) - texts.append(text) - return texts - except Exception as e: - raise ImportError( - "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." - ) - - def transform(self, row: Dict[str, any]) -> Dict[str, any]: - """This function transforms a PDF file into a string of text. It uses the PyPDFLoader to load and split the PDF into pages. - Each page is then converted into text and appended to the output string. + async def _apply(self, row: Dict[str, any]) -> Dict[str, any]: + """This function transforms a PDF file into a string of text. + It uses the pdfplumber library to convert the PDF into text. + The text is then formatted according to the page_format and + page_sep parameters and returned as a string. Args: row (Dict[str, any]): The row of data to be transformed. Returns: - Dict[str, any]: The transformed row of data. + Dict[str, any]: The dict of output columns. """ - if self.ocr_enabled: - pages = self.extract_text_ocr(row[self.file_path_column]) - else: - pages = self.extract_text(row[self.file_path_column]) - page_contents = [] - for idx, page in enumerate(pages): - page_contents.append(self.page_header.format(page_num=idx + 1) + page) - output = self.page_sep.join(page_contents) + loader = self.PDFPlumberLoader(row[self.file_path_column]) + texts = [] + for idx, page in enumerate(loader.load()): + text = page.page_content + texts.append(self.page_format.format(page_num=idx + 1, page_content=text)) + output = self.page_sep.join(texts) return { self.output_columns[0]: output, - self.output_columns[1]: len(page_contents), + self.output_columns[1]: len(texts), } diff --git a/src/autolabel/transforms/pdf_ocr.py b/src/autolabel/transforms/pdf_ocr.py new file mode 100644 index 00000000..7906ed3f --- /dev/null +++ b/src/autolabel/transforms/pdf_ocr.py @@ -0,0 +1,61 @@ +from typing import List, Dict + +from autolabel.transforms import BaseTransform + + +class PDFOCRTransform(BaseTransform): + def __init__( + self, + output_columns: List[str], + file_path_column: str, + page_format: str = "Page {page_num}: {page_content}", + page_sep: str = "\n\n", + ) -> None: + """The output columns for this class should be in the order: [content_column, num_pages_column]""" + super().__init__(output_columns) + self.file_path_column = file_path_column + self.page_format = page_format + self.page_sep = page_sep + + try: + from pdf2image import convert_from_path + import pytesseract + + self.convert_from_path = convert_from_path + self.pytesseract = pytesseract + self.pytesseract.get_tesseract_version() + except ImportError: + raise ImportError( + "pdf2image and pytesseract are required to use the pdf transform with ocr. Please install pdf2image and pytesseract with the following command: pip install pdf2image pytesseract" + ) + except EnvironmentError: + raise EnvironmentError( + "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." + ) + + @staticmethod + def name() -> str: + return "pdf_ocr" + + async def _apply(self, row: Dict[str, any]) -> Dict[str, any]: + """This function transforms a PDF file into a string of text using OCR. + It uses the pdf2image library to convert the PDF into images and then uses + pytesseract to convert the images into text. The text is then formatted + according to the page_format and page_sep parameters and returned as a string. + + Args: + row (Dict[str, any]): The row of data to be transformed. + + Returns: + Dict[str, any]: The dict of output columns. + """ + pages = self.convert_from_path(row[self.file_path_column]) + texts = [] + for idx, page in enumerate(pages): + text = self.pytesseract.image_to_string(page) + texts.append(self.page_format.format(page_num=idx + 1, page_content=text)) + output = self.page_sep.join(texts) + return { + self.output_columns[0]: output, + self.output_columns[1]: len(texts), + } From 9471c3eeb48ac2cdae53bbd39e7e18632fc5d1e7 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 4 Aug 2023 12:46:41 -0700 Subject: [PATCH 09/14] added dependencies to all --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3c802d5..97a7fa80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,9 @@ all = [ "google-cloud-aiplatform>=1.25.0", "cohere>=4.11.2", "sentence_transformers", - "pdfplumber >= 0.10.2" + "pdfplumber >= 0.10.2", + "pdf2image >= 1.16.3", + "pytesseract >= 0.3.10" ] [project.urls] From 045e42a547de627fa56e4649566c45a4c26b9881 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 7 Aug 2023 10:15:52 -0700 Subject: [PATCH 10/14] both ocr and regular transforms back to one file --- src/autolabel/transforms/pdf.py | 58 +++++++++++++++++++++------ src/autolabel/transforms/pdf_ocr.py | 61 ----------------------------- 2 files changed, 46 insertions(+), 73 deletions(-) delete mode 100644 src/autolabel/transforms/pdf_ocr.py diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index ab8a62e5..00f080b7 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -8,32 +8,68 @@ def __init__( self, output_columns: List[str], file_path_column: str, + ocr_enabled: bool = False, page_header: str = "Page {page_num}: {page_content}", page_sep: str = "\n\n", ) -> None: """The output columns for this class should be in the order: [content_column, num_pages_column]""" super().__init__(output_columns) self.file_path_column = file_path_column + self.ocr_enabled = ocr_enabled self.page_format = page_header self.page_sep = page_sep - try: - from langchain.document_loaders import PDFPlumberLoader + if self.ocr_enabled: + try: + from pdf2image import convert_from_path + import pytesseract - self.PDFPlumberLoader = PDFPlumberLoader - except ImportError: - raise ImportError( - "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" - ) + self.convert_from_path = convert_from_path + self.pytesseract = pytesseract + self.pytesseract.get_tesseract_version() + except ImportError: + raise ImportError( + "pdf2image and pytesseract are required to use the pdf transform with ocr. Please install pdf2image and pytesseract with the following command: pip install pdf2image pytesseract" + ) + except EnvironmentError: + raise EnvironmentError( + "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." + ) + else: + try: + from langchain.document_loaders import PDFPlumberLoader + + self.PDFPlumberLoader = PDFPlumberLoader + except ImportError: + raise ImportError( + "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" + ) @staticmethod def name() -> str: return "pdf" + def get_page_texts(self, row: Dict[str, any]) -> List[str]: + """This function gets the text from each page of a PDF file. + If OCR is enabled, it uses the pdf2image library to convert the PDF into images and then uses + pytesseract to convert the images into text. Otherwise, it uses pdfplumber to extract the text. + + Args: + row (Dict[str, any]): The row of data to be transformed. + + Returns: + List[str]: A list of strings containing the text from each page of the PDF. + """ + if self.ocr_enabled: + pages = self.convert_from_path(row[self.file_path_column]) + return [self.pytesseract.image_to_string(page) for page in pages] + else: + loader = self.PDFPlumberLoader(row[self.file_path_column]) + return [page.page_content for page in loader.load()] + async def _apply(self, row: Dict[str, any]) -> Dict[str, any]: """This function transforms a PDF file into a string of text. - It uses the pdfplumber library to convert the PDF into text. - The text is then formatted according to the page_format and + The text is formatted according to the page_format and page_sep parameters and returned as a string. Args: @@ -42,10 +78,8 @@ async def _apply(self, row: Dict[str, any]) -> Dict[str, any]: Returns: Dict[str, any]: The dict of output columns. """ - loader = self.PDFPlumberLoader(row[self.file_path_column]) texts = [] - for idx, page in enumerate(loader.load()): - text = page.page_content + for idx, text in enumerate(get_page_texts(row)): texts.append(self.page_format.format(page_num=idx + 1, page_content=text)) output = self.page_sep.join(texts) return { diff --git a/src/autolabel/transforms/pdf_ocr.py b/src/autolabel/transforms/pdf_ocr.py deleted file mode 100644 index 7906ed3f..00000000 --- a/src/autolabel/transforms/pdf_ocr.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import List, Dict - -from autolabel.transforms import BaseTransform - - -class PDFOCRTransform(BaseTransform): - def __init__( - self, - output_columns: List[str], - file_path_column: str, - page_format: str = "Page {page_num}: {page_content}", - page_sep: str = "\n\n", - ) -> None: - """The output columns for this class should be in the order: [content_column, num_pages_column]""" - super().__init__(output_columns) - self.file_path_column = file_path_column - self.page_format = page_format - self.page_sep = page_sep - - try: - from pdf2image import convert_from_path - import pytesseract - - self.convert_from_path = convert_from_path - self.pytesseract = pytesseract - self.pytesseract.get_tesseract_version() - except ImportError: - raise ImportError( - "pdf2image and pytesseract are required to use the pdf transform with ocr. Please install pdf2image and pytesseract with the following command: pip install pdf2image pytesseract" - ) - except EnvironmentError: - raise EnvironmentError( - "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." - ) - - @staticmethod - def name() -> str: - return "pdf_ocr" - - async def _apply(self, row: Dict[str, any]) -> Dict[str, any]: - """This function transforms a PDF file into a string of text using OCR. - It uses the pdf2image library to convert the PDF into images and then uses - pytesseract to convert the images into text. The text is then formatted - according to the page_format and page_sep parameters and returned as a string. - - Args: - row (Dict[str, any]): The row of data to be transformed. - - Returns: - Dict[str, any]: The dict of output columns. - """ - pages = self.convert_from_path(row[self.file_path_column]) - texts = [] - for idx, page in enumerate(pages): - text = self.pytesseract.image_to_string(page) - texts.append(self.page_format.format(page_num=idx + 1, page_content=text)) - output = self.page_sep.join(texts) - return { - self.output_columns[0]: output, - self.output_columns[1]: len(texts), - } From 4d948e7cb1ec1b5d5308462ca3dc37df84a5ad54 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 7 Aug 2023 10:45:57 -0700 Subject: [PATCH 11/14] updated typing and some metadata --- src/autolabel/transforms/pdf.py | 50 ++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index 00f080b7..870a9b19 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -1,12 +1,13 @@ from typing import List, Dict, Any +from autolabel.schema import TransformType from autolabel.transforms import BaseTransform class PDFTransform(BaseTransform): def __init__( self, - output_columns: List[str], + output_columns: Dict[str, Any], file_path_column: str, ocr_enabled: bool = False, page_header: str = "Page {page_num}: {page_content}", @@ -19,6 +20,15 @@ def __init__( self.page_format = page_header self.page_sep = page_sep + # PDFPlumber is required for both for metadata extraction + try: + from langchain.document_loaders import PDFPlumberLoader + + self.PDFPlumberLoader = PDFPlumberLoader + except ImportError: + raise ImportError( + "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" + ) if self.ocr_enabled: try: from pdf2image import convert_from_path @@ -35,27 +45,26 @@ def __init__( raise EnvironmentError( "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." ) - else: - try: - from langchain.document_loaders import PDFPlumberLoader - - self.PDFPlumberLoader = PDFPlumberLoader - except ImportError: - raise ImportError( - "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" - ) @staticmethod def name() -> str: - return "pdf" + return TransformType.PDF + + @property + def output_columns(self) -> Dict[str, Any]: + COLUMN_NAMES = [ + "content_column", + "metadata_column", + ] + return {k: self._output_columns.get(k, k) for k in COLUMN_NAMES} - def get_page_texts(self, row: Dict[str, any]) -> List[str]: + def get_page_texts(self, row: Dict[str, Any]) -> List[str]: """This function gets the text from each page of a PDF file. If OCR is enabled, it uses the pdf2image library to convert the PDF into images and then uses pytesseract to convert the images into text. Otherwise, it uses pdfplumber to extract the text. Args: - row (Dict[str, any]): The row of data to be transformed. + row (Dict[str, Any]): The row of data to be transformed. Returns: List[str]: A list of strings containing the text from each page of the PDF. @@ -67,22 +76,25 @@ def get_page_texts(self, row: Dict[str, any]) -> List[str]: loader = self.PDFPlumberLoader(row[self.file_path_column]) return [page.page_content for page in loader.load()] - async def _apply(self, row: Dict[str, any]) -> Dict[str, any]: + async def _apply(self, row: Dict[str, Any]) -> Dict[str, Any]: """This function transforms a PDF file into a string of text. The text is formatted according to the page_format and page_sep parameters and returned as a string. Args: - row (Dict[str, any]): The row of data to be transformed. + row (Dict[str, Any]): The row of data to be transformed. Returns: - Dict[str, any]: The dict of output columns. + Dict[str, Any]: The dict of output columns. """ texts = [] for idx, text in enumerate(get_page_texts(row)): texts.append(self.page_format.format(page_num=idx + 1, page_content=text)) output = self.page_sep.join(texts) - return { - self.output_columns[0]: output, - self.output_columns[1]: len(texts), + transformed_row = { + self.output_columns["content_column"]: output, + self.output_columns["metadata_column"]: { + "num_pages": len(texts) + }, # TODO: add more metadata } + return transformed_row From 87b677e9db056e11e8c31daf10429d3af20b067c Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 7 Aug 2023 11:36:17 -0700 Subject: [PATCH 12/14] added tests and moved import --- pyproject.toml | 2 +- src/autolabel/transforms/pdf.py | 24 +++++---- tests/unit/data_loaders/test_transform.py | 55 --------------------- tests/unit/transforms/test_pdf_transform.py | 51 +++++++++++++++++++ 4 files changed, 63 insertions(+), 69 deletions(-) delete mode 100644 tests/unit/data_loaders/test_transform.py create mode 100644 tests/unit/transforms/test_pdf_transform.py diff --git a/pyproject.toml b/pyproject.toml index b24d0e04..deb444cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ all = [ "sentence_transformers", "pdfplumber >= 0.10.2", "pdf2image >= 1.16.3", - "pytesseract >= 0.3.10" + "pytesseract >= 0.3.10", "bs4", "httpx", "fake_useragent" diff --git a/src/autolabel/transforms/pdf.py b/src/autolabel/transforms/pdf.py index 870a9b19..a24d6553 100644 --- a/src/autolabel/transforms/pdf.py +++ b/src/autolabel/transforms/pdf.py @@ -20,15 +20,6 @@ def __init__( self.page_format = page_header self.page_sep = page_sep - # PDFPlumber is required for both for metadata extraction - try: - from langchain.document_loaders import PDFPlumberLoader - - self.PDFPlumberLoader = PDFPlumberLoader - except ImportError: - raise ImportError( - "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" - ) if self.ocr_enabled: try: from pdf2image import convert_from_path @@ -45,6 +36,15 @@ def __init__( raise EnvironmentError( "The tesseract engine is required to use the pdf transform with ocr. Please see https://tesseract-ocr.github.io/tessdoc/Installation.html for installation instructions." ) + else: + try: + from langchain.document_loaders import PDFPlumberLoader + + self.PDFPlumberLoader = PDFPlumberLoader + except ImportError: + raise ImportError( + "pdfplumber is required to use the pdf transform. Please install pdfplumber with the following command: pip install pdfplumber" + ) @staticmethod def name() -> str: @@ -88,13 +88,11 @@ async def _apply(self, row: Dict[str, Any]) -> Dict[str, Any]: Dict[str, Any]: The dict of output columns. """ texts = [] - for idx, text in enumerate(get_page_texts(row)): + for idx, text in enumerate(self.get_page_texts(row)): texts.append(self.page_format.format(page_num=idx + 1, page_content=text)) output = self.page_sep.join(texts) transformed_row = { self.output_columns["content_column"]: output, - self.output_columns["metadata_column"]: { - "num_pages": len(texts) - }, # TODO: add more metadata + self.output_columns["metadata_column"]: {"num_pages": len(texts)}, } return transformed_row diff --git a/tests/unit/data_loaders/test_transform.py b/tests/unit/data_loaders/test_transform.py deleted file mode 100644 index a7b1b2ab..00000000 --- a/tests/unit/data_loaders/test_transform.py +++ /dev/null @@ -1,55 +0,0 @@ -from autolabel.transforms.pdf import PDFTransform - - -RESUME_PDF_CONTENT = """Page 1: Functional Resume Sample -John W. Smith -2002 Front Range Way Fort Collins, CO 80525 -jwsmith@colostate.edu -Career Summary -Four years experience in early childhood development with a diverse background in the care of -special needs children and adults. -Adult Care Experience -• Determined work placement for 150 special needs adult clients. -• Maintained client databases and records. -• Coordinated client contact with local health care professionals on a monthly basis. -• Managed 25 volunteer workers. -Childcare Experience -• Coordinated service assignments for 20 part-time counselors and 100 client families. -• Oversaw daily activity and outing planning for 100 clients. -• Assisted families of special needs clients with researching financial assistance and -healthcare. -• Assisted teachers with managing daily classroom activities. -• Oversaw daily and special student activities. -Employment History -1999-2002 Counseling Supervisor, The Wesley Center, Little Rock, Arkansas. -1997-1999 Client Specialist, Rainbow Special Care Center, Little Rock, Arkansas -1996-1997 Teacher’s Assistant, Cowell Elementary, Conway, Arkansas -Education -University of Arkansas at Little Rock, Little Rock, AR -• BS in Early Childhood Development (1999) -• BA in Elementary Education (1998) -• GPA (4.0 Scale): Early Childhood Development – 3.8, Elementary Education – 3.5, -Overall 3.4. -• Dean’s List, Chancellor’s List""" - - -def test_pdf_transform(): - # Initialize the PDFTransform class - transform = PDFTransform( - output_columns=["content", "num_pages"], - file_path_column="file_path", - page_header="Page {page_num}: ", - page_sep="\n\n", - ) - - # Create a mock row of data - row = {"file_path": "tests/assets/data_loading/Resume.pdf"} - - # Transform the row - transformed_row = transform.transform(row) - - assert set(transformed_row.keys()) == set(["content", "num_pages"]) - assert isinstance(transformed_row["content"], str) - assert isinstance(transformed_row["num_pages"], int) - assert transformed_row["num_pages"] == 1 - assert transformed_row["content"] == RESUME_PDF_CONTENT diff --git a/tests/unit/transforms/test_pdf_transform.py b/tests/unit/transforms/test_pdf_transform.py new file mode 100644 index 00000000..8f1f4574 --- /dev/null +++ b/tests/unit/transforms/test_pdf_transform.py @@ -0,0 +1,51 @@ +from autolabel.transforms.pdf import PDFTransform +import pytest + +pytest_plugins = ("pytest_asyncio",) + + +@pytest.mark.asyncio +async def test_pdf_transform(): + # Initialize the PDFTransform class + transform = PDFTransform( + output_columns={ + "content_column": "content", + "metadata_column": "metadata", + }, + file_path_column="file_path", + ) + + # Create a mock row + row = {"file_path": "tests/assets/data_loading/Resume.pdf"} + # Transform the row + transformed_row = await transform.apply(row) + # Check the output + assert set(transformed_row.keys()) == set(["content", "metadata"]) + assert isinstance(transformed_row["content"], str) + assert isinstance(transformed_row["metadata"], dict) + assert len(transformed_row["content"]) > 0 + assert transformed_row["metadata"]["num_pages"] == 1 + + +@pytest.mark.asyncio +async def test_pdf_transform_ocr(): + # Initialize the PDFTransform class + transform = PDFTransform( + output_columns={ + "content_column": "content", + "metadata_column": "metadata", + }, + file_path_column="file_path", + ocr_enabled=True, + ) + + # Create a mock row + row = {"file_path": "tests/assets/data_loading/Resume.pdf"} + # Transform the row + transformed_row = await transform.apply(row) + # Check the output + assert set(transformed_row.keys()) == set(["content", "metadata"]) + assert isinstance(transformed_row["content"], str) + assert isinstance(transformed_row["metadata"], dict) + assert len(transformed_row["content"]) > 0 + assert transformed_row["metadata"]["num_pages"] == 1 From 205962c428e53f1e5d112a6d7e81c703875aad97 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 7 Aug 2023 11:41:33 -0700 Subject: [PATCH 13/14] can't test pdf ocr without tesseract engine --- tests/unit/transforms/test_pdf_transform.py | 24 --------------------- 1 file changed, 24 deletions(-) diff --git a/tests/unit/transforms/test_pdf_transform.py b/tests/unit/transforms/test_pdf_transform.py index 8f1f4574..59d54e3c 100644 --- a/tests/unit/transforms/test_pdf_transform.py +++ b/tests/unit/transforms/test_pdf_transform.py @@ -25,27 +25,3 @@ async def test_pdf_transform(): assert isinstance(transformed_row["metadata"], dict) assert len(transformed_row["content"]) > 0 assert transformed_row["metadata"]["num_pages"] == 1 - - -@pytest.mark.asyncio -async def test_pdf_transform_ocr(): - # Initialize the PDFTransform class - transform = PDFTransform( - output_columns={ - "content_column": "content", - "metadata_column": "metadata", - }, - file_path_column="file_path", - ocr_enabled=True, - ) - - # Create a mock row - row = {"file_path": "tests/assets/data_loading/Resume.pdf"} - # Transform the row - transformed_row = await transform.apply(row) - # Check the output - assert set(transformed_row.keys()) == set(["content", "metadata"]) - assert isinstance(transformed_row["content"], str) - assert isinstance(transformed_row["metadata"], dict) - assert len(transformed_row["content"]) > 0 - assert transformed_row["metadata"]["num_pages"] == 1 From da0c40c34d124adb65b54b8f22525e1b8c5de602 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 7 Aug 2023 11:43:38 -0700 Subject: [PATCH 14/14] removed pdf_ocr from schema --- src/autolabel/schema.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/autolabel/schema.py b/src/autolabel/schema.py index 756594d0..e0ef63a9 100644 --- a/src/autolabel/schema.py +++ b/src/autolabel/schema.py @@ -219,4 +219,3 @@ class TransformType(str, Enum): WEBPAGE_TRANSFORM = "webpage_transform" PDF = "pdf" - PDF_OCR = "pdf_ocr"