mmhK@3-aFd_38UPTNE)XV>wfD)bv<4GzD+?^4thE zg*~QSYT%gi<}h;O!0X$RIYnw+04=wrbmiM)>mE(M7)inO^lx|kMPIVoMf#y;p( --!U=Sn9Ed(f}U7N6z55-Oc<3i>d( zU()ikn}GU040Gz2C4k^;0QD ^7=a2zCJ^t;wR>BMy(l!{lGpbSPhsoiO z)=WQjZ{bE2J0#Ea;eFr7@Q80CI|4>RcL-+z&RlpY9gmN^2sucWDu~RDZwM;=5VG&& zwCHYNKidStjPLF^%OLE0PvR`q_sJ76$pwJJqS3cJzMT_FLAoJyi|tmzqumz3AVRCs zAo2x9@Y?xxjx;7HFtscsCtQf1TsRscKTA65J7UObvyKDuyR!sUzK*c<;ZKuK3CPd7 zGxsC;#t``tU7SCX|7W@XXUP`;+)s0F;`)*Mbb$LzcBt 1hYJXfY^~N zFjaF5>4nGeDjSkmP*93`+1Nc=2^Fpodx`t)Uye#ALD~nPNjDy`*scbqZ;rLt4UTvk zg7KevoAqwN6XFUfvxsvImCS9=V_gTqEw^~s%N$Ntj$hxpe>4SvXz$pIix4>$mqMq} zf`m$215|A)TebGVO>-f1I(@s%F#^gQg<@=y=7*$_g1J_;7hPu?d82?24u_SGs#aG7 zV#b`ip7E5;Ud!2uViKgZPxd;AncJt4_K*aPdf?qufnWSnFiP7s?Z^3a;J8{r;{g*s zK%J%jH9U3~Xaf@7KQ~Z3zMIxe{JF <^7dh$pNIoPa)$<>nybgI9Zz)> zM73`IwOf@{Hemd$PW#j#G*qNXJYSaryAZu}c;!axyLfEB^~Y8k@s!!CndI= 2hdrAcyuQj5v#iD_ve_C!2`j;z~le)bIISdV5opAXgty7hO8 z%Fo2;%x`#XW^kcViZY{R4=Bmux4NZQ4{Ujp65!f3jE-bCsgW~M!<(c>?l`MGjDwk7 zlCu-98pWE W1rt zy75(LLedm{=(aRgplsgrCwAkNHULU=kVr!{i1B2AP_n!GaG$|>4L+bgN`c340Ob$H zT-o1Aj8{?%QJ|y;7-su%gT4cUo-8tQ!w*UY*6v79iBF{Auup4ujuRvc6^I{2nBpq$ z&tSA>HvOC|+*`_XL Us_@2GUt3Aik-MPFCpDTW~6ixQl*k1w>)= zKyj{Cc?dw~3X~DPt#R^?&Tlc>AqVh!Vj}w`j@M}bLD{cX6T- >5eI9^D?iQvXbYUtEx0l2Mw!bo ad_gn9Vni*BxBxtH4Lg4?8Mauz1 zOCg&H-G-0>e3=-{5WuZ~D9jtIuWfSU5pI14=)lrdx6TlgEJI5Vi66&xS_Bwr%?zT) z+O}3s6vhE1Fr+F;IR4pD0mST8D8<`!Hyq5j8t_C3qWXpk=tVL8&E c Zn=k=Z9q8J #fPN;0r+2 zsa;aC@v5E-sXBx6q<7*gq%q6&Ap7G|z8mAh$VD^Dsn4ZRrary*d-w#iHvZVbM(N{H z0sl}j`PB8*7ca}1{E^5En&kHLFRvyj0hLwiy+z>F08l-~B6pi@WY AU z%~b7~+Kv`vg=bVO=HyJOpZ8aSa`IF`?otCUlXR^JX(1#G4gzlodiXTF>Y^bF`nhBd zO+8ys8n56{DTz;Sm$>VUuIKDjXB9no=!|eae@|hv7LfBzakK AuXZ2g+$b#4M7cd*yCe@ntlkpc3mH-?r1$GcRbzi5YI^+E= z9({h@E=qcJ{Pcp})6|<)-LXDVhDF&_NZFaR*WPwA(m(^ V9KmxSEE ^?VaGxtI&7F{;(fcBS}%M!-Oy&%7!< zkvpBv60|?(C`Q#M!2E$bMs=^;2!ZX7K%erSzx6?GfOXu6W*be6>Va5?P6=Qo=Wa~k zuS#YF7hQ2i`m_Q{u|DeWDBvYMtD@Ja;NOMcK=cbzCim3 0g?=#Jw1lkp*?Y6plUbIYp+S&Dd7@N z%vrP*KGo#TUS}yfQL?6Q6k#1NQ-M9#ac2Em?l{UQ5~UER+G+hzj-7$97vM`lxA )o#w o9I{ju){y9ARO(6C^ z?oZAtqyTm6m2@_yCuPfZ&S+FQTd7X*$ANg4j>p05blWaOsPFHkqO)3ZUEXaJ;TwCO z?L{%RnWbaXf2c8T!)^Us%I5oyD4CFaH*WD5V)Zh!*6D%uDe0o3oZ`yj)IE33xG&qI zIilpmPyxq$6IEH5i@ vGOy1GRg7Pp{sWr~V@h?^n;H~Y&lDc0gi*lz+?yiGtL|;m-Dn-b z_V*{Q^wFE$n?#6pz|6$+k>2?D$x|IT*~$45{c92&WqVjvE(Oh|vk6^t?(T%yYcrai zDIcfeV$+O)6HV-3K?e6kU`&w#gTBF{)p7iF*WLRK-^9I`FIT zfh ?3XuKNg#sFo%mRsU*Hta(+Dz9B_MaN*?yz{g~StqE#*G1B2^!v4@hPH+1sErSf z!Y |bv}oq6!!Qbm6_>|qG@@ZY5=4pZA1Z+t1GO=NsJT|w|3Xz5A47<$oc{vlw8 z+D?h J$c->^ ebFsc%LYJa{3732%Qw+b zhB<#PM%CKbHUrN+bw~#s27Am3r_(1`Q;OyP=lBGbT|ES+)c!!DT;~1C1R`L)6G1m$ zM!;<6?+{=m3re!S#AYWTfV(D=yTTIz9A>&@4@jq P=p+?qPK%i*O&p?AGWeDygJR9A1Q7lgGNP)zm&j7nN6VX z)z =aOnVlpczt(1AB{VUJn4^;YLeV4#tkQJY;X>#JnsM=9GHh2Sn;CR)U z;156&cP~F1>4Ql!UC79_sO++Ak`E{>)qUs8o9jK;Yx&N349%h=8@=oAJKW;gTPD>R znOYP&L2XYSCAqJ^CjB-*NbPEJ; W4Tu zrvJ8v#K;_b#kJ=AUQ8};bCpV3E{G |52n<8@58xItvES2T_b^Fh|}2(Zm`BJkCiS>9RY;>F 6JwK ~M={2)x=f19m|r-% YX@Kc8`nSnIIS?ckEr5j$HcA^Z(3b*Z zAh0is0=UEq 7jT$?G9M|oz#4SIvRpZOYeE^7_=tDB7n$o6PpWoP^^bQ zafv!D5So!((HA2`m~IH#m-csaDR3J<#i%EAmqQ_#u4_F%0Qv%EX93Li%xHdu%afsq z(FK=ILzDM`Q$xLMrBEj&j2WCd%x;3u*x7ZJ02I>uZdG!<$;si}t?Gt&W=}u>P+o4h z7g{Ip6uK{FsM2x8BA0?_QP(W7~8JDh?TY@7k8PYR#d03QT+BWf);v8=x%Q z8AYy!B;TU`$>-r?b^_m`K3_X)_sKY**857WbO<*)0|j+cFVOy|Y+lR*z^h+4pswKk zy9a5kR8M*ecs@K^iJKly+IQ(6 z?Q)I-LrIr2FKS1?s+lJ07JyQY<_qAJ(hjPkfI`i>6j {Feyz@|cTh^5q2Z&D;x|ICBjrSL20AM=&BS@Wg$R|4cfibh?( z-)Aj6pm_L8PPnakndq4{%t7>i{Y;*&F&h*m9IBVrgYc*rzNP)dB!t?C3nJ_e8r4V= zb{n~tGHq2QAMbN(+A8`6YYC7)!AF2Hng*~m<}QibDOw;~8^EViFN?v(woDf7kascV z^n?1$gL0o*LP)Hzy=)8>E49rL+i2tu)g;(7MvR2Z1iv_N2L#>d$L2nEQ_KveIh9e! z;EBiOUJ}krUQ^80sib??3u-TpU5}Ubqsw7E7d=v+^Gd<%-LBN`8&B-3SEkS?OHJ>w zBOo|kFnPXZ5r{cp#Exdo 2l{&c4bYy)uW_(VFS^8;6`CR|Xit7kc zViBFV=V(n8w^@#Z@5~1FPEU{i2NSRhOAS& (M!%`KC)pTnsU)tS=`_lzm$(QXb(mBZpLA8WUrVBL`pdm+hHKhA~8tiIkn1ed z z6$z688#Oz1t>!^4SHa7*n7`?AX+vED$JN$Wh5C9mz5ez0Y@lc=^3C>;1+a@%FZKDg zvy2XB!G1MnQU4 Er=W+w}MvFmfAEwa^X8TWW4ypG ~G0#HrQgjE-+>to-B4TjF(i3a)xHk(@jQay?K(1B&%O ztfBEj;h0TNk7>bldhdGg39tWKP!Dv}CFJoVP}aNlWQ EYMjc%f)RLTM~4Djj|GbmhdU-2QQ4(6;R{&H7TvaH@1>GWScN7bcc>ze?0krCOk# zmoN394ZpAQ8XhZlc|fAD(Y4~b`92$S&l=n88C7^K1B71nPdgb1p_dN`BZ^u6x3jo( zpb@6y{_1Cc%Hq@?O}gBqkzdkXdO3Rylcj7I5kY0=4O9BG4df-;tvu-QgcEjf=u(E+ zcC5>O@UOq_`W7>Xo`0yX=k&*|!{kmsMvcrT3sibQ_~ks7<&O{DBp{~OJ$>Y;VzGSu zT=lc5ClouHQ(1@K^c@_R3-ZyiD)_h&tz-P*#d3w~YDp B^G=LM9T-oK=@)a;vnEewnGLdo)g!h7;1L=LEM%*??WJy zi^3xl=`8f@%;x?s4v6yxU>`&lZO46rZYzV1jSYFU5DquqfNWH}G{hVK@J#{KYFosf zihz9V4_&x_X$uFqtz{QrvCK*q zC_$pSd!R%duPqkj;N(u?R$Pl9tqbtQ4% JSW{cb@1FE=15;FIO{$9%{3W`;vKUk~}j>e36$ KE93d2=_zG=0-uUo z!CnsRHoT|!whwvCSEO%IVL1;Q#kOIg+E!&XUl9)5)@P%A?ivX63?~eBZr#+P1iY?s zrvXny!Z}|N3BKsf{i$VNGj8F{& X^z6)bbA08yH^Rn0NF9PWf z`R4S^` bO3XaqQZW;NdD3p)YBI7JQ2WsGUA)Ve kB@D>`PE6o)LT}EVNjj2TYU2r@8>5$HK{Gx%;! VUCuOWoMl+Cq0ee+*j>$+f0bCVY y-B*U!xWAi?yDHlH*Yz*vD&wlU6Mh^e- z)}`4Pjar=7 mE@N&8W?Mt8U-n5Bb5R(Wk6$|M-`&s`- y}T;=mm87u_b=!y?+2Z`@D&xs() zp=-ySpKsN|?MuR^Ios=ODg-gA!_?2@@ajDWsrLqx123W-?1%8?IBtkWiBBFkH~dA* z1{3!lv=AKzL36k?7JeG!Y20b;gjjYrl&6X0zRJX<-_4eWI%Y#H5yKN;&qrBGmk8e0 z1))muyQwCFgJq*ykBbf({3@IM*ZOu~_ujqs38eiDwBzKt2B4esXx!d|Ao$k#8hqn- z`1OW@n3Wh~?0RcDo$R=l?s?EzEa4$gE(7+MHE3H%7f6GvhUd?A%Ir4VX;Q$_!C*#c zH2~^wL9Yhz2(TZK0lP~)v5VkO5!O}!W_GpJgU|y_^9Rd&Bw46L0^A$Pyw-I_Gn=;P zNN13<=SFtnuWHc*7ai$Z^o9YG_6hMZNd8sYy8z5hFFxP!ENDo)jZW^;L!(|Zw !-s-k?J}(=~Gtjoi>DjTDeKFwq!q6y>3zzc_pq_AwRiGy9qGn zPN_ALfI76G{tYY8`&gIiLfMhi%@`*$8+!76W>yT@(~3!6Of;Do+EOPaEasIKc`}+ojMRhVZJSW7*%XjJ_O0+XpP+Sw#JMOvQwzosMj3J~t z-OJZI%B|i-x=y?qtDaG3+vDBqm2} KzFa+Wy+Dm5>Gt3I<9gy79(0+>rddG!~nUv04qpwA$~ss^?VG`+z_^mO#@b~#-xbSQZ7 zgX{cV4`bb%^m?F%p_gTXFPO9Io&gjMoQ!kI{bhIQ?Z>lDa+Kh?Xdz5qfHuXBrrv{x zjD^Lt`Ug)~knaRp=AIi(F$9r(`}h)g{CS#lWU`ZG%jgj7r0r@S$e=4*{D5MPAc={# zZbNK?3b%+WPPnJ^AA!oWf&JJ^!jRuQe@+yp1}a%l;TgZ#^9VQ4yr^5*xDAhc%)vg% zrhl!s2PXV``y|bPfV+E4uHmw8ML?(`UljBik{> Y`fq3f zHa3Q@`9Lyqcn$>iZo+Wi*4kU3z4XQ{Dm)M*Af&;d(hBzY{wwJt2tv+Uc0dKduyJ4@ ze|sfHSR9nr&afbsA#Tu~pGKP!ru)YaPyjmh=vzKRceOZy>AvxDl3?wh)lv;{t!oCT zp#wb$3@tE>(9pp>6MGn(I*Ta8ZKDOJ)1WdT&-9(65F;i4_!MNsKf<%68v=lUfXAZH z()mDjfsg!?@Rq8EA~?a)>HjdnJrXhtP MIaRaQ+{o1hm_tboKEO$Q*yz)DG=;-;jHz44ul-1&t4mr?}@m zAkd8j4Uq;~T2tIEidz6CzUewSR01TJn98Gsq9_Y`@GOc6;W3B*@v|tUP*Cj?Vu(Y* z@0Y#?PBm7ld*isW8~7rD8ZX>N7Qo&rS^%tjNz!pou_1nV{$m2#6TB{W5?6oy0YUK5 z;h%o~1A5kGVlw9o1l~U^9pcI|wqX~ g$JqJ+?to5gyDA5Su(g2!|G^P@YLc5e){qeT z ||1 zks{b*fkrrf$R@Z~!t9JtKl7W1iU?$S0wuYZUnKgK2U{O>5x`}WXzU_5cfzvUpm4jc z_b2`+Lc}XcUm}o!=h2@f9(p|D^LRF4Z{Szrp~p~&E-Mq(P9RvMF3*~y@QGjxa+vN-~U;KQHg{GTQMx7vV*34g8gztx8Q z@9O+-wfU_!ARYXTHlc{$PU^Rl0xtV^Yy*n5zhfJ)G5>c+{5vFuUQzMusPcCv_4lL7 zKbZ#pMw|aKv`M3DR~tAGgzL4036Sj%Ucv~#r@E azrR_tk- ^Zp8$*gEzl=Li9JvT@s}ZD~u2!MCB^6HpVF#gZcS9Gv zh|66v>K|VP8fm^Xpc8~Irh?s-8*R5j;=gp>T}|6%3%!9Rt(2sq`RY_T7>C+4464L1 zANm*-vt1GyZQ{eWldk 0%QG~q!fcpiADjEhshXLB7u55(u znHEa8JNDu4_&ZmDI&Z5SD43|`3gI3WenU5%^nQ5$(aYo#8vF~&X{Uo8e1NXOH_Q-J zq-(;h=MU_ IgwO$1LQIO~!+yu2E z! DN#&bAiR4 fhK9eB{^V;EJq{|O9uW&20) z?)JTA;Ps|a5mc~~ UN^f00ucz1i|hqexX0Q$&8#k%PDwl)+3rPedzpyuK;Mt9bZ2 zcR~)Nv;N?XQV?JH4hi>3Zf-QrM1|fzmK50>yqJGFoNQ)CJoH-f+v|*K-t1w-WNX4A z>}OtSd$1Kex%-m;P_7u*BomwxLq!Rv%$Lc%04Wl(WQ(~~_ef+`?D{v&1sB1K%cf`I zLjv2*83uty9q4m59|!vK!i)P&@P{3s>Uay^M{S`F+e|HAjjzbaqS;hX;bvbBOYq_0 z2t7eh9d6D;oW9#poUdNFRwC~m$p+uP7D~Fu8o3D !R(|W%PB8{M|y+6ChpQHnVg$`6O(i6Nta8(r`KbXubX;% zE$_@s*?Y6hNOF^qn39#O>$SQJSWGqK`XGuc$1`|z KfM}`xL+7s_gV-(Fy1DMG0M_6x2<*2j>QtiAY|O-O%Ej zO4E49MZ>A6f9XirGZd_Fz&jYTF@C37p0n+9UgFvv>+S+aS-o_jZCSd9i0k$$I0BO9 z1|3L`Qte!5Lak+RPcF?hD}=OIp~0(Yx9^{FrJ`AsHo%e}m5(cbjMkR@kRDz-RxME@ zH+HLX4lQ`*jw_mCn@?nCOp`K=Tn*_x&+XHSZ%DKbh-0 JmJ?&LUh$qt;V6& z0w1)?z~5(^bolp!M_=|Pzihj& 3Yv$!)OW?Mw3pb)2)R#2te=Z9gUvTIK1HO_Jk z@#e6?pd+%$wqeKAy%jkst1K0+2x0}1e#gc-64wU#mrFju;LkplBq4Xd%M&LDPvs;M zU7=7-PRtb;QdA1Os4YNyb_U&--6&IV*;_E;AOo0ejP~oIJ-eN`12V&@Pt7+`uneZ7 zghV^cL(2Cm1P6}ox;Rb}WujX=-*#+AA9;%DjohNM!|{2aKJ~vy6x?j!igzmgXc?b> zg>^L(BgcAQ<~$8&i&|(SsiP%X!?s!QuE4uY@DDVC-gMQyGhGJO-LXW2mqK>s_C!~5 z(9!bov`hkabL|sKSe=?6DUH~bT8kPVQ0uBiffr(DILl1TJw0T0^(e9Yy;Gs6leNhV zd1r?dRh%yB@w7P4px0fkigegsT5~PB9LH|c^xl8hIl$L2S<&{aV3pKhfBCq?08)dL zw1kpTPGX=>KasWJ)%arTWFB?D4E$L@L1~YyOs`+-^VUG3u#4ovH?uU<$(|nYquq03 z`YhS_nW2i+NfuwMVPX2RbfYd{Z{7!`y4g>!y5dZoC3N!=NG^$Ol>BW9L9^np!1a}d zZScxND^A{1o+cl4*;qa=B*P|x6B{ej9v>uPLm={iSiW6GLb7?WkI4(lVVFc96Wr;w zw?aV{I!% awFt!@#+rj3$bR|`~`ZqRqadT8JQht zJl<6G-do1{?yfbUR{lgwOApp|!DdU >_!m_?qN?e2U@Zo#F!B| zq`1@eB?1h7>mZ~GSiGEQ5kz5>R6x|OBNQi-Woc`;8NqF#Rr`T3gEjwf;R+7khZ{+E z8}R{#!=ox;2NHIWM2ABo(egnuIkpRLHPBGZkk%;jQi_Mk(jvG?p`nOSurT+~UNw?G z=$RlA(~gjwDL9##qxAl&EcwG@PT&v`K%B~b7XQNfd7<^Q&=cbZLIXSz_~3Wmke7y3 zgdzf3Z*CN1O?u+)MTyYwGaSbD1>{ujr-P;dFT)z|D>r#^<^Y4$)oZ&{_8XL}X30W4 zG7EL9%Rw=h_C%YO-H=44NH4tAUniAjh9+Sz37!84XN4ajC%Z_Vu!HjwsUmLWw9v|9 z>n0`80}@obupOq>ct#;8!0RC{k$4=?kJ9&!|{x9xn0IygR0sX?>RHZ%Y7M7}o35b<&t z#2Q*XuuStJK*Tg!dvl0`1R_~!i)3`T=sG~B5M|CSRigr`W(!yiP|18dun2G^V(ZG# za85GPUnnKCFh$(LxM^_<<6*z6K} oA{sBY-@n!EvN@ Ud?JFhgO2YxISNQC zoFSp2;c6W0=_i$4{9x|4JQC2{-?|EDEu33|=G4=@ng)i8*5t!!oE#~b_{#ZSwKeJ( z2s+qlPvE2nOFW-W3goP)rNjXp&-LZFvu#`gZOw(|H{5hhUHvffnkVUuXx7~hQ=jDJ z8eaLtdozXd*j}I1<&};~7q$5Pf+wRkm*O`ap3mLxUwd# TI@3PQ$Zf1 zccU&|JI+p8LZM-9bF_Z!WXOhTCXruSzIoPeK!`33GW8Syb&!0s6x9`gtqsFB2H>wb zqV|b8+_h-D%rgvrRYyXdEvR6LL}_o|RTRwjOT9fUIR}o(RIPSak-)srEc%cJx7a`f z_SxeH RW$qyPv(O0Ddz`ZhsEn+} W_v%Z!+`ZR3P#Yyb&*1@nr{}r+ zCV1;=;a~>aQ>qCr_QfW8pVz1Dlivx{%S*TqC8`%PA>-s0JKu-od*#Jn>{P&ZHrOky zo}L3gh%)7djmbu>d!Xc(>s_4gbW+JvYxWpR>O;6=bZ(7VGEO$jZKO>cXaOP&QWoB= zxZ $iZ#kG4&bFE=~PNtPB?{aq4xPH15va#IQw^wiOZMO3q z4xye$ U{6-nlcL+&*fNBHKzB^3ft(ewpN~Ze81?%U-p#Xj)=zWBLSF_EO zaqebM?~4|fnE_~}W9ux-^+reDRgNa!7+1PbI>))$spUO`e*dQL1*Y46Go&cQXX13R zD+hf2$+C||i64XyvY!{};c-NMwDmXjAk=*TxyuDvfOee#!wwIPYEw~9=FuK=8@_2@ zxL+_Y{zgH7LL^FmSPc5Xov{ OU{tH$cK@$H_c%1*`DGOp|jYgSZ@@^ XnzJNXSL0Q6)f0x_H0KZD>uc< F&in ztCwG;_a6H|B?K=We lZlA2QhaDad$%oIa;Q>$1)!}BdO_BQZsmP_$%2gSUvFxHk zvz^^8GwAI_RC^?QJ=+n|$%UF;Y%hh5bh?8dQ#<6bI#EQ^E%5yHfNCpe{6XF+dzj() z 0 zss bB`-c$@ YAn`XZgI bH z*Ifh5eNQM4H#g8J4-n@56PG*A0VYmm{3iO2-U@G`85w!_KzL(CPYEmn1oBiEC?ej^ zLZ{7-hP*X~>>gb7{$K ?dK>Mf$ z7xlVAp!x#Ja+u)noj%x2n$HxRem;3R-?nGYuH$KMaCuE@BiSNLMbX}V$+mrxZkMD# z6)Ruv$V{S4NGi|zEdOAaU)SSYD6Y@x@OC?6Of2YEwS2^Re>h)x St{ z3J2|o6=t9r6B}GnyB&n>G7*q rp6hdwb6z4Y$2yQioJ zwe*OZ(Ou3et}_FXs_{32BB!jill}j{_TD?JsdVigc2vfK!l)w|L_h^WM2LvgP!s`0 z1eGc+^xmX~j;M%;fPjE>L6P1&1Y$#KKsupCS|VKt1VhXB>^L(=<~VbF&v~!kcYfbD z^M`X?D0}ax-}hSUUe9)Vro_Mu$|wCCyp2z}No0(qaY~FZMLjmFFO$VRqb5l9 jGqvws zjE|vWQ_Z@Yjj`o5i=jU|XThnn@chrOy39Ij-h>bOg*nF0_jK&@8 sZr0GMLLIf~S--OFG^a^I34ic0ISdy_L-Huzyz9tVMFPo^$TyC83s``?>bk z+Hq_R4er%fBzsV9I&I~ydEOA8@+l6+C*af?V}=xSbbBy@h*CgiPK8?>Y>$st*-2|` zhU wW6P3+BD?RBO1S21^n8lE(_(!4Rh)*hC$zIJRd3xTQS6w2Tdzt`n5_YC z+@A7~EoxAHI}<#2t?v$@SmcVZF@u}jBE$2S#ar?E3r d}O zy0qX;WrkB(*G~;v40h7iCv$cYt(AswmAxoEiBCaF1ltb%ypji @cWljdxS+lC#7&nj zPI#A#Lh)|P7Db-zw9{D)5qLoJh&q^3A^okfm#tq@v?EZZ{CH1;oC!SH(`i9qg>X|F zJE;O+{a7E3SywGGXo-6!!n5g}^cc6iijURyyF2CXcAfh4a-oFqbl;oi32uzGo=}6L zi{AbmnOHEI=b=z}^Ju &$*HG6}g8b7pDV-U<`7qn-XB!o?Ob+H_-P-PwDNZ1+~t~LMIam3Ogc( %3beOCikvnWYp+Y-|C*rNtB(M<-TyKIe z*PajV`VR-8ER3yEs!PtAQGNW;tuKA}Qdyl%n @UneRFuLrz>yu+K%4*(=TPgZTBUPIW2$*b;g>A&!f|cB_ ;5E3yXua)~9_)0Zu3Ledz@jXe+xH`*O*{Meek%7{4{yp-sN4buFNUpi znVB}rR^FyxX9oM_(_t?x`1zhN>O+G2T`y}FxjofxVfuScA@Gl`8O^6u9Rt{0aIp_S zWF$Pfx}S4k+zhi4l59*gJwm1ICSMNH117{XzS??t2g*CG%W-vTc~J!W%V+z5>Xj0L zlZelo!vdBIY)MV{@efSbxY%8?DJ?0!b~&f@9czUM870=*s6dmc4^nY_J+)2U{X o2#pwPs3HFvXzyE zR<8!#rDb-Z)|p )joAu}}1R!mt@%U$fB_jfXXw!K{nCdSuVhN%J|R)iUd4_Z`=27|#+-W4w4sU%Jk; z@1^ ^!4lrhO}lr8;Mb|Ei)^S1<@6anpp_L%y!xkI6AfXxGkQ_v5cIU( z^MrR!no*KNLq}HzoDe_0+k@gZBM1BvXNUuBdw2J7Oz&L~5a5+f>yYStmjWls1;!+U zX^GU2p5v?D1>@yadV>;c6MLt2%x-UJ!4^r?j8|5rW-UmX&dW@R6ggG*+CDb|2lZ^T z6j!*n*T~1wv{Ge_Su)D=wsXY&Y5yK#UQKr7LE#X|SG_?}RW}?ym0$Q#QaD`iRD%!y z>ZL$l{JSEUOX*?H0t?vbjfHHIHvJx730sdT&V; s@TF0}rV zw;r@OuG~v-Y8#OwNB>ZEZJY`IVD^OL9V6E^EWcEFE&p j&lm>Kk&1oJ_!BZ!Skx B9e{VbvV|uxmb*6}$kHg~uPG!tduFo;Rs!Zj;8o oh*C?iL*>1T_Zs=>p`ZW5UU`}Ek)Qak~Xl828kt^+$tFkoy>n&J<(&A$F*9F6SK zzIKy4e{Cedljt9Ju6R|K3^PNab)MGaUl25&49eV#(=`c248N6TUTnHl 5!Osf0~)Bg}x~kIhVDDxL$}ca&BZr4V3iYt!f@@(fjzD7T`2>!EZU$ z1hWlb0OH;O#RJ~SAf~DD+;pCtdqb(Tm_J4h$(a7^-8-De>WngJ ^YFw0?bkt6X7d z{NjRl0UH1VHbm8&y&olTi_AbB0>{TL*5&tKss$_=H>SIBlIk0TR^(gWd7ou*&|h<- zHjr$x5+X=-{Ln&Q25wJqnvL?(k@rziEp{_BHlN#FP*)kGjG#=Eer&g$Pe~t4_T2^6 zEa;BeQnxU@`}M#FWbsV*+T9}1BI)Zc74Gu`S spMmY$= z{yieF5Y>nA0e4cTmIRdToQZxc&T{_P>Sy`3+9wHFCd6$x|KI?&wYgH$y{7Xcl~?t} zPb5FEOA3u~0TE2y;^E6$$)G*-KT+6`50EBy#8C?Sk_V+`3 zoLMdTAMz$AzXa`14ux&9Yuoyu0C4 zw=H+%HGMH~7VeE0wgWo?pnI#_!hH2F!%@iMEd8{(Yv~d!R=QYsvCh(;y^()xS3nIZ z1pqGO+f5tC20*-oGcKH;zFq2Vt`Wo9XJn=a!UzG$02PhqTKeT@37&hb>OyS&5o?ip zvL6?{8{o*tG2Dueb;t}nd-1AF@OulkZA)4Jn3xW|5fNfn6+Gt4S^z_$)_YPUkzO(0 zNW5f#RnpOtxsZCh$XGu3YK3bk@H5AzAmHNFq}P(3vECx` n z_S> 7OHVH2wo1$h)%TUU3J`dbC%356pY&rYwT61SS$_WY=v(eKJ z9^-&9T)Lbq_r8f*`Z$CsK=u&P%@XU)-BCHN*UHM6`XxMw*%)D7c3 1cj@%Q4mM+doX{fr3Xj3qY5utz2R!^H!UVeJ@)cU5X`kEJm=y@LCCb& z`s+~rwpd=>dE8X6XH42U)s RXlO-!;L6hWss4VBKA0yCnA&m0plUZm>nJ_Z zUj&TQ`YU8{a(9)%u6NC-sTh8?byisA?A53fJyDvGb38$?aSU&)RD&JIK@S^Q)sbt_ z(mO2@oh_d1iQu-}4x+-x_cuj_=}nZ%!KhgW&wLMBY+a%$!;iGE6 r9c~l>Ymp3jO9s{`5yONXF zwb1F8ukOv|hETif*k*MVYivYFrt-Z=XWP^)XqPldg-|7hk_b@;5N!QGoGM?>&M4-J z&l7Stp91JucVeI_oC!W6su=wmg0U|V)t-%5COV&Hwo^OFV3iF%Yx3ZK9&>}tY11M0 zr@6;Tkb6iRk8yU}s`2?I*ew 65!3-D$M zs=*J^+}mH7lwZ#fO)mgjZKfs-pZjJ{7P%#th>Y|E)dqbg>_1Ke>-W2N_Wlyf&3fe? zOI(z^6Y}yBZJ~cNcd%!I7n9}JJPO?Q;dN7FA?i`)Sm+^^3m|iBxM|!GUK{k6alf0* z+h5~;I}rEZH^&Q1eA=G U~HwxRB0 z+#5@wEX8WqnLgW^wV9+?I;Ww-)e0H!Vm|KOs|L{Xou`nxl#(4-OZz7_?+$-@aBfic zM$YfB%oEE9{nXqCW2u$=ff`4#?mYf99RHhR($Z}@(P-Yh4by8fB)q=#1q!n9AeRwl zS2beY*+m7e?W#3Y4Hj%;Ih2169A @L~LyC7f9#=z|RIT{gx2k_;mjb^v+#?^-8R?7Pb>C8=wd} zwJ@u?JHPEHyc{5n24GdCcYxU3!)Iz5acDIh-z(l!NqFj(Z!DnH;urCGC2nO|by$=X zAfOK$h7fmL!VYq3i>%hy+oJddiHQ!+V@|t1wwONiN+np(bW+2=6|kN!jX6r6&E#?Q zBYREF;zbK~uPd`k(g`ox3kJ=)cQ%6)npwomx7A{pfgAn?XZ27w68$(o0+M7X6o>sX zPp3ivd*SExgu=I633;mGC|=JuFNn=+_WVLcW^C5)mTEDNW;CQX^pFPgce1eQ-T}ue zre2qFjf-;RF-sow XVBU)9q0bCaVZ4qBH!dBz TAg&fE )~RBkY8 zIYzYh9MwxT%DYj7JEgmJcLHH?H^%Ko5_6A8-*i>$kjiji#HE+~H*9cZv{scvkXDV= zvzRl(FHKOoqFVtDoih6q4+szM!r(S}z2=&8U+{ZeH|(8{!{@Bhmp*Tng-2KAV@J6{ z1kw(pE*)pNfYd1L7uTo+z`b=IO+w*e3#&^#_&yvUvK@r;_uJ#VY8Zx@PZC1ewd~on zsvEMW@OFuKib}fCeHI0S-u!B9Y0j&Md~b=lH@;FBatO1}jB?;ofo 2l=p7Xm}ADv(73NK&R0oxxARC8~hVVg}&;zfh3S=qyD*XYGPG=V+QJ=+-%rF9cNC z3TaLpXfG1hWpW?}s}f>QmN>tYP4j`+G$02u{fQM@jceQ zD+duMUsM?MJ&wl$0G}o^xu1nbE&)CUa9V#WXXgJmlrwUo(9}VHVFf>Jef$%kO?M_= zz5;%_ iNa42*8sJ*WveGN z5xMF9p{x578V^KEq7V^UCx2J+PlF*g($@DS*ZVa@XnkdN{J2b zZ(iq36wd0Fv@W&r9GdPcL0ntjCmi)p8)deUT+>mwoS6FdtP4o;ZXPJ4{$HdxcZTi; zC+iO6#(Vq0qQ>kSMYV?|EwjbEru}l@57$RNY1;aI`%Do{KNEZm&fB4WA=9FZ
wN S8ZHoHnxCq6IwO#!r`GHUz_I2&mCpCBEp9BUh#W;9B!`7@$ z^BqhS4ctD>SWgZZD`6>nGM*(I%X5I?>|Bf(&vo04YuWc4xOi@z8-~b5iwgt!9|ek9 zKY2H2sD>K6lBriSe)30-^H7#(y7E}+3jh8LgRX`4vNjs+h9^6C9rEjhXfP#A9e;@> zajLiG0-rq;Y`w0{!s@)ZdL~coxw!O|y(I}sRJ2}wISO}xP@4dcr8j4dcnEIJjL06A zp#%8xdmzP*4!4C*pAl4!KhY_4x9wDZvTvN0bL%s+SzJeRxxi{x&Pn%-QtR7l*(cq3 z+T3UnfGkVkKK&!65G97k^C;T1Q^Zr9$_hnnJX)Hm1QkJRLV=mC{>sJOm0i)k%di`3 zAFKqe@P-YuFE5F;e4a`T1#4hy5R8oL)R|q25hFg#7;p%aN_*DzVK{QCxqAlD>~4Pe zU`})L&$37cxh$!quEc1S_J8IaOCEPWCY3PFxh|DcTI#SL!#JAfBkHVkxm*tXoR2aX za3!8`nq|Jo<-S8c(?R~xY0DF?Ec yJTC4Z^JZL`_w(n}) zNj4v #vXQ(On!8ahD(dbaJ;uzE_Tz#oN %<
1sp=Jzuc NoPL`C_0w;DZui0Ic4m} z*}hc`R|w!X@P` rGvt4!XeY@|kbu+W<`YVSic< z(7kZV+xP*<#W*gC54521au> Ew+@m|YG!w9 r7W$MXLqHM7raaeg4Db@l4!v3W)>5cZk>+{Tt0A|==OTQq86>@Ki!Hxl z4^=_N$P_oT7P{EJrc_?RKmUB?VV(Sgn$S6SJcZC`O*OoPCC7C1c{HzotT1aiuKA<% z4vV$Kk~?PeMwcei2ydo^Q$7@gx#~NjuDdmi_iWdD`AKYFY17CVnCpIA-uT;e_x7V= z%yvEIGmf3g8mrXhi4XL@@W6I7j=43i06w;#nG2B0_9f%#*8r(}Zox~>)oYLG$B{y} z>&iDs5-Ga$nX4I3fN+()ztq0pN=l4)hX5qabP1#8p7&~lBuRi1u<}6x>Cwc9zf#(o zT)3Xa=X!jzooVc<|0>=NgOez6RkB_ZmNs;tKj6E*!59-q9d;rdTn#-fT|R}#86DJP zg^I=SGYw~{%&Yh!Q}fW)jktC7x{VYPAOgwdZZ6}~80t<^f*jQ*M9MC+fO}yox3YLC zYyG&UuHdJsD^;$2RWbt&L*1>V)R^EY9f1|3kn8)rPdH&dO2ddBLQ%XVuO}~$pb#)- zQl1d%vN5^0Czi!tPxR*UooWlrq^eW%xF+0UT;{qy!8h >+n+x!|Xx*8cw#a9bHuTSnlPr1eu(9;^rg;W|KnrDOf9M2Cp<|FPkH10>OUSHNl zoY9Pm#hx!T&Sp)ULD#k@OtZpzyjR*ah?VozgYNHxn;;6;CVP!F%_RY0Qd9#}BzpJ- zYL-%FExpdRWl0G@x>;i53Q25;ItNsV8Ii-3IE@pFlHBN A#?rfI$xWVq+~SUt4@ z)4pCD(1a7&FE~UnAp{(j%7PflGR~AYO+WJA4(hXnnJ+Ar(u)Zr!F=eKS~WMut(21~ z12S1(<}m7z!=2G?bKefK#@MvXyL}N{jAPSv(o48*>c*&LhYsb*dOTm d+TYgdN2gynKy zYZ3N_R$M>Ua!}&UVApU>1gyED_)|d`mscy0mCPqZq=O7j&1C*H@`E{EY1n6}#%N=S z5JZC8OTIDVJ{q@@B!Ib5G0+>)36ok~&S_BanNJ=}*hQfiK(&43KV8(I;cO1(`6-V* z$Em#D gW^=ez(xqvo^_bL9*hqc zpJ~smK*0AN-C0=XU=>Qy-+FL@gZXN<(e?)?!O70q+9TK6YmQeiX>}miG4vysXP>T# z4qDo{CWK2T9ppofNfR3#jXkR>?(sd#b=tuMPw~m2rw^(CtYKb46$g*>)rMBjKI}zt ztR>0GsOPhCXz 2nkhkmY5&m}rC+#+5Effmr_mF*krn^A2mvq zhI^kkPXh&;bOwOVzm9+>dPY(f2ZH+erS_Q4r&xUYC`1RkZiQe9=c2^B1Rdg!s# ZI){qE_XuT2>n*-qKcO${ZT(e4u@G4rr!uA~g{q zDS`5?`K{8LvDKG~VYkV4^qxKb)uSUUB6x%8s^intvNA*0%J)tVpqeyHdLJ3En=uET zy# G@wn_^>)f|PID+)!P3aAfPU9qL?hzq+gk_-u=I zz~oZu^mVIeZtEff>}sT_K&|NA7b&@E@jYGNpZJ>=fDJ4}FkO2c1~%RsQm0vCXIbUs zszUdU2kc@Ir1oAaL%_XY@JwM8P&v;oS6&9yMSx6=?O+-#I0>{} M`tRrAn-)>nNfy557P%ZIa8%ciS#G;Vmb1KO4%7dQ OxJXUuKb2bc7Nene)*cyE6zDV>06s$(P3*j=^ B^A1c)%d{&>{xRjXu{US|k+`ZS%)Lsl)Iy2- EmPLtG&kaDgFP{W7TNVvDJ0)mCqU5NRG>r zrKn4&m3?`+PK$P0??&WAjwVoB>^L4U!57MWOFXAseagb7`48h@N6g^8jRWq6uw&Eu zRfF<{RbIXQtI}?-Y6-K{<)k}_5iU!WpGGx{T^S34PE><2?EhF)mTQ*OhMf~On&Xdg zUx~?~JG4ARr-MPYtXLN<%wN+AMDDQ%t6Z{ugB_%_vW zzac!Ffg{{)m$S`EAFkPPr~pd%da|AvWl-J*Q7ATxBv|%SUom<&udRXSp_Y3wDTDG0 z^3?=n+9;H)cy1y{p=j-60!*X5=I-h$em(SjsHF+arHJ@yEqA+|agk?sXJY>gCFxoT zYClms2sSIB4{&&VZBQnb6{JL~)w%5>{yEldUYu3+kCKYaiN!M^C~2u&?FysN= %B?CoCyeTp=?L!F?Mr*!AO2HL(031LO!-Vki-jrIU?uItCR$?L^hVT$Dxr~ zK{PdHq#}30ZlK4_kiYz=d}d3xKTxzv9!P@}tqxKA{xn5vONM+ckR1U 2|+3fkE=TuUB2Jj7!%>tZ1%@e WMV4)lc9Jp2oh>=OrOHD>ctq*O#1=4?Wvk#YE1G-qEmFML@6DzOyoup=BKmg^ z-Pecs{W!%)2-FC_A};X*uNM$$fP8=W?Lu}X0ljE*C #Zg4%MrT3eL7Ef0YL19 zY13#+P@&4w+TkEr*R&>7ycA|-mK&}OG|^!Gaw_asy8upihd M67{|#8E zCs(fu`$zrz5A=|de1I22ekwMzHv;=)8*-RA@J$xir}g8~BHYz>520F)$xP+%tSyMj z^?LU|5c}83*SNHvEb3_rh)%GjxNx9j9hQp$0&S+S00-ir*HnsVQXN-f0AAh2uw zQL!X5r5 cMy3ZU7f_ zS!Hk QfmwauWMz7mFRvr736!gKgVaS3jK0qhi2UMRR%@J9)DY !IGH)HO@JSr$lBmb{HPJwkLW-tSxDQ$!gKS2rU+3_OW<2@5y^f0ioWemgTN zy5`1Tf!eJ#t46~kT?D=k?wFp#j_ dHoE!VMvmE1#QF b(hX)^p^Wtn-dG!L&( )c+>>m26LXa=|$$ShjRx@zl@g{|G+s{UN55FBzho& z{%w#UAl~cb-+BhzqES+|Uu+4Sb%cP;gB6*j1o8pMJO+5d9@W2jL7KV7AL !O>hWGD;{nI|l={kRG zJS_Uy!_3>AN8z4yk&rxUsgFNIk@x9wh_l7s`6(6RY+nniHM2_>4oMVxL}>M31|=}0 z)N8driG;t*H}s6zc5%j>>&s4QAZR$9e(5+2_RKV1fuYTquGd!fb3xdDGo5S>(aGg* z4qu lQXPCknJ(iCa5~Rk!U5MyuiFjvdU_My?2J(~i1+sgSE2 zkHn$=npo~l6`t{8?MJ1L^XX9@YL&Z{xHl~-5QGBMhyfYNr9~4-W%SI;{GPAT <$ z;cVk@hv#hO;X=CU249YU%wro*!{``?5!vM2qYuse_OACgr+BgnIkhI5y$i0pjet^zidm9ui1i);5&2j3QckcoM22s|`Dx%r-=#W3>cBg+^D} zrB&YN)T~uuevds|cgQya%OJS-_Ajh%>x&^rVr#RQ`IYgIphYp(oF5tUbj?fc@P;jj z(R2$_hSVbqjwiI2DA{E6cl_r7C7~8sdyA)ALD&Mwng4Is%w7XEvo!IJmHZ7_c8slm<(kt;t9JaZg4u6T z!NLFi(sKm-zn;r=H#GlClwrVq_*ayn2%rqW2IIFJ|1`5kIOncRsD?mv6QTKUAq;&l zhrbVK`72i5cwT1rBT-wr8|WhfM)YR^4%T06<1QhrqLG=H1dz Pu?0@{{-TzwyZJ1skR7Uo*qWS)BqkpdRL9L;g%amXuJPuFuj|r?t+A z9r3iv+~k0(r-?qFNBv#fy)8%^St7dDc4!yFPx0w8AyC0LJbM$y_DED870dE7&*f(3 z#(3Yduc+9Vj-mP7@`S$RvwMjopfu=mcFR4c%>);=b8Gbatt>Y)IRUS~5d)9O+SquO zGpCHtddHPSt*lJ+9p*;(L?3VAOC|w2_x)?bks?sq25c YdVay^%s?P0PLMwJOX+F^GM?G$vUN*Sa%t*E(x$ U1SZ)!XAJ!wm41`__5U_ zT(35|*O$7FfC$>a9rhmR^wWPv;;s6P9DfCf+RJ_L$LmWgw8GEX`gip&au9(v8|lW_ z9DT}dvFWZnA=m4*8(Zx(L-o8ouaIHMUK0H;Lf5d<-ScGF8Zoh7z# 5&lvonp`NTu22=C~~;OU|M#tiT{P*}1Ee@u`rzpK1RqqBB$zizm61Rrs;$6piMy z@D4Jv%@Fp?+6A%?EG1}g4}QK%zr*Kj!@I^r-)L#H&3ncZ fMgx`TkLOs+;^Az#RMsCI$J zBZIXAfNnL0qzgX|<|awb_bR*t#mg26r&cyTe((!?!+teR>gN1MJ4#s{uI}2v;9-h- z*K`6aPTziDY&5S$Ebm~4&n#4ynuR2{)=ER aeD-vYePJkfK2k?miZKO$|OZ=AmVaJ`7ykL>IXVnx)T z#HmqQXi>m) 8MXoQ?$l-ZBQXezwAiZdPtRf%z91b zMY|w^(X8&H>Nouff{px0uZ_v3zC^_Z{QQC>H%WmBZgN;v761iTUGNGQ;kEtxg1M`U zsW}&BSn2JKa!{v&R=YTS1(GlkM^|TWdp%6)?_7Z8;R?kEdD>=`!`2W8=$XqQzATF& zdjfR~0@~RDWSLAVoF4JlIbD=%_uA`qpzoQR%U65~b)4xP%i^t;*&e?jqyy@@aXG8Q zd=n(>*GZ}D7 |={O*O^FH!>Utd76UjjEI``)Ek@Z8QFb(rTuu zKngkp#*utIYTAU;bKlDQJJdnfc|%a+YPYYhtK_3V^iTSbfkH;Z%`hF{k~>Y`*9Vy4 z9Us;V`uNRBVqKPgd~y9s4c9@$wj{Z^e(oqJ7_2zIAu**!qTiT3JOKzR_)o>58@MVV zaXu*%m_nbVuu)S>Vo#Cu>+*~GgN%o8LwQs2&j9>gjvrY_wbVgdEVl%u)NWl(;1{G~ zed~fi@ixH-)>_@@ jFF{efy0_X3PTl}R?@^}c4s`!&HuJBQw@>Sn z>67&K)HAM80`DUPTs;G1V^|d%l{puJII0KS!z8&_S`%$%NW}$g2O4~}K()Bcm3u`q zt@wL3RP5eDK)o)5eF)gO;lZmMYS*TA?0r%Vm_W1n6g#m@a5n!au?fBY;90{qIq r*%^lVC%L4YGs&UKj$ zys^q{KBft7S6#%vWPFU$Y%rg)hLY>x2Es6-AYku1Q_3AwwPAgIs(Py&3OQ!~LrLA* zl&uIOI_Vf 8Q%WPV^BTYs0Hiid)T|n5i$j~bqSh9 zr|bM O0k;J~CY#1%)g}>6+q|I%n6ciAOe7?51=a z=89u=NpJ$OM9!4XhHpv6!iXG>)*s4q=z92N6TJKIP)u~f8zM@&xrTJilV73Q7AzZW zOiaQQu>CoxgU%m}0oAkXR9qeB1G^tw=(o%=712xk_-j! Ph^0hD!YqAZ6yW@%fn^iY1T&YE&VS(ws?oGY=nCc*Q2y z1SV3m6r5N3x}vO$$ek=_U)~~xNx*6$KA1Xn5(2OhO?eyP6Q+JSv aN@{K _9v=Da@>?a~`4XXezw#zgZfDozTj0+l?j4!K^S37UZ%r)C z1L{9EK=P}TC!G2mM)_2Qx45^rNTlFX52xQMyN>mWe04K={rQ_NAyC`-51s#Cl$kG7 z@92)*u*HaQvp=(ENbAbxU+OEdE~5bbh!s<_yVD|9D$RNef)PMryTjKI%~qEKh9$eb zd>dM^3VAHXFO+;DD^G@hr}jRTH%Z|8VH$(^5uN4iGoIC6 5V~UQesTR)<}T=p146n-EI^)#8r&9B*i$j zJZXH!k$gtoa3DZ4S5T*H?ldvM;qmJ}<_pNo>8KBp?VyHZ%ci{Qi&{ZNTeYP}8{OYE zN W?x zBuqtTbF6zda}SIvQ>wH68RhO*%a|+tjLaG}mP_9s`*`#r+AQ`y809gauklvI>AP2i z?oql{u!I0bjToFDSEg5w+|WaG=M%XFmrQ`QesN0m>aUx(+^#|d5X9MawyMq-33 WhG6EtjD3A-~>ZX zt8%HC-{OF)nq4g~%u0-wIz!`KlS&Q`x(uRts1+n&D$jNrx8=l{B7_Y`s` (H% z@=rT?z?8`A2mfddcOSA+hHggSI^Z&)fN_1a A#+*`zSy|YNCD9qkQvmXF4$=sJ4W{?l541L40@K!4?<*48 zAKW;lR!-WUrTh;+Yj(x0W9N II+OKJTI{h_A! zbUy%GSYuXbw$=am3vio9(ybNHX|Z*XS@@=3IhB2VnB)!KmU3ecjh@vpNM9z0j;~ z?7hvuaqArGcJPG^)t6ZQ@EefDeN}L0xs&CguRjcWrOQtM&K6%hX1n >Ry^AP{)*Z!@>@IxarXAlb= z1Yb4(*10YqHF!QLd1;-t5Wf1fe`jR=jmE@6BZEaeA^g+GxIiQG_cZ&)n)!R0{g2no z-_z`WzBT_n&HkQde;=BEADVy1W}8AL5JCMNoBbV|{YgIazk6sJ0MMiAfx~#DW@+D9 z`ymNqDrS8YGkQ+yQ}qXlm+rSnTZlijY>VF^%6OCQw;%XDCi;2K0T Date: Mon, 17 Jul 2023 10:51:54 +0800 Subject: [PATCH 3/5] Add scheduler to executor --- .../utils/executor/action_executor.py | 23 ++- .../utils/scheduler/extend_apscheduler.py | 195 +++++++++++++----- pyproject.toml | 4 +- dev.toml => stable.toml | 4 +- 4 files changed, 166 insertions(+), 60 deletions(-) rename dev.toml => stable.toml (95%) diff --git a/file_automation/utils/executor/action_executor.py b/file_automation/utils/executor/action_executor.py index f5e7434..c38c898 100644 --- a/file_automation/utils/executor/action_executor.py +++ b/file_automation/utils/executor/action_executor.py @@ -1,17 +1,18 @@ import builtins import types from inspect import getmembers, isbuiltin +from typing import Union, Any from file_automation.local.dir.dir_process import copy_dir, create_dir, remove_dir_tree from file_automation.local.file.file_process import copy_file, remove_file, rename_file, copy_specify_extension_file, \ copy_all_file_to_dir, create_file from file_automation.local.zip.zip_process import zip_dir, zip_file, zip_info, zip_file_info, set_zip_password, \ read_zip_file, unzip_file, unzip_all -from file_automation.remote.google_drive.driver_instance import driver_instance from file_automation.remote.google_drive.delete.delete_manager import drive_delete_file from file_automation.remote.google_drive.dir.folder_manager import drive_add_folder from file_automation.remote.google_drive.download.download_file import drive_download_file, \ drive_download_file_from_folder +from file_automation.remote.google_drive.driver_instance import driver_instance from file_automation.remote.google_drive.search.search_drive import \ drive_search_all_file, drive_search_field, drive_search_file_mimetype from file_automation.remote.google_drive.share.share_file import \ @@ -24,6 +25,7 @@ from file_automation.utils.json.json_file import read_action_json from file_automation.utils.logging.loggin_instance import file_automation_logger from file_automation.utils.package_manager.package_manager_class import package_manager +from file_automation.utils.scheduler.extend_apscheduler import scheduler_manager class Executor(object): @@ -66,10 +68,17 @@ def __init__(self): "drive_delete_file": drive_delete_file, "drive_download_file": drive_download_file, "drive_download_file_from_folder": drive_download_file_from_folder, - # execute + # Execute "execute_action": self.execute_action, "execute_files": self.execute_files, "add_package_to_executor": package_manager.add_package_to_executor, + # Scheduler + "scheduler_event_trigger": self.scheduler_event_trigger, + "remove_blocking_scheduler_job": scheduler_manager.remove_blocking_job, + "remove_nonblocking_scheduler_job": scheduler_manager.remove_nonblocking_job, + "start_blocking_scheduler": scheduler_manager.start_block_scheduler, + "start_nonblocking_scheduler": scheduler_manager.start_nonblocking_scheduler, + "start_all_scheduler": scheduler_manager.start_all_scheduler, } # get all builtin function and add to event dict for function in getmembers(builtins, isbuiltin): @@ -136,6 +145,16 @@ def execute_files(self, execute_files_list: list) -> list: execute_detail_list.append(self.execute_action(read_action_json(file))) return execute_detail_list + def scheduler_event_trigger( + self, function: str, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, scheduler_type: str = "nonblocking", wait_type: str = "secondly", + wait_value: int = 1, **trigger_args: Any) -> None: + if scheduler_type == "nonblocking": + scheduler_event = scheduler_manager.nonblocking_scheduler_event_dict.get(wait_type) + else: + scheduler_event = scheduler_manager.blocking_scheduler_event_dict.get(wait_type) + scheduler_event(self.event_dict.get(function), id, args, kwargs, wait_value, **trigger_args) + executor = Executor() package_manager.executor = executor diff --git a/file_automation/utils/scheduler/extend_apscheduler.py b/file_automation/utils/scheduler/extend_apscheduler.py index c791e51..7ed0c98 100644 --- a/file_automation/utils/scheduler/extend_apscheduler.py +++ b/file_automation/utils/scheduler/extend_apscheduler.py @@ -1,5 +1,7 @@ -from typing import Callable +from datetime import datetime +from typing import Callable, Any, Union +from apscheduler.job import Job from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.util import undefined @@ -10,119 +12,204 @@ class SchedulerManager(object): def __init__(self): self._blocking_schedulers: BlockingScheduler = BlockingScheduler() self._background_schedulers: BackgroundScheduler = BackgroundScheduler() + self.blocking_scheduler_event_dict = { + "secondly": self.add_interval_blocking_secondly, + "minutely": self.add_interval_blocking_minutely, + "hourly": self.add_interval_blocking_hourly, + "daily": self.add_interval_blocking_daily, + "weekly": self.add_interval_blocking_weekly, + } + self.nonblocking_scheduler_event_dict = { + "secondly": self.add_interval_nonblocking_secondly, + "minutely": self.add_interval_nonblocking_minutely, + "hourly": self.add_interval_nonblocking_hourly, + "daily": self.add_interval_nonblocking_daily, + "weekly": self.add_interval_nonblocking_weekly, + } def add_blocking_job( - self, func, trigger=None, args=None, kwargs=None, id=None, name=None, - misfire_grace_time=undefined, coalesce=undefined, max_instances=undefined, - next_run_time=undefined, jobstore='default', executor='default', - replace_existing=False, **trigger_args): + self, func: Callable, trigger: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, id: str = None, name: str = None, + misfire_grace_time: int = undefined, coalesce: bool = undefined, max_instances: int = undefined, + next_run_time: datetime = undefined, jobstore: str = 'default', executor: str = 'default', + replace_existing: bool = False, **trigger_args: Any) -> Job: + """ + Just an apscheduler add job wrapper. + :param func: callable (or a textual reference to one) to run at the given time + :param str|apscheduler.triggers.base.BaseTrigger trigger: trigger that determines when + ``func`` is called + :param list|tuple args: list of positional arguments to call func with + :param dict kwargs: dict of keyword arguments to call func with + :param str|unicode id: explicit identifier for the job (for modifying it later) + :param str|unicode name: textual description of the job + :param int misfire_grace_time: seconds after the designated runtime that the job is still + allowed to be run (or ``None`` to allow the job to run no matter how late it is) + :param bool coalesce: run once instead of many times if the scheduler determines that the + job should be run more than once in succession + :param int max_instances: maximum number of concurrently running instances allowed for this + job + :param datetime next_run_time: when to first run the job, regardless of the trigger (pass + ``None`` to add the job as paused) + :param str|unicode jobstore: alias of the job store to store the job in + :param str|unicode executor: alias of the executor to run the job with + :param bool replace_existing: ``True`` to replace an existing job with the same ``id`` + (but retain the number of runs from the existing one) + :return: Job + """ params = locals() params.pop("self") trigger_args = params.pop("trigger_args") - self._blocking_schedulers.add_job(**params, **trigger_args) + return self._blocking_schedulers.add_job(**params, **trigger_args) def add_nonblocking_job( - self, func, trigger=None, args=None, kwargs=None, id=None, name=None, - misfire_grace_time=undefined, coalesce=undefined, max_instances=undefined, - next_run_time=undefined, jobstore='default', executor='default', - replace_existing=False, **trigger_args): + self, func: Callable, trigger: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, id: str = None, name: str = None, + misfire_grace_time: int = undefined, coalesce: bool = undefined, max_instances: int = undefined, + next_run_time: datetime = undefined, jobstore: str = 'default', executor: str = 'default', + replace_existing: bool = False, **trigger_args: Any) -> Job: + """ + Just an apscheduler add job wrapper. + :param func: callable (or a textual reference to one) to run at the given time + :param str|apscheduler.triggers.base.BaseTrigger trigger: trigger that determines when + ``func`` is called + :param list|tuple args: list of positional arguments to call func with + :param dict kwargs: dict of keyword arguments to call func with + :param str|unicode id: explicit identifier for the job (for modifying it later) + :param str|unicode name: textual description of the job + :param int misfire_grace_time: seconds after the designated runtime that the job is still + allowed to be run (or ``None`` to allow the job to run no matter how late it is) + :param bool coalesce: run once instead of many times if the scheduler determines that the + job should be run more than once in succession + :param int max_instances: maximum number of concurrently running instances allowed for this + job + :param datetime next_run_time: when to first run the job, regardless of the trigger (pass + ``None`` to add the job as paused) + :param str|unicode jobstore: alias of the job store to store the job in + :param str|unicode executor: alias of the executor to run the job with + :param bool replace_existing: ``True`` to replace an existing job with the same ``id`` + (but retain the number of runs from the existing one) + :return: Job + """ params = locals() params.pop("self") trigger_args = params.pop("trigger_args") - self._background_schedulers.add_job(**params, **trigger_args) + return self._background_schedulers.add_job(**params, **trigger_args) - def get_blocking_scheduler(self): + def get_blocking_scheduler(self) -> BlockingScheduler: + """ + Return self blocking scheduler + :return: BlockingScheduler + """ return self._blocking_schedulers - def get_nonblocking_scheduler(self): + def get_nonblocking_scheduler(self) -> BackgroundScheduler: + """ + Return self background scheduler + :return: BackgroundScheduler + """ return self._background_schedulers - def start_block_scheduler(self, *args, **kwargs): + def start_block_scheduler(self, *args: Any, **kwargs: Any) -> None: + """ + Start blocking scheduler + :return: None + """ self._blocking_schedulers.start(*args, **kwargs) - def start_nonblocking_scheduler(self, *args, **kwargs): + def start_nonblocking_scheduler(self, *args: Any, **kwargs: Any) -> None: + """ + Start background scheduler + :return: None + """ self._background_schedulers.start(*args, **kwargs) - def start_all_scheduler(self, *args, **kwargs): + def start_all_scheduler(self, *args: Any, **kwargs: Any) -> None: + """ + Start background and blocking scheduler + :return: None + """ self._blocking_schedulers.start(*args, **kwargs) self._background_schedulers.start(*args, **kwargs) def add_interval_blocking_secondly( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, seconds: int = 1, **trigger_args): - self.add_blocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, seconds: int = 1, **trigger_args: Any) -> Job: + return self.add_blocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, seconds=seconds, **trigger_args) def add_interval_blocking_minutely( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, minutes: int = 1, **trigger_args): - self.add_blocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, minutes: int = 1, **trigger_args: Any) -> Job: + return self.add_blocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, minutes=minutes, **trigger_args) def add_interval_blocking_hourly( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, hours: int = 1, **trigger_args): - self.add_blocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, hours: int = 1, **trigger_args: Any) -> Job: + return self.add_blocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, hours=hours, **trigger_args) def add_interval_blocking_daily( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, days: int = 1, **trigger_args): - self.add_blocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, days: int = 1, **trigger_args: Any) -> Job: + return self.add_blocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, days=days, **trigger_args) def add_interval_blocking_weekly( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, weeks: int = 1, **trigger_args): - self.add_blocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, weeks: int = 1, **trigger_args: Any) -> Job: + return self.add_blocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, weeks=weeks, **trigger_args) def add_interval_nonblocking_secondly( self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, seconds: int = 1, **trigger_args): - self.add_nonblocking_job( + kwargs: dict = None, seconds: int = 1, **trigger_args: Any) -> Job: + return self.add_nonblocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, seconds=seconds, **trigger_args) def add_interval_nonblocking_minutely( self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, minutes: int = 1, **trigger_args): - self.add_nonblocking_job( + kwargs: dict = None, minutes: int = 1, **trigger_args: Any) -> Job: + return self.add_nonblocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, minutes=minutes, **trigger_args) def add_interval_nonblocking_hourly( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, hours: int = 1, **trigger_args): - self.add_nonblocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, hours: int = 1, **trigger_args: Any) -> Job: + return self.add_nonblocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, hours=hours, **trigger_args) def add_interval_nonblocking_daily( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, days: int = 1, **trigger_args): - self.add_nonblocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, days: int = 1, **trigger_args: Any) -> Job: + return self.add_nonblocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, days=days, **trigger_args) def add_interval_nonblocking_weekly( - self, function: Callable, id: str = None, args: list = None, - kwargs: dict = None, weeks: int = 1, **trigger_args): - self.add_nonblocking_job( + self, function: Callable, id: str = None, args: Union[list, tuple] = None, + kwargs: dict = None, weeks: int = 1, **trigger_args: Any) -> Job: + return self.add_nonblocking_job( func=function, trigger="interval", id=id, args=args, kwargs=kwargs, weeks=weeks, **trigger_args) def add_cron_blocking( - self, function: Callable, id: str = None, **trigger_args): - self.add_blocking_job(func=function, id=id, trigger="cron", **trigger_args) + self, function: Callable, id: str = None, **trigger_args: Any) -> Job: + return self.add_blocking_job(func=function, id=id, trigger="cron", **trigger_args) def add_cron_nonblocking( - self, function: Callable, id: str = None, **trigger_args): - self.add_nonblocking_job(func=function, id=id, trigger="cron", **trigger_args) + self, function: Callable, id: str = None, **trigger_args: Any) -> Job: + return self.add_nonblocking_job(func=function, id=id, trigger="cron", **trigger_args) - def remove_blocking_job(self, id: str, jobstore: str = 'default'): - self._blocking_schedulers.remove_job(job_id=id, jobstore=jobstore) + def remove_blocking_job(self, id: str, jobstore: str = 'default') -> Any: + return self._blocking_schedulers.remove_job(job_id=id, jobstore=jobstore) - def remove_nonblocking_job(self, id: str, jobstore: str = 'default'): - self._background_schedulers.remove_job(job_id=id, jobstore=jobstore) + def remove_nonblocking_job(self, id: str, jobstore: str = 'default') -> Any: + return self._background_schedulers.remove_job(job_id=id, jobstore=jobstore) - def shutdown_blocking_scheduler(self, wait: bool = False): + def shutdown_blocking_scheduler(self, wait: bool = False) -> None: self._blocking_schedulers.shutdown(wait=wait) - def shutdown_nonblocking_scheduler(self, wait: bool = False): + def shutdown_nonblocking_scheduler(self, wait: bool = False) -> None: self._background_schedulers.shutdown(wait=wait) + + +scheduler_manager = SchedulerManager() diff --git a/pyproject.toml b/pyproject.toml index 2dfb6cd..41e2eaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,8 @@ requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_file" -version = "0.0.12" +name = "automation_file_dev" +version = "0.0.14" authors = [ { name = "JE-Chen", email = "zenmailman@gmail.com" }, ] diff --git a/dev.toml b/stable.toml similarity index 95% rename from dev.toml rename to stable.toml index 36f8e0b..2dfb6cd 100644 --- a/dev.toml +++ b/stable.toml @@ -5,8 +5,8 @@ requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_file_dev" -version = "0.0.13" +name = "automation_file" +version = "0.0.12" authors = [ { name = "JE-Chen", email = "zenmailman@gmail.com" }, ] From 8ddb45d69f8827985ff71e56cbdca8ba9ab4ac0d Mon Sep 17 00:00:00 2001 From: JeffreyChen <33644111+JE-Chen@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:55:27 +0800 Subject: [PATCH 4/5] Add shutdown to executor --- file_automation/utils/executor/action_executor.py | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/file_automation/utils/executor/action_executor.py b/file_automation/utils/executor/action_executor.py index c38c898..92be5ee 100644 --- a/file_automation/utils/executor/action_executor.py +++ b/file_automation/utils/executor/action_executor.py @@ -79,6 +79,8 @@ def __init__(self): "start_blocking_scheduler": scheduler_manager.start_block_scheduler, "start_nonblocking_scheduler": scheduler_manager.start_nonblocking_scheduler, "start_all_scheduler": scheduler_manager.start_all_scheduler, + "shutdown_blocking_scheduler": scheduler_manager.shutdown_blocking_scheduler, + "shutdown_nonblocking_scheduler": scheduler_manager.shutdown_nonblocking_scheduler, } # get all builtin function and add to event dict for function in getmembers(builtins, isbuiltin): diff --git a/pyproject.toml b/pyproject.toml index 41e2eaf..ece61c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "automation_file_dev" -version = "0.0.14" +version = "0.0.15" authors = [ { name = "JE-Chen", email = "zenmailman@gmail.com" }, ] From b6672aa5a6f2a8abb5dda8e04665af4a5666df61 Mon Sep 17 00:00:00 2001 From: JeffreyChen <33644111+JE-Chen@users.noreply.github.com> Date: Mon, 17 Jul 2023 13:36:22 +0800 Subject: [PATCH 5/5] Update stable version --- stable.toml => dev.toml | 4 ++-- pyproject.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename stable.toml => dev.toml (95%) diff --git a/stable.toml b/dev.toml similarity index 95% rename from stable.toml rename to dev.toml index 2dfb6cd..ece61c5 100644 --- a/stable.toml +++ b/dev.toml @@ -5,8 +5,8 @@ requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_file" -version = "0.0.12" +name = "automation_file_dev" +version = "0.0.15" authors = [ { name = "JE-Chen", email = "zenmailman@gmail.com" }, ] diff --git a/pyproject.toml b/pyproject.toml index ece61c5..40699d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,8 @@ requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] -name = "automation_file_dev" -version = "0.0.15" +name = "automation_file" +version = "0.0.13" authors = [ { name = "JE-Chen", email = "zenmailman@gmail.com" }, ]