M;YLf2kI*q3=}Bt?hpQ$43zXZvIZgxIz62AZpi4td;XxDF6L5N6b#atJ
zGDv%ZWQWTea==j3gm$=s0oVe=nIUX}@%dqiaM#7INC8g{5hn-=|GhaA|050PS@;JxNe3qu0tYftEajvOkLw@NdzeYxpLAQ`
zr}iI3nV-iM+xh1h?sd8uj28{`Y~FtU4K#cED0in0j0J1~JhictCm7vo>R!yA;3~Hn
z?=~JW`uPv|`@l>Z<5j|=W{m1E=gfQ(dCi^bcM&d4{2fWdp+);rzJKF2sR}+
zkhgvKoj)^+G!}iQxW)0o&>aG>NCM0Sd%A(4BN3J9W3>BFq-VT8(i7?(8UaP8KSESG
zElPa80L(IVvB8vjl`|$`qp34T1dd0RD5p
zWR7Og4xO+Gas|Eqwpaw3Znq+dKA*bjKeGrreQviiusYTQ%AUVTc4@c-j4~cr!euO>
z?*8#nFz;zG#2e|g>>^rq2pU0SjRbg)JKznr#`XjJCXt19
za7qCR++fce9PbTxkByH=ZIL{^Pj661O(5X)2b`_3eF;Po9v8lI4WAPo9|lvS*2dCN
z~y+D=Oltv_VD_6`%kO#tYK07fe)QM}S)ak)?#S4oq}IgR%>O7WIBN
zXa}ARu?qn~C!s+?;10u-S_9v*aekW{kcZ;(tan$MoYp*Y=+B3mE6tr0&C~cK<
z)W8x29l0vD3&4X-3b-PT;_pmrv;+32zM#+79NP)NZ3$p;g^YqMOV9~}hV}#vSfDTc
zu)6F>;XpM@!s3)^bpdEc5R4UdvCb4|SCaq4Hd|v_aMZXxu>1#C$F>7_eS!t&XQkZ*
z#CxFcJ3y5EO|t(4u|ypRiJn9%8@X2N!x;7W{I1wG054a8&tr^oz^v>8v%u_|*Kjud
zT?df8QixGzdNXDGoAG}c4SX-0y4#p8>#S9;y9bULHzOx?4SENC6@3-9l>s9UXG#>s
zzh*cLKZ_V4hJFlRzDB-?J6ps|IHxbb4u_!>m)BXA<8OV)mf^BGiw$30XQ?K+dr0m^
zbQm{1sxQIq4nr>fo!e4NPw&Qp$MTJ=tDMt2Sex5TU(b#alP@l@8nf}r2|Albc{uE|
zUJ-r<_)CgZQ7EX7tw%q{IL_lnqFB7uyGo`{|
zW}8-4-x|;rcJHDw|zX)%3SQenK5Z;Th
z;}JcJ_pP>c;>S1eMR?0*z5rXDmi4n2IxWBZKg_nCIM>FZgQLtu&Gat(1CQlD*>%%9
z4Ymv1_?BnPx%lN1^l}>I-*pCMvHZ6wmi`HajZbk!v&)|1PVu1d7*z3u3YC3w
z*feX(;@`7M`jz-|4gbEi%)n9{9j2_`6ms
zWA>Ga+?3kKz_O?wixQKgk?xVm7`~~_kXtw!3d5)+i~m^PKtvg{66x64%$MbB5hUmT
zVwtdl4i8V=!j{=IiwzIsJFnoD=_@D9GdwYM*K|S1S(Q
zpUPS#LtwG91(o8z4IowrKQS6a6-=dXCWrVZT%htj4mnjnQ1ygoCY!aDmC>ML5A%0way-|`#Ik3Zp!0=~A8GjkwvJ>;krqo9zB*O-lLv~r?tW&vwE+(u9Ii9<>{
zR7~0GsQg+(qN^Z`CYQIX+Xr^@`HQ0!GG%RB*S7Alm!_2luiE*rOQR_bDg}u2icD2$
zWsIK5;%pP9HaZskEbOd-wdc@KPl7Et!wrRDF>(ibBH~0bf8D4Ozj7J3I4ZgVXKb@i
ztK+kBDd8a#hr%y>1&T|fm9afDR@T((=(bnYH~9
zSEQ>q+$9r{QVdmSN>@smIjEupS4w-QlqSr`HNilyoLE0&VQm9I>(S3bD-oUucq#$4Iqi$rB?#f+J?dA)ReTrCla`n<&FL9Q?H
zkUT@>n%yszIrj#$TC=02Y}|AMUA9mf4bl_IJ3*Y|xyp4jro=uaD&@;8%T5eVjfZII
z#J(9L*?yuJY!YE*)F`C&XdX78I$dwy!Ko&*SK?;xAhO`+w)4h(awcP0sW`5qZ!T27y)2?QB8vaB7o^FfPN_`&wMkvE0
zuvo3188H?x0P+C*xF5dg$nNVyH2aWU*o9`l#uzO|)CGC_v9(cn2N7epZp|#S&B$&O
zy#QxT4;Wh}Xx*0p?3|7-W>6z40%vZ~9b#7BYWmO^<(E@obPEO7!|u1{)>VZoNmz~`*hvfRR%{|KIE-!^~Fe30k3
zH^I&KKe$ue2zL>;j&pG(oQ3@WTz4N~Z(}Fe1MFJ1oHZEUG(2Ru(J*G%YH%2=QT(_14Rp@l|EA-Fk
zJLq%tm2`-%2RF5UqnFSF=oX8^l5b(mZ<}8>KVklX`J3j?p)1i|^C9ylbCua-`m5=e
zrn^n&OqYU#T#IR$$z=SC@n^>3Zy2vOb{T`lEdEXYF|>;R7Jn_@&u`?HqkL$ZtDtq`
zv&9{zt611V=o+CR>vXfX?=gK*cl!a;n!64#c3m5FD`p=s)kRHBa3^LKtp5#0ivKZx
zHNS(;=U(Q%#U10;a2)$<_6zJTHplQ=!?lK0`Zx7A>ce_F^Ad9_(*syu(A}upqZ8AfF42Df$v=|^*nVe>|Mlvz8JWHA%Dvz(NBA>&9P-wc+gIA>?FsoTm!Kad*rGZ
zc!Ht2^_xV)a3sxMiElb&%8N=R!O@G91Q8xQm|n64Dv`1@s`13YzYBSrHi3+#)j=TP
zxdvDu2EJVg4mmc8jHUVT6G)M<=JqZ2Hn^o_?@ZicSseqvE>z#PQRYk&Sjw|&f`gA2
z0SBHI5q{I)@kG5T`;e+eUkp6Dpcgh0Cew;5Tx!(S5?cy@EgfxA_h}y6Njl=sZJi2CTTg-Wm=R{2l!q=9UJ5x(E_nInrtZ`;BrMk
zWTDdn+23HY9ZZr{7Xwc#RM)nFaH3IO=?5{(E}k0OlhCVT;AsU?zJ3l<@(D1$q06**
znQ|Q55Cf+w)VOJV8bhQ6E^IUvsLF!(6e*j=iCFdklr2;624dhYg@Uc?Q&|uT-qL8w
zbtnZl#=t8IZQ8v~q&Bl<7NdSsL3YwoyEO)WQK)_II$YClvf=F2rbYOIe$&E~BH;cc
z7P0R#+4ux3zG=X;Bx+Cm9@`KDe<#$oa~&ZzGYLwwt4l~DK8dO>2EI?IetoORj@m9N
z8&;L^9<{48?Q(8o3_PAt)23FrJJe-V2;?!Z&P`$fXD5O|?j&^?6^8DpI0b4F5sZPy
z6RK-%CC!lm9kz9}?&W8me4{7q|RuG7I}hUJVT
z9C$VnIGNXUI4MJ{P*q=T%%YD{$XU0RaF`B7n443TI|lAd$lKVW=1Z}ir!Yq9Q8N57
zaA-oomX=h;NOn}b*awsU9M+Pc;I%|RQ+ZReNtJV$Q?i^f@Ku5?Xi+hz*xR@a;+x1v2i!J%3l02uBc*2P$WtY6}0q=nqjm9i^DB>-N)8YrAl%
z`Ek=~ewuxcjwb)4%_;Xs{c6}9x6`xBRts;^uv?}-55GT0&(64nQ-;}_R|%(4sYTyP
zFd`<(nwVx?HXSuzXS&N25}sH7W=o$km+2YqOXy($|1p|1J#D@LF%~=h6ZrigJsW!7
z{GfQ9=sx&83)hieG*==sBeYP~)7D(!UZDkg&n;w)F#@$4XtP3;43gq=`z_1xW&15_
zvi9i&s!`5rM3D8#eoG!|!>{ePc(eLxfg*t?2pmaqWyn&3*5ZvJ%j&FSNTBx0nIy%}
zgy2-ENBa*dsnx~y2bKE^B;}N_)~_k{y+2<`a9@0v}Nk7!^ceIOdrN~
zG#JkD(-*V0zFIU9!N2$=w-`qn4XfE5(<27k!6wNX|K-ooJ_{;~>8CGY!EuI;XXBf%
zr)~IwlXNK!Cc%#1Gd8^XDw-!xIBYsadx^oWfLP~@c+Dxg2!F>Zl)`C8{`4?w+X;2m
zOLf&Y7_N+#P49#Aj37PHBpN7^x0B&apM;K(%(>EMk0a~_Cr_VtAykxVOwld{?n!Xr
zKct|FN>&N&vc#Ao76-GOZF(xxpxQW_U@OnPTQvkSX*r^eeHnA3ZD24mxG79
zkU^lDhM{Uvu2KWde)y9@HPl&;3|mgx}i8UySWr_!l{_h$RoZYL#D^54*sgle{&6
zJIuy>y005MH^aTQfBsm1QFLO-^dXQ7H$73G5P>=IL=x6bM{k!i;0n4(6V54v?CIS<
zr$`za()m$)sRp(bo9wBE>4W0w;WZ_R&Yy!?0|hEc@Af%RqUmwjOJ=8J;=bHz$udZh
z9`Kqgxg0w&jV6R4;7Ab$a7!7+XIfDC3i7Sg-cWCMR6<*iIkZkFd*IA+Y*Z|tCw*5z
z4E0Aw!r@Waar=03kPE=2K!GUTWJ+UPrO42kU
z&nD6cO2~mf+8gZd?R9mjhom?nrGnH{x=hWBPezwvQjw6VMJrMncT9&_TU{M}O0sfh
zpd`a%l$$PN=>+1Em&`tG5yNG#G7JCT_fzfke%2O(S-(TOpX5ne3?@X3=uy9?%irzq
zai+C{SP~U~N7QcWVVc07
zVLf%R69e-{?n)e9uQhKUO%y{v7tgPu5!2ha0M@VhTF*_BP6J;h#6fdM6#?5IGsf1$Xjp8X3qkm5rZl;@4yQ&-F`iO*4N0SKP%1
zqc8)?j$Ub#LvXT|wRJbq6Bmd=C%IUqC?m4ootPgyUGA=6I12lIUa}>
z=ZHyVCo^#w>WC&P`7GRN(+S5`2PdmP1G6HpqeEapBuk3OYc#Cen;
zY9(oPBzg5g)H1aeD@zpVN^Kma#2uy;=8GoN>DI~3WMhV|3*x_gpRYh`5@OEdpDz=P
zxNrksGuwABpG~_!&cjDYth27Z{=Tu}A#I^gmI9wA$Df!*e8QWQ{9Me!DbWxd6yxF>
z=%R`)`4>cCI+WaZfUjF}<5Af#*@PQ;V1
zK?&R7^yJH?M~qh)SMy(h1B*F-&{n4zVH@)?d?}_`(Xoa##n()N*9GV&r+NI+F>V$9
z&Qb8X7$4^r;=RW>E57+*y%(Q(l@TYRM{(retPMZ)Bi?}j=ra^Y?PrgJ7UBi3DsgNn
zZY}C)a*5&V>V|8!&Y(xD&=si?rBx&i4+u{Mc-&}}O_>2UoIQqLG#d+}S|vkHt*6vK
zW2?^|6%SEQ=80QZtx{UgsDH?u$euW=GJGPy9yxt9906bV-ac`^$_|f!pYh1?sXIZF
ztaz0X#Ek`>ZsiN3_MKbXI(D{JPHa1S1eU`_SPs*hT0BIR!<@wN0}t7v~v5|F@r7AC@RdU-J>RNP9LeW
zsK3W9Iy)+QPn~Q{?X3a8o%g5FgFMtJOGi5CiJ_k0f
zWu_MPjNxg+3Vo~YAG*cH>-pCaxEfMZ)aBIWFq+p1IycOQ;y>;M`b%e69$#0`9|^mXEs_R@1kxlW70GKK
z^J2oqmvc6_*`P%JBaI>w1b!+YcB;x@{jZz^rD!kc2eOL|xd)L(K(abdkFzW6k7)CU
zEXi2`A3g}{Q|K~|RZ)_LN16k)v8l
z!v|4z8i}gr2`MN(jZ75`7_c@>hC~CF;$NlNq$xz15`9JeO4^Csa5=|vN*7ALc^@Bv
zxqjObF6UgEUXLq}a@Ba-v#|1>@LMc+O*5Z|Pdv+25zog)7~yrMiFK35z$2*b^VQpckbEjCRLj+b(lR@Cx%~hGY6a>bEnG(ap?=?oPCmx(+67WWL+XnznPhq*>Xt
z20C%aep$0^om>Xy#d>-Ij>y25SwmS6Pj^IDuyJV5Al$=s^uo1%GR
z!TC9<@QFV#dAS5qKGvf?n9xny!C|$Go+!s}@8aREMkm~wc!Yc`=>+f5
zL!EpnJz0#e>*VXujTr3$L&$BNFakg5`F$;eEBv$d%^_)!yUvblwTsAA?#19UjeCgH>c&e|4dkCT(ggm~O
zY>Yai&dDly&Q}X+wTbm<(5n>i6~83O)aEG7fs+x5!F-xBBUx3
z#O=@u^>!$gmPAomutQ#m4Fu7B6swhtTr74mSnsJ%<|8^IHZ6jc!YRXQ<7B15w$D$L
zp@JwGNvYh0oT=o?olAWjXC{}6JKm9kZ&IqmvcVRxPU05K5bCVi==7%+wU5C1eVQf6w?$
z3La<+$-8~;DSClf%`tHo&h^N)a4mmg>5`2`bBTss{K-pZ{Vem8@KJFUe-Z*-ET_1K
zIF@G7lgLNSQp@46y>-Ggna|qV%g}`K3-8^b^qKVi1
z-nExcE*9^T;CkEyS5MG8v-|h(5YO|@3;1yw=lqLZiOU#c*X*?y@wv#XzOaY6a-oYclRVBn#&x2H$*i3x*^e5Ifm(I@
zF|G!tVZ-I;&pM77;6hkd6~BNo=wdqSdbZc_uwj}0LfxrMe=v8K(=-dPdYVQnAzgtM
zpm@jBh1eH7!L3GJ<~3+w(16Y?|fVpk-qRjJGu-EsA9+Q
zi6X8*v430ui~y?A29rb
z*{u5;MWqLQlKlo4(@Trf41=!9QBl!C+%VIbNX`N=^9uu5T
z^pUHbi}0Dp^`;!f{gU*LC(z~Z3P=3-&d2rnc=&OBm3DSkjj)(N8yAr|PlW-xA~>zX
z#gFSN@B>ePxE~-N*-z->y5KZTo^MAd6E{yyxcEts!Y@CruQ$ano~9)8>OOLPbTPi^
zNn-1@N%u!dUHH#W>bK+Dey-n(SN&XnF)LRqUa-UVNAy{VCuEi*qi^acsc{MZd9xmD
zwh!r7;-CIRFOI`3-1-xJjj?VDOofpqG_g-?kaOfR?2SLsuS7iw8W-WO{6xP1GY{))
zjjdDT5{cP_#8vp{!}|S+F>-*6jNv(>!nk&73^bVa#Qo(#k0lqwuro;$LBXT?X?kiD
z-}$Kiky{T)Vk64TDl?j*Hqhe|m1#ga#{z;0G
z@dMrGAxzI8$jnm4_(!xKzT8LC*FbU!KjE1M4J}+x<4e32n82(f$(EK
z=|R%VSSd+elV}ZNzVa}X29AkTlqmT`Fq9%770uId@^H8w$)XofiiIE!MmJf{xca9igDW5XrSnF}m~M~2`f
zJ92zBeCO|(eCghHPPvc;^T^uiG7#!kS{CYpgp<5VD8dlEq^mb5E;gn_0qPCXfhF@o
zyz(KNSDb)IX_m@~z%=Nt-HrI=$Mw0sM4@E84u5aB#|f_om?J=mqG)kagtW*|5QySa
zkpN32<0La(!rEF}>8rrKO1bT?y0<_s|Erk(LvnHD6pWI~gm|_L-O`M|+hBpW(vUO8
zV`Jhj&@TUwH-gHqPf;`U+7@%9OgKH=bzSA6PHS
z$^Q+a4!{KLm@dZK8ZCD)6Yl9E-0!uVH=Qh>289$XBUkMeX~c~_a65c;7NP{)iHsoj
zL}Vd4pLmu?8ilM|9n*zyqSJ!F!GTPoGj|wy63y+KV(4)>j5s+GSYbj96j#Fjh23TN
zrNO8FlAdKQWpujZ@c0MR3W&{qp)_lvobzWLS;%zKKf|)N@^W;w6Ysm6TY?UWX2@AE
zL;fCoNh<_>p%TM)&ThMM!Z2gNOG*VV&Toe;+~HCopc|Xf<8v#7TAW`i|gb#l`3*Jud1&
zHWU@D_p?`)2}cd7K6TZf$)`>^tQC)L&B`xwbUSqz4k|w0!tchLR|pLxbiu7Bpc`QI
zBEjju1)({5DZ1CXTnJgdVt&JPr!k-RapStf>~B~X@{>PUm>Os<`)g+%bTo!$3W>+b
zdOGHp3?*7`4pVNJ%OMQp;05I3I9bB{&TtUs=YY7eoc}tzSJq#o<3fAe)Cr40Gnhf*
zQu}A9Qqo=Lt(eRpase^$0x=fKAD%o1Ms>6{9ra$MWdYAx3ak>QEJcq|MG_9
zls?SRqoU^W@*=JtT@C$nl9?!;>BF{c?j~lUaOMy`v6%Zxw0;IGvPqkqCdcw2o?#H!7-TFO1!t4Fc;{(*i4xNCG7zl#2WG%Dd%T5?Su+cp%)#);(2+i}
z5h1qa-tK_YQQqy7F1=@@aBvLX=$0uyhH_wNBu2JUrE9^5$sqf;p1^8H<
z!Avab-4HJgq;C#ONyil^lHyGZ{HA2iwPmE_S5CNQVC@cp*Mj7*n97noKS@r8^3(@G
z(ZU{Ak3Y4f^h+!VSB9imCvs$oj0Hn;Fs933RC$48!f#Omf#K9JQ_R{90^RU>g89SE
z$b$-oQqb${g~+{`cO5!#H-b;iPCU^_EY;HQCJM2LRK>cb%K#-`e0VpLvdM=NCH-N%)sty6j+gbU*@GP
zby*Agd%Iwo?$HE^Dzzcs|0jN=gKT06t{Y^k}O(SlH0y41G2tG
zSzm+uFwa?+<-?_!GT8Zo^LBrv25gwBuSVGYA?rJo^&KethBZrow_2*QEwkt;7VJ6V
z>uQMgW+5vCDC>`{G3#346X7A@N+AG%r9!kN9)vf1)HR8#iu}q$^16*g1UIKoiY^M#
zX48Y9c_BzCheBzHVeLG4n)J^Vawv
zhFj3p9QQ#GxHcuuWgw6qakUTL3kh#6Nr?>!A^xQo-uT=ue*mKDdjU%#+$_8TLy55p
zF%KaoAcQ&92*l@wD5*e$qdx8d=tP*)fG?c}SVvM?QQ;rn!Xrl9cBfPik^B;|lf}37
zxLg_$x8ZF)L9g56wFowfVcw;z7aEH6G3H&}7wJF43)3D&Wz<#H0pT&h3+9$pvb9A{
z&bAvE!NzTZ#92Cj{U`y?AXkgv%?Pxfp-<
zS?*SGSSH`@L>wk
zMx*ittxlb>Gz-l^73~d6vy^7x)a|769IZa@(}aK*5e@gjtrT*X1Z=FZj|_NSnk7*k
zp>{n4D3cd$%~G29G$_VDTxG~(H=aYvaT&ZTLaU8JPR+-tF?RGE7-hkC?378|S85j1
ze25z4W|!SNo+!ks
zx~GIt4SwguoiK4ul=M|uICB&~{;^PxzxD|Xz~!F^0ea>LzVXjY6MpM$cyi?@V6J}V
z6Jfb&!a4&c-1aIo;lxc@+#>ws+kzce{go*u-xlDacZ70!W{iy6Lj1LNKm#9tN3i0j
zNCEf>%KIpO|EL$vo
zONqr|{>c0%^V8;s&9|H5<^$$NbE%m&{oeGD>6@l&O#`M5li!qY{K)uw<9{1(F-DCc
zh%pn*;@{vO;BVz8_yc?sye*UBUW7Nx-pIj87+1?JWZ#81T;2^~Vve&Hvh@%w$7uK)
zgp2thysqt(;jm$Yp;Z5&{u%vG^xxHgQGZH5uJ3{n@(ucBdK2?D^EC4V=1XAJxrk|D
zDj1XQ9SAA&knVr!&gr7M2t>|t>y|9mF))Y!n!caDg+5IW(7Wi>aFf&sGt0YC8-uFp
zJ>pewaH&$Rf1(3<406(Y|e~FDtZsOm#u!hFPA~J^lnuZ(j6ak
zQ9gsBZhCi0C34+KM`1#hvt6WhXSgQ{ZKZnN
zA=bP@RR?T_(KfN>9Vs=C=k3z-6#V4*)*J>^&^yEy5|NXKTSQ_y)u>_5ABA_(qs;{j
z^3t8D_$7q^ZIbd*>yYy{icQ<8sz=U%S4g8Ey;(fNRHtt
z1=dRi;=}YZe>1
zBT1yeW%8#q>6*Jz+nH)+}UD8NE$uMe?XodZaWSdDLKK&=PtZK}U>TQB2mDTzYxIFBe|TVzU9nULp}^5ptc
z^IT$2Zy~j&W;n%+&8oUY@j8ecVmEJA)dXkra$c>Jmr|3QRU?YP=43_k^J-BJwZn3u
zl|>AyqccU`LRC_BRH<*SkL?PPam|QYs8SqW8KP#P<>Khc7#IsxNcmF57p6OTqh%sn
z+HtN>x!Be7#j-+6mojKQErzaIkT`KcWzthIQk7)kB7v?@X$b>2?B)qfg-S$vGsdAp
zb`iroVW&{BR6=fQIM0oOrG=b_C8;YCyCYLnDYQh)&JZ?g3>DfT#2&33C~5#UX{bOd
zpirRsh%u{gRv
zCAJeJn?KJ(Up`+1#wc+2z7)k;_%Z9X@tyTS7`(;B5{k5Ny=5#m~aDwTw{KetU_7=
zh!8JAW(YHcII$Nph7v-olrPt-Zer1a8X3fvXbwl`Owv*=L?i?W&K&XwG2*z_4C$jR
zDLSc$Oo5g&Frtf@B3Sz~#_mCA_L~=(R_US^(1JbsFHzRVtY@q{twq9H!uKI2Qg$O-WO&Yrgzx0nnMXCSJ-|Q&RN2|y?I#9JgZF`&u
zfF$j6?czqTmb{MxB4=rAnn`(S(IGbzV3Vytz#BLSgxqART{D4@*M=03O7gA^ROOw!
z%T|IOsnJ)Hw`-uev>5s%T18
zi!w7+8;foR0B7A?m1%r%ER82g?{UXA0fe_vQ#nuC8{d!4d)6m#d
zyd}Yt(CZ{xq8o
zjI}^{cYRVpjFQX#Qf3;137}>PNX?rHCWS5ec~z_lV58t7
zO*271Q6TNHM#wI!QtO>6YNa9#QW2R*@{AObi6cMrWA#vENqIsqCHV`0U^~ezjje{<
z^6GYc=moTdi`BueN@qL1?gdoB#a79`?iPQ+IS^3Oj$e2IYn6o5TetJOr2A80tz@`mw5g)Fo2LYL3A%
z4+JjEsP}?$d5|C??U)fuZ48ckV46u&jU`wzTJ4g0tFYjX!TAnMGnyK+su6~zcMK%K
zToRbfd5X@g;y1Oj>X;LtYJ=%&w!&Fzc~8ti%C9Hdcl2mVsCEJ%va?N$_HJ(>fwvBh
zkBGxH5UT|^_a?P+uIT0}p0q)0B+wMbU3mF*^_lAwUACVA3H(O-Ryh
zD`yr!#1R*E0<^LSII_sDp;9lgn6h-x5T7ijBl)T0rHzWm16Eff9^mi#kUyA~&NB>s8s-M=6
z>2K-#^xgUg`ZoOq{ZIN|^lSP?{jk1PU*kEFHG1dywHnkEk=RE4FlDnEQlGdhFn`;f
zf%zJjiTPtL7W3wg<(OBlPQ(09?n#(WYY)aernLz3neF2-=P*&7T``!ib;V-B$*L(EKI{CyKA8LD)cBMwG)M@=CWs3Zjwp=@{Kcs}IAF9`MhxB)~zbtE?>T&7|
zS}*;wHduZ}IU+a8v*iLgR(VPF$fx8#b=!AzNK9b!jPC2P`6Sc`urY(_vWEIOC3RJc
z=2tIXT)m){P(jTR%uoS#{?=}KYU0A_X35hTH;HyG^L!lFk9IEgkodbvo#&qT5Zbv2
z%L3r(7(?vaYbEw3EUsTrF|WjzSJzj~@7uhT$q?ZDW}~;~SiFNR3TQ6&d>kLhHBVyF
z1ei2cb#Ot=%b1~_vsR0S4@@~-mbi*G%4lEdS#!qQz=GO&Jw$?j100d
zS>op&KFf(ERbuz&;~X}H=x{kOv>Z+bVB@P;
z$oUI*)e$3#b5a7ko)Ug1glSLlD{U9Wx7hXKawXcj#pU91B2q`_J-GeCVQdq<4lW)N
zA{E>x^eEmjbSQbu1edAzhE=--lT3s+O}#(VjWl`BSS10ve`AJ5V0~74M>7uC+
zy+??L;CxYnyAB95;PePngje?nDrDx60N6G~6<~RqnZ!(+JOR#+6@y{+aB~#Y?-wGt
z80i@2f{txkCdmf(R5b)Lw`&Sq+or|I(lPdH-f?dtappntR?Pv0+q3}ayG^r`EPvf`
z+q5jJ+Em{0a3X2$V^s@~oh2<*ydyiCG>?M~owNfkCF2N+elLh{qKmeZInY>69FYDf
zE&4@KtLapz8KU-s!djw%tCFaYrARy%Bi4`^-AA(SCfYT+brM#mdnKbjz`6wL@Hy@{8M9;-s#ZLapc%SN|=k4_C
z3HA5S|C0W>=%-ds)X9xA(NwH-52opuGBJ(DWO>eXOqQn{g=r+F;h0kBRa1OpudP~I
z;@j80tgODitYXgms@nR0R!%*cGdVANYF_x%>@gGa!Xy0kKpiUzu-l;zNAJo$%sVnN
zZhe5AiAstM(sN*~Ll<5vszmI?e6
zH5iKXISDel=zG?j(k8*d-g*dJs~};lGqx=jZk{niVBa}27*?M%br^Hj46|PIt#e{J
zynh;{#i^zNm(H4w0B1;BKJS<|lsGf0Yb&dkIhD4Uf2@;W)gIzKyNXtcFtIJ&<|s>}
zJ%?onoGlTP6_@kjwlv<6mPW6XA-zsWA?>iR4sHFLSZG3{{Sj5&~Q8*G_H9&ECT`H~a%-KY_4z
z`o)c<;W)`%b*$lKUEGbt0V*?CZh(Zu?MJm|z?e+lTOuc?HB-4M)0#-r!0wWUEsVY7&hn<&|mexT^{Z=v9lY12c=P
zv(C!Wf9yOS8Uyra{jLk0(6WzE|{f{+<)h{n$w
zz`J95O3pX>n6F>CR`zk`V~dlY*4&Qh=8bWAsWbTAyQpbRZ&&Fq^hz6gN`rJ
zRC)-l&cWv_T5h3Mqj%2Cq#spbQWG5jhsJY~x3G}=17T8E4}tMv`X1Jqzd9MhUeu1s
zE0b0y`Kqs)@2l?F5GO&yGuqPI6sK;~-t_4VqKmk9;aV=YACDHY5I;Ddz>Ylb4C@ST
zp9njLsJ~_c+6%#+s;&{+C*V#(z)}TozJ=cYSgQJK+?BiPWP_{1c~$xm9G=`>xyg2I
z!H>s0mV}05g?X6DFqLA$c~bXb60C$E?_C|MV@0(d3npU9$CQhLKU;#0b-4MhKX`G|LzCftW)=C%X66G?@E}u%2!m1=2oY
zBOGa>2l((bS+QFn#zA1ZQ
ztUGSE!|dbsTBD`3<3S(7daTN)3||lloft{d)P8V1gQy|nx~9YS7$X5(W9>QK^w;gF
z1P*4HiLiZ;k-%9&bUA+J0i0&X!|>j5`#LGF<00NLAHTQM_q~u>4F0Us0Hx(o+1P4RzjQFOu{YFd3ppila-+lUyj=WrfoSjPx>^9wBm
zlmaf;`U7nQ!K>Dy{V#I%HT#q*AV1L;Q}gmKySP?}l|XP?)C
z;nigV?|m%W{x*VK^u?XHNNwbaCYA}1{sMfH5QCb3Me_coO>l62)R$=CJyDuO3G`7)
z+zi5bp`SPUys%Paoe>=?;DJnYBf52NJ9f31^SjYacwj!!By)>VP2oOU>i&KKu{^I=
z&JwTdkyx(NQI!6GwpZ&T4C8O|RkqJ;)4A8U9CjPqk9mwCFrZkx(tSQFkU(;s@ILEg
zT)p7j>)J4K6t47Wp>X+i)X9*eS_JR1^NuNzG&qlG7*-#}bCf%(^@S}*wb8W8fWIBp
zI%$^;Xy`~
zxAwZ$n;_D%OMV()+gI8Q@5IHzXJk{USe&Zj`e?+U^z9n2e6I|VR|p>qG(XpN%*L~=
zOgUYK!er(5s6i_v^<5u%j$~alnhRa1cnq>y68#>_qNL!fX{v*!-troEs|F!4-m=N!
zCfpQht{8~js!h1X1w6WwGhpgg+zh+7YC}a=^c|^#I@%Rw(G6=v*Pz=-0|$J*O%q`J
zc5RQms<&&Pg=Fc+XF})!h_i}G0Mnz%wt=cn18MGdoE-Y`{(g3*Cs+_*=>Q{yeCDTUtO74$
zucwJmQE>ktBgFd*E9nxczMW*xJPnZ~3u;S5V(rr)5Sqn&gKJ(fiu*|`=fRYeaN$#OKa3>wHaEzmZF7gqWY!!0Wy)H`;dxU
zqpnovtJBpSHAxLtS*1t0gfL>C@}hVBB)N_E&c0v%C1a5ub5X;yyvJ^sqZrSDlF&`1
za+c2r;_yP{Cg2atX}IR2WpC3dxvAwAS;&SnhSm#sm(m}lmsM}C^~#qd$k66l{%wQ4
zSzoAk>L>MB?XaHZ4d0-As(Q|6)p+M^Q=YP0rrlp2Luqs)QE#Z>!bRa%Y$KP)%j_6L
zHI2$@d6!(`jX1AFa`MVCt^(h-YE6cTo3&_i0hVsocEQ9g+ChlfqDA0-lHXCf2s7H1
z44I-70?V_+aFzO9ON9&JI`VFht$;VPxk!@gJv~#MKtRnGC&B5lVi+teQ8VGUC29t%
zo~J_H*?qhv7nLJKcE-7oxR{wnoP`!QXhO+U^$BaW;&8R1S?7JO1b8M(yy(67p%O#7
z+0q-x6ChqE#xUW}luE@>f@urm=I9gT@8mt~URGkJN#9d((g7(*TtYp7J>k_l(2e0o
zd_3L$uoW@|g1%p{lc%j{>1)6D3_Bpa^OMUAV4cbCk+(rj1fm12NGH&Onu)Y~goRIE
zkevzc;dg+XL)}B6Xp7M;udH)pc#51`>q7r`eZg1kR-+GT^M`7;qOk_=M_Y{`iyNBc
zPKLzo#+z0!lhA!6D;5Lp2zQ2sU7w}h>9BpDFj}QHIET2A3a%JRJMZuEKPMl
z|45@hEZBvy@Y}nDcp=|C%;vavsGlmk)I@VW=&Mry(R!B~BXi`ESEt>^B`^OOm)x^k
zsQNb`z1Q3;Ec=J`OLC{cuQwYzcI{vdh1Ciu{ZTzwJD?Sy6%W_+#jNV%LrJ#v(Ba7|
z%0PJVijrmI-p{DMcnC$A1V!InQTo8ESCPT{=86(#jGITRl=X`d%JHE?^i`$5UtW4u
z8D!)xC8{qhLpergI=pvPiL&HlNUkr`^RX}L&8|qPjnb&sDf(G$lo-aP?qx%NZ|E)x5|#9Vtk)7w1`*5}#NASVywh|YZEXb$Ar13=5S$I|Yx
zmaHEHC#won=G*INcL7|;w>Q)7e3(&S_t5S@fIiee*!~5cWEaEE|9!FW(S1${7jOZQ@&U|qgyl4&Qe7=}YyYGU9MdAq9or|L>
zDG*18t70(HS)Wq6u1!=qWsuw;ZQyITKd>qEQ`Dyt;A-yG#b9!X!fc;!nv}PAw6=qI
zuN_xb*({T9MU!tP-iOXAt;E~-n(`MKbY1#_NZn)y9p26CxEZ#w2_1l?#U0vB-AkHO
z&hXmKYu|?T*NV|4Y@=-N+S0i#?04)G<~gPp-Hfh2o7#r%@&t}xwd~a0wY;MU$Q~R?E&81!_8>MOdgUN6F#wMYWT!y6DA;@XsE7)Nk#Tx@83q53IRvD*$Ho?nHtO;
qgq+vR4D$d@Ed%$fLIcywUFYY)f1tR1Jh*$ooq2FS+kJ}#kNz*7f{Td&
From a4159a4a23966dd7d573eee78ba2db49cb81a886 Mon Sep 17 00:00:00 2001
From: eeintech
Date: Tue, 1 Nov 2022 16:30:50 -0400
Subject: [PATCH 12/17] Fixed style
---
kintree/kintree_gui.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/kintree/kintree_gui.py b/kintree/kintree_gui.py
index 5372324d..75dd30eb 100644
--- a/kintree/kintree_gui.py
+++ b/kintree/kintree_gui.py
@@ -1155,7 +1155,7 @@ def main():
if CREATE_ALTERNATE:
# Alternate window
original_part = alternate_window()
-
+
# Check if original_part is empty
if original_part:
# Create progress bar window
@@ -1163,9 +1163,9 @@ def main():
# Create alternate
inventree_interface.inventree_create_alternate(part_info=part_user_info,
- part_id=original_part['part_id'],
- part_ipn=original_part['part_ipn'],
- show_progress=progressbar)
+ part_id=original_part['part_id'],
+ part_ipn=original_part['part_ipn'],
+ show_progress=progressbar)
# Check that name and description are present in user form (else the form is empty)
try:
From ce1191566d0f9ab50c4bdd970dc9ed7f124a1f9d Mon Sep 17 00:00:00 2001
From: eeintech
Date: Tue, 1 Nov 2022 16:57:06 -0400
Subject: [PATCH 13/17] Fixed number of test methods
---
kintree/database/inventree_interface.py | 1 +
run_tests.py | 1 +
2 files changed, 2 insertions(+)
diff --git a/kintree/database/inventree_interface.py b/kintree/database/inventree_interface.py
index c0bf53aa..85261ae4 100644
--- a/kintree/database/inventree_interface.py
+++ b/kintree/database/inventree_interface.py
@@ -575,6 +575,7 @@ def inventree_create_alternate(part_info: dict, part_id='', part_ipn='', show_pr
cprint(f'[INFO] Success: Found original part in database (ID = {part_pk} | Description = "{part_description}")', silent=settings.SILENT)
else:
cprint('[INFO] Error: Original part was not found in database', silent=settings.SILENT)
+ return result
manufacturer_name = part_info.get('manufacturer_name', '')
manufacturer_mpn = part_info.get('manufacturer_part_number', '')
diff --git a/run_tests.py b/run_tests.py
index 84ff79ae..70d6bc1b 100644
--- a/run_tests.py
+++ b/run_tests.py
@@ -244,6 +244,7 @@ def check_result(status: str, new_part: bool) -> bool:
'Sync InvenTree and Supplier categories',
'SnapEDA API methods',
'Download image method',
+ 'Get category parameters',
'Add valid alternate supplier part using part ID',
'Add invalid alternate supplier part using part IPN',
]
From 622d32c65d383a6c928ba3ccdbe9e5f5821de8eb Mon Sep 17 00:00:00 2001
From: eeintech
Date: Wed, 2 Nov 2022 10:48:15 -0400
Subject: [PATCH 14/17] Add 'smart' way to check for existing companies in
database, found Digikey search bug
---
kintree/database/inventree_api.py | 13 +++--
kintree/database/inventree_interface.py | 66 ++++++++++++++++++-------
kintree/kintree_gui.py | 18 +++++--
3 files changed, 70 insertions(+), 27 deletions(-)
diff --git a/kintree/database/inventree_api.py b/kintree/database/inventree_api.py
index c2d1ddc9..a0a9e571 100644
--- a/kintree/database/inventree_api.py
+++ b/kintree/database/inventree_api.py
@@ -349,16 +349,23 @@ def create_company(company_name: str, manufacturer=False, supplier=False) -> boo
return company
-def get_company_id(company_name: str) -> int:
- ''' Get company (supplier/manufacturer) primary key (ID) '''
+def get_all_companies() -> dict:
+ ''' Get all existing companies (supplier/manufacturer) from database '''
global inventree_api
company_list = Company.list(inventree_api)
companies = {}
for company in company_list:
companies[company.name] = company.pk
+
+ return companies
+
+
+def get_company_id(company_name: str) -> int:
+ ''' Get company (supplier/manufacturer) primary key (ID) '''
+
try:
- return companies[company_name]
+ return get_all_companies()[company_name]
except:
return 0
diff --git a/kintree/database/inventree_interface.py b/kintree/database/inventree_interface.py
index 85261ae4..1cd7cfa3 100644
--- a/kintree/database/inventree_interface.py
+++ b/kintree/database/inventree_interface.py
@@ -334,7 +334,7 @@ def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict:
part_info = mouser_api.fetch_part_info(part_number)
elif supplier == 'LCSC':
part_info = lcsc_api.fetch_part_info(part_number)
-
+ cprint(part_info)
# Check supplier data exist
if not part_info:
cprint(f'[INFO]\tError: Failed to fetch data for "{part_number}"', silent=settings.SILENT)
@@ -346,6 +346,45 @@ def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict:
return part_info
+def inventree_fuzzy_company_match(name: str) -> str:
+ ''' Fuzzy match company name to exisiting companies '''
+ inventree_companies = inventree_api.get_all_companies()
+
+ for company_name in inventree_companies.keys():
+ if fuzz.partial_ratio(name.lower(), company_name.lower()) == 100:
+ return company_name
+
+ return name
+
+
+def inventree_create_manufacturer_part(part_id: int, manufacturer_name: str, manufacturer_mpn: str, datasheet: str, description: str) -> bool:
+ ''' Create manufacturer part '''
+
+ cprint('\n[MAIN]\tCreating manufacturer part', silent=settings.SILENT)
+ manufacturer_part = inventree_api.is_new_manufacturer_part(manufacturer_name=manufacturer_name,
+ manufacturer_mpn=manufacturer_mpn)
+
+ if manufacturer_part:
+ cprint('[INFO]\tManufacturer part already exists, skipping.', silent=settings.SILENT)
+ else:
+ # Create a new manufacturer part
+ is_manufacturer_part_created = inventree_api.create_manufacturer_part(part_id=part_id,
+ manufacturer_name=manufacturer_name,
+ manufacturer_mpn=manufacturer_mpn,
+ datasheet=datasheet,
+ description=description)
+
+ if is_manufacturer_part_created:
+ cprint('[INFO]\tSuccess: Added new manufacturer part', silent=settings.SILENT)
+ return True
+
+ return False
+
+
+def inventree_create_supplier_part(part) -> bool:
+ return
+
+
def inventree_create(part_info: dict, categories: list, kicad=False, symbol=None, footprint=None, show_progress=True, is_custom=False):
''' Create InvenTree part from supplier part data and categories '''
@@ -577,29 +616,18 @@ def inventree_create_alternate(part_info: dict, part_id='', part_ipn='', show_pr
cprint('[INFO] Error: Original part was not found in database', silent=settings.SILENT)
return result
- manufacturer_name = part_info.get('manufacturer_name', '')
+ # Overwrite manufacturer with matching one from database
+ manufacturer_name = inventree_fuzzy_company_match(part_info.get('manufacturer_name', ''))
manufacturer_mpn = part_info.get('manufacturer_part_number', '')
datasheet = part_info.get('datasheet', '')
# Create manufacturer part
if manufacturer_mpn:
- cprint('\n[MAIN]\tCreating manufacturer part', silent=settings.SILENT)
- manufacturer_part = inventree_api.is_new_manufacturer_part(manufacturer_name=manufacturer_name,
- manufacturer_mpn=manufacturer_mpn)
-
- if manufacturer_part:
- cprint('[INFO]\tManufacturer part already exists, skipping.', silent=settings.SILENT)
- else:
- # Create a new manufacturer part
- is_manufacturer_part_created = inventree_api.create_manufacturer_part(part_id=part_pk,
- manufacturer_name=manufacturer_name,
- manufacturer_mpn=manufacturer_mpn,
- datasheet=datasheet,
- description=part_description)
-
- if is_manufacturer_part_created:
- cprint('[INFO]\tSuccess: Added new manufacturer part', silent=settings.SILENT)
- result = True
+ inventree_create_manufacturer_part(part_id=part_pk,
+ manufacturer_name=manufacturer_name,
+ manufacturer_mpn=manufacturer_mpn,
+ datasheet=datasheet,
+ description=part_description)
# Progress Update
if show_progress and not progress.update_progress_bar_window(3):
diff --git a/kintree/kintree_gui.py b/kintree/kintree_gui.py
index 75dd30eb..fcb9e53c 100644
--- a/kintree/kintree_gui.py
+++ b/kintree/kintree_gui.py
@@ -1152,7 +1152,7 @@ def main():
except (KeyError, AttributeError):
pass
- if CREATE_ALTERNATE:
+ if CREATE_ALTERNATE and part_user_info:
# Alternate window
original_part = alternate_window()
@@ -1162,10 +1162,18 @@ def main():
progressbar = progress.create_progress_bar_window(font=gui_global['font'], location=gui_global['location'])
# Create alternate
- inventree_interface.inventree_create_alternate(part_info=part_user_info,
- part_id=original_part['part_id'],
- part_ipn=original_part['part_ipn'],
- show_progress=progressbar)
+ alt_result = inventree_interface.inventree_create_alternate(part_info=part_user_info,
+ part_id=original_part['part_id'],
+ part_ipn=original_part['part_ipn'],
+ show_progress=progressbar)
+
+ # Alternate creation failed
+ if not alt_result:
+ progress.close_progress_bar_window()
+ sg.popup_ok('Failed to create alternate part',
+ title='Alternate',
+ font=gui_global['font'],
+ location=gui_global['location'], )
# Check that name and description are present in user form (else the form is empty)
try:
From ac8932e33995cb65960e1b0ac45c46801c4a04a3 Mon Sep 17 00:00:00 2001
From: eeintech
Date: Wed, 2 Nov 2022 12:36:15 -0400
Subject: [PATCH 15/17] New Digi-key search method, needs more testing!
---
kintree/database/inventree_interface.py | 2 +-
kintree/search/digikey_api.py | 17 ++++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/kintree/database/inventree_interface.py b/kintree/database/inventree_interface.py
index 1cd7cfa3..08635ad5 100644
--- a/kintree/database/inventree_interface.py
+++ b/kintree/database/inventree_interface.py
@@ -334,7 +334,7 @@ def supplier_search(supplier: str, part_number: str, test_mode=False) -> dict:
part_info = mouser_api.fetch_part_info(part_number)
elif supplier == 'LCSC':
part_info = lcsc_api.fetch_part_info(part_number)
- cprint(part_info)
+
# Check supplier data exist
if not part_info:
cprint(f'[INFO]\tError: Failed to fetch data for "{part_number}"', silent=settings.SILENT)
diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py
index 4d091613..dbefa093 100644
--- a/kintree/search/digikey_api.py
+++ b/kintree/search/digikey_api.py
@@ -80,14 +80,29 @@ def find_categories(part_details: str):
def fetch_part_info(part_number: str) -> dict:
''' Fetch part data from API '''
from ..wrapt_timeout_decorator import timeout
+ from digikey.v3.productinformation.models.manufacturer_product_details_request import ManufacturerProductDetailsRequest
part_info = {}
if not setup_environment():
return part_info
+ # OLD METHOD OF SEARCHING DIGI-KEY
+ # THIS METHOD WOULD SOMETIMES RETURN INCORRECT MATCH
+ # @timeout(dec_timeout=20)
+ # def digikey_search_timeout():
+ # return digikey.product_details(part_number).to_dict()
+
@timeout(dec_timeout=20)
def digikey_search_timeout():
- return digikey.product_details(part_number).to_dict()
+ # Create search request body
+ search_request = ManufacturerProductDetailsRequest(manufacturer_product=part_number, record_count=1)
+ # Run search
+ manufacturer_product_details = digikey.manufacturer_product_details(body=search_request).to_dict()
+ if type(manufacturer_product_details.get('product_details', None)) == list:
+ # Return the first item only
+ return manufacturer_product_details.get('product_details', None)[0]
+ else:
+ return {}
# Query part number
try:
From 55b756eb69aa34353be731e724da9a162c71e22b Mon Sep 17 00:00:00 2001
From: eeintech
Date: Wed, 2 Nov 2022 12:43:28 -0400
Subject: [PATCH 16/17] Fixed Digi-key api test
---
kintree/search/digikey_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py
index dbefa093..c4bab0d9 100644
--- a/kintree/search/digikey_api.py
+++ b/kintree/search/digikey_api.py
@@ -150,7 +150,7 @@ def test_api_connect(check_content=False) -> bool:
test_success = True
expected = {
'product_description': 'RES 10K OHM 5% 1/16W 0402',
- 'digi_key_part_number': 'RMCF0402JT10K0CT-ND',
+ 'digi_key_part_number': 'RMCF0402JT10K0TR-ND',
'manufacturer': 'Stackpole Electronics Inc',
'manufacturer_part_number': 'RMCF0402JT10K0',
'product_url': 'https://www.digikey.com/en/products/detail/stackpole-electronics-inc/RMCF0402JT10K0/1758206',
From 6568c05e85cce9255d2fa8c16f37fdfa0707a866 Mon Sep 17 00:00:00 2001
From: eeintech
Date: Wed, 2 Nov 2022 13:24:40 -0400
Subject: [PATCH 17/17] Need to figure out how filter works with new method
---
kintree/search/digikey_api.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/kintree/search/digikey_api.py b/kintree/search/digikey_api.py
index c4bab0d9..36aa54bb 100644
--- a/kintree/search/digikey_api.py
+++ b/kintree/search/digikey_api.py
@@ -94,8 +94,14 @@ def fetch_part_info(part_number: str) -> dict:
@timeout(dec_timeout=20)
def digikey_search_timeout():
+ # Set parametric filter for Cut Tape
+ parametric_filters = {
+ "ParameterId": 7,
+ "ValueId": "2",
+ }
# Create search request body
- search_request = ManufacturerProductDetailsRequest(manufacturer_product=part_number, record_count=1)
+ # TODO: record_count and filters parameter do not work
+ search_request = ManufacturerProductDetailsRequest(manufacturer_product=part_number, record_count=1, filters=parametric_filters)
# Run search
manufacturer_product_details = digikey.manufacturer_product_details(body=search_request).to_dict()
if type(manufacturer_product_details.get('product_details', None)) == list: