From 2374fe0d1586ead9c928e9f365ba49269c9b5e48 Mon Sep 17 00:00:00 2001 From: Casey Date: Sun, 11 Aug 2024 21:01:36 +1000 Subject: [PATCH] added select tool, but still need to handle ordering --- Alteryx_datatypes.ods | Bin 0 -> 29631 bytes scratchpad.ipynb | 368 ++++++++++++++++++++++++------------------ 2 files changed, 215 insertions(+), 153 deletions(-) create mode 100644 Alteryx_datatypes.ods diff --git a/Alteryx_datatypes.ods b/Alteryx_datatypes.ods new file mode 100644 index 0000000000000000000000000000000000000000..003728c07c997ed99fe7f266804a8647c91f4ba0 GIT binary patch literal 29631 zcmb5U1CV7wvo6{_J#E|8v~AlnZ5zAI-92sFwr$(CZQHi){O7%UBVNRb^WtVi#;RJe zvNEf3Rb^G>Coco`6Ac6e3IyZ|21X;$iYtsB1O((i{O=WrwYjy4qpO{Xft{U|xsidR zxvdSuUmIh3TLYjuklxnL#Kzdx$jREo#*rRq=U`%B3^X$_ag_i6wE3@@|3g^+N+Py4 z0CQ6(hyQ8~$jsnqYing_;K1;ICk3#wHE=ZfpQLd98(ns`c20Kx%ys&|)b<~d|Jg>o z|3;gEk&%g&$-j=;{@>d9pE0pBuraavAME_^G}<}XnmU*Of&a6u|J}#{9St0v{(s~M z|C>IVTN{{~02zeM9jy)Qfd8+#P*70+^*jCh{{Il%zmm0qjXA&s=t%Ei4490cwDD&| z3b^JCG?`ivWB>xP)E+cIO~LUzC&u-eh%bDTyELZ`#-K^DGq{N; z)>kkRT6QeJ(I2nvx6B|*(!By#Z|>#Zuzu&fe;%*?4NcVa%2o(m(V^~6XU&zi|F%b> zncJ0-o74Q@?KWwGeg^Z^pR;xz*YD#m`sr`%z3PU)qE>=a`KE6%#|?LcDiYQ5GLTRW z&t`MrpdcXrU?3p>&m8#ghVQ>-qlu#dy^FO~q|&&}IwMlYGtEzSwa|iJ<0!;bf{0=W z(26PL! zHB zRA;=!!wu1rOMpS^Z7OsekI0%=;LVhb<>20jcjgtXNqdG)XBk-$1wAB6+f9I&P(n$0|b$}p;0JNb8C zWQ28pY6^RlINAbZ35P7JR{$!xdE61ST-^^n1vJH*Xhmt)36~5B5fABl>og3P!pSP4 zL_&K0gv@j|mF>R|QPu=l?_Dh){Voh>48kH>gM=%vyg*z=#*oVLtHs{l{RDhlQVi?- zmm}TOtB&u~!gp5aEu<$(Q*Zy9l-P8J%sFOlIo~* zg$RJW&k~I|YF*9~qh=+iPz&`X*{>FW_+mQ!(bH~)uvJ)I^{gA-X<0si=(BA zhzHQeDD!T&-fFx2I{vsT((h$Mj=Q@f$;c!#5R{%!(mcZ4EsnO$o4;{*+~k^O4jW}Y zbkT}Y6jR(}CIQo@KdeI$?ITeXA5u+_i6ToJM?n{*N7uazONerE9jRxvICr8w6xIpA zEi5y^yZ+lAzquC>&inPkztkWZi;K?FQ1YZ+S-?KOb1Zd7f>E$1%;gw{YuAWA(W8im zD&AngoRv9&GwR|WaOX7Sv#vXGzLAWXgM1_g&Ql90pd&*!JEjpcA~v@E+o{lXfwO*e zn6V!k=}BNTV7Ff}1#aui)>3T2QEEY1Lekd@n-^0}A8B3QX3+4#oS;?zk5=UIS@*l7 zZOFoz`B8-JZ04HZT7}Xn^#+^Bc%bVgKT!d(f9t?2KmE;mtgjrjDnf6TBGI@f{3o{U zchWfmVNzSB9b-B8sQ1^IE8k`4A~%#p<>iDYYUp@S5dIo*yO`F3HikRJ86*wh?BBFX zDzPVo6S*Oa==t%5RZ*?-0r4UTcZ=lWk^+AB-53r8XuySpXrFhjEc_&&I8@u;%u$@m z7Zz^_Xggf28{AaYC2GW`8k&E>N1BXwwtXh2g4sr!UHL6ABV zTomNH8M9>22Bmg&Fu|FThTTUd4GnN`(!~_4$6J_Qs|degd&T_hgy+veVJAd@f~5J? z-U-z$fu1bT$oMFXC#4g@+{_mq5 zf74}2LbM4^$V7+LyddKKPX_r{qWzbkvK_Woaq=-=(~TB_8>u|EL39p#7~RiX)0^GJ z5+~1{d=1QK^bnnDO1Wb(Y32r#zh91VmAOTP5-E%r+%ADX$H2?j6~Hq?pu`N!Dy4iN zs5-(FsnMw)TVadGV6`urW⪙M-0uxrj5ABB|VfUB`c3rFex8tCG?|EP5uZfs{Fa~ zai_F`i7VtSe5h~sBv{!4YRSenYwplcjVn=0jla9Nm}|(_&*;FCQqQ*t?j!T=F%rPwd&Xk*lup^!@jMV2)T!nlo-klICO9W}~S zop;zG z*9ocjqGY7CuIsT#R*M{x;;v0AV!)GG<>*o)CFYli`1j>G> z=m?dmc;xF?ixYL$8RI(?hu3#&$l9649MY2w2H!*GExX;FeC7CGKVCK z&J70#5+$924&w7WtLAy%k=d@5LG_G;-%uU9+KJKiO8ep(@9+50eS86Xw@+-+wsn&v zALkbs73F%w9wN?8nlt^jTdn8L`f9@u|_nDXzl>FEv8f1O&1qfV&chTCD>!xs z9~RA*DQLAEvF-&U$o$c8)l0|wfMv)RkLcD@mu$>laBSaC< z#F;y{bkHz1b##D*56}2g_el%{%_C`d531C<@F^>7l>8yR{3F2%jR8MK#YIsq+e^sh zI=?L>+=;NyA~iD8ZDVQPwQQhDXUoUO$M0LZ&D6?tvE0%tovYTtav_l84~TM^sSk8! z+;DM4zUOS#AukO+ZS2^9MU6`c6;65Fx3@J*B*m4Hh!&sG132qmtD0-9bZO8lMX+OGSKVcH=&j+=LFG_ z17~MkU;~`L)ui@-q=UD<{gb6nSj~l3TQ_S{ySu)~)Z69JmSNDaSBL#RARU5MmR2nQ z+5@Eoe@p|71-jcT4@YX>j-uqi0b|Z*RAX;SbfB-|>cT5C^-N8`xe$*m*|eN26wtZl z)_GM0se{bmzU+N&keXQ2UXU!rU^IPyo|Eb2J)`x+n*^< zY!1?rE~?W`g<+F@73a< zMw>GeBx`n3SD}iBey^|-yNLH_h!M&;n#0Ed88x?}rk}NL#4loOGJUo7deKUQL-~#( z3z{rJYMcDN1TL?iO7z2G+EdkLApF#ATNMf8rA`|^)nNM!N~?+{z|L9rqUKm}dlE9; z%A@Jo4%wx={Bf#ki3xIOvzAl<&*7Q@&V)vX^LCEw)qStwx335vC8qMufZzPS zNmU3Y2|Qn=h~)!{YPyPM3kPm{sPn9npozhzM(@=#5{3Wspc;|2q^?D-uop6GnY?n} z1xBOx)NaP>mKTn;y6=YSwt9P6cPGR7wRVVkokdK)`86q8nE;_cf?B#~tC71wtj$Dz zCwgvB6&xIH0=yB>t?eZu<&`EC*1ujY)jCHjvGb$%ht*F@AsvA0#N%OkvL@c$u;{nA zEW`9x9pULSRcjvc1K7A21cseA(Dg|R2m19LCBn;2oLY{gqS2T*1|6+^V;tvW57 zIr0T_aU0khBIQpql-v{1uJ>nw`0{I<+I0vI)(bbeABI`;te_}l8;#05tp_FDiM1_# zQ#>p!G=cCmjoA>p8mjdlGhu@LwVb=Mae*n5M&bfmKnM~9c*A9la0rh{K4s(iDr>|| z!CgIJQ%swwTsvUfl-V2O21Ib*`!_Gi#UaI`U@SEMR+d~+P054|` zCci0XqYP<4=&xPS$4ZAwvfryBH#S5sYP)>+YmoizzSvzb*SjNJSX=!P{p(jzkiq@# z41| zp=J@W(vL=zed^|x_Ikstib`R+2w%S$TOji{$EncH9ZDN{x?+UcLCV*KHt8#G%MtvDkV%yJVo6xv|D`bPfkUrEbasdh zy9kE4aZeV+<>{$ZmOzXKmRvdvL)e-}5Y%)<3`4*lj^31*Ydl#AA3ha$&9*R~;p+<^ zC?ZK*bnV!LEMwM^i+Rn^*x{VFTSuqUx7%XI;&X3fOz)|3UbT{$pQ6 zHBQl_Ty^{l?ANriFSGc8$KPl00$_<|!~Gokwm2_?n_jS+?plNgfFqg{(GdO+xPgI|3oJ-Gff;HL-c=3bOW%oLnt4j zq^PPCrGq9W{Y6i8c4Z$S;;mF}#HnoP%b=6Bm&q&Q**M(lgIg=>hxPx|I6{6@0x0Lm z8m7Fn#F;cLCN3gKh_k}caQZp z>@_pF=S&lbD^5J7m6;3d-D zrUny6lwd&r2172i8yMk2$A3!o0zktwKOq4)KyqI*r{JYxa{B928!i2!s!XnA^hjsy zfV@f|L(0*{BI&#Ri6Gl3dlP#ln`bc=2TzwBD5DRfv_QU0V+Qgyohs8iLQbQ-|5Qb1uo5^C4@ zBfTfzp-^YP*1j`AMr2PDLLhFm1Y02zqsNtEdDyEKbIh{Xv_#7wG4 zAL{Vethx_0ueBxSzNrrSSD!b~p4_*EE<1+<>7ZR9(=(@*(V!+>=+x;5N|0xHvJzGf z{h$__x)8A(iNKX|2F+^fO>=-SO6Lu~i@t;&1Z@5l6x=%)q(t9~{eqK5Ko(>SMK+&< z7pjArOU3&h0V3a@tywSSA-2R`e$3ymU*1tY)Z4f;LnX~&{HWo=(8y<>P+R^@T-uDe zV4z75N+9~Fw;L2nNv;~6nGl${jX|;3s0-Zrl)HsSCz*5r5^}zWpAm&8O-DXWOZJGd zZ~OI|zUWK$p#omawIS>vuazv&D3Ejh6#2%CQVWy>dbC&&{8thl1)+i@u=}Gsg$|B< zH~wH}{mp?4W`^2&e+p&akuELpdnk{AFYm&wr8;+M z6QFYSD?b>?V`F%Co8d6IkW^6dlp^U$uE+0i@RjVGqzJo)sqDo`4~Mwt{iE8piT?(3 z;xnK037E3_5attqVe_FD?|7!H?1KTNi;5dF{m9U%g2*Nf1y}x1ojD5O4HCIoj&VC* z#KHuXGbDM+7kocHf8%pzj=F?FmDt7~i|qIU%YG85Li5(Z`|ghKLSHyu>=v+OAaL*J z<^f-OSxBB;T*?lzR7d#;upIi6`{Up9_Bs8h^!FU!{OwI)EZmu~or*lg-irE)_5E*A zYlH@Q`MG(}I^^qGVJS&~fa!d!9LB)oz$rTUbVV*)-@q6k3J%EG?~n;XMtX3r=T#D7 z6fiKe@RKqI$%A5U!7Mfm=n@6qss?g{jEIb33vZA1QhL|!Ur&Re#W8eLYqa<(9PeUw z^J>^^DGUa240kQ>e2`@6yq$rvvf@Z-qFPZ=L{DPQ75Vs}o%=#KF&=9*-N-9sWjvwk zDaa}kk@TwQ(?A5HFp}){5l99Lr1d`kb8{ty%J=m5v?GJna|@hXTnKISda*PA$zkCi zX8!&yi>X-+&Ljwm*=3z>`02ec6pK4vbSY)dCrWvBuH|^rL^>&rbFBhI5u^6Wl-%;c zk<@-dJQ|O0QqxVEY|7?1Idbj^8eJib!WJ168se}Hs8@42`D?6bncBX%?VP_lEM!b* zebDK8KiN~+16MEi=oMb7yT8FA0F0xHP)NeN%JXU@2zcYnX`n9ie%y>r|M3KAG z9q~7K)PzAEmVM%Jx;Dxyg<9z>coF!(!vbidd|4&){!k$>%H^@18mC*dg?nU!P&OU} zqq)Cw$t;Xc6SzdRKXo!icUP5~JB?uPR1H;hYVoW>H(Xw|*sOedc{}^-eJo$@cZUtl z&Y1;tZJ0M}CVtv5ZmhaL8*-erRjTLg1yb9TD zhFFte71&KSoR{4t9dxd16_$L~RiDwU7WoDSnI}~>kujE3lTwfvD*_`6 zY6c)_k#5Do`JJi+@#Z7@j4dB1$RBFPdqpT1vRf&49#2tk+A!9PnA#EYuO}~tc-H-w zFRun-WZY#|Z9ht(R8#nPTy;oa>;S_v2CX)ALD>C+6oMtK%*{1obTZ=13!Q%YblAH& zsRB8)mvM_h;+jEW1TVXX2H((MDz=B6h0yIspzb#Au9r4bwX?2mn;DvGUgrn1{?|u7 zS5$SavSpZN2ANmgzTT=x>RH9!`BQLa4(fc+Q%p)sSj8eYXI>7a{5_GiZ+{G61;py8 zsH?&Lr0HHUGNQ+wBH(*peh|bdj6XTvV{6 z8AO7?$(|~RyVos%OdwTNBs@WaoNBfQ^oKIziGvA(;xQpoRNy-Lj=rh0lJqASwP}8A0DL0P*ArY5F%Tq7MX#1^0Awwi;>tFhWRvdW zf+Kpy3Jl3tbz`}F%GEkWudO!M7xNBBMz~N==u*3qoEuswEm#DKZ%mvrJ$wUaL~tlJ zBqDB|%*;!oo%+LY&n9W*p$Dl2im|eg4T4UeN|ag3(zyLc`^J8SQweP>rgW09w6$%@ zvxedbU3#ZM`(4+D7)I^WMX4t}Xr$6PR!}H?T=AsOPv%cu8~B zJcl0_`24nebR|Tg`mrIJl(x+TQN@1c>(|`ktam_W>P~4PG=g2eG8i|2nk$VoMF@fj z)i}WE33*ZIAwl!ovj~@3O^+qs1`d%t*#)zpnr3NIgs?|I_iX9*PcKmrU5$B% z0obY2xEM7M!tJ-CJHP!lhoC3~%T|!*dd+ocfM+&I^1Ylr)oAYT)Wl8NLV`y*R7+oX zTNFK2^QQs80r${HU`j`SdlLfgbq{YvV+fzrgXI7c`lpIAx$%yx%%tT#9!C@V2Qv}8-Agsy#_21H@kWyYhNDzVG?PMNv9<=P(}sW!LtBL*4LZ_)g~cjAs^=Kq=hGmuGb2*pbjP-w_?}0sAoi~+&rJjFI6DZj*>+K(v zO(GX~@5WXY>$B1#&x@!n3yxx!Cz8_G7CKgI&9+#p(>R88+2?D>O~JTu)uz(%WsWCr zm#2~nBFU_^`ECDJWOEGb32Mc$$2Hi)cdpikY~gvM^I=J4|8aaw1CI@PZlf!OuU@+1 zy;yNF+p_1Jv*};wedZ82it@z@OT;F~R>RJ#j1z$|NV`CoGqh)Mjogp*@4e2Y_bPOVlTr;Vv@rNS`H45*$+q-!=Otx)=%MmhC-d?R zw9ziGX-6u$8X*51%>2lu(mcR_Z*rb&!}$q*eQS1ERBhP9 zadxQ;4_5+vjxC>zFTv=vU0GNVT5$ctj3k%dIwzN&7X^6|AA$}&_V>$@4+M�^euN zLBWqX;;whWMa#;}Dt_E8L1i8FKdL%^t@`$`8=PeGC9R1Dfms73j^AZPJsAepr5l~l z0>Orx1zvLI{wC$H(IO}3?tST*UK2L$3|ocyRgP)u@Y|F$xY!N;*SCHcDSJhb4LS^<}}MgATKs) zpXX*g>`B@hEMwQxm4S-BI@Q&x<@+k{Sc4r~ynK9>!_Lkk$hQXi0hulsh&F6EnQnLQ z296))B}VHiLs8b>-{h>;{tM{LE6M!h z@BGcz`d?m?ltPgXh)X+#*1gp4RD(cPqk*E3;hMH5MH^{yR`3cN(U7BX-bg_uuiqh2 zBfu#BtUX!~<$4gX_uQCY;=fAgpl|qC)(*1{j1Ii(W6}x7gr2pb%iWcNF@Wo9|!#wyq5Eu%OoHrhuaB0>D>~XdFac+j_kE<4i|u-5s9z^ zAmE>&+Q>&OKd{qLVzq=gvCVtT1Wt1K0Pbm8lF$0FM0~8 ze(dpg$QFdyVOAE|4O*3mof0X?`0*sOtcwX!_EM7JBa=S(hFfXJj{>Y>89i$s|9xK2xXXnstt? z*0oo`$V&DyCk}*vNe&bxH?X?PR=XFJlRt9&eh+Bxfg9k4S~*hndyz$B0wJWnKs^eI zDA8v6g}AMUB&01j#{cFEf^FY*7k4?A?YA7CV5eg}C#Z8eHs1q4O%Gk_)wkZ7(Y*j$ z;DR8usOlE3v2l^zgjtT;-t7`*D$IiX@O21&ZVwKa7P)H}t>q&IWk^NC5`O~9yEl{T zQ6lBC5b1ndDV$GFcl&T1is;BBinknWZM_Cle*&N2m-f2FDnDG`9cE^+4NF`;fA|ec z^TM>2_qoWh8J?(|eJz(2M__(((_Pzw{jt6M&PlF+O|pVQjCC=}L@sIE@;SD51>Y@6MhJ>**d;N)t(9)Cl-WK(*Lu zLF(Nm&vx0k;TJLY<2hP04|4aRST`?P&kyyIcw+iYRTleK{qYkP0tyxe91I2m1|A9=4h9wi1_Kru7v<*< zG#D^ccvvjRpEz(}xbRS@@Tj=Ru&BtWXlS??SopZ8s95OO1X$>JxOfl<#IP6?F!;=9 zxa7$AObEnmIKVm%w#MI0sb?jAjjAiwmejB-H8JMY>x|*ws8mKB5X(<`%YFX+iTIgxmYfIYc z$s6nISQ)5V7-~5i$~hV-0RdWn%~c%Db=(08o@UDK7FwpJrY2TEO9vN6D}bxLg^P=e zy0xE?QUZiVLtVeKguupJIKzN*AWLkJsTzFh+Ttsk6TtsqmazJuva7JxnMnPt7 zR&-8vL~c`RURh#sYj#a{S#m&rdSY!xaA|Hzb6#X?abj_CaY1EcX?=S`ML~OAX;V{E zN=0vG-B4lkXi3LRL&soePg`~GWM%JSYu|X&$ZFTzM(fmW=iFg$Wy(-p=4eaNU`NAj zXURbOWq;Srcwg&cZ{^BJ)8gL+g z+~&&s`uh4*-__XA?a<26=+^D*;Pw3Y?fm-Q+}7#p*6!Nk!`9x`#LmU!?!(I7*~-b| z*4^jMM9=Zc@czca?bg`d+QY@p^zqKp;qJ!e-u(Ig`u*BZ^M?#0Q$`T6;2jN{r%nJ+tbU_!`sK})7{Iz z$J@i#*Vn$MJn}z$<%yK2kh06#WhSgLn%dI!@InM*9W%e7Uv(@fr6FGTF9=jp@!mfQ z`=qo3J7AdOvlEfEu4p8F5b~m8{VYTzM63+x9e!awa80$o>=YZl&`daKJp-^mZ;Z@} zh*gbZ*Uqy%%Wy$^&GsfTQ-BFqXJ>91Z;Rg9!&RQYzkjH^Z9KpR`7jwxVAU6gYgn3` z=jg#|sd|0!NifEEzkDALR+#65b7brnAqpRZ3|D%dd|gK#nc>{O#kLEO0y5eAqfT>s zliT$dJ`0dy?qwTGPD@8Eb+ zk)$o6C(TEv2I6SUeEy35aff&uR%;%M=YRj6=d6s<_D)UFLrIUE&!%W(U^AEMO~_>o z*#nLt7)eiBx5IMWJ_l#}v$a&QxBsB}DRcEA1Keho3W#fcd9{bdj<6fruJ4KYuUjV`hK0I-rwE_%sL z2*{%(nFF5S;_@xo~F?q$w|Zft5Qs-K2i>MF6Oav*6v z4cgfjaf&BZb-Hl!60qnFE&zF>r8okcs<0jd_zOILid$|uMb=tr3EMfOsCoa1+I-p&{|vc)ERBdXlmhx5+GgcQ1#q|wok#7gwFeoxxJbNnap%~BqZRy zQJO2C?vpt)C{UM>eft7aSI@xsp;n}*rdTML`!wI7Q^C@YRJzO$U zD;#{2KBnlQ6s#%E=7%Hyd%|iFWiv^2A!ipLCQXEFWFI(| z;ieOpzj0xO(M$P15~a71H_8_qV4Av1y^_u%tgh`SvhjLzD__iMrlryUGNfq9KDPyB z|5@Q>@vwQK1%@A+;*ka}v)0c}UsBhx656B|)Zy9Kqy~_gxgB9_dbek8Mrg2HTqL=h zpUd;PiK9S5DVT+RVf&(mo$w0X+^9##ZCUgrtvjXITKzDFN^s z!13hnk;)R#p^Mo0;?>&PR1Q!qmLx2ep$H~-?vzsq&nXH=fz$fO0+-aNRWyumm-Z*` z0wU3Ofic}j1AD|*RgRr|XfcWGKZE?o*V*(-?jS4%W0wB-4zpL3Ug0>y4}W>$w_Pgu z{B_>FyO>X7-waC;xxzmRDz7843NPR7rLufyyA${j>-ja~gM5@G8%nl1 z78cni!tB9Dk@Z8qSpPR$Ec8L&2sybUYUX3iz49-q`+5@Qzn})H(yHSVBjSUx!=j}1 zw#&V~s}EI+oo@B4r8XC*?7d4g4<)8Z#NF=`XN^R%lh)2fNpI1nU>;GE^5sS5=i-1Q z?#myOCp~my*oR`?GlVzGm*t%wdW#wi;pF=)b=JS7c7d#$z4n*91n<%rHlH?S^*754 z8cD~yGZ`1C{kFY>N|#ZT#KyXSi4)Dxj9U8&)iriKYyd|2$2EQpTgl?9hqzr17t8=Zuor?y*gdLpB; zCqW`rmAyO>6LWC^+*h<-z|%sD6AY^|P^)P_FH0oJ?rsos1iQeMD_xuxV!rY~Fa#p* zEGr7Jc`=&W*}5iE!dwd_q>iWsvg=Eqq-||A4bX~he|Nw8-y($f7I^cZ!IBhBZ?SkY z^)UynxKGt!qirxvS3$oG8GFA5Y%D@h(ucSFMDBZmh)@Fw>HomJWujk4jSKAEUvJ;?1>9OGD_XWV`qDPG_;w(e=@R9%d?$-`(^j>^o}*Et%$(p;(}AZPJ+Qq?(M_igZVtm9zm z8kS`YHg_T@)l*8`gBjB$t$=h?dQ&(~6dKusaVSpWRIc=BS7Q@f zAfvN-5}PT0a(TY$ql2r~==qk6?dDI`)MfR?!A)_xnL6wYEF*^+n<>1VIf%N&GGgl; z>%;56_e2_%~l4>~3#*g0@kXH zyrn5;=)8LHqx4eLV#*TWgz{C21I}XZo^a{B8ys3_Iic2}%OX$W&!lg*;t)5!ZX=3# zV3N)XqM?DhAIM-Cb_xR`VI+F}giFpg4`mdPQF-WV($}b<%Zg~j10n+rEP_z?Uo8ui zU%GE&J}V((j9>SL4mod;SJK=2a=D<32Q~1>9A_t4(!BM~#mmJwVrP=LC>85?Lv+jojs$${nj z%zw-boAF4_#GB#$^wk|cU=s-cDzeb+;s%&_i#z3e=d3e*!1qu4VzZiHpZoA{GMOA9 zy2ULd7SbAd!5_gIo+76}M>+9P=r@7gUv9ZpB51YttLeL#L2#9Q`rg+d?{4%$HxuK24|69?}SA~oo?@a zO$uwBfB!O?7Ba8k=8oB*lrt9n!e~i6kLVlQX5*Hsh z3rygZLo<#o-0DKDl^f1HyS(f132CaP*xQunI#DjRjpi-JtN`naOB(dOv5H$ueW;@w z2Vcde`gx|LM>^R+%0G6>mRz)dOD{)Voq{d#<{z8xpj-7ioEL9*dT7yG% zTr*!xlDT$Ck$p;jaknH*d@kAx0ZXyyh^%;7+rCQA&>oYnn-ai&qZn1jg9CkNOFj06taOQZ%Qcfk{Pj@oYi zt(%K&@IgxWrj6`%?0w*TT&oG$+7aD_bv*9kWeEPMNWn3kBWv_&PZphSCf}u#jE`4< zb;0xMT4Ys-N&6rG);g9CE5rFo6|?utWitr8cxUcsEh0-E)I-=#4kP~rT&h$qLzmmt zdaULc+^_oc6-%2`rv?`cbcsxvp1(9}fom3oI49w?DrKt+3jR6|#O$G97qi4>VZEWHI#5GgNVHlgJC=|yL%^lkO!ouW&x5I*99^W4`AIgh2*A{0657#azM#yEkx?% zkVpg~ccs|W{d5nIYI4C`zXNp8!gevvOTp_9*1<>*)45fl1#AOxCsj^JP2HI}LtUuU z?K=-WfS%&C^vx|^>pWUV{E4hp!-e2#mDdi~~2 zs2r$U-?G@lxay=A6h z8Z0{d&iqPzHZi|FU}cVkzKiE*xU_zh*IojWNp#ZMz_(bubx~X& zUT>QeH4uTB75KnLXEgrcnHuYPJ^@BTQVJwCma}B8l~a7vNlUTKF~U}<7c)O4IOC^D zGdi)4@_!gL2~uTxMO-un>(X+dCS{1ieB$09%5I*twM_HlCMEydpT|k;lOV8yb@RZ{ zGw0;o<|ywH#&4@x2kBli*O$rdQBu+uqtwfOV$-hcz#Dn^FVt#euwXf*@=Pjes%H=flK|>Uy(2!dqbH5+XrG&`j7ET$4 zoOSFLPVOHgZDY(TR{0w^Y*5R$5Gha`I+Cm?$&q6I-YGfsCq{P3)x{Jt zL8MmLUVVFSUj>YE1AVwK#YdKt_&6(rThO&2j_3!~T0hNq@MGrOY<}y(1=3ga=^r_e zllTi;-LzyNh4CiRpA`npO@j%D^Oej|>^XbYjQHu4+JO`%z&G*)zryny!`b2=dVs3@ zH%-TCb_wCbg-02I`jW-Mrw0Iih(@`busXY692;V>7W9}unEy`9SyozW-TN_(Bl&A=0P}Cp)`A5*_Xt&X>UgLL|uDeCvXGy zM?r7?{b*&mQk8go6gUk=(DtP}3EoQdve46PL=c@yrQJJXMFZhyP?0#+ZOh&SU&*pP zY~8Ax;6&kqNH<1&HB-0`w!XmZw{Q=Zg5SZ$$rADNWLVZFsyV_#8}RA4G=VO-b_eaY zBKT&j8;1FryX8t5x#Z({m3x2kxsLQGv4&W!Mex4#3fA8-T^N02>c#lTT99^+D}mZt zjS$k5)5^uT<@|ag7@b-z^b~SFbKpp}Z~<)L%+%wnEdrie5#aXAjf3SMhdPauda8!% zoFBh&PGX$n>+!*z>N2sE!4p>t&hMdzX|N9D8J?xz7CeMBl-sEDby8T1Ih~fMtO!sO z9R9^);Yr;16u`gLAujq>^y-V6cxoM{rFcTwBREfxabGDW!EXPUCKWQ5qa*J{)38s$nY3nMoMATI8ls`8yc4fZtqpz)`7^_d<^Dw1dtQ zHSn42Rr9k_nn*YnOH?bj)LgKV!1gEwp~(!4qMv!N8Ya6*+m`7E*Xywc)p4+zPt?=j zI5eSIsK~aX6Bp*HOg~HwsxFjy0%eh;Ml?s)Mk-tf?tZiw2i1bJ;&yV&uwZP;a}M7) za~(ACtukNX*8Nh)O6607b$15&m>o|Js+&>g_R|ssEDLE)zEg}k`P569vNnZ>$N+-y ziZh0BBuG@&=0wv@`6uFXrex{2!Y`J#yiW{`pJEeB-bB`$jT*5ej17-`Cb}+6nk=b? zoEs&&W6X?$dbDWwOp$Zl(_k*Gc_Kl(egWea`6nZZu@t%F|t0a;r_YH()h*Oz${Ue5So+NX?G zW%XnYB5pcfbu3yjoVuI1}BAWbONkD8fT>XP+6rciznU4VbLcTWwlLLRJ3n7Yi% zP+(gtkwMzxX@QuEa@O>#XAyHKLTVG}z`x5W6>?^=GSkGS7IKdu2O>PMCQ~Y9(n$x9 z7qMG&b2~q050NZ?XYcNhFHG5HDREW)3XZ{sA-LW?WV-6WPO2TfsvW-%EVXgf=--DE zQ|h#sn~dV1b7}#u2kp1Ks`n7GPA|~v+OuTJa5EV;>ptRUTpZ|*957GH3W$t~GVp5V z@278pZmL7J{DsBUt)wSt#Eql8cRO~4DR#;ZkR7vf$nvHf7p9NXLkK{fN82(%4~H#q zra1IHE?-D)veMl+Ed6@B2?cjbviX1=)}IYCGIlDRFLPKq#;DIQW;fP}Py$VeS_3;T zP7aKZFalDn54F>cuXR_>(B5z5aV=(sAG^*sP5PdO$vrJY&Bu(srh%Wgo$IP&>z{v5 zUospfue~(}MNi9QVYUutq9srOP;{-P_2HrX?WtSoGtI%D5a1Abk?G)xj<^r|{I{Ao zk#Mhr6Pj^J{nk!FHQ$FIPe)F*f?fO=9#<9L2wy+Hntt%9PiJ8ERU)iwy zyvghLGANzqBqHDw!>0NARI`t?ad$pPTVJaINf$*oE}QGagX*p3|EsgB42x^ovJgTb z!686!4TNBUMj9t*aDoH~mLQG0JHa7X@W$P}aR}}-t_^|4-GVzq-pqZu_s+ZXV|M?k z`nvZzb*k&Db86SBz1(-hSNLCcN5%_EarO#VHl}%;brGg{uuY7%T$`6K>pmNF%Wksh zKXi>DDR=Nbey2WLmeyRb*aw(XLo>-Ha~+wWe5Yx7$J3gDk~e$-DsOYrbq`?mCBNE_ zgug2?5t48PHgua@N#YtD3J1437IviQdexqz}We4(?aBM$hi0D(U632&5L1){ho4O4x@Z1`uc7gmW7_NP3L=EJpXM?D)2pH7Vt^1n ze{08GFxm-lQYiuGxG=gnM0^K ztU(^0x5$em*W$%KZ@X+GP)+61 z(T-4vOCn`v<Bh9wP(&AQ0a&(F83b$W&Fwa)wc zt}f2er$aKOW=;qFtCT63lk7%1(aAXd%QBRofI01EZ^vCcO{3lvPN}DQps-`(L0E&J z#x@`W31KR68c5mw5prP&W;(5h+tcr-w(2{7)YZ)Ii96rT+& z8wZD^XBhZsX^DUOK?q|D>#kp{W5pb-El)2K;1Q`20e@}F=6RK zphXA7=Z`ZX^aaYIfNPaxVnR!zMCmn12Z}eQjWu-*q}*ZB1_kg&TL1>ztmm>JVl#~a z;g_A*1}B=;d7*Ny?7U{4I%u^$##Cg1oH=%WuzHk-IZfdtNQ4c;DIV-`Zd7@ab907V zJ$MZb(q5JZ!<@3KXx14tHi#|oQW`5ExEAAFY9HUN$iwLo%9?1;((?w^ADN=-cjXTq zYDjFnvd6-S2F$MSjc(-2?E6R=T~FiA`GG>!X?Pi_Rpn<%FM8AqA*48G`?#4WLLnNX z-NElUY0-+P)r`DBS$wHp$}COX^E>lu12TC}s;`=p%N$S4D$&q2Ju$QQ9Cexmcl?1& zc>8yIWn|eOZuYIbZkMgnJkC#b_Z{!UIQrgEor^y_Oj_#P&WClQdA+L^-&LGVH_qRq z?46-iwpmw{m;UK7KE3qKC5YTlV$$hBam^^nFjf znWT;2(YYr`nP*5ckA(s4@P+n_{UK&V*|Xoie@aT$uRJxBsVxt785606E^FvaVlU%Y zG?9)mM{rxHzsK9h|6cjg@crqEJ;;;`;|0%8;V>pQZQ&Q$g?FX*J^OE`PNgPpB{BW> z5K`JEpzJJLSNJ3M05K8_?)`d-FYv6LH2wPE(Z;teOydJ}XbOI?7UqH;OR8u_Cw!Y*Uk>zc8I2*^8!#&!*tD*M6fD z{Cq_Y4!Q1nV|a;vi6$#@vP4D9?UCRRfc2FdS%CT2t0@`(%Is+TmepuB;l_24Kvf|I zws0wA-GiLlCw9kzrOp-(W`8-CLH13&487qhQ`L!LuBqK5LlM)w$O~G4UbFXPM*52L zT>t4LQX-C0y8AhKlgl7h(%@btn_uDW3Po>m`*S1>BZHcJR)7gAC}EEJ`mTXb&t2`D zdAJmPy#0(~9YNw?;VMv@kE1Nzs+J;ocXzCVBPAKQxosunwaYXW77xEhF2X1Ua1QQu z-9c4$o)jUWLY^eiUj3kQMv>o84dvD-pD)n5wY>?hFhcGbFPU@l7B>3XuO`<~sy7a; z95W%q7`jsvdg;YG2N?m=skh}$*i{(v#Zqe;#-gCH%ecvUS8${hTrpD4*UcH z`lWNH6YX=s?>$GGuW>=g&+~KPH=VhK^gr$j)lZ_7OlXGVV<-C2=CsS&+v2g@V&pdi zZtVa{**bF^w)EfcaEnN6CBGH7kHq9iqUqwWaiJKgwMUY;@UN0&avXC^5?zrJBL zjJ+>*4P~REueT_T&wVc3XO&^8SN{XNhv<7+f}89Xx_~T{KPQ!Uk_kbU3NWNt&TFul z;9?D%rh!Z!Dgtk> z566^lj)QFuF>3g$RVrX11eba|hhl6dTUWQo#^G9b*#%kf9u+(k_`2WK;vrr`J`%W4 zlVf-3J;-VQtO~e290iuDp>G2M%bnM&fUc!*{E>`UKk#8O-|)C-wrrA3a9Ow?h-y08hRP`1+@f%!_d|v~j~Vz}U@eQ%{}{+&vtQ0vZ7=Q-EijL^8tu{ntx}sm!|dhFa{WHMOJm^J z|LNgb4N>%ij<6|!J!Uw<AK_n zHF`jt?1e+zUEs{jMG{BtxdXv#AN{s3+~^W1!MPXpy28qS%ho+l37OP6`E@@?<{eW9 z;XfR!4QYCoM&lYwh`GkJfcq8VTu+v<7h+BrK{Jov?5^W)C9G3JU5QaCp!LYK^7(tR z$lZr?dki|4#PE)*^)Mahs$2{mPV5dwc7s^gYjUJP%>`GgM788@1Tg#NiNjBcqIh=~ zIR7ghBK1C$7;b-;wOd_yYqRcDp8h7U`A!pjX~UPtW^Z&yyx1Y76jb+2 z5s6eFEc17eI2BOZ+-dB58&tQGTb_r9T8xvV%>7Sp|Dfm1nqso z7uH>c5smx1SVOZJp3v2vqJby83U3B{>xW&zoCjR&ML|F>x%f@i{p@rov^FW{4E` zPRV>P2bS|0xb@CP?77F2IU+owB0_nCbw3bC_NVcB;Bqggv4WzgV{tQpiuLQTFP#yu zEoOFcP(EMhhi621cx4Ow(_(e6)^O~iBr(c zr|0$tvGYo)^T@9{INuT{S28vvf?OCTH463`oRh)mtgj0gKKi8|ZdF`SUoXE&uI`O7 zOkObCHhHx2u4$&;M2FvSivN4bzH#)PjdsLJ!zNZaVI5_@{kS_eXnp(LbcI24X^?!3 z`G>DrBhRMo@l&mm9py!}MZy66eA6gzQ(Po|LV;r*{8-}aAM0el`Wal4D6%tbgbtd# zvNx7-jC|=A-ALb(?9xvCW@CQZTAp>V>NTmk-%pA*#-Z%cg1wIoU%S_kuXjT7oL)RD zDgDOfavYl24NS2ZyKfx>if)s>r*@eqbClGFjjlL1nI%!6LTpJ2vZTtx8MUvt?ByAY z+$z>7#``(PO%rq1w%VtK=R<8LSHl}jkhQ5|G_ErRxK&xzYFXudqJEB&S5uND+aZO} zZ6sF=`JcF01RAIDTI>&&`sZb=#w;ekYFe3=22X~uHVS$6dROqG*;b5C8c%(hhuXW< z&yVSie$kJ{KwSpaKF(1SK+G*>@K*(Xp8h5J!c^DTL^?as3D21nmkhn8Fh z`V(xRGbUQ39|w#U3`wzaT3`l!#Z@Lt?_t|YM@o6O{XkK*J+D8DGa8aAPk9bMTGdxS z&w)E3^YkEMtY+ew_0`ln#M_j=8Is|&kVrL2CO$PCU8sM>L-8S6lk%b&h$YickO{EW zTZ)LYn)17$;%Ft@!9BsyVk^sm-fShqBl#s2dtYYzLPII7-J|6N#h%gf0tj5OB4IKD z6NFV$ouSVG0$g;US{sRqoVOC7NPdee6H-Etr}L#XQk%Khcb$^xEiakp3v{li$hf5F zTg9xN^R#{Gb5FNwTi7zNKVyLEEJS3raMQ9?X!3 zt`+QNTfVDquV4BZk4~Ee>D^r>eP3SiWulH$$w1q^?d}3H)9F`7+%u)WZ<2moU&hsY{JRSBgTU(jGg0mkxe~Q zBZ8ovKj8(N#CV=|g|Y9bF9~+x8r;0o!X1p9<9ylYH*LmA_EW5=HBl7#TFE7NVZ3}N zw$=PI=5O_iHo^sK>Yd1{@WQXj9=ImKIAJS54F#`{7o~8dY$kT;^!-;_U<@=oWKs@w zcI}87jT5CX(-Y>lZa#KuyhL68!aNQ3?P42)+~#>}Hv@8wB4TAnZUbg5u3_$~5OKWW zr*m!=kWL*|<~jW_jfUHKH;FlZ124JMddAKr`D-8LsKK8BIgOMCC6q!%E2Edu)jv1Y z+zkfRa1agg-D9u^;@ccq{fE)$Ir%As=P0&VzVv&W#u9p6uspYV-zy)hluD1Lu>h9K z2%M|DcvZRfQa`I)e93{?fB2r`B#2yzziHDqdpQiTztwEiU4wyQlDK0WN3oxKy=iK6SP zlw!t=d!6t4rm>p6Fi*^Du4ue?%MYYm0p?f-G2W)H7Iw)QMH&K~L|VxjUKW@@Tm*Gu zHdpl@QtzBs8yv7FQ^`s@D)-`91vHWchvRhD-Tl)|3QCz%bcQDty^U5@A*qkAR(f*l}OWM~a^iVDfbg z7Q8rprhjIg@^&NtCqwpl7&Okh;)hJr%~z*T7&dR#LE8^8X7+KPEDeTpV#Z;PL$O`oHBZf}CXNA~=!j;~Kni8y~P3`2=zLW%UdANH00KPCNc z-2d_Y&!sVJC^5I&B~y!#wCA{`#F`tJC++boUgWOHqr@n?(Ef9FNEd?_hPHf9A<4v* zYxUKW$n!DMWMDloYesXJQlQtIbt93+$8>keE~Q3?#L0EvFBk46$$W&==c3@RjIGb1 zPG+sg#(it&0yBsO#Inr!OuNv%}r#(@_V zg1rAn$Nte_LLMN~oSz$7{-lOP9BU`HHfnMxG#oD=bedjB!Zif6gH1IPsysYHMsL|Y z8-uzsF)WkXFcHRlfZmzu(rw(ma5<`bR=;)qPK z>Cl1vS6C0c7pJ4_wi3H>_#P+i2~pc1Emg@HdCVE1bvp?|kPDuTcK-#5fTo_lzJ)DOdCw*v{_^lR z?*m!3quA0#r8q)p<SLE8N&hk~=@BL%%s?r$JcD2 zss*j}*LA1bFi-1F^3S{RlrXKxl>$`A#HICIaa@-&IAPB#$5QfsGTaJ{t9I4-^{{{+ zm{dVry1}2G2+%M4@o?-`g9O>Cwf_6ZZfeN!!@gA#R*EB;bp#cl#w7Y0I{S*JzUPRM z@6%`@?PPv>__I9K$wC~rh6S)T^Nj6}ay3{aA{abjWjj(TO;*GB5#v*2dJh6M7jR$d z&}+WSvW+2_t+C~z6pgMdV@|gqd+4U8SThu+Z$5|Dee6!eIIK>Q^6M=^hQ3b6csXZNP=WNKxL&T{$wfX?#RVGF>Zs0^L-< zJZ2u|Cqqa)z;b>~3`ZpF8A6%K$2Y&R_txKS8VKPo^2l^YqtwheQI`tk{<@)ta_bJ( zeqe~xrfqA#l$G)c(*V^b&Oa@A53)2IG0#QbaZzf@bBJi0OOnRyQ?zzey$ z3zF<}hL4xS7eq^GUv7{wgQ~@hrWqh{aO0)LkJumFdYW`@0iY?p+o+mh-ZsY?M9X_q z4B-uwRsC|WuNo97eNb0^%Y{pqS`*Q!GtEmu;uJsukLE7Brp#WUrf2l~g`-`UH%u_Q zGT1Wjt`|~iLW#Xi@HG#>Y}d-r!9K)^V=%;T(>XJ5x!1N@38tN)GL;|8lU<=nWBwua z9g*gt-2va8^5LrPV%DBhP!(XGN9Up*FkT&%?bfLz4LN$Z8kbgWapyfTzHn!kV)AQ; z>0d+Q;gktQD)FqRCv=-ypE~Qrl7&Xm-56n97!}2fS3K zhaac`4Rh6+EZ(U5Qrg3$8@{5WeM8-IyL2&bnso3@COHgH{|=4(-bq`ANw<`!t`tlf zXx#C54gx)G8_aD!lVO8Fg;2JmXR?(;2%ntJO)DuRUX2a)nDwh_e; z$Lf#sAosn@vh4(Cz4ES#pY}=_V>9`r>2Nr$bcj`YQsjw=w1J4cq!*~%du4$xeL~}D z1<}km{B|BoZdjpAiEPl|#BDbC=_<8uG}u3~YrZ_Ksr&8K;nMJ~NySrp(m`SBY z9!5{tc>CSbF#5T#yjhVG@a6-{G?2vx0bt}hqemj&OdlDBrp5C7P=OV+}xbKyI%9dYWRcj|1r2#xBh)xvC-BkQrmOW(@pOcZb?qU~wGkYrIDpj|+$)-95VXWbmZ$Y78u|wgXVXL# zFtsP2r}%wHK8Sx7-GF%5uZKEp?Bxj?WlJfT_$v;g7ed4`qdz12ZtgBT8oz3WHhdUI zY7V>zB}UKeB8xYO4Oad{MZezYhjW*_YeJ0ND)zW;ElHc)aEw)#vu8&c z5W??@*WI{FH)Yy|m%h^PSqR0|!^1raBM-K_sI=wL)$Tt=Tq91+_Ic($$l5ji+W47v zv$Lv-CY$H7Q0~%OcLZ3Y!0g&-Y}0OUuXL*@UA6&nS|aHCX@i<)NIRc*f?k}6O*XDP zlkUDMdAAz{NKkU!&fw3}v0D@rUE!=ZfGQ4M;f5IfKBVIXdAF?PnB*akvVu?_8V}pD z%!<-han=Zt_!YwI^H{Q%s|S+g^}sTC&yZpfh3#{O2K{{W@o1)ZF#v&A5D!(6?Z>_> zT073$QtTaYB(_{b8$}~Y+Y2}{#+5jsAXz`yW(6mLlznk&CI^;FQ{!~5F@I>CCzYo> z*@}K{_+&`R`cm~48!9dS?wYwFi7YLDC>sv0q-4HU^sN1k&^}R^;aFF9jgq=GWNm1{*-xmjBc?gE50Yx+1nh;U(IRj$gehPY~HMbMxzrbDnSOp?k)JVZN3rgi(?LXYn9^&@j3+7w%6 zS}dopAw)~$)AjyN44WsXn3|)DZH>u0CpPC5c6bj2qAqf`4GGpRCa;RSk`<+XTaR0b zvM@kXN8Io$q*%0rsSFp!?Fbz~F(X}6)X9 zl65-qtGk*l85500DN6`YfaKgg&_aB^e$a#D5G3Q}AP1j5jUy`$y5krn$|vzsa6Plv z4prs8y`@HlBcF1LpDovNsXBma(=F+O zmrr(NHG4i^&-5xgFmg65-=!ffnPnVvPyamk$X~k@oi}ElwL9*_oM}K~Nt$kT+sQ82 z_&g_XjcGavERXAr)lz@Hz-KFq&oFG8Zj)JKnEhdhF%HfQ%0#V^vYw+zl$>h!M2Uou zG@)=*(b7!6=guHWb`sq-qs}(XftygxBYx_Wdf43DE_RVcDaA2=M(K=FrS1+>Fq*iO z?1x0q1&go5Y82i64BGn8A1Z#brKvph)DDM=v|J4|C?+G5=DdpO2UP{lCHv&?Nr6%4 z){PiShIPPVewQZlLB7GT%sNkKZ(3EWE|Gqz(CV|X7~!?HpUYor5p%V?OKjQjcd^=M zhz+KIxdb)HZ_)ZLztrwPaoPE``{p0>+8Q^F9`?%RLT87QUu0QO(SOMW&_gR;m~s`p zblT-aEz5+Kh8AHsDuQ6i(kY}b3GZ515unUGdsmb&u9j?QkSrAaHg=U> z?gS5|PIuJI({2D;w`m|Z&m1^)p0YE7KN-Zy_p`Ul0%*$YWhK#IN~g9u!QY5r*|1D$ zswkSx3`=K=!)J-}yJ7mzjJKriB@(_hE;r4on7mI(iLy?3J*qKoM|J`E&|AC^4sRm4 z+Chg1Z+k}+cQS7JdDCflIYFd9DH zE;ioq=Gx1nS~)r$Y5o+U4s4eAD8wqbc1)Kf=38a&Dg7wUO&l+)z1zdxt<&VawU!+< z0XV=b-E+tJ80tW;Cp8pR(kru~a^UA|>urmgO44W(Ub+k$ccxK^3&aEMHL00?V>K;~ zwoinU?z~ELr==2oW>fdn-+)2+S*lmN8ePFsAn!}MK`F8wB(R-34)?8oFL?F@E~qe{GVMmu{w*4Mm+f?a?V z;3$2r2q>c?FXkZN`!e1M)z6#(o6v(iW!y&A@D@6zQ@kXQ>&wn5l~ABXJ_I$WY98Yy zUPLw&o&9+POC6yleAIvh1qGTnSkAYQhncUUt4fj{+@#gjitixL!(kj0^3q|b=efCf zcys2J@~JA)NepNV6qDZV&7T280Xr%U*ICnTU>XNao+iKFc|FTv>O$%!Dz6beo6f)( zFQ{5HC-^?T_N}sGYDQk>?i0^~sq@H5?mkNq%&9dIjjnlx$>~KMo8~1`LpA@8PSu%O zl6SrV?(ov7s&7J-8}vM;vr6&OcJ}z338I5NifQ8#@%ak3c%gMBn8JNw(CkZ&nh6r8?hpmE%cT6_b_2G!E$KP0T2*FnHE5Xvep0mkJw7-fCm|Qy5S5%{cD6 z%w8v{eWsXwGrJxk8@>t@#}o<_ylS)>70$6AxY(y~$!2m&Jum$H>j28teP^-uc_fCz zQM`7;{sByHWWw`&%YaTSdR^La9h)R~Ed5n}0xa*~wfG6fE0!8Tp$QE??CU%gqijM1 z6cpe`V%bX5IPmGt0b0@UsRR1W-HqZTp;q#76oeE>ll`LKZ9CHZ0y_}+ZIs@J^5L&V1-^P9$sUG?VIH?z4LNl%~Bu(*pQ zxap!5TJ@MNMS0YlCOao>LVL{*ZWI`W zxDpaH9EE!^qb*pDi*B+jNd!Kox(TcA?%`2pM*Fhc$-9~+;15cu$)VQgFI=W{aiuT_lP78!ZErwo$(!0DdS_FTM_ zt-MdUzLOJ~-m$U!8Xi;Maa@Bk;9xl$pAsifUnlB9aq(rnet%cbTfl%WJ+LGsOza@0 z`+;x&&84k2Nvoo-urhqdGI^bx1leRo(DL3&sG_%h%=RPpbGguB!8eT!eKvW{)>?bY zN~r}7CERZd74IRH7CfsaGyvB4FYu!pRM_Do>>m;L|mL222sP*LU(v)FXb*^%wuaW_h%(I|za{ zOBzM3youK`_<$q>d^vQOpvN%vX6N_l0`i>ABbq7q#FGXXLmACW6}!~wum zu7?UAz%8cDC1?nmNzL61Lo7Gu zewQTPww^r0Q!Q-`HMA$sgj6a>bC-^+{E#XHVw)E}Y`!0@U!{Z&iQ3bJjPOtgkMv`V ziePq{f=n7m1ZMg~Odpk@3xR`RO3OI{WkJy(?^Z zmTyR*Wo`e8ME?;S{S}P+Up)jeqbaVIF^mQZK>0_OW zGUPh~?IU7QtltYoPeiAReW6Lqy0 zke5Ot6paXFd5ZuC2wUe&Z!VXhDt_QSmyamPJ(rty8HP+>CCD>92-=Yg@~(EU65_uc zC;e0;7U-8@@G*5Mmc(8G_zg95k=IQlJ;Bb%Q+nLsU7*x+Sl*2H@4zuELiQfZf9)!8 z4{IIxkn{#YN?cy7ROFM-KPyN*WD!G<6IXf5EF~|&`fnw!2kD2BXgSLcN^FU99{;Wy z_Uye}5u3{V3A}8SbdYuAq4%ets5wGSo8`ig=Q6OLfTjbspuzo##%)wKjR*|4HM0+h zH9DHR#An~{3x#GZ8)r|Rm3$GiC01D@&E2W`%#(TY8brrv7zuUyi7 zsm+jZ0`(BC~@sH(1$;%t@^xP)Dyf~BTm$r z#7EB&{#ixu*Z=d|{?OF>yVvhu{C6&--*uIT;yxJ=a)|3Z^M1`zgzUbRQvuL><{6& zKTFd6$}RZY6du6-J$F2)`28`6|M2{??Ab5Ly5AQ5kgDrJO!8j>c7ON!vtrG!9ACf9 z0Qt`XX@B?ob9TgEdN#ie{!rEbt#0#o$3JHz`|AxBQT;WQ;vb&>l(FphC9UcHIe*z7 oD1UU*pJ$I>^YU*SvG|8sT2Ti1LDL5T0q5ak_%P{+S^cX07cO+~0{{R3 literal 0 HcmV?d00001 diff --git a/scratchpad.ipynb b/scratchpad.ipynb index 641976e..bd480cb 100644 --- a/scratchpad.ipynb +++ b/scratchpad.ipynb @@ -9,12 +9,11 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 293, "metadata": {}, "outputs": [], "source": [ "# Open Alteryx XML into a string \n", - "\n", "import polars as pl \n", "import xml.etree.ElementTree as ET\n", "\n", @@ -27,12 +26,11 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 294, "metadata": {}, "outputs": [], "source": [ "# Parse out nodes (tool data) into a dict\n", - "\n", "def extract_tool_id_and_contents(xml_string):\n", " root = ET.fromstring(xml_string)\n", " results = []\n", @@ -55,27 +53,41 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 254, "metadata": {}, "outputs": [], "source": [ - "def selectTool(df: pl.DataFrame, col_specs: dict):\n", - " \"\"\"\n", - " Reshape a Polars DataFrame by renaming and retyping columns according to the provided dictionary.\n", + "def tool_select(col_spec: dict):\n", + " \"\"\" Generates select tool code\"\"\"\n", + " dynamic_code = \"df_output = df.with_columns(\\n\"\n", + " dynamic_code_suffix = ''\n", + " for old_name, (new_name, type, selected) in col_spec.items():\n", "\n", - " Args:\n", - " df (pl.DataFrame): The input Polars DataFrame.\n", - " col_specs (dict): A dictionary where keys are column names in the original DataFrame,\n", - " and values are tuples containing the new column name and data type.\n", + " if old_name == '*Unknown':\n", + " break \n", + " \n", + " if new_name:\n", + " alias = f\".alias('{new_name}')\"\n", + " dynamic_code_suffix += f\"df_output = df_output.drop(f'{old_name}')\\n\"\n", + " else:\n", + " alias = ''\n", "\n", - " Returns:\n", - " pl.DataFrame: The reshaped Polars DataFrame with renamed and retyped columns.\n", - " \"\"\"\n", - " for old_name, (new_name, dt) in col_specs.items():\n", - " df = df.rename({old_name: new_name})\n", - " if dt is not None:\n", - " df = df.with_column(pl.col(old_name).cast(dt))\n", - " return df" + " if type is not None:\n", + " if 'Int' in type:\n", + " cast = f\".cast(pl.{pl.Int64})\"\n", + " elif 'String' in type:\n", + " cast = f\".cast(pl.{pl.String})\"\n", + " else:\n", + " cast = ''\n", + "\n", + " if selected != 'False':\n", + " dynamic_code += f\"df.select(pl.col(f'{old_name}'){cast}{alias}),\\n\"\n", + " else:\n", + " dynamic_code_suffix += f\"df_output = df_output.drop(f'{old_name}')\\n\"\n", + "\n", + " dynamic_code += \")\\n\" + dynamic_code_suffix\n", + " \n", + " return dynamic_code" ] }, { @@ -87,26 +99,139 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 295, "metadata": {}, "outputs": [], "source": [ - "def TextInputToDf(xml_string):\n", + "def input_textInput(xml_string):\n", " # Get XML for a Text input tool\n", - " root = ET.fromstring(results[3][1])\n", + " root = ET.fromstring(xml_string)\n", " # Extract the field names\n", " fields = [field.attrib['name'] for field in root.findall(\".//Fields/Field\")]\n", " # Extract the data rows\n", " data_rows = [[int(c.text) for c in row.findall(\"c\")] for row in root.findall(\".//Data/r\")]\n", " # Create the polars dataframe\n", - " df = pl.DataFrame(data_rows, fields)\n", + " df = pl.DataFrame(data_rows, fields, orient=\"row\")\n", " # Display the dataframe\n", " return df" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 296, + "metadata": {}, + "outputs": [], + "source": [ + "def getConf_Select(xml_string):\n", + " root = ET.fromstring(xml_string)\n", + " dict_SelectTool = {}\n", + "\n", + " for field in root.findall(\".//SelectFields/SelectField\"):\n", + " field_name = field.attrib['field']\n", + " field_selected = field.attrib['selected']\n", + "\n", + " try:\n", + " field_type = field.attrib['type']\n", + " except:\n", + " field_type = None\n", + " try:\n", + " field_rename = field.attrib['rename']\n", + " except:\n", + " field_rename = None\n", + "\n", + " dict_SelectTool[field_name] = (field_rename, field_type, field_selected)\n", + "\n", + " return dict_SelectTool" + ] + }, + { + "cell_type": "code", + "execution_count": 313, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "joinByRecordPos: False\n", + "{'joinParams': ('Col_3_renamed', 'Column 3')}\n" + ] + } + ], + "source": [ + "### WIP\n", + "\n", + "def getConf_Join(xml_string):\n", + " print(xml_string)\n", + " root = ET.fromstring(xml_string)\n", + " dict_JoinTool = {}\n", + "\n", + " print(\"joinByRecordPos:\", root.find(\".//Properties/Configuration\").attrib['joinByRecordPos'])\n", + "\n", + " # Join parameters\n", + " for joinField in root.findall(\".//Configuration/JoinInfo\"):\n", + " if joinField.attrib['connection'] == \"Left\":\n", + " left_on = joinField.find('Field').attrib['field']\n", + " elif joinField.attrib['connection'] == \"Right\":\n", + " right_on = joinField.find('Field').attrib['field']\n", + "\n", + " if left_on == right_on:\n", + " dict_JoinTool['joinParams'] = ('on', left_on)\n", + " else:\n", + " dict_JoinTool['joinParams'] = (left_on, right_on)\n", + "\n", + " # Select parameters\n", + " for joinField in root.findall(\".//Configuration/SelectConfiguration\"):\n", + "\n", + " return dict_JoinTool\n", + "\n", + "xml_join_tool = results[6][1]\n", + "print(getConf_Join(xml_join_tool))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working with the XML file" + ] + }, + { + "cell_type": "code", + "execution_count": 40, "metadata": {}, "outputs": [ { @@ -151,7 +276,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\casey.morter\\AppData\\Local\\Temp\\ipykernel_4012\\3571569777.py:9: DataOrientationWarning: Row orientation inferred during DataFrame construction. Explicitly specify the orientation by passing `orient=\"row\"` to silence this warning.\n", + "/tmp/ipykernel_579015/219306832.py:9: DataOrientationWarning: Row orientation inferred during DataFrame construction. Explicitly specify the orientation by passing `orient=\"row\"` to silence this warning.\n", " df = pl.DataFrame(data_rows, fields)\n" ] } @@ -167,163 +292,100 @@ " print(ToolID, ToolType)\n", "\n", " if ToolType == 'TextInput':\n", - " print(TextInputToDf(ToolXML))" + " print(input_textInput(ToolXML))" ] }, { "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [], - "source": [ - "# Check out a tool\n", - "# 0 = TextInput\n", - "# 4 = select with rename\n", - "tool_xml = results[4][1]" - ] - }, - { - "cell_type": "code", - "execution_count": 27, + "execution_count": 292, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "'c:\\\\Users\\\\casey.morter\\\\OneDrive - JLL\\\\Documents\\\\01 Workspace\\\\01 Python\\\\Polaryx'" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%pwd" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [ + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "############### Input dataframe (TextInput):\n", + "\n", + " shape: (3, 2)\n", + "┌──────────┬──────────┐\n", + "│ Column 3 ┆ Column 4 │\n", + "│ --- ┆ --- │\n", + "│ i64 ┆ i64 │\n", + "╞══════════╪══════════╡\n", + "│ 5 ┆ 8 │\n", + "│ 6 ┆ 9 │\n", + "│ 7 ┆ 10 │\n", + "└──────────┴──────────┘\n", + "\n", + "############### Generated code from Select tool: \n", + "\n", + " df_output = df.with_columns(\n", + "df.select(pl.col(f'Column 3').alias('Col_3_renamed')),\n", + ")\n", + "df_output = df_output.drop(f'Column 3')\n", + "df_output = df_output.drop(f'Column 4')\n", + "\n", + "shape: (3, 1)\n", + "┌───────────────┐\n", + "│ Col_3_renamed │\n", + "│ --- │\n", + "│ i64 │\n", + "╞═══════════════╡\n", + "│ 5 │\n", + "│ 6 │\n", + "│ 7 │\n", + "└───────────────┘\n" + ] + }, { - "ename": "KeyError", - "evalue": "'type'", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[31], line 16\u001b[0m\n\u001b[0;32m 14\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m field \u001b[38;5;129;01min\u001b[39;00m root\u001b[38;5;241m.\u001b[39mfindall(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m.//SelectFields/SelectField\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m 15\u001b[0m field_name \u001b[38;5;241m=\u001b[39m field\u001b[38;5;241m.\u001b[39mattrib[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mfield\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m---> 16\u001b[0m field_type \u001b[38;5;241m=\u001b[39m \u001b[43mfield\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mattrib\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mtype\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[0;32m 18\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m 19\u001b[0m field_rename \u001b[38;5;241m=\u001b[39m field\u001b[38;5;241m.\u001b[39mattrib[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrename\u001b[39m\u001b[38;5;124m'\u001b[39m]\n", - "\u001b[1;31mKeyError\u001b[0m: 'type'" + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_579015/3571569777.py:9: DataOrientationWarning: Row orientation inferred during DataFrame construction. Explicitly specify the orientation by passing `orient=\"row\"` to silence this warning.\n", + " df = pl.DataFrame(data_rows, fields)\n" ] } ], "source": [ - "col_specs = {\n", - " \"A\": (\"x\", int, False),\n", - " \"B\": (\"y\", str, False),\n", - " \"D\": (None, None, True) # drop this column\n", - "}\n", + "# Tool 1: TextInput\n", + "xml_tool1 = results[3][1]\n", + "df_in = TextInputToDf(ToolXML)\n", + "\n", + "print(\"\\n############### Input dataframe (TextInput):\\n\\n\", df_in)\n", + "\n", + "# Tool 2: Select\n", + "col_spec = getConf_Select(results[4][1])\n", + "\n", + "# print(\"\\n############### Select tool spec taken from XML:\\n\\n\", col_spec)\n", "\n", "\n", + "# Generate code from \n", + "code = tool_select(col_spec)\n", "\n", - "# Parse the XML\n", - "root = ET.fromstring(tool_xml)\n", + "print(\"\\n############### Generated code from Select tool: \\n\\n\", code)\n", "\n", - "dict_SelectTool = {}\n", + "df = df_in\n", "\n", - "for field in root.findall(\".//SelectFields/SelectField\"):\n", - " field_name = field.attrib['field']\n", + "exec(code)\n", "\n", - " try:\n", - " field_type = field.attrib['type']\n", - " except:\n", - " field_type = None\n", - " \n", - " try:\n", - " field_rename = field.attrib['rename']\n", - " except:\n", - " field_rename = None\n", - "\n", - "\n", - "\n", - "\n", - " dict_SelectTool[field_name] = (None, None, field_rename)\n", - " \n", - "\n", - " # dict_SelectTool['field']\n", - "\n", - " # if field.attrib['field'] != '*Unknown':\n", - " # field.attrib['selected'], \"Type: \", field.attrib['size']\n", - "\n", - "\n", - "\n", - " \n", - " # print(field.attrib['field'], \"| Selected:\", field.attrib['selected'], \"Type: \", field.attrib['size'])\n", - "\n", - "dict_SelectTool" + "print(df_output)\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 174, "metadata": {}, "outputs": [], - "source": [ - "gui_settings_text = root.find(\".//GuiSettings\")" - ] - }, - { - "cell_type": "code", - "execution_count": 114, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'AlteryxBasePluginsGui.TextInput.TextInput'" - ] - }, - "execution_count": 114, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gui_settings_text.attrib['Plugin']" - ] + "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "import polars as pl\n", - "\n", - "def reshape_polars_df(df: pl.DataFrame, col_specs: dict):\n", - " \"\"\"\n", - " Reshape a Polars DataFrame by renaming and retyping columns according to the provided dictionary.\n", - "\n", - " Args:\n", - " df (pl.DataFrame): The input Polars DataFrame.\n", - " col_specs (dict): A dictionary where keys are column names in the original DataFrame,\n", - " and values are tuples containing the new column name, data type, and a boolean indicating whether\n", - " the column should be dropped or not.\n", - "\n", - " Returns:\n", - " pl.DataFrame: The reshaped Polars DataFrame with renamed and retyped columns.\n", - " \"\"\"\n", - " for old_name, (new_name, dt, drop) in col_specs.items():\n", - " if drop:\n", - " df = df.drop(old_name)\n", - " else:\n", - " df = df.rename({old_name: new_name})\n", - " if dt is not None:\n", - " df = df.with_column(pl.col(old_name).cast(dt))\n", - " return df\n" - ] + "source": [] } ], "metadata": {