From c2a3253e99d01dead4b439ccab304e87b9af6670 Mon Sep 17 00:00:00 2001 From: jaylin <chieh.lin@rwth-aachen.de> Date: Tue, 26 Mar 2024 02:04:49 +0100 Subject: [PATCH] add constraints and S,SD exportation --- __init__.py | 2 +- __pycache__/__init__.cpython-311.pyc | Bin 0 -> 346 bytes __pycache__/__init__.cpython-39.pyc | Bin 286 -> 266 bytes __pycache__/main.cpython-311.pyc | Bin 0 -> 87647 bytes __pycache__/main.cpython-39.pyc | Bin 26650 -> 41145 bytes main.py | 891 ++++++++++++--- models/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 163 bytes models/__pycache__/models.cpython-311.pyc | Bin 0 -> 15303 bytes models/__pycache__/models.cpython-39.pyc | Bin 9641 -> 9304 bytes models/models.py | 3 +- templates/app.html | 1117 +++++++++++++++---- 11 files changed, 1662 insertions(+), 351 deletions(-) create mode 100644 __pycache__/__init__.cpython-311.pyc create mode 100644 __pycache__/main.cpython-311.pyc create mode 100644 models/__pycache__/__init__.cpython-311.pyc create mode 100644 models/__pycache__/models.cpython-311.pyc diff --git a/__init__.py b/__init__.py index 16518bf..43b770d 100644 --- a/__init__.py +++ b/__init__.py @@ -2,4 +2,4 @@ from flask import Flask app = Flask(__name__, template_folder="templates") -import my_flask_app.main +from . import main diff --git a/__pycache__/__init__.cpython-311.pyc b/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10a22a88935ac987aeb97db41430e3e2d4a6dbd8 GIT binary patch literal 346 zcmZ3^%ge>Uz`&qb_9r!gfq~&Mhy%kcP{wBq1_p-d3@HpLj5!Rsj8Tk?3``8EjHyg1 zOlz2zF)=W#W`e3@h+<A*Nny=l$z_dVWn@TU3ue${e+iP*WW2@dmXlbVeT%asHMbxq zu_U!vlkpZmj1!-hpOcbW^pcT*fkBh;7E5koW}crW+bz~KkftJL1_p*(9P#maiMgrq z@wb>03kq&A6tOcfFsx+w3^M<hzJ6$NYEiL%az<uqhQ3Q`adt_5fqrgcYD#=+MM+U& za!G!XetdjpUS>&ryk0@&FAkgB{FKt1RJ$U61_lO@y~WN93=AKb85tRGFo<73MGx4x h8&W$w7Gz&wGrGuTbcM~Rf%yT0&;@L$h?jwZ0RY(cTyg*a literal 0 HcmV?d00001 diff --git a/__pycache__/__init__.cpython-39.pyc b/__pycache__/__init__.cpython-39.pyc index 32aa421ba46b9a29d50d12b8ea9ead324ab514c9..206099422aa956812462937fecf40b36f1cb7cbb 100644 GIT binary patch delta 124 zcmbQo)WyV`$ji&cz`($uSoSA1VIr?AqsByS89|m5)*O~x)+km+h7`7722J*fAv(M- z85tNDG#PKP<R)h3O>B^}xW!P!#=yX^lA(y3fdNAN($^0yPAw|dPtM3p&CqvAEzT~< YFVN3TOihVTttcr<OfJbUn)q8800876<NyEw delta 143 zcmeBTn#aVO$ji&cz`(%pf5N)dw28d3j4BhgWq27<SaMjRSW{Sn88lfZM(9kOC>I+g zm|Gd2mXlbV9iLcGpqHDNnHR-_EVPoLh=YLvLj1DQ4=qkDD%MZV$V|=9cS$YIF3B&@ MM^=u)nfP280HMb&9smFU diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d15975a5b1a12e3a15f7be1710a1cb8b3dfbcf9 GIT binary patch literal 87647 zcmZ3^%ge>Uz`*dPjxkM`lab*uhy%mIP{wC94hDwl3@HpLj5!Rs3{eb>AU0DDV-!;g za|%lib1q913nN1ca|&w?Yc5+9TP}MPdoD*5M=oa+XD(M1S1xxHcP>v94_F^t4sR}B z6d#z)p2MFj5G9Z+7$ul16eW}^93`A95+#x=8YP-57A2M|9wnYD5hVdu%aJ3QD-|V` zD;*`BD-$J?D;p)7D;Fh~D<37Fs}QA-s~Dx2s}!Y_s~n}As}iM>s~V-6s}`k}s~)AE zs}ZG<s~M%4s}-dMHj61oJ4!o+A%!VNCrSrQ>qhB<X}u^tFs&b@pTdyBnPZS^7-g7i z6lDa~$CYE8YZ7GwW^?D5=9)#B<(fyC=UPNr<XT2qg53eK+bYTm%m=yGI?5WX1|$wr z1JVO>FGvpL2ax+gZnlZC0s9N&ZjfKBqpZR9@Z{Kn%_T-HZ;oBAeUv>|A773`u49xV zBSQ**Dod6VG%guZ1Z)@>7?>DR`Ia#-Fsz0NGekM32&S^cyTAleIa5tigg|Ojxl>J2 zguyJHRFf1D5Q~W+l_kp+rZz=%86yM3YDPF;tc3y1bT_aLceo1i6p0kcbfz^@%a|A# zRx`oHr7?7Rpy`xJkw|C6&?k$b&l63b99Dhu82Y@>^eJG~r--4?8%>`QR(;AC`h3vz zsbJNoilNUJO`jT8ed-wc{Lu7iVAZFIq0b*ppB7eq+8Fu*(DdnG)u)T0FAz<i9#(z& z82W<H^ci5)XNaLM7)_rMR(-}8`a;n3nPAmtilHwQO`jQ7edZYY!qD_tVAW@dp)VXw zpA}Yp))@LC(Dd10)n|*LFA`0k9aeqz82X}899kHnqEj4GW73#XoLX3-VpE)37^31* zTvFrHm{MF@SfUbA98(k1m{QzYSfY|r+*=r;l2bfd7@|^AJX2#*yi()SSW>)OSfWx> zd|DWy(o%d=<H6>!r1-V4M5QC~{aaX~GLZNIEi6%)Ddj0aEsRlFDZwoaQQ0XWEeugP zDWNS4QMoB$Eeuh4DHSQ<EsRn5DHSOZEsRkGDUmG<QH3cLU~{5U<5Qwj<5OZ%V^U&M zV^ZQ$V^ZQ%8B!8b8NhB3N=a;CjVgkwPD+hWNluMVNkP(qsyelWHL4h@8fIo1R0qiH z^wh+ZjMPM^*_kb@Q6(u^sWB;z;84hJVTmeDsZ7afVT>wEsYuCfVT>wI$!lSVsz}Li zVTh_sDQIDcs!AzrVTh_uDQaPes!1tsVTh_ttxGA%2A9i4!r+*!hnLZ%Ysyf|dq##7 zreHAEta=Gjrpb7VBPFpUwInk))$b*!UeIK`#hh4BpviuV+qty3BtJJKF)1hY77v8u zoS##gn|F&fBqKF9^_Gxxer`cgYH@LDic4ZiVn}5{>Ma5Pq~g?~vc!_i{Ji+!g4E<& z>_MsNU?~tIC_g7vlkpZ;aG;N4PI5+SZlxyYE%vP9{JhMx%3Hj-iP@>~MXALF`FX{u zw>XMYQ!<NElS^*#7p3N<q!z`Oq~;dnB$lM!VlPT9EKMygxy4?bT3no&pLdJBv?wP& zEx+g%3&^xvoW-emDe-BUIjNe0w^$v6f*d1nu{n9Vd-{dkVt4ZQ_i=UfyT$6}<L?-9 ziy18F=@;Va?izH9#Vgp~?-sjXsIO~~r}Hh2U|&ZcACL;p5Kmv%;1EaOfLkn~p`I?c z*uxxyoIM<aZgKdgmL!6L;1*kONl|8A`Yqn%qSVBa)cDlA^vt}}TP!816(zUWGxLfI zQj<$GnQrkFC+8QWro_Ws&jV$E0-#8f@fL4pF(`Br<MR@8Q;RhvZn1(~<#dZPBse50 z$kET;^%e)n!9ih;KDSt%B12pqi)0xX7>X1b7#MB|xcG-U`MAah1i3nU27CJZ73qQG z#6fZ_L9UKIMLHlpNG(K08^i_c2dOEN0&!(PgaTMgh-*YhktB$(1R~@>gaHEsgC-}$ zQdIRth9G%t${BC*I;AF-mSm=t<^<=L7HBfw;zy(e*P^2QBDY&|AQ2yD4_DvF_>dq+ zXYY7le-}?TPiMyvPk+DQTfDiI@x`ghMX4q6*{PLPs_~%2Tb!JjlbVtgpP5&ZT9lWV zqn8X%n(--#C5fN}lU@Q!pzKAd=|%ZDsV^rmGB6~Ak~}CQfG`IG0|PSy1H<Pc22eA) z1W7In&Q4)0VF4+I;Moi*OvwB!kbbZfh74TSY=*gP7}=6Jm_d`puZq_xF+~BIw5=3= zF)C;>RdFMU6<L5no%I%bPJVhiD4nsUfb2pmmdkFq2dr59^57J*uS#UN)Z{L<DB zElw>e)=$pJOwG`DNiEJU$uH2)O-xORPpv2^N=z=vFVfFV%*@j(sJz9Wlb;@+l$a6^ zbw-f`0|Nsn85El`FfcSQJZ0f-@x37Axm5jvkmm&<&$aGrd?2j7=6h@)tcxsOS6I9* zuy{S-mc76&`xz7>zZmszu>|-BhumUzcMZ{Gyv3fIT9T2UQk)Eug<&389EgCsGMo&^ zv5>-u9&RPzf)AoO3(ihqf)t1on2Xk-=x4^OA0BrL;Bf^p8A7J8pz5wgaa#=osw^V| zCqoTG3Trml)Cr75Dm4r#3~-(-in}n<Lk&X-H^f#DnFV)G3VRAGYC{}iVo_raLkYNp z2vX0$kirg%{&Z9^4v<(1D~dW!kQkaeE|3_SI&QES4~kx1kXR<F9eg0MbOs1a;h)2T zq66HeNnuT41<9ufqR9#&^ri^UVMfsj?#!Ts8Cq9{5hXTK#L#1-gclMrAQC;UY8gxT zLE;cxgO<u_nMwp9G9a>sag8{t*|p3iunsr_149j38Y>Y2>4M;RRtU|&Py#Aup&Zl> zSS<^RFC|dzVr0n6CN^c%G7K_5g9;<4pHn2!{e{RYMFJ(Dx&x}RhOveLHO1#Krm)tc zreb!423U?O;xCbg8UczGCRF?K7}FVQ*-Ah)9#j!pEU>3D)N<emX^wO>7u9m&P|1a% zk_)B80+$US2jD1iq*Bmr;;!L_l_EtlHQXq91e`}vQjc^EcNGHzM#)5y8+cJeE=2}X zw$}30@TBlSOCPQpEG0vUJjiDdT*C-*cMZ=n76yjZ@Y1Z7w*=G#fhb|9;jLj$kwuD0 zxfHn+xn-;j46EV!rk1Zn8LAu<t9+<#tK~=Wy*#QcBSQ_R3q!16tpKvB8Ub`wJwi2{ z=?oBG7BQ!=)(Yl%qWUF;p@uUB-7i8Yei0(VFTyB(5k_@Ktst^1Qxs5T85v4c5#f-c zSi@SwR>M=nUBg?$Un5w<S;MtPX&Dy-!)kb)5X_*=P@;y=qry-mQ!9dMN(xVk@*0(8 z%nS^x;VCnjDUu<Dp;n|$Bp6DUGnO+{Fh??!GotE7bs^Y34HWz2YDH31OF&IcsBn!4 zs-D>lbD2`q(wOkssj2RFi_x&i8PwomG`htDYV#x|7N^FS7G>UIxW#CEi!CJy%n>R{ zEJ{x;i7x?lU?A=2TY?y3w}gvRbJF5dQqwZ?Qd8n{5|dJMif{2lTi?a0IjPAdsVQ!^ zIEpj!%i|Msa&B?wCuOB3mw-)N$#RP|wJbHS<Q8{sYGQF|QED!T!(N=44rXve8Q{JF zH^{B=pe|r)(MpzEoM4B6+V{6uz%5f&u;sUS(^3;lN{dp9!8Y6yPA)AfO3f<)H)CPC znQt-r-(n2D#T4vviwkTl*sxoSCRNNPdZwC;w^))g^HOdxn%-hGyTu0a%`IjN3yWJU zAw{LBzc`(8N>dg563Z$>ZZR7g8s1_yGBT>-NGvGO%P7gs(c}d83ll*hdy5^Sy|^e4 z6zH&^35EvEEjF;##kbfpa}(234WYErE#8!(`~pxPC$l6qxA+z}IGn*=EUt2QOU%qk zO;ISxS4hs!%S%lz0WnK5QWZd+0okgj5Rj9aSe&YmoRONGtpJjOs?5yKQ%FfINzBYC z*3)FV#a>*RoSa%*3~Emn7bm8tRtY=iDS(^B3i-+4z)P`GfHa)NZm|{=W#*MY+RJQ^ z2Iwu8wEUvnTg>UHB}Jf$ya<%Diqt`U7)=lXO2oIAQj&^5CC@D}m;=GF6Q5R;p9@ZH zoaw10!Qd|2Ef5pjK`y?<mzi6TUsM9>9)k4U;)Zk!-HQ?nGH&str{<*=fqRKXDMg?* zMv*ZnggIR+l2Z#n9q3!k#U(|WqD7t{VK!J_64Z;l#SiT;#;1Xs@0GWBa#KqZL6*el zCuJ4ofn1&s)>Z=Qs~6wmheT66EJ+uYfRvPihyoA+j$C$#BEwr;=^!V@yZDC~7J<4$ zw^%?4wWt_mB2<wPk|Lv9jH$(<ppJq97Eojl(#D#Zmy%jhG?9UU0aOkZ*Rp`R6CYUo z7+H<)a`1K}cXIV`b#UF~7V5E^5q61N`69RST^_*+%AH<4UL9U{rIqH`T$k3qB(1%| z@1nHz6=~}ZACSDxMIQMpJn|QK<UcSuG4h5o-4K>vV01}X^`fxq3Y{G)m$d9IYT4Zt zk({AC(QAs=1g{(7%1g_aRBy1@5p+@A;flJ$C2@xf;tn@-EatK=;J7HEdBehLF8c)q zlZz6jH_R<TOyi3ZCO0gtK}^$&5@t6{%|T4VixNgR^o-}SFW|Z;p?$*;#Nxdup?AZ= z>V(pTh^Px$IT!SEFX-paWnTamclW#y9C{%k=|XAw1<Q&HmK7jVpa$*;I*@e1!1IEE z=Uny$JQpQ&?@GwdNS?_xhieAcQxVA-suO*t_)PG*E337_=Ax{{6<Lc3Zg-`X7MRVn zpJPA4<*u614z>$w_7~Ocuc+Bi@V~35zk&avqQezMhpA3eyzVM%FSJ@>HNgid942^u zWn)m$UShqXVu#R0Wt%I?HsqU+2;K+Y;TNJ~FL}pa@Q%GIZMZ>ght5T5_bbxw7o^=U zB&SZ`xT|ZjA>xXz-2tUbx-J)VUFK>oaG9w;M}LO?38e|_cO{i(O3#s=z;;7PWdi?Q z1@$G|7jzsBC|y)=y`tbcf%mSQ>YVHgTJ{HoF3P!Fk#m{AeOF0yiOzK;qf1Ig8!UE& zTvT$pqU1Dz|Aw&Af|N_bS{H=1ZWuaDb-y5^dr?FW<bTx#`WK}PFNzpl5iz<TVuYNs zye>+cUy(N7VRlK{aRSE;Vd)vAmxNU=2&>%CGMef>!|S4m`d2mvY0VX48{`kDoZz@9 z6?jD|a02^XS&bFq8`O44Uomn$P;w&VLU8y+*@!E$5fixXN~$i%x+rOQMbdBr+fxD2 z38@o#rtnPQxgn$Zfq_v_0ZdGA0Xa-%qW2W<3EmUDZwM<aaJnR{c0pL}hOGPqw+U`f z<#bkrZ3x+6dqVZ1T)-8%fC=t*6?ImGZ%Eq_eZ|c4gvo`#&<o+w7Zqc!D8@|ix+|-> zLhGWe=@nVi32q>xp~>f|xY~lM9UdprFC=7qU|^_b^kVwJz!1z71)`FfGC@=kQw50f zU=C&ez`&5qoCl&L@|iy}Fi7Mxe*u#p7#IZenSVSLR{Fre#2d<lOhW81{lLKB%@_hQ zNg|X9q&Jiaq!*-?L68vn<HwI5A6P*q|M>CaD;I-^@&c=i!g^PP^)B$}{dmeP@_~Vo z)fhqC;MZLmyh3nc*pjdd{JIzTbypN$;5WL!fsVlDGBdIo|M>Bv7*wn_Im-IWGaOX# zW%E;KIH%6#&&zzyP|%-)`8*S&KQHrnW)^>WhVvY(AU3Zch%L_*z|MSLQ#gQ$`MeEd z06X&qCe{FU<|-cjjKsW@oK#&<y@xWW!44bLP+<ZOEJ2$;APO<CRLfAqumC=U0g{7Z z^kI)$Mw}|qx;eE>B_O+@1|bcQFk^H~m`gaJ$`}|J(1$u08ETkPSfNc~u^Q$D@CG?T z4+8^w$C;5KPrimJg$=4tvWBsSxs0Jmw1#;B8?rta8&iJ`a|x2l6lTx>0diLzMHZ<O zxC~{Mvz7(91CQ3xV@2t_v!pQ8FsE>!byL_%Kvf&mnKf)RtSOvG-4w1At`sh`ZVEec z2fv0L%{?3_9bAqz+^F)w44OQCw^%^Urdw=@$)JWnkuRtQWi~M}xy728l9HMN8qdtl zFH21+@?l_La4X7YU|<N*WCT~xn!MolO%ce^Mb)6X9o(#`02#rSSWu9fmvW1(C>5j~ z($)dZ7!);t6gGm0CJ?~~8ffG!vIS}41UUuV-YJ>_k^{FKT0yK%5YY!B`auM!c~A^W zNAN}fr0VB}<cxSw&M2DAz`#(+z`#(<#t3QvJmnVZv1xF*As{lHV-m*<v5Nu<R|FIq zJa6y{_D6I@Ovt#%D}RMozQOITi1ZZi3koJ1k}ir^UJ<cu@V(2z*`eC#)8f<MbAz3C zg7_tNi3{u!H#kJEbI4rckeLyAkwg6ohx!FDy1~gm!SWKP%mq%F1(GXrFDP1HP_)LT z_J)dKtNR5Wg^MhTU)dP=r01G1u(%?vbCFl~3a@U1+g)C<&gh=#2DiI>GBd&!gsiaL zp?Z<e;R>HagZo`UnHk{=(pE%YQM24(azNxj$wfh*D}p`^UUzvVXJ}pIRlUNi+Tiw- zTWE&b1#ZQQ+=^GY6)&(T7CAC7FjNWa7o`@bmgqwID7xUL81{1H4kNf6K^{;+8?<6% zD1mq37#NUpK2r)a>TpUe19BOGIW(5efIM7~!UE2N*v)3e7_MMK?!?xhl}<R_$d<y6 z=0=p#2`vY+AP>IOFxD`E@-SKsW<^z#!jR68!j7hf4SBdMouP&$g##&<bEa^naAM?g z<Y7aMT%N+!!cfENg0{k<mKC`)s^O?%UBiv)TS)$A1t$h@_Ac@QWiC(=P!s@Sftu>j zERQmQ0g4q+R{so|4w%l6&QQY;%Tmi&!#I(thc%dCB}37CP_AaW#iVC&ixr%{ia|Aw zf&w_3{^GL9$t*4b&79a(se!GJhonVFA6m~QCqFqcr`S#pp>6>vBTIlM1}^YvLC_4* zj#3Eg0-qK*X=^fp3#Ot%koBBJppppE)dH7J;Qkh#G6+;n7lDf#ct@oOT<~;*0)uer zBZTRdqQwjh44@*b_$k=!PgyuS)EoU;{2Ki33QJ9~o>8$v=%TRB6=9tQpS!$LGi)yM zYFy#fXmGosq6RA*?uyAy$-bauydmVGnAH_As|J5id8B`lMd}KR)CCr)yZmB3)ibOX zlwRc5y~3~C-~lR4#F5L6_zfvLJTCINUg2|XaEBEqMk`7$@|j-YGldi<K?}rIa9>e0 z*^sis<$%gXLH8?y?r6oy18~4EP`|*Zb&*f&3ZGVkJGg{dA%20|@FKV26>h@|EQa8S zsN&a82bVBOpms`PNg_)50xG#dY3Q>H3%GoN)_5R_%ytT63Tz-v9=Vi3Zfm5Y4Ns;p zr7+JyD=C5*Y8X+5Iw7SH(vUuiEGr`4qSmanOvtT=8m1aX)SOp~QqG_c<)gMEu;^w5 z4Jy~LW`V*H6le@C46z=y>^1CJ@L@PuYp7TeMU)-VMk*FV5#@l2a-)cHLPU$CYS<Pa zO%j1E1QRvvS)ge&FdNnXj0`=VD8u4t6Hm1q=?t};Xg)AP^8puh&{q}3RoqZfsTxi) zTxE>nDo#{a*K#2b)MJKwI&@mCXe)kowcIt_E)21YKttCxTxASJb85I2AeG?Y00R>> zJg6>UWI!3*WvXGU;XxfHt>vxZbzz7t#y>!v!jlai{4P?h;ldG0JjjEpHE09en1lIw z7UY?<08~$a{fbPa@TM@K<_Jt185w#?dKM$+<{Fk7jvB5MzBy=9U%?ET{C>B%!1WBI z^$4oRG?{L3frc0%b;>PXh;%Y|VM%e3AE-zNHED0Lmn9Y@XCxNgV$UnhO)bhyzQtBt zQk0pOUIZFFy2S~e^)E@xEx5%}TAG<s1Pa(+j5?akMWFUU(Q;73l|KzKlo}5;UQ-0U zWejTO-V%ln#3398sh>bq(=CO<($u0#@c1ieWD>N<1mgJk<ovu6(0H#vdTI%Hos4H* zaY<rca_TL1sExNk%WD!r!@ET@Kwj$wIg710zo;ZN<(43LY%?CTUIpsHqM0ChA!u_M zN#YiBPHJ9J3rLo|D77FbF*#L}AKW@GS_(23R6T+$1-l0v0=M`eA)5(x5+p>}Anv*) z0&xP|AE0GD#kY7Mvf#)nE?Nh&O%TZu5b;~wNUCmefaJYXD~oS2XQx&cgR&F6@eg5u z+ZMO@5JT~xLCT^-3=9lQK#hlPCgfg#py+goNfI-3E()q&5mayRy1_0uA><Of<OO!g zyZmBT)vb0IT~xQbqHcE~F!+La=mqi6i~M0%_`@!6ggsE!1y#-R7g-eU@=Bq#+Mn|2 ztti`2b&=2M3ZGMh`%^)c1>qM3b*~8OHh@OK#MBm~t;o74YI#M}vcd0$h**Q~4Izaq zLK+_!m{|3|L<idqZowY=8JU;3)h}?XuP|F<za#UKw)+Kb_Y-Dk>@UQnUGh%5;GK3M ztKdpj<p&0aIz|_!PYev%j4q%t5D;~PNAfz4!X+Ms1!7C2FY@SK;nBUoqx<8jwBiEY zi_!*Hqzx{xNPJ*p5D{zec>uRw7i7II%=+w0+?p4-HCLpq$v+@=NzeU)p8E|RiR(P_ zmw4nCur1-f$fI+GN9O{M&X1>JGBaW>ifLUD(`xX)%PuiPY=P88cAYEiIvbcSu^U}r zH@YjMyCQ8v)<qe|D>9B3I3#aK>wI8f<&0s1kR9GP_@!p#L&j5X2*_U-P`f0cwxIf= zfbkUp;|l`DccsDl4`^SM_PZkOH-Y1Zv@&Qk3uFMJU<}g)=er)>C!9}moZ+|-9CC{1 z1kVKj>mpj0M6_0T?^WKByi;S3#zh^ciy|&pL|iV2xLk<JydG6>DXQQ?QSDa-1}7$0 zP{_G4d4h(IoS3{o<3}LsJ4h}F6u3@IK_Df;Okp4@f+-53G8&>X8pJDR1XIzBF(5$@ z_2b8nr`$pdtS&f&T*$1rAY64pxauNz^%d^w3oO-;W|APdnFOA!(1oOSl!g*$_ygQf zIsxg3A(f%zHI$Hx(-IDZaVboAnlkWa21*ByIfVta2(M+XVN79xR?kc|jCqVHEGTxc zg1U4_Y6MX_OH4J)HH@g09eDPf1-ZMFg4U2=L|uD;wsMA%0l5K))H!9x7?xs1v4sP@ zS_XBmVg+kax^6WXZs`$1Y4)*z25>N&n3`OEw*<ja4lRtq#T}#r2CkMswbw0SXpab4 z;VnjsB7aa}$!Mv`bc+X4?t&E*-{M1^S=8h}uYo{S47jqnB?Ovcbjd6($;?YG2}{f= zO)Z9Wl|Yr0COf3GKLRW5!R7ufHmEhc$j&M*iUF0ZprJoV5e(_y-2zopkQq*>4~xz+ zFfg14sfBifpkpx-GV?iRaa>RY&465yFulMc`V^x?y(=g&Mf!rQ!3L#^f~HplO&h#! zSb3i)x?~l2!7A{Asu>7{M}kUZ#fvOT54@r;#3x?zO1$8ec$HUv0ow|$3ns1?dEKt? zx?SLPyO5sQ;0EeGDK~nxcr|!|m}-r_ExrxDPa(rTrW=YbidbC{v4RZyK-Jw4lmm?c zi-L&`R?sw3Y9~(*PX|v24=9g^?WnyV>~}%f?;^MV6>k3vEdG%EEv}!KlA;Tmk<^98 zk1jYMQF1w`MFGy`GBn8LNU5F)Bh`Z%m`r7iMe;RF*jI*N87gH$84P6t<!R)6oPyRk z0}bhdS3@u(t3vPcFf#NAq2xf88m3INp{8I4O;*2KLZHAzokoY`31)M1^P-cWjz2qk z(g(FcA<ZK2hyXa5vp|wLC{cp-Ly|HmDnLmY(w5*%Oo7hS7hPmvV0Z#bz|$f9cxVEa zl$|dzOX7l(*+oh7E0X3HSj6s1DbCTppk}=z=%SRv6)A@cEaErVB`+wNTwn*G8ywQt zIg~DOC|yuCy~ts9g~RLuhuIBHn_l({0;(4|)IbRmHq<Dn0#0sV0yE8B5K+0HW_dx_ z>LR!G6>jSbEY^@DCjd@zptg@LWC;LDS_75&;Iwvu72Nek8rnnDC@GAfb#ow`!ZZiH zlxHXb1roICK&(Rr4gV}aG7YK#G{}#ZBH-(aL~9s{(*deUYMF4VW<@Qn;pQtLm;S`+ zMl~ysIh`SeZ4o1=Zo}bLwBb0o?YMo1rUUFd7M$)wtBZ&*A590i7G*^qAVrF4PK;Wd zt%fOu3)=OMMai>lDcm*8HLPgEnd~*}*r!=Q-6O<yvKodIE<_unjIl@;Rb&CE7C|H$ zD2q6=QR9q}VFF`bKn+t057ZYiXuja3$QL{qzCaS8rZ4i|)i9;-;qU{04QOf&b(I_O zF~Nu72P6@y`k|+WDMbK>9|S3i2LTK}Ac;`b4_Y-$DMC;`sNl#)!kOqRC^^8x6>OmT zv4#UQlLbxE43MppH4G?Xj39N$1NNx7r<N0C`Vh1(Py|cMQq->~0@Q8`1`*L90@RiS zS8bZikkNgx0EoE73te9Z>37`XDg(6y<3S_ykkOG4a7)v*C=^Wl7KMS#O9T<&AR-AQ z%my)|C<s*VGJ!3>B?zi}pbN+#{eDd@aKjTaM+llT08bg-Vo5Da%qap5S>9qV$&XLT zOfCV}(zm2g$4Ma77{sU|aOKSfwh2;$gS>f*3*4%LIQ}xobyq;H;{~tE%7ZS=11+e7 zZyCD93|hT(15~4f9002GAsABg^Mh)BB!3h=0PWWRslEgmMi7#mqCF#bL(&PR3t<r# zGO{k{W?#_Fz9^J)MJT7i`zf?}Ag?^XVphcpwu|!mSLF3Km|T)KzaVekVbX8WWii3+ zBDeGvZs`l$(mR40TyALVueVrbvBCYKw#^l7n*%JDv|TP}yG#|CkTN}EQpSw(i(=|m z#MCbeYFrW2xFD!;LJI^NylxnpZkO03u|xl&q1P2duM;Vk41+Eh22F68?lH+@M*Kw) z-76xx7esU~L_{_C!YuK*sO@+~+wnliC2h|O+MW|ckOxC92&$hbYVd-#hmAJ`T@<mn zB4W|t`+%Lhqr9oUq5iIz+Jf2*$vdnLR9zGcx*`_T!F_{U>^isHC2qNk+zMB?6;^O> zaKEBqbAcO#Zt#fDV86nnaGgi}5|8={?&})Xmo%&|YS>=Uu)WA*cZJ990*~F#8=Slo zWG`{bUEq`>%6#7|8cr9uK?r0%?-d@k>pZ%bcyw1}U)OWIq~~~1&-sd;^F<z)D?Bb2 zcwByf%$K~xDRqHU3SoZHMKP}{VqVzX?|DVT?gBRmfz0Q;!lQJZNAnVo<_hiWTDF(8 zY%glrU(vF^$m4K@$Ke8x1K4~N_owXeI>B{OEcl98FgEkaalgbRPRR?Ll6Uz;d$MQf zt`NG&r*nl*r-S*1tX2nehwnuWsT+zaz3dZsFLKB|kkx2)@9@3IBK1_l=%R$l6$uki z|B72^f%OH~unWTB7lgwvaz|X@j<~=Q0cko)KqrPl2_M>c#4<Y!AC!m#Z(jm66G1qI zaSjVgkp}4+fEt_7k`Jv&V<-Wc1eHr+u3^B^;AinG0+(grQVo<_HJKqr$!$<32bBdY z8H+&TkR3>n%qtF=hXeTnr5Gv#9cCcS$iPr+1#ZMYP}A7KbRY?|fKE30f^0N2xbC6` z*98&v3z}9Jgsm@f+g#zcxxiur2`q8_%)HDJU63uf!wS??28F<9(B`J;C|gG`LIb1@ z78;CLLxaf=>{m^uBDnX99)SuaP`ex2_Z9=W3Ot64>UU7bTN&hc(CX3#hNnoLyvr?w z%ag+TDXBTBC8@~XOoloVOu_pTGR$Q6CqS+T8^*wZ)G}qps8Jbfp!<E&89@CKXpJn7 zoQ%){6=hjc3f4X)@(5KHJVDkn*D$9bP4CsBj6&6*jWgA<*083qA=)FgY&C2t(5bc} zLzFQgh7_d9e)M*04T|gnP}oAl1wqxI^>!E;@>bTcrNAcZWYAjG#G27w!<NDg4N;I8 z$Zc9;%}A|bOW{Q_1G!B}tQpQVY$^OuGi;GVE=8b*HAN5+#x<;{V}*<iHH-_8Mk<j* z3|Sxg(h-n8lwu3K_zP?644yg|(3e^ek<K)QklSXU_7XTR-(m$fyNW=qn4)W-LJid1 zD!K|{ff`^%;071C=>=+V6@i;x37}OxypWu!0BwOqfm&e5?JPtQfoO5@6{RMoc%&w# zq!xLE`1(Lb{lHT}%mw)cn(UBf)qRlVph6YWzyePOF&Cv4gB@86>Zd`PO3)HV2;var zGN<S@$TgtS29(4b7@p#9F5Tr4?9rZ(yCCQym&O$?jRxl%>_Rh&HVEw~0?odNg<KE| zxx^lFfj#7|jM+sQiz_k~7dSxEG#4baFX-A`5Vya`?{J0R;R1&Ptl`DQ*HPZ1+u(ec zOJaubMK0MZT(S+$H@Nva%6hCDTtE$(;->0`YS1`d_6*%Cd>R)yG_P=IUf|HY!67`s z_X>y7bq>u-9GWYXFLLNz;n2GPMxZ8yaF=w4G)R&A6%Lu}9LkqClovQ(<j}anp>Y9> zP!xGy;gGw|p?Zl!bwTn)4y`L3S{J|wq)4Porb7l~5<%-g&di@tyucdNh!noaVS0tb z^a6+J4G!*p_Ad4b%oDt?2q-L2fv-YCGy-m5Z3N&c@Oi)!6230FnR#jX*h~5=kdhve z!H`PrTF_opMno>HWkfEWYZ%d|eQTL&m=+*ad<a9)#}Uz%OrVVo*0R*Fq_7~_%UZ*N zTz;dK&MYX)IKX>Ykw+iV%XdbG8rBrpA_?%aOlS#Jk6c34upp0cfD%690GwKO<h~tV zl^nQT4lkh%(bOQ7)-@bu3`L4H95~8C)KEdvhw5HN29%|!>@^%IyqNvX8dmuDL=Y`P z0%dV4zR=}FcWn)43P0vDff`o$SVTMWm=IzaONt<<DGS0WLKq{TT*SvA@h(phZegh5 zN)bV%U5tf%+{kGY{b(h$88EaZzG(5tQNvloog#)AGARtf44UG8Mb8-+7=CeQl;q~< z6(klFrxx8}DoFvC``|%@BG4+nB2c>@QbU0&EKtpJi#a(z2fQ>wlj#-<Xvsv;bx<t= zs=YLsi#~wZU;)ssoFZ^d25!=W8uutm3qcKL&~W`{EokSWh9On}G`5_=fC!-!q!MN# zQ;%RUgC-N$eJ{c8gDgHQ0xcN?4_Y9vJuF%TsypRDiv)aK;z5NRw$+BIpsEXW1VsbG z1tDJuT46rHe1`cIVdVvmSA^9dVi$yb!JPrHHC6n<kllC+`QS}e#hT2JKmhw0RE47~ zm^K29P;jJW=B0pk`xU(a1rf;S;7S?1j3Fd5Hx+UaN-=m!%^$oU5Gn#*!2uo(xy7HJ zTH*=X^8}Lut#;JpL8`r3it|egZZVgn6c@b$*#l~A+~Uar?Lmai(4j>UICem8E(R^} zg4Xw73ev~oMhd~AUknTko}eHDo$S)UfKdr^OUw|Sk$aI_=L)w@gUbyrzJA><-5D&f zdKr5qeM4AmLirS%1&mYd8+>l4YQSdrKr<xJSvhz;J|SgB@}%qu*?0M6XCyDsz9Of0 zkzfA`zkUbHT|vbK$_uiuC>dQ8G`=Ee3|ig*Y8%+hVC=D<k$Q<+`2x4{3g#8=Yj`#U zuHoNdc0t4Of`;P_9-)5UF5el-Gt%eiEGV3#f00M)3Xj$W9<8VRVhdO{uw4*0zaVaY zk>BD9zr_U(3s8LyZW$2cg1Z9p3z!#pUr{i)C}4O+z_5e$F2v9Sq8G&7FNnKe<oCG3 z?{R^{17s|?nE+k`x<Ge_(g~*vQ85>C@-7(WUogzSC{}Pqte}G%)0dcb+yE!!4cP}Y zubBB<5c0h!<a<TPw}bsI2k!+w`HLJ1S2z?da43M<8tI*UJ$xN}AXlkvFg*}{LEQI( zxbH=NzbpKH7dZSr@H23Ub;y8IeibjI!QhHzpa8UJ5LCB+-UMkeAocTbEE>mXAE4Gs zwG7B}kBHMMY8i2u$DG2H!UQhOSujd-CeT(BM43{{4BA41#Am5tOhMY}QOjDxh}?Ip zWvgLC-kt$kyja7Q&Q!|*nm<7@hclg_maB#-g&QH}!Vv3S%bmhg!(GNuWRSuOrgdt# zv2S-t;lpS(ao2DyK-#c^Xf@Sfl>a?uB&hXC;jdvVV<?hFuFbRHeYYCM6n=y+QUp-f zf8;TyFx2v-2of*>sSk(M1YVSpZnU{WMg|mFw0=06EJjZmMK9Vc8Jb?SrJ-oDT&U?1 zt(Ik|VXk4RVXXnxwp=wlHN49>85mZ>`=+&gD6IpurI)q*H9YXe%(^waaJD+iW*(3o zXg%N<CI*IDfm%V7C9eE60yTmuLYOr^vZ@r}8h$K;Zz6t>MJb@!0!XDF3939m^*y*r zaElvM(Zebb(6IY0-u!}+^rHOI0v}M_0<M#bVnJ;RMvY&Ln!gy;Z*k{l=D9#m0{bPX z8mX(AtE-x#pz2|z>T9JMe2Y6bu>vN)k{PzD6VzM1#gm(v2kK>lPZ7Jtlbcup=M}Ls zGB9ZJLADuzYf(@gh_nu12gv2%m64zl=@v(ENl^jF^^n?D0D3%8yss-{n&FlpxPF83 z;=$I7fP_F!K(QD!2B|3o*){;$!~_W_@LEYwEnD;lWHBggivEHseAuRb@HEREkO&8K z?P&2Wb|~u>S8+*VQ3?3GsaqVN<HbNOy~P7r9$J)IT$)n?_HYqsXHyY7sOAOr@xj$I z3IVCBc|Z<_`K(BY5wup2fuUFdGLr#Xa5KkxLB$54i_#`nq)jgJnqJ{GZEyqakG5%W zfvsmaVRRup@<K}5h05v+wlx=QYc5LFUXiK=EpHH1SrC0i(6GVl2DjXdvMbzb4K6o? z#3s~U5mIgNz9AwpL;i}0W`i#hOITrn?gb%(2Jai7^^xGkkvI4S8$50Zt1YO%B5eMF zft6JiOmwi{;O3j4-eK2aHzRpL_>8<6c{jvlr{_<~Uy!`Q=%Se36*0XI_ZvJS{r+A4 zGu$uos9fPuxd28FunFD>h`o@QdLaOWrb=89R9axNg7=ED*#!%4$QG&#f<70r^BcTw z_(flcPrl#>LcJAN_!Sn2EXcj0XmY{K^CG|36@IS^{9YF_a~nKBTROBZvWQ+`5xu}7 z3TmCGG<vsqH+X}X+80^GuCR!K)`hTecA)Z5HEA^ZxA-^sKNVJ9;I_hR2g{D^6V?|Z zk}jm=T*#{c@1S)CtuhpG0j(|+abarkxhtr&z~-W$!4*M+2CoN*^{X>7R;XN()4L$2 zcURSNhuQ(H6WJG4qpqk%UF1;e;F*vEs@Ag(=v+w1xgef<K|J>&f8G`TybBz8Pq~FA z_$<)6Agp_lTki_D-USvtNF5Iu6bQyw#cK@DDxR%|HJz!J9j&V8s9{9z>eq7CFd|p) z;OZW!YhBBo!V9ewK}{lPZJh<Lz-t&&c#*1AKCIO$tYXGx0&>NS%>-WLDjjqA6^blo zwF+8p4Rv=7X4Qx+i&-@y%c3oPVPrtD7o+il(%}YGd7L#|H5@hUHQZRMRZzhXb0xlN zRS&6J)j%0KfZBnflA|0kx1F2XAuvDx_s!{~8R;)q<D%K)IeZd1h%mP}+-Vy*; z44~QoCc>ARmx9EH)VPd{3=Bm~Ac7f0uz(0~1<8ZEf&^8IMI0cNpfz&0_+d3-Fr-2( z;$mcAxFrgzNzki6BXBPiUmXbdP7x2t0uc})3L?Zn1gI7);s>z=K!hNO0J)aP+6~+j zg}G9ak%3_ns6M<0+4D=A$_;;&CM-UK?~1T`gU=0d=?gM?SH$%%u!!6cmAoLObwyOW z!4FaAJ)mo4bb(9iB8&7D7U>Ht(xAFY?;?xj6&A@0ERrA|uDa=|u+jpv6;>C_-A~9} zh)lhZUUZ?P?gImZBa=61DZDQe_{;^7FmSaLMr5^QwZrUy)dm0f3yJv`RST}D7LZ*v zLAsy8F8J!ERn)7SK)D23!XPcI#n;VbtYJhRhz2cVL>Y_*Et*H}X0oI+pm#OF-9@$( z&KfpwcaaNB>(sE}7&zy~=q|F=u;N(Wi@Ai|j0Ckl$iwJ}`V7<+PC>5!IBM9DYq(m@ z8qOMybf#La8uk>VNy1w08g}FkW-SkL?T~_TfEuzamObCd^$un?5?K~=7ap=K{u&LV zu1pcEVF#T)fl_y|BTqr1b{@gqX5JJbth$5{?E);i_)t2u3^hzOTs17<<Jnkicx(93 zx}E$eH61^yd9?yab)s$!A5ymysXnU#*};uE@d~aT1#5*+x~Bp)f;B>*!!=QD31&c6 zl_F9jfTi0hN~m@OZ8`w=DvSPsI#>Te1OuqnK-4p!D&`g+^iVWdT?y_71-n2hv|C(| zniX`g)-4VQyCh$e8QihD#R;n9z=F3pLESwt6D+04i(ZE!x|X0$7<g$Lq*uwDnOAZP z-nE2T45>-M{Y#J~2-CqmNnuD&5<J9?=tP36NM4W^`9K7?pNa4+qEZDdZYh!lDUkya zh^kZ`#8m)QjG#6WN*@uz0rwP(KpPOi#8!|}W&9n)r_hz|B=!vH-Q$CeMiX0UBPwc8 z&knVcMD4O6_481b;i^j?=veG<xuD~4LC0aL@Pv>VI#)yy3;GVEG<e<6FxrrELBr~T zhE;Fz1d|zDSA>->@~d3oSGmBia)7151J*s{J`i;wF#bYf?uGpF4-5=dkiCCyOn%@# zqCdDY_9w0~mN(cSwnOSb%tiU2EAl}XIb=FGCs=~}h&w7TgeP1OPrM+Wc#%Kp3V+fC zjwGCYM1I7!1Mt2il(}QjngLMN`FRq7gNcx5c0k7uA@578WkQ}yM(jLDVFr!xrZc3l z%wb0DI)PU0yD-EG*0P}N1i&b=dxSt^!DS3ZmNm@Cl{5D7SS)*DP<6pOcF>jrm`Y)9 zVOWDP=9pJr!vtTuV_CyogR~Ei1ly603Bo=u3_2nNi|rOQ%*Y*A5^O{o&BkFP4>teS zFeC3CfgH3{!<52X#!w`NQk|oOL=D<z@jOvfwa9Un!iJUxP>w-K;j3ZAQswab-4cME zR~VFEUJO1)lMlLjI4Hjye60ZFSWR%^E-s1(EpTB%ngs=|$w8UP1f>E{Vfz`hj0bh; zA7Ygo&I7hEW<t=9(*d934%&$g+EH!zOVZNP(o)yZNY~g<!N}Ok)Y8h-QqKSceu-cV zvg)a(tz<3&Wk^Vm4>VS~lJOQ}1=1?YUmP|$iMdHBiFQ>A@Ws$z&m&KmLR7o~)fT$o zHI-{UuISkANZg;gEA@cq1^=vzcG*|#vOh2|<T1K{$|hY`P}!vG%GAIB9$V66EIJ6P zzd;kl0~UNHMOvWxQ5!_)fCycXhd|R+kR?t<`iu+=goEKdNEb91K<5tbDBNGVtMowR zg@Ejf_BmJVb3QOI<TJX0f<ey>6byQ9uwVdBiKF-*J`oPOQDHjh%r<Z_f=;AhB$QwV zO=dq>!Y~3a`qyNHEWIbsTc1GQ0-Y}2z_224edwyt4VF8SF6ug7(RGAHA`@hp8hBnG z)OFY7L>hVrWdex(;E@Hk)QS?&$zUv?lL2l?pf2?Xt)_*v&me;eY~V@kTfDH$8DErN zUVKXcnlnMXc<`AA>Y&jH#!T3B0cZh3F(?BfHEwS4p(f)ZYeoi!2Oz&~gNzkG*X)Z( zT#(ehBBIma3+}(`ToKp3z#?)Nx`It<htdI~3%YI>blolrxnB`-Z}5J=D+Su3CkQ4w z7(o-LiJcrh9331T91qyVXINceSG~Zl3R>yqe1${eI*0rv4*3PV@P%av3E4{=vL6^2 zl)>B4m0g)GaL9gSVBiF&6;3x$cN<RL;FO#Ze2G)x0;j?a4&e(TDpxpEuXE^J;?P+U zdy&KX3WxOt4r@%4!G<Wi!Ay1sr9w`3u*op;0f+Demn$5S*Etj}aVRX1y~v?|g+u=W zhd#(Oh=klF4!H};jv&M#cag*C3Ww7L4kyHd^BZw_7m8{x#DUONLCqD~7tGwQXt`hT zjJzlqbwx1hf?(8F1_l=<(EfdfSf&h+{h3TTAjiZq<$`$mOhuq<;=)u65(H5VUO1Z` zqWY;7pwn-4AuAhoOY(J-i_1_NAfU-NP<i*+0%I%yQ5d5w9%iUvDCS3Qa-g4&*u;pu zoj!%Bg`tK4een)z{|D5habbv!s|8IyGb8$TDJ;+hWui3<IC|<#s3tL@T-<=(`>kOF zozus_P^3}Agrj;!I}M3A^EPPo#HX;PfTp>bYZ#H2P@#_~r!a#~bb+2iV_(CJeM1(g zQH0V@#%pF37Xw2IGl)bD3;bb@I(3UUAqnFII+hxiEcobp3PTD*E9f>uhE}FDq()gS z3utmT9<(S1TofZuqh^76a9~lil*C%YvH)pB0jvZ}fR+`m;Y4k)fEF7r5CBV~5h<7> z5unBVP*pXo3qbu(C=WrQx&<}lYC&5%*|I<-G+09l4`>@&3U3Xv82GA<8aD7LJHKT7 z^1(-ED-;yvmu04;rYNN47b!sUpB_d920CW?vk3zOgEs>s1E>;<6|QAO-a&`nmJVZ> z&dA8nBLZ5Fwvy3rC1cTc1_p+eOyI>_x0petKWK>;>>xQ#8&LI8QBY)8r4DgJJfsYW zFUgNjE-piAs=yR7GBGfG1|7Big5d>21H%PT{R^V{3yh~G&rqJ2F(m`SM<&6|6!6lv zB2XI-Jo616W=HWYXl*zs7(avVEP@`1$5qRi!cfDQ!U)O=vl-?xO=Rlf4rW-%bc-o1 z9%ABrgzvxyR%0$z(;(ol9N0;EJWLD>#h^nT8W=8!M?lbu&<he8GeQ>x&WyYup#efL z8L+S1Rx*NZ1uyHXl20rtNleZFohNUt0J_jBFEKaO7P9$HFS)qvm#{PB7$RMl%;JLl z;!Mzip}#mlH%{q;q<LV{pw=?<1f*LWpcw<u(LJ}gz$Z<(g02y|#hja10a?HXieB)h zzakS*70z0en3tY<i>V;x7AL3??pBnTn|g~SGbcZ}XcK6*oGCN^76;_+D^LGhY>;Ta z#ZsJ_nq36SVqgowOX_d2L!4d=J<$TZvH&_1R9xf)YUV|PtYk__yTz4UTo#{ST2fG2 za*LxV6>=>Y=1O-FY;jQ($H>6&3)GHS32R4O;g-J0EqjGqw!!5FH$SLdV0o8M;)104 zMLvrwe4zDWU)dOh#UQC{g~|m{{fnadS48zM3h7@F(r@toz{VgT@_~VcRTfNiFoRaE z7xtJ<2=21&u)V<}(C^*lJ%jlokIWSwnF~BJclki4muK{3%#gmsr*eT$<pvLbzgL&n zgc7)tugnbmLKBobkre#^StB-~`~shRgZmA3p{dC;)F<Xl$ypG1QB3U;yZQxo^&1@g z{d`?~ki@h#b%*y^<`eEGdCu@$v<bNo8g|hp?4nut6|?XQ;t?0cBd&-?T;zzn!V!6a zBl0U70~b$^%!HJSoYGe~r5l`XaB}yEPY?t{#|cgooF<6&$Tc{9U}F%HY;bA;9gZX< zGDUi#>=fAsuNy3E?Ve4Z9a#%l8$2(vs9j-EyTGD$gN3i%zsY}sBYaZ*D;tA=?2M@E zvIduA4KB(WU6D1q$Zvdw-?+i!hMs?e#}yWl3!+*VS+sAc8-jTg;x4kNJm3~?fS%96 z2WvXuYcm{y3|fHt1K?5;d7Bby=?Yr@PNO!1Z7mbZAO>1{0HwwU6-H2(BB&J18Xwdu zf-1n(g4m$ZV~4!_I14nC1J;94e}axmU|z&n%TmLL;!?EX7HFHmriNt!d|?^bKq!&I z(t^6k3$JOUw+3ohYMA3edw#+8Auq+sg4gP3vC3M*ya4G8NU%mQQNx<Tx(02)0bGB> zTLNHZP$C6$_8a-?8dmJJKB`+V>Tix3wk*(<4q!V{z}IZ0aG{8Suj>KV-#mUrw&<79 zf%=&gRntZGprX_PL^y&<L8SV*$Qj&khL)O;vKmX3ti{B@P~-wO05qz?QiNEG3|>M7 z8t*5!vMq80nG33Bi#*U>2VM%Uh|6{Giq#y{Vt`ewVHd<f2usBZ622fF2B}z!JVDli zf}%<lYqgq>xV^F%RJj&;fi!r72p>iUhLu8)4zw>wzz;<Dg9t=z83^JAfe28&Srh<b zfhx$N5D+UAM1+Bea1a5i7>go6EKrTN5_-Z@6i5JE4@QGnAgQ7l5GxiGP@sGc={-Zn zKyLA(1XK}diM$f1$&n4N^}ez(@CZ|>-eUsQdrYjd9n54_Y+pIhDxVK*4E#nH_zW-b z8A7VM;2Gi*W2VGhVpqJtt_ZK?Kv9TR%Uv`Jy<!%6K|JiDc-R&3u!|hwS2)5iaD>ya z-U<cRRQT&H0a(2ijJLNtT62MmMKCd1bAf{aN{rTA!3+dzE+uG@7mT|*8HieUu|jtI zfHuy61}(vtbH#%vk8iPp*KXZnPR&c9S!Je-+rjYaEDq%2Bv3gIx(vV#sisoIT2nzM z(XrK1MIet9rC_Zt_|cqFl*7ot;0dZQKm%?K41>SIaAmx}=W>D1h1xa7Q$b;>_HzP> zs4oQJ^@R)G3S$uoT^-b+QKAPwK^+-T;{sL?;p@nN8mCZo2ntI_h6wY(9U1uW9s>hn zxf0fn3=^~?gKRPjYU3NJBZH(0$(_tC3}}O!Nb1n{7?atNfp_7M7xy5BF;P1YwV;j+ zypw})DFbMKGs_yZ8-Kwa8LZu&6pUqKC<mgmVjt{8bqi`(*0QCr*RW;5hdomucL;N$ zh=K1526tq*{XiuT+ykIwLfbwJykK>~T@{pq3RdK940yvUs46JR02Q}1?Z&9Uid!rW zgctoyAQxgS`muLXphZ7umrWv;PKu@=QYVFa{SzD&iU7I~iwYPS81g|i3aHP6S)&j& z#zvdIhTR1|+Y5ZQ)UG+cvN2G3jEsoB21-r?6=2|bG#!k2v>JvK^jj_%aUN=kR_ND) zW?I0@cv4vAu+)OKVj>;BgZ;KL)ZzrRa0fJZQNx71X_H8u;KBxU9~aV$9O6J7Cgi;y z#F$viGJ!FV6?twDu~ijyb13rS8`J`^h8Z#!16~cxl)?*|jY7E2g(3DJ_>du_WjM7g zIBZ0#w!o`%kyhQNFhl1a<!abbRw{GUu;N${#DVHoMur{_)Rnv`{Gch1S`OrqLzE>u zXd_`Mtk|l*Sg~5p8jci!8ul8_6jqRlh!6nrQUpO{6)yurEk}=d4Mz&<ZW1(Crm%zT zXhojO<EY_C5rXIk-H%kpSR_h<pAXfr)-Xd3b401;F}9u7Fb6Yeiue`11ub-W2O_{b zD#4p6K~+}~ct_<YkOX+V3pCC25|qoTxIpV6bivaZ;3=n<VCA4;CCJiO(5lxeW_2Bf zBJd(u&`wKD&LYss0!5!evnAl!7tjO^cvb2x)|~vrlw!zq3wXo`Je~wvm7&Q6zVo5z zJIE0~Km_QV0O*y!5c|Q)Pr<GSpRLH4SY!!m_=84Lq2ojf&}liySq7j34W@uf=T+bV zqPqkR0eB!GwZLhE(1D~2T%H%WJR6*Eh)c~doKiNuep3BKag8hD8V$}J+!t9yZU~7^ zNSvZJU4N4PMIq%YLdp%!9c+!>H`uv4%v!1^BttH7xFISrLvTvi^!Q2f7e&>sh^lpP zb#grwm!49!KzTvp615dFSCq^yikn{%H}7EXaPQ=~AuQU#0XBO=(uAZb>K_<*IUz)c z(+x@K89rAeH9NfSO3KcWTHtk2QvZsieuvi$e#se%J+%v%F7Yc};8(gKA~&OOithx* z8v+v3`6ux&V7w@xbVWev0vJ7j$S!ccD55@r@vfxY+~NgRGwbKnuPD4IZveTt6LhPA zIhdH>ctb>VhVYE46^bhyS1Qk`zbL7<LG+@e*+mhv365ad37J!iXBbW_n^HC-abm@U z%qcz~n*}EXPvn@uctcoZhQ|fSm7*dt)BPv;FL1mlqIN|@?ShEfj~nvp9n2m67dd2} za`1L!bn^G`ckn;p=4<c*k6(hW?JFrQ%1Z_%HCQtXG^+qgm7nt<$CM+@hhQ|JnQE9o z-8_g^q@5Cs(6WXfxvWB;1kPitVM3Icwan=ZwJbGEILac%8uUe<tl-1?L&4=Ea&Biw zE{)LdN-RMdv4^;^SOTwlcvF=DcJq{U4GWG#ci2*p)_P!+ongpLcl2%nBSQ@fWT_ul zL4I*bdQoa|VGd*t8g#{rCO7y3>09h6nTenq#EU?Jki$bjw~+@!E{YaTNli;E%_)h8 zT(AwkZ{QYq-xTERSIEg;MY5oL54tuTQbK^s1rEr1bnsI2HjsP<h}Z`zT(}^beKLzn zigtqpVE46vR%8`}=5}D^0%URdR*+5*u@zJ*fL6^nFx=%9nqbyx-(wFx0Cf%c<WrtY z+CCSweR|_&cwOXGzrw42fmi)PP}r5AtOmCm5>gjfL>~x>UKdonB&fJRYegFTRsbQH z4-Cw#reLCj?S`1ljIb$r9o%=h`6qB+<d(U@Ewhy661U0)Zk4qxE21xISYFYv+#7mH z!|8&C(+wWk>pUu#cvKdIt#Dfscag{B3XjPJ9+Mw;SvW6nNnd1<xxylIfkoy9H;4r1 zkz`O_fFy7R22d#o;(u1d(k?=canR9%`0_?AOAQ0|ojs^69#hahA!e*5F+oyrku=IY z9Qu)2Y$?n&OtTqMSZdg2Go-NAFwSO}%T~*t#{m}yiLlhN<Ej(!<quRpV_*G)>YrLr zdjpheU||ZUQPnXrfbS0DaAAnuQp<(11BMHEAuy^7N<a%3p(a8@#}s+)4|!io4T?F~ zXZ}##glZQmua-N{4VO*G-R3ONEDhL&sP?0pM!9QhxNA7UEgeqm^Nm=J5ZPLSyv_(? z|8EUvG81UM6z0*2!3@d_h783l<xCaKkqqUGj0}+sj0}tnpyfT9?0&b{iVJfxOHzxf zKuahDf?a)FokJ8{JcC0#{hUJ-ez7UI1^N3b{9@DtuTaqx0vC2g)u7%qxS(TC&d<wB zO)e>_0j=a<PpwEzE-gugT-?QxmRgdWk(iTni#0hXzc>|KXx?H0oicrkwWPElC$(q` zsMKNsT_*)@9~G5?3<e((0WSM^kjlROpu!7O0N&!rEDjFz$t+2|#R9Uu2vi{6Vg;Yg z0ID{@hcg@isRmE2ft-o`2&JNJAVnbJFsMA-4Q(YM%0qtPo|-HC3JW|g@#{2rd|+eX z66lxflDo($d4*H*1A`Q+#Eg&!-26Q@SGZ*s@Lb~7xxlS+S5$d{7i2L>huaMi{{__- zMEoy^`0r4I&<Cs_bcbt)>jM!tkhI$c5w{&$7eqjGhbw4aOKiH#B$>G>3ruHb&&j?h zsC7k9>$;%vB|&3IQQN`xfP<%>tBVVA0m=h#!%0~30|O_g8kp#Ce#$RA!FdMb6t~Wr zo|?HOm-v-0@GIXHmzf*3z<CAZ61U~vi@X>5F7bsf*$_9rB5pi^Wdh4nkdmb?OT1PT zZ!la_c2ULbii+8F73)hX))!T5uc+8wl(oAeYj;uH{))K$1QyT&7Lgei7r@5D+FUFb z1yrvHs9q3I{ejfBxxp{~!BmP>0vv7#;v*;+zkmpY7&g|AA3v}OBgBe9YwxzWvbwP` zA7f*2<6=L?%jm|%UIe;R6r~jf??HpMs7+_6VaNjKc@Tjp%Tr+OB%T@uMC%E$<s}%G zK3K^gjr~|#>}x@Bs0Fzc<SZ0i!-!+m2HFnG6r{r#QA;pX9&(Q}m>~tU%8=a;TpkoP zfff%i>+9>k{Qv*|e{czKi#;?iJ1@UHPm>d=6kskb$|=eN<z=S);#({Qi6t4gIEo8E zB}+xgEsmtb;#AO5wp*-VF-;~&wg$xv%5u9}P<{jvPeJ*y5WJn?20L#{-4%AJ89A5O zwJxx0-QXAPsk*|ibb&+Z1}A@y+!aol1ss<+H7;;!+~pVSDZkDye~DjyDbo`6>k0;! z6bv@VTsL&SWaxar6?#0o(G^3#iwgc%6#Osp2VCI~xDXh0g+KTLNALrFiH=H0!vd*c z%>WwT1ve}%fE(Q@jG!gNpp)%TVg<}Wt&&_AV$anwfi`R+I?XN&v4?A!Q$R~}m~q@@ zf^kQX3q$OJTGkZi8rCv~qMQ^KFr9%s_Fuz_y}5#Zf+uoR<IsUNRDrx34EwU^6t)z! zE1Yqdj5Zj6nh5h4Q&?-+YEUnDMjPWs-nRs8K=7k9fY8)~%mfcDu=FgfVX0w+&K5A$ zu+%d49O*e<!x+q<$>|49*65pA!M%J?-yXbs;}?^Tf+l-W52$hi?bIpi2C-&>2v8#$ zT(5zb$!bE*?}hAGP0laPD*^AVxy1>p^Ws5wtKVXU2!oD!yv3YXnv1!{Q538q9(oiJ ze7z(nxx54A3(zn?1H%mg!3NJ8{8Etgevx1C3cun926k2@Fwwz$LsDfy@D)kT4-9Oa zN?@YH^M;6MhwBYVrTMb6WEa%$V7e%2eMQpx0|OJUJ(!rldLu6JLTc89I55gDx=>bq zAs>XMrp|Dk?>)<V1@lE2ohvdrD*~^`=wFvHy(DA0A^D<=)fE}59fntAY_H3>UXpP= zkbFYvqKxkq8Q<$N!Ixx$FN8!~l!?3|6M0cA>WWy@1+l0PpnLtCnI<sb5D;A&vO;-b z<dVp(s+UyFFQ}T&6`m11Q(}(942d0q7X|DluufpT!7nnQ@)Ezo1%8DaGKv#8e|+Et z`AUF+NAv=V2xguIZ9D<zSy0npIs-|0Rtj`L0P<;es5uxl-Gb8>V~;dAEipmTk|q-* zrGSQZG?^g>I?n^870@=TVvw_7!!)-9AyEw)V}hO)U$lskf#DA*nnCB)H!$4b;calm zh-OAsb<mnt^$zA69K2J7Cqyq0z9Oo6QBduQpxQ+awGP$})(6}I4L*>N0hdis0^UuE zV<Y>tc+fBe)HsY`2b2vN7_v+$%%IL!3JW;gz$d)JS!{3?JA{R=>*T@^%Lm#BkODfZ zmlHGyIh!GcYc4ZHXAeJUS0#j<!c)sy!+?G00=>MRz!Ym<%T~isgLIO-3&RAaSgl(2 z8s-#Uh>11KDSU7ie+@e*V?u)iiAoVj5k%cqFq<Jo2<Af2uq95NBKUM7mv{tDqQ&Jh zF?91$(=_UlKw0n(Ax`%aFt3)QhB-yNhGRBEiUjdyq(~xaO>oGuLc$!y|5E5K$7#Ma z`ptVT3=<e*jcPelWNJ9e7$z_laiqvXS<E$@*oTo)WKiuxIbwqg*=03cs4CIUCnVN< z<Xy#}b_i1Xf-qBLQSCu?5%z1RQ{>QNABSJ%QB6kW)uNn;jM-rYU%wq&%Yz&yDGI1& zFf!C|r8Cy@g4L?k^40Jzz&<mMc90a>dCR=$ruL{KPjn!qUo@4qpi)A-h98_Rd63gs zPuv8?9s%s8qzKk<rlU`uA)CaBY|;eA9uv?Vq~Np*nygR+vzR9^_2^7s>aj%LiG$Sn zP|8HV%Sxa|fVeU&R<D+=hB-wUVh>tb1sY~TE`wCysz8TnA(eVyKR`><8s-$Jt9$fo zm{U|CdXQrled`$`19-wk4e7pxsu~;c)@6m{oW$Z{TlLax$az5uFote%ZemUjf}4?F zmRf}1reuQlIw1*wZU`?(O;J}UE~(5(wN+0pEh^40vQj9>&&(@HEwWa(vnc^>Ke4kZ zDYE;;X;YG6XQN+|VOOPXQ<8!13f<I-g2cQObvy9Y-U^_;3Pdq4NFQi74TP(PPcQhu zO^7-+kTy5S4x?YJV6k5!Hu@z+U^k>Drr6n(B;}`6+JPINRq{3^MGE<O$vK(H*|zHG zsU<=A<)FPc8YLN-#hU7NRRuOBDRwqVr6nc#c?zJxU0ZbsM;+#qq>?;c@F0u2f<j_Z zW+LR0Q(JXs(7=m2!f?n<5kdLoh8n45sd*(j3Q#+2^dTmKT$p0_iv?sOWLgX~RR;D% zid_|tO-TxP_nB^HiXFJ!dyCE7$k^P-7(6>xB><NPPg&WjS23yE6@3Er4B0G<j4h0e z(OcT!M)qvb;1Qddk%^g+3D~M4JMe{OAZHZWRdInF<8Py1l4AEuz(yYubT<0n1ZAhm z3%QR7+%N~##o#6sWIZ;x#SR)t0*@};VofQ{EhsJmH{8KZcyQ|-G~A>qShN%5NPb9z zEDhG`z6BLXfi};Ii>yE`IncZtWZxF3O;<D*<a}<(VcuYWgHLh4#RqS~=j4~>=G_v= zEzK#(EXYZPnGPAWXD-PtD0&a-`j&yr2HpKp4BAhifDRyo_~2VbKm-dj14Hp=W(I}^ zhNqD0s5U4aaJmo_0y-m9GyQ^Q`bEBsD|{IZ?srACR+wKDHNGNh+~9v#Onn9WMKOab zVg?QVpuwg9CeU^tz6shFS!BVi3qtl6S?s~g3HldV6v3<uLRJ@9tQx%T@(N9`zsRe6 zg;yEW1l0l)9gH_Pgf56_&L~_UIJ5YIm?i{W<k0Nk>fpM;!goPP<06YjgVzT(23|f; zgI27+rmLp2uBYw;gDk7kM-Tz(vnVVOo+I;tL55T5BZ%nmyul-OLCWSLk4=Zy4IZ%% z3=Gn~pl!*#zD!_&3sP<iB$vxBl3h`~!|9@;{S`&~3sP<nbdkpms>XAL<a*gvvKz{F z6kXJGx}xcHLCO<?F7kMGc-`d@oS@O^-{arm4`OOg*PW!hfPIC~MIoIlLOSr<+wV%r z&CyyAxjc4J><054P8U_}uc+EXu9)rc2D>w1Mf3%!gbPv$J3ROM?(#jMejzyYqD|Np zo3IPv5m#&?A(9uQ5-##2pjeBgPbR<{!1$2?L{4D6AuKjSb%M=An+47*Oc(ep^tqv` zwLE%J^akOJs-{;|O*bT7QMH)hcUMGmhTcUH%_}0B7eqAgs_U!>T_3$FdWY~uUE3?V zwmTB9=sH~2b-SeNcA)s8uJ;vP?-Pz!bp5XDhF#JPyAU3AAv*S=Zrl~!xa+zpmvmDu zq-I{!&AOtSbx}S0ihA}15tX|lpd<7yifCUE(Y_#}eM8CU0|UEgC?kZN!FE?dc1Ffb z{yF?J_;1MTUXat9!F)s6_yYrzq&k?G;e10ze!kZ%uLZ?3{pa}4@V_ggxIlTP*Bq}I zUNgMzs%kEY+Q7Upeo6cct{W0c7t}&_NL^41xu6zupbSD^Pz$*z5i)~o2G>V^ki}nx z7?jkONUaFkAiBc)qN2qWMT-euH$>zvDEV)Qx}fBLLCODsDTKbD<bP4be}dOUuNypq z69PNEI$Uq?h|N&H$fMZd)#>%oQifCMs~rQk5ZXOB{6hT|T@{^GJyjnV*jVMj1gP63 zG$9RiQV-V+4xtYW4C3I6>p4A{IzR#!#MEa5Ul3D=po<*pV9^P=7de!{tP5f~GlCZg z&xyDorUOA2Ids4>6Vfko$b$0B15ln(xyYgdxgi^I?9mNw&>^?rj$IM>%mprRF$g|{ z0cDsIw9*pP+5GIq0_y3dFxG;$yfcAoZ>AbX_@Wb^8YXCe3?vU}WrJ9d{uqb_>5nlX zw^dR&h*)jPjM9rn8=i#r;FwZ4L3go0dvM&){t;6Te_mk?BmDkYDbVRaP?s}7`(YrL zLHc1J7C&580K&=(tYJ(MgpLz=6V#8*k3zKeV+~V^FvPV;ZoLdT!XWm1Eo%)^iU?Ge zDMb{*LJm>X5kS;da0(Y_sST=_3q$O@TF@;$T+mRFt6@qJ!#Q>+o+5$Kf0cyCk6H~& ziWE`iNaHoftA-^-hA4An@tTu|*DrD?V+kxNT%h!Z8af~myf)0QVM>vQ#6r)7yfknu zC8NcX0z{s|STd?*p}B92k$jU<!vt;NGoc(Pf!50b-6J2&psD0n#f{XoE?NR=w15^& zRjJ}@9fOX@1viAN_(6?h$en2r0dON5)T)JW@iwx-jV!P_lvR<S@mlcuW6-^l@S_gI z@E>)M!T`!7&;t*|Qy4)91H1$cZuu4A*-WVoI>(?Wu_QGfbjA&MWiI5r94z}PeVG{; zKI<?rFhGwwkg~rZWxv9CuJMfGnHF;_79`HJxd0YJCc$NU6}SrlJEUMKD2zb|G*xNf z3uEYNVQ}zNiGhL{DgxO60};m?yx<)&5Opf}Y)Z{b0jt7(Y7!M<qG%_`%T$Vn7-j~B zBGB|MIB1JN8NX;PXl{!J{VF58{i>qnpsoTtWZjN2cygg=1<0qME>e*ph*bn4j6now zNl(!lkUKj-9Uw~jf1nX&-K1jOl+5DXOi=$jH$NpY2V1`ozx%5=VBIfGwj%I?bHpk} za0d)@RU!E7O-(j%^P~uLv=g|~1i3B(+9fImFI@zm0fBx>RFN6T?;yej)WPuvcW@pE z%7J?!uuclQ;Dq2y>=GB)CBPjI+lw5w;P%r6F?D!zEB_*gDpb(!B8OcE*IjWbNIu(O zeL>3pqLlp=DSOC7b_dU0L9q!n7X?+W2&#NwVC2*T6CIA=X1(qL!v%p0jW0;)LeNDX z-43q~uMf<OoO%y9g(nn4?lcCw1aAEWF^7vB4p95`E^_EWnGmOd)!JO-umQLAIE5#a zT;i0zz$pzDC(;Zmdk5U&g!j@ogeHVQ*KrEVePG}L%^~q{Ds?y`8qFB}vAd#D$R*oF zQH?928Xp)qcmo+BBxFq_<0P{QX5dD;+X~_J;;Y0rNbYdDsA+#i)Bb{z8w6bxahu>Z z!Rv;o*bK!<VH3h=qWl4<qYwafgT!9v1B?e04>Irc*yC~0(B(kVMNQ8unw}Sw0wCz3 zNC4PL@J>UA*9S>ZxX3Vw$bVp91%(hR)(}bs_gGVzI=pa%5F@WI;|B&1If3zpfYb#! zp9R6o!xx3Gh}g@#!*NIAPUoE*dpIuYIvh~CsOow})%Aj$4+LEl@R`6hfeR5S9bOMu z_$FvwWRbzsaX_g$;d5o@!1uk?FcdR^s=^uul#@RhQ<y>XVlE7^+~CatEH%uaC2R2Z zAP?y7CTQyiY4$vY9l0%pJ|BeIzI0)TRRFhqI3VWKFw`I|VF23<Y7=wTup-U%F!g|J z=7#9#QLJG~;eoJgn1UHJdHt&RV8<n;<(KBA=s^bDf~YbWi891k#R95ti@-HCxPk#S zNx-!XxXg!?@}R=JXdURH945#B6?l%24LtS)K3xY~QbGoUz++64L3c}oVhA!81Q`KZ z4;m{15p|#f@-SqX1h)`qBu3!|zx)M$`3HQW6B2u}8r*LPrZ#xp;1>hQX@CiE-xW&- zwSx<(0Qkzrz$ZMxa4PgX8rX0T3#k9g!YT(I?t%4xMWkmqPVoYjm%3o0!v$Pu>MRhs zAf*FA7kP9*1?CM7A$b25JSGI`-y-*DL91M`r%uoz3)4}q??z5ph$T9h$1#Fp1L+=6 z)EP?jC7GaQ)1a6GPp=?uqlWjEQCA$G9q0mHHVt321Ij1Zm+I6oU@VztNnvSWs9`}r zNUfF?d3j|D#>NI@S<HK>YgmIBG}(|3^9C*R!w6sSsaXNw+@H=+!w{>C-fTv``4KfV zVaue}OM`BfMa<YuWa?1?9fJuTA}&$@C0Nipyp@dLO%Bl0#K6O!#h?Z5ux$(A^FWl+ zQ%jI8UPNkE!?)4R0401-E3<*&f<y`gt#CUkd?4(E+mX17jv-eZLoP%lUvf;o;Fvr^ zYysOGxeH+S1;=D?8?Z_c)E_A>Nz5%!fHcO6S8^4Bju=52OjZXu8@|*Lyuz`l7SuSY z0}&OVlTz4<Kq(fooC&lx5xf==oRe=MY=8~-VzhL=fGhwJlR^FgjYBsu+{JTY25Aem z`Gl_X$z9@;n^CpG>>{7h6+WW|_Z#5Q+K_%hBITk)$`y$e$c0H4xMVM~$X#KPyTBrc zw%+;y(t2y8f}H_WD1nEUMl&V@11&SA1bEpQIAgYxo-vD4kcaJR81ZCf=*q=nZ=!WT z_pKEh5v>Dy%4V@7(K=WmgG1}<YZ#H&mQQ5r=?G@fWQP<mpo?OQWI(wIbogJ9A&3P! z*a^HDqv#MQyB-E@mBJ`%RMJyRP%d@FR@y89<xbG4#0?A|7#PF~nUTpAVtbPhsGSfy zqIc0Q@QPjFh0wT5c5xT%;;ts8UP#NikXw8ut>j`->6N5X@G<euOrIDSk{O*rXRLy# zuM7+xkc(V=n1Vop!Ay~$6IXqhqCm|?52k1k4@7-nV2EeR0BQGt9M}q?zA`Z6G8KUI z6*85Bs0yY!kcL90dXV;9CI{v(ASDhUN4qe4F@FN_yg<BMCNJi%3=ICvK_Ff*b0mn0 zVom^Q2xd-X{s@vw0`dHrlR$E5%mpCr{>%j+9*CNd2HN5A0i+Nv2IWDDSw6@(53GzW zIs$6@;3+hr>T#BsUqLYoBIbkQ1XT9Yy2SjzgoyDC=8%~G2u=lGz+{6TxWL49vCji; z(Dgpxz`&??M~hEzD~q<p=QBjRCXbBblla0AR(uj)7{ZEA;tNA)@wq+?^-80OOg;Ia zmL8-qM3jRPpxi=KIjD?wfi<>ba04jIfp*Y>i$PI8P$2*(SFr63J|K32?TFk(+kh*! z0T)7IFWJUku#KfnF_;c12Gc<$o(EGp(^m$D9Hx8_uYjoxM3pnuf{MWcraDkDn8Q>L z;(@3S3=Gc9zMyg}hshV@1rRkMY=+yExDOyh;9^i7IO|sNW`b__Cb}4dD92ffeFMb{ zh*%8@(k0*y9BtcuAD9^6QMEz+0|SWn5lnsolMTM08_4u8vPfNFk-ESl1>)&mWRbYS zB5{F5;sG~^1c$`S6ATOtC>!tK4M7p`$u;Q=wahgP#H^36WhnubfY34keQW@<Y7^0` zLu|Z&^*D+oQRZ~e`*Cb3OyEs*%%DwmDJ*kYYuWR7YgiFIx>}G5M$qQFxy-ffH7pB2 z{(!m#L6yL_gfTFnZ&<Bi1kEbWLAx0PdyfxwrD-h(u8pdw>KGYnIEtCl89>`IS{Q0r z7l2mpA{+;0)iBne9f0Y=fND-HXALv<)qt2+N!Eb2^{Lcy;nIP+O%Js<h{|JR=uri) z1A}fSsp5lfFVli-Dl2LQ4Q=x&_;~tyhA3F&mF6a;7HKkpdsMgBAY7C>6VxvU_f<iS zJj4mlsEu043D260kfz^!P$vy?b~SiZ1+%wl0^T<Up2h)fQ;AQ@FM>?x#3w_}TE<os zo&)7uP&wPcaDmSof@TzV6i+bgsfO?_@OgvtizaVT8>sn-voF~Wl2rp02pu3+Cx`&| zFG1bGTP&b1uO>I7g9&Qx6rBT2An-ul77vmx>I3P4_daiN7Z>Kh9A7j6BnLj060{kv z7_=w=GL!@+A>&CKK(>R3?I3S~F5+llcpxm=;PZf6u-~rB?jpDJ6>jMU7o@GiAJ`Z; z_)zxc+!d5xz;#hj`--4;2ipTNuLWfn#Jnzud2Qf=&=<tKI=DNy9|($G5!3{24t88n zbwSJ#f;zY#@Cx?Fbj8e7nNfXFO8bhG_71NLK@k^pBQNMiUgV9s!W(s+H~A88@<ra% zE4--<ZZ}vs+P#{*8hu)P!0rT%Z3*4r7wq5!oiypgc!AG*LCSSCvrB4b8>$YNT~zbB zqULph&-)^u_Z2?x2KO5*0@qo@FR_TvV86(saD_$T0*eA@GaGoM46(Rg<T{7+B@XGi zE(?Tbdd~5@$f0(HL+t_>J>V9<z#<NgWRz42nm`1VYo9?8h&p10etkhLV+p870?o&W z(hmC$8qf$!4MQf{5ii(x<)G$&<jwD(RUqK)>{Ufv;54A1pa9L6kcA|W9qI~jN$>{H z%)GSxqTEE#dE?M6?0R6;#Rzrb9<D*I3Sd$2_I3qZ1uF#48f+doZ-a+mHCe%XM2l)b zX%CdCi$EFS7G%q4eqO33Gg$2{X3%DP&@K*?jT4Zu7|@o}8=xc$TA|p$aF>(!0|N^` z$43ShF3v9?qQUtq7XzO_Pt<i@`AfX=3z!$=Z1B3EYJZW};R>(AbzaX)yq*_%y|3_s z7DDm*HMrg27Hsgvj8ae}fuj`vO)#i|UdssD6aqfsP?PbN0O%BUNYH{qr&yDz2%Kf} zK_k2%3t(elb)XOd5syG#lL9vmK5#Jb3t#6`zQm_|kx%sspK62q4Q`<ZKZsL7x}j+Z zF)RZ*^$IohVbripsP!-Cr2KTo6vkyN3=FG5Wi8mu8b-vZ9%%1IFoPzOUx+3XWFv3` zsC;EXDxSc8S_twJ$OKRVgOo8FLB0YJuRy*6O^Kr%J#d#tYKGYYlNDMkWHyvs<gvNJ zW3wab3XeyF>kV%BJ{C|EfZfEvfbb`1)ieQrg4!d<Lsu{}&}&JS8fHWu#M0uxznPyk zg$cZwpBX$q!2)V`%w?-(%j1BHLpJlX)v#b+$&Iq28f`BdYRw8hK!_n593Dk-DXc9F zHSEM}@JDYK<T0i*fZHPQJs$9&VPHTD9Z*{Y)W?8s4Q&S1D=i=bREwh&fS`y6<!H2v z7;se6pu}4Qs-}xD&aMWJ2CEfk<d?f-7MEn^C6|CNFet`WBmV{^D`<_(V|W3KW-xaI zPY~=0gYYi!7=p8lCMP86f~#e;(Opm(1Syx`m9Zu#QtF29L5I}2pmM3`Dku;@)$A=+ z(7N&BV$ennP{IdeNa{ZYO8p?>1IU@6HNz;W{|0J3`+%LhrREB|#0;NH>>3x?HBhS7 z8+@J%q%ZJ!Uf}cG;0B?0L_z2d<__iud=4OKhYNfT8@w*?f#?oqMCDpMQ*(~yMP8LF zyebWDH%zT}lx&LI5C;jM8D;b9XVqVnH@G5iaDm70B9Gw}a4?<_YH&rXZsA!HRN2AO z45)1ZN|K*nfJZC|?5jq(*9YVL8EzEaAXCA%)UqNs`r#Q1a)t(IT96f#?NZq0qMeWc z+SG+T)1j8xrVOC0g}p0KgO(+U$#BGV2C$#pkjIVv<c1oKB9j^p<TKW4I8r!}3Tdud zPULB!6wVrq`)yF<xoWsjCt?{HYB+1S(wKr#ZUIQaxEnr&CzxR+FQh>D#SLvl_<H(j zR7q+oI0h?#7ZZV6)~TS$799m&#|Vuoaj;ZsUJ6_SsW<S8(fSr2s<vA^$ZD$ueKYei zb4znUn-IXpT2%@9CRU&c2}34*!3v5MATz%!nf&}#auuQFc<_7<xcLn(4?qRLEsos8 zf`ZJv^x~o`pu7#9=s|9R{{iJ@#C7T5QlKByEaw6pN0FKgY9)b}wiK~}^s|FTo<W%d zTp&OQNU^{Tk_HisEDQ|ApabO_7;b1AuCcqKZFfNDlD6LkZ9mjQctWDCgd|-FDQj?h z$|G<=uzp7M1;P3Yg7pWYA@qgJd?=%+2Fehu|H#0=>j)bE;B{nb0G%&^dW;1ltKE#K z8+;Pi`4lekDJ&4Z$ftgVPyITd!6iNe$h^^YKI=<-)))C~ukhJ+Fn?fXWVHjew;{ww z21Zu9FCYRThK&V2zyU=kL=_hU2RCTXveXS#jSHHVS5&QbI3M7<V&gv{r-SD%2k!+w z#RWDOISj6F7+l~mz!12>FFhmX0>9=34$Y?^#mW~sRIYHST;Nc-!7tfS^%-<3#6cM= zCne@XN-R!l?1wZNoz&Qqk$d}~xB(S0v@E#TuohgPH55pP|6nP&*ooV_Y6_~yIdBwQ zXh;1~t>EG$vEbq?GO6K2DY!UM3NCKc!-P^eF$*TNf{PoY;Nq&`Ca&Nrx(mvDk3sVh z$fwMRf<(nY1eOe5^b{lk>PREyY|x%XNT!C6kgN@wwJQP<VxX)In(ZPfYky#15b|KU zAg>Q$K*$q%5E?>WC<NU*EaU+i?SzokjNoevAsjdf%Ke_;-0wLEb3X@7bAQn@&}A#2 zybW$XFfcICBd;@K&Fhdx3Mj|pYNUXU9YbzV!Q7125(ghU#)>1aqn+PDwY<(oVqRw} zGO1xh$?I$=d7Yz{9VIVw)}Wmtf_iCS3RevWMqX#H;UF%rYeE|-MfX4>j$+^z2&9z( z9@dApOhmxqpw<Rh6scKKbRSf^b0cZF#R*qe#RqGWfVEjw@xj_7P`;pFz5<w;3TuiK zJpz>@;Iap~cL6$&s0iMgKq_nycgJJtTPTCf0ufrEk_L2%4M`=<1;OeWH5UY{F9=p2 zuz}DQQnR6qyb35ou=*lT%@v-S!Pk616d>|S3v92*8*VV(P<_SNd4gpJC%Ca7w}A5^ zht?GitqUAl7y>u=C1z-XPOsun2Dcs*FLEed;Q${l1Zq8i%MZw`8o1F1K2IEUia2B! zE4+$G2E_|3yTXeY7cS7oD<UUDI8g^<&^r8FHC)(+CTh8ni<%mS8nnT|TAmtKlp}nI zC|+uLYgmz&eiGAfs^zO;A=w`O6y{oi8Xm+o07ZhxJkZok4G&@%W;VlI<~%lJky^nT zL9i-jkX$-rtxydM$`MCI_%?+NGK?~Tu}F^$-RzLnDH9lr_>rfIQqT@NVq~b{S%7tr zrG_6ZriE*Sv*4+zND!HaxH%OODtVxVxgs^fMH`S+7OkuiK|b*iaj#kmXC~Uw0%Z(E zlh9I;2x?JVD_SFp5=%%taQKTN+yjb<JhmDnky^17t{Sn~3@O~u*b+uoox%f_V?lO3 zviw}GT7eoN>}M`QbmH<SYPc={HPc`@6i(NOpsJe9kirW(5MnOILd!gPoUVYRK@?xj zMceF%dZbDU#(@@~yMPcqcw%xKEZr7K5pWkTC`M3yiRK!#6bL@uQ@n=Fg&}rMtwf4I zjYJtkQB8^<n69XiNMT43YDGHLcLCD5z3_l#ND)A_Glq$Qp;oe1szwTjdSO)cAeAl* zvG%pnHQXs8kX%;82Tehevl&uEYoup0q=<pYx$Lzvc_KC3DdI47DH2F3B|&OZq(J0c zj#`--aU7`$?Qj=Hh8~BWCIX==fh}}<=Ax7Z(x^UTWJnRlemzHxP>ncf8zQRuTG<+b zS~)UO2()Y}QcID6h6e9!h7{SkthMqr>_t2^@+opqaZsaFelBF|uY3&$hMWRSE>A9n z8Lk^<D$%-A6l-K>Go&biZB?$3o6V4-Tq8f5Aw>nenLu?ecdY_S$f==*1S3O=dJSJD zW36Hhdl735dyQg>2KdGSjw03?jvB=jO%M;o1g#nc)UpgM?Q7R4qRVqK)ZjRm73B<8 zw8L!B%~=2%cR*wZC<}G8u~sQhTbZE*H2MUUS79iUt5rtvfliGw=;C7ZkVt1pW{PA; zVW?HEQx1mG<&5PF72s1|(-~@2YLr|UV$am7mS{o^XJBBcQAIVUR*j50j}4m3#A?(Q zh$75Juu>Rn_|Zd^jPwh$$BryFf;)XhOesuxj46z@>bTrmqmJ6z!EiS&KQh&zE&O0) zs8PXDvS6g+p0hP7E)21PwW`Q*jUGFU3_U_Mpk?&BevrGW!J7m@lbztD6X1DN(8bF+ zY2dXDMed++$sh&>hF@Ghu5KX;UjCkb3cpws{QVSuF)P@Dn2x~;x0no#ZZR7g85jKn zsRv(MFbUL$03E^tKKJt%i>{rW`Y%>>&>$}O5YS(|pbMnqQ!7e}5|c|bezEE(sH^Dx zVp9iqnQpNd8X24XVuQ>8|KbPD{DSxC#)A(-`NgW>=;xyFi(kRcR>2C=i2@A>|Ke4! zu~o1Fb)>*tcIXIl5qMe&w2}80mx6zgi))aAQ>4N#J_W~MXXs)PQv-urtRPEGi#CCr z4sVG6;tCG*Q3xzeEvmHo#T}3cy1cq1wW!$YmpJ&|YX!&w#hH2O3gC4gRthV{urAO5 zO;v&}j4R44fON?~qrkT~(^E^F^K(Flxv->V=cg5cW;&5}41lKm!N<$oV#&(S%mdG9 zBh4o@g903MjOZ=4%)H{%q7qFFq{*ddkSyMA24vO=G{t(07qp`jykG@1=~T1}r1vL? z0ENdbVaT#u=mIoIG~D6_IVnClC$Y3R^%j3xX-*Dwp$o_a*s2kb--?SagU-ScLNx@e zN~|a~C$S_o1*$VMuec;JFFEy=IM_H;Nw5Y1<b;!%pI2PO2Z}K8L>l;X9?)diEq*jr zx41xSSc+3Y2kPJA1hGMGFNQG7Gg6CEAxv-_-eM~N#dC2H=)#Pm2_UC)re)?O=EN7J z7K6v(Z}H^jr(~vOrl!Of<(J=rp40>GlNW<7qK6FJ5hfu+dz>H-fCxKKhx;iT=l~wH z89bzwGB-pdrubYDQEl+OE2_F6ZAI2aQOhf$mJNP4ghBUXSWLH@WOq?m`HHY|gU<~i z@fnO$G^XoK(z__6bVW$1!TW}k!UDlLn)CH%=|k4VTwoEuAtch^{lF{YidXyvv5FaT zS0wc=h`C)5bK4+y#n1)91d*x5S5hk){BMY<UJy3DAZ)sV8$w?YG6Uam&cWA_*izYE z-&B8*T>>=U7<P$W`69dW3bqx6YdF{Quj0Q5KB9j|>Lm^5iyF>%Ie0sgJGpv5NBT&p zU6(MpBw=vD(B-0p>lF#t4&EDf-dF4bF33jAh`l1Ke?`{ximcTIS*r_S5f?b5Zg5M0 z4k7ho1WQ7g7fjsFs-Mxm5SDx)HT|Mj#ucxO3nn0RSJiYw+6DW-i>g6aRD&+?D1BgJ z$Yyi|4Ps_9f(Ja+Gnp=cc8t1RP)h?L&-g2zDIf-z`+<SMk0}MD+>a@f=>m`P2POvT zM5YVsb{`l(3<!C^A3{UO3t`Du!ZILiIGG0-QI$>v4YEQ=52jF%!5&Oe5Go!aC7s9w zItWPoI*;5X9=QujmLC`x6udz43SLZm-S>E(;Dww9bloQEl1<cw=-i7oc~@-mkd=L4 zU?_!n5v~(7fa?vijMp1v7My&*1GQ;^{snoD3-TTte6N_eL6{eLJg@M0Uf}TrE#y>b z^ltHP@P5F~(-GKG(O%b7cadHE3cL6WGuZ9B!q+*ZE^$cB$iE<McR|{20}q70$YFnl z!~Oz?J;)SQ=vpgr$qOtZH-r^FFmSWlgNY9I8{B*o)I01t>}Dh{2%nKRBkzWS`U>V1 zLMxb;@LX3gx};#V!T6$r`4t88i#)O&-ZMmQ2#Zg*nq)O21(NhTTyBU<e_&wcwFDCr zm~RLxEbzG`Y;Zx?;D)Bf1-0l4YSBBaA@m7n2z?<e62h3^Kf(WosM3OrOQL!gMD=c{ zWo^j6pq6z(E$aj~guW0J3uUBcKo}GJZwM${7f`<>puU3tqJYH}0gDS@^nn}X3O)v2 z2}m@80@wC}yzK_}3-Y!X<ZUnV*j?eVy8uR@U{P!IZSigJeIO`0U2>A-4C@P;mKUWh zFA7>+5wrrIcO@v(;CTZh=uI!ND_me#xFO;*BmRPj&jk^m6*Un0f{0HC=M8RA&}Pa2 z#v44M7bN``$X}53zaZ(q!5Tteko3RE<KN+VgIgG+%nzc>7o^Phf}}4<8Hm0h>3fmK zx5M+t0}(N}nSl_+0SojmNCsSx4A|fep)W`VT;vHL+RO(6RWr0M2vl7Vs9NC<p?73L z=o6X{`a*mblp#>n08WYOjeaeD4So;!q-R85;MHt!yTL99ng!IlA+OoN)=_hjL*@oM zKS)IFhOBA_TSwJJ4ymtf46IxoIx{#gvM60)QM$mQ1PXWE8DbY%;0yOyScPuz3H9f6 z<;*o%z&X=?j{OR^i+sjc_>8af*<9kYxyWaCh0hMOK;p+;ZjlcROsql(0z4|Hy@K%q zpY{bl?TZ{bS2%PoaOi+n6LxUI;1AFQCMy%G5Qyet5EKR-zby2DfrC{YOmr~c6_=jj zJh5_0We3j<ZovtPJ(m6UUG|WRPZo$>;#R-Ft$u??;yRD~B_8<&j2D#6F7lXP;W59! zV}65MV1i(e1!A@30=7%sY8SZGZpf=HNSsrNSedaQ?2^3o1$pZkiZc`s2!hHRn1S3Y zLN^$$iMYsPc7@060*~1Z4(RH|8IChb5VN8jg4a1DE^$cAkU?3^$S(m3c2II+74Be0 zD(XA99&n3XU=b+>t-3ugWf~yBa6y18z=HXLlyHDC^95Z-5ZjnFz=F95F?$9Y%f>z` zTEsaBMn&039<Ai9VIyNyl=RV8DvXMzGt>%^)!zd5nZP5VtR#=4LPr}$M?zu!V9?0W z=twBZBcW_0r#$EgJ}pK<35@=+fZ`Z+M1_%ofF33?^nm9#(CtYPz&?^%BZR85R=h@_ zR-%UVaE0dRBBK=8JPT<2P-re|tz->*kx-3fiZFO2j|IH!RRqcgjRuO&<pdwXAz8zL zp-v1*oj6Qgo>mGo+*FutOtTqMC^9ugqDFi+Ly9EWMyVQ!*$gRCHK4I$X|RiB=5p6c zA&<YHAEwR7kRk^jZ<L0PH%h0-gGLx@AmfeFDGDGSiV2D}Qs^T{DDp}*(&+Nw@kXR= zMzHdOfuTeZ!LC6&>Kxsi1)vjS5z3(~wDCq6@OYyNRFZ*#0X*I)i{e6M&=~DpjHBP+ z<BhU)vf%MX**aO2@kY5C85f4wZME_xpsg-YD{9aV5wBGst6l=F5oRb7t5H~hbcYz! z3I+y-6jm(bjije{#CRj=wI8euVoG5IT{}{%h|8@tim2@f^wdxzhhq#;0X0`MGW2Y( zk#k{)6|9v<jz9De2ah4j1v6->;2uLPdIK8Kcn=yqf)61Uy#<NA0}<~*#0L=Z5k!0f z5#X`HpCHyR5b+zdu0jNDd=EV8SM&!YC4@542a)&-()bTVz{mTF{)4y-pgXb|K?G<l zs|Y@}SHuJoV+Ii{ObiT~qBsZcir7Gkz$+<0!*)gNAT9@pz&2jT36cOU{KPg=SHuOf zN(pJ04z-P2gjf=Qa42F!7B|Rj)WNwT9*`I>h~NVe{2&4};8r96VhMr>ArK)7BE&$1 zIEat{5t1MRKF(Dn1>%C&qe+8UGE58%#h|kgh#krj2Zc9?SPUA<x-<BPvOptW;Js&~ zBUg~&DA2Ih=*SgOBUc~T7+3|avxr?{5u2fZLEP$sxYY_D2z`;o`U;En1s3ZYEc^%+ zb{E9$F6h{RNO8N1EcRDe>@Tp`qbRYtAa1k5?}9jpzQ|&Gg~j#)i|q$C1|c!XXwXGL z^(%tv6psUWGu_}3{lLH=<;}D}9zsLN3&ze6286s22^zta0*@0)c{5$eu7rpqlaS#= z_&AR{%xHJ0(e9u@Mk#kDsL^n4cpAtQI5!it`cw+Wg-C&nc4tBxZ}Ndz;KQ^4w47DS z2W){4(*+Y3kTy6sDiej9Q-#chut661FwxZQ4+QE#<689>1nNQKS|A!Ut_7k&<60p4 zLVW%O0Wd?Leso-m?&DeyC>p^!$8PFp%W%$?%U_lGJiD;J0`qw(Mi5(p)nAplNE%cl z%7O?v(9&V}N-X51!y$uVT^}<UBUsF2j9@X5Y!9_Yu$agl2>_2^k**us-yI#ng7vIG zBNd|~SR{{NF_W8a2Vew?6%@zlJ<D{4+R^oY41;X_AL*$ITAmTH{tvWs7T5YekR0~) ze~RF7H5Tx8ccOHsD1pY7Qk22^RKV;1RKV;1RKaGb%|&1Tr;eU7Yxru|YdBIg<}lYP zB9D5cXcAcGr-i;gkC6d9?j`^$>yYUIS?8xs<8^+d*N3q2Fx>0>u#LNso=#!**ny5y zB6AfqY}^gsIzN=w55~9~?sa}F7;0&?&QFJ`V|2Kd_Z58xHE+Lwh$ay66-0ak5#K?? z4-kPiv<F$b2VO80pH`HgTLoFV2N5XAuVPn#to<v(JD#V>3|XxQZ(gH~=oPVo#@djV z#32UP!0mF-q%vgZ4Z;ZOjI{}ahR?99Oe+E%YF0$%NF3=)%alN24<i19#^Y{`j>nCz zBpb#n$&km|u#Uz(7+q^dzqMxYbz$<5^<W!pAT-*#Fn`GMF+b2+GrtRxejsHa`huk2 zMIJx2vA<x5;-Cfg7bJr&NCs_)fzTHugD&y}!N>6knfXAV8Z;(XeL<iaG$se4L1S_t z8Z;&cqA$c}Ul0H@1gcTT<YYB=a9s$9xWF5Efj6?jZFDsm<*Ui+E^_Ey;n2Ilp?8C# zvAFX}rU4QR7bLg>8JI692?tm+Uod3^v8`DH8JLS~K}U@uAKV2xv;(w8=`(1Z{d9&D z##%=3HEb@Rt4UHIR~s_cFu5?qn$|Ld&Rl@C>T8&h586X*&lQQ*FfRblFvC>C=oGYF ze6`Fqj47xWpD^~AfzBlduXbSUaRr_DrpfLHzSpVB4|MY|_~NF_yn@ma*iF5q*}BOY zsma+%`4#HWyL=J+MC7Y`VX7ck@)l`>8V8{Jqri8GRI!2Y6Xw!nzQvVU3|oj_1PY#8 z9K|I?X`nq7nk?Wzyu|~)stkH)BxFfEOLA&XPBG}LF3@BlWMw-S$OaIR#LB=>3_9Bl zdJrV&WJHk<4D75*U;=c9h{y$rjEfu@9XvNg#6K{waO#7J4wt+9k~376a$Mxsyuz>f zfq{wF_JO$c^x8?a7gVh;irZWfx0%3lgGb_mjNSs13o?2Tbdg7|!>7aN28YND&Wjw1 z9XuU8$j2*!TnUR5(4Jn9CqEx!0Ieg;WT<7SVMt+s?nz>*VF_laVJhNBiG8%y40((x z47IGN`>ksjYgm^tGcbTo<b_yO%TmK!B!F_sB0~*YU!azy1b*EI0|V&78q~B^%YwGE z7DGSCy^u2kS&P6sLqyV2OOi9<GxIV_G83UYG>UI2KrX0`FUgP3%uCKGO-YSM*=$jK zO9G+~dYJ{{7}MfgGBBCE;*z4o%)F9fBw0;1$kaA?vb+nl1RxMZ6oPh9h=7l8ERKil z!$?g5pEindi3KQ`6oH6hP@-uCpI3N;mmjpYmv@3lQ(Z&d4SvxM-VbaHrY?*Zq<!a_ zEa1N=ZE!`};DWTl36Tpd5?|RE_>>l?tx&sQ<aUwQ{R*#pgBxg)R{*pk)rs-0py39! z9cmW^9j^#FcCdj|t1U=dk#>>S^a`&jxJ2jETA{W<?IN$`6<*5*w+Dz*96>1s5}gbT z@RXCn#K6$bgnnoso)QA}tTMDqUuqdL8ETnP(*f$Z1t^7P!P79(5yY(N47F^abMYX1 z(2Hb2yY-<dSsd4q#5Ig-(9&})TOQ~@a9AGjK{CayhJ~0TKwKDN4R9ZVT*HoTqYFc< zcr8Z_8;(8g7`xwlBzg?<L~!{CZIu#RIzugICsPrB2U8kj3UdnwXfHCF8qPeX8n#-_ z8cr96Sg%?RBp>r5A1hLW=8`<N8WzMZ`dTjJz2h|;=*JD!a@TOzFxPN^R(Ykcpd5UR zRx^NhXx4DY^TLu^k2m<>V^+x7Xe$|uK-mlXA&H<f3#T$dPc!BK9U9unRK~ELt%h+T zQx9h_1LSN(&;f~>%tfGMwIO@XG(k%}K&e|np#f5OgHKD;faG=5(gWLhiJ(2^#h{g7 zKN=V=h=oDW0?{3;7aT(`Sb)%kQV4y)A{2aX;w{EJO}-)pP)8Yj$shQ#9MD=X#F1*Z z_(7+ff=dm^A*V&xK{X1fwuYS83O;QORErmZF1`X^8LtUFeF+poMIc`lfyy56*{DdH z+(5U3-r`DxZCNY6B?vuBH9rk{9&It?yjX4|+i&qeI1r`aa4QBK1E8Rg0IKuA7!r1> zAYXxqZg3gK$iVQUf#EK@-~_d~$`{$?uCU92iX!#9f^sv;E()q$5mf771DA0q8@;5a z_+1fEZ}0`p|BEzu-C*bKC~2x`sF}gLLgtc$=>-YX8={ieMKv#pYHkp_AgXy$)Z&V$ z1?UFA8=U+TM7m@;WM=5Du(>2<enHCohN9|qMYBtaW{_*GKQJ(XE+u5*)VshVdqYm? zx}4D^Iim|!-WTMIF3R~_k@EpvAP2g(kXLU4*9`%w>jH|G1QZu&T@=v0BA|N#j6N_k z^6GtH2ARacASMNI%0*G_E27#RTp!pNghd;?Z?H>$U|?odya7uT7sSF=Xk8EsyC@cR zMJx<_hO?ML2loRp#SaXutQugVgX@8*(}A2zra>1>gQnIk$hs(Qctzasg1F&@h?px8 z1syzh1*B(qt>C#RV0uNsw1f4AtP*s0#|<&{xym!rW@^vThFq83!P)~lCmK<PBe%`q zW%w)b8IvfrJOlY9INs8msfGb->CFsodoV5lrDs^ihtsI#l?y{`82AJP)>^h2ChR40 z4QmZs7W|BmHRva~u-CA=FvJ?wa@24j&*Ii{)^H#n?+kAVvDYvm&JoE2jhZtf##U>& zYB*~c%NUC|YdC9I${34SYq)AyYB)h9U5{~3IB_Ml3q!0S)K}akNISu6SZmM@W~*hZ zVFR_WK<1~gpp?332ls<ZQnq+dHU)=ej}f>{&FWXA&BVZPi?1j(Ew!i!wgL4PKb#NE z<hr0CL|y{L6l-QlYHl&)Oy;!wqSVavy!h<Y%3^Ra4Y}eA6seen6uf9E(g(Rl9JLJw zYMp`0-&>sEg2yj0Hx*p!++t44F22Q-mVHYU?B3M8WS9@Y<>W0vBw>h0Zb1sN;N*<d z+(c-p1un3_1f<kb1C?4JVjZZ|vS9>mL?uvYJ>?dHUm_-gR9?v|USN^_z{Vh}Jl|)Q z&rH8LejgY(S%VoN<V6mN4$cXt4?s7HfzF^_;JG1nOU?z*Vbso7_?$bKZwQHkwseBj zvvCG9b~xQoQM;gSenrLN0*~SY8@mhk0at7SF9e5QvI)On6TX1uih{)j1&a%@@fQ*b zuf!Hz;E{d6ud>4J0>2RmnfqJ_$-ZC?LN^?PE(oY!(6GHIVE5w#FUa%{LJZ1Q*Olxq zDcN6Ca=fDC_<?~1R1UGQYFy-y>)@PV{Zv4DhUbFnivq@11dKaaZ^$Tiuys^k<d7t- zumNR8P!;>Ri51k<pi^PPNX&`uE({ZxVoSl9j~R4lCa5|G=NA;BhPj3Xac~btk-}QT z3O2_dls!=j74{l7<U*yE1I6zgC42~LP&+(DEGaCt91|Gx&ZFr-nrOq&!3x%~4NV8q z0U8)O*uXmGqUk_d9FC!b9jv1TO@|mBe{g_xWTWW-U8w`rgkc9KSVs_=4$ukixO8xV zby%Y505u75>EH(IP(afG+M0(;2M<^WJDLvAm5kVRFsJa=a^$^7Qx95Ij7vRVEl1vU zH1(jSB`)>+wH$f-(bOXyf`S&V%qar39C^#p)Pv?$aG5Vy%aPZQrd|uG7<8^V69dC) zc%!0-IYp?JBd;7yJ?Mmbs1^+O2-kAt#iOYQ-3)+By+|!bo;#X)1E^wb?h&o!$kRtt zZ-hs^SS?4MIGTDBoa&iUz+JZ#38dx#XAN75Bod#i#2lf&hNXr*MG9?jgu8}&0a7|e zQq2K6JUT^s4q6w9k)ehqOBSYX0aA*Ei7_zLU>q?$fvG2O0#i>pxDg-&X#_xSb^)K3 z0oot~x%W>SG-?4d9&*_zDEf*(X|@RDt0ItXMWBpW1WK1h;485}Ik^aQR$mdQu>snZ z%1|T@GDR0e=z|DQq~7AlE6vGCOv*{U#h#LymROoo0v_JeWQLpz=m@HWoj|P)d91At zbC7%;s2c?C;n2awMeLx#8}8!N5|_l1#E{B@RPcqQMWAjNtha?|$XJ2g2i_I|z9j~{ z3j}mkYtd#<2*7;@xvLcvQs4#%lz_BD)Immoh;yKJ$R{Qu+962&rUx=gh&G2ctL8@r zYgSEIi$h54x{%T(A*BVD8$!3lTo6*aDCBfS$f<+<hOpEJ1`AHjk07GM`G&CAgt95t zpmPMIW|&Q>?(hJOLuxE_fsI3|^D5tvR-C{wk>jqQ^bC)s=@$j{t_bRVU{D1~s30U# zFAC~h5!Cs>pbU~wLP(@QF1Pu>pa_ycGAi?;purVEgAWYyAPF^u4!DFYngrZ(X^;ex z<!}i}ToU4F5^(2=f+UnCa7=)DFzuqC?iE4Z4-CQ}31x&t#zjH>D}wqT7z9BQst5_V z4t|gXk`B15ctH{>2pw<<Zd8e@Y8Dp-Ew2b#eqi7P$><}rz}?FZl0b4V+*zz3352t* zs+nCBG`}Kf{(*rRB%_PPEJl!o(gcnVYzz|8Gpwf6cX-?olboSBC9lK%D;tBD(gM{L zA}fk6iW*%JHR^D?!6O7Y+u??U!VMXf8`4T2*jW_#J}|H-@O@-p;pP7VB0hi#9tK{a z9^dOc3YT~k7KmNs(YV5+ae+tUB9F$$LPiTt%?~Av3_L=xOJm@*3IhWJC>MfTk6kQ- zuk{!^g&J+g*aGyHqYFc9DtI6f<a<QH4rQU=pa$y97Wt$w*Kovx+S*`MHEby?pfVfG z0o57sCD>(*MFQz4BB*LXoy!^)M5h;37GdTB?6;_)4HYsn^rZC^_DroIvOOLfjufJx zY6ao}B$7aDoU?`#dEuxFL#!@|At?e5Nl+OLwzY;6B_uhJh9~uUB8c|aQzU;Owfn(V zfC&Qr;;P|tVTe5l8cap@UoB4!H}ZwPwY)Xl$md1Z^40LxFqSbCEiM67*I=6%7;1QG z_)yzpE)21gz%Jyk6~Hyvjd94TU=8B}q#g;>aL@sHyr@I>IEHG_4!5iosu3*ms}U;l zsS&CXjE8T<t>H$A0hTOyp9Y?m;UcJNp=lY1EWxxai0TVQhMvhihkBlYTTvX)R@7bw z28IxDTk4i@az1o!C^ZFoW>|5NDQJX406Z`O8gGG4ENKekY~|b%1C9Qc=4BR^rh@LD zgUvQUMpZz2FmG{W7DME3u|w!$aI55&AV_CHHj=JeESbdx*|&HKvf*wkDY5`LiMcAX zpr{(uI05hIv<5Z3gdvkySXwo=gdi3n8C`sfsWiC=d;-~SP)msy#o{86fkieTo4C?I zE`pqh1`3TLH;^bNT=W)KQED1Ayl!!%WkXMf<0yc#ib1y^L0d}56r|Op0ctgY2w^q` zhT>Tywwf@;YHUG6HMY=p(+ye0>#`P?WG!|`o-jRQb3xYPqHM$!S@1zzLMk5^*g0*% zM2FK2dBp{KSL6*pFtG93f{6*-Hzeie%g>TuA$?KO^opeE1hyN(k{=jY1Z{7qX-#mO z;(tS2a)#QJsuiMZWG{%DT@W|BVPZCebq?=>q!nUIvMx#(-Vl+T;=4d;LD&+lD<Znr zMNBS<m~05UC}ML(#O8vC4QL#P*Y*Q5$Q2?eLr!e0HXj(+SZ!e5kdV19p?66_Z$;$+ z(IYY!B=jyy_+63k>)^d1t#Luq?uxWMXx7;VOmukP5S5x9KPi4e^hHs_E24%SZa4Tv zK{L=cH{_Hiuub8<p=P|n?TVW91hy*z%0E7EvT)jb;6t%r0Cb|20INOBe*6Poe4O?l z82C8tJDhGvN?(vMydr7T;dMh+{(^$R6<Na$zZ>$37nDq{$eUi^k$J!`yFly$zxoA! z^}G5;8>}wa`(M-#xS}8Mfq_HN9!y*m(Vh^wfa`|53h1<-6_pp|Ew9L1PVl%PuR6bK zR@DkUh`@Du=S%X=2fQxI`(KgwpWyL8T>b+Ci>Unq4={B@!(ax-9Nrt!vNO`=Sgi<M z6L~?}?1D5nj$G$>LgPrr@P>r!98PE)N$6geFu5dQvLWK4gxwVhy9*L_KYo0W1^G{b z0nxev4Zg#QN>F_bZrz*%Ps67$rZCkoX2E0`W;3KP&t(Q3W}CtSB0;ygx-i5l*Rs{H z)-cqtA(ddr3q0WZL3gWy>J`+x=)lV?SbJ1JhX_E};N>3*SWHE3i6NPaS}sD&=uxa; zO<{wuYgj>-%KKHBrevlUB^G2TRC6gPKv>ZVMTvRYDVasKN<KkK3Q6h7`8oMTwo1xr zV4$QB%T*<>ppciJlBy7`P+U@(lWMD!mYI{2nxdpooRL_NYO7Qwp#(B5B{MazBrz`? zq{7g`0IbzgK_N9I9i%m}s3^ZYBQ-I_Rw*w(FI7naEKrh|nFA95J0dB+Bm<<A>lY(e z6;mx}AqXqjnp@oIsU<G?CBYy&VH}^#yzF94cJSodE!L8Z)ZElt+@Pz~AcYp_XvK{D za!_Zn_!duTacXEmL28k6VsYv%#$s?KQ4DI)K}#`60oDjAgF%Edr~m^^cr-A4U}EI5 ze83g7!gY=J1+E|n`pC+}70md7je*;P@gqAkmj`2m;|DGVL2=Ne8feUzku{jHgXIPX zXGhosm!6pGysDRYRTreLh~A-jAm$=(&=uaGiyT26j2(;*SXetm8a*2v9|(YN>eK}p zp^HWDMP8jNygC;-bYQwf8@(DFA8_+Dcz|a!Q6^(S2^oYxZviJ}<e?PM^@5P}gK>Q# zV+|wn-Z;?3n%LJ7fr4f=#1IAsw6y|=nWY-E0Tb*)x+&;$|16*tB$~{A;L~8MSW8RN zbS*U5Zm||rrsS905(cMT@S5%Tl>8FVom+{wxRQ%f6H8L#3-Z!$v4PGIH8Z)znPg^? znwOlPl6s3RB^5+#vfko^=yvfBxy1uv=H{oA=A_<Y1@mt4738JIXXYlRr$P+og_w{6 z5d*CUVF0HXNP1}kr56wp21+jh;DsW0g(a>C>#T^pC~SU3*u25#29LxH!5;qx*Bhcz z*F{w?iK<={Rlg#t-r#pvMCyu&%7VCyA|_WvOs<PqUlOt2;eJuX<%)<)gYOM)_zl&` z@Q`L;U;s5(!7)1tBW6KGRtj?pQwmE8OA2cWTMBy$$1*MkhSl)y0dhJ>;Y{I7;Y#65 zVM*ZvtL9GO!K0cng$c=QUQDx7_~tMNGidVrRk16ZC4*oUyK<@l2v)Hxr<sCa6}z$l z7~Enn$}i1JNlm%M?pu_aoL`gz(q(C8X=VvN^tXy#*)Y-6$ifJu6<ivDwVGR`m=}3~ znmB9_U7E~Q+|K#Ax%qht?nQ}}McE7t3|0I{A_`9V6`G7ipu!!p?!XZgXZ9eX9!A80 zVh_Y?V0bDnGb3^Z=LX9gJfagS7ig}C+`)Ok@<j4SW=4r%#xD#Y@~NoQ49f+X8zeE+ zh=J9J1v7r-VUST=Ai2VGLnT5L#{ta~!5^6!Wr9E^$pkTe5a#A#`@jGpSlPhY_!dWe zd~RZ9UVMCrCgUyEl+>isbWO&hGobM{&?PIH%zl1unhlU;acl*Nc`1p-MWBn;z+-qt zpc?HKZ*G1{YEH2pgen5h{DBHPNHBo9pGBY~ffy^iC6JbrSezYST$q!X1Dc?#ECQ|k zxWx(<E&`>wBG7?8MW8N55vVs%1Zv1b&ItxhQWSwEON&6058#7tVTOQ5AHl<tpz0Aa zUMUR<cF;g;5qNM@4#WlZhakh8;L$|zFsCv|0z9}0UY7wHPKR7`nFwlTf>t~gO$7yt zHAr0u=sFudg!A?Ci*k!VBkDz<v%+sFBgv#>CV~#(E!Hc@FD^+hN-ZwT(E}A3MW6xo zTXOhS7J)})!DF%DK~>OrY7uy3)d1uSLr~I`!e&HqVNPaA>MiD^Vv{1!$oDN|$EH>! z7a4=JvL)x|rDdjr?-0JlRh*h!lv)Cs$GydqT2z!@l#!U1l9O6g<N#93T9jW}l3Fwo zWbbScu^2=g0ug6H#6=MC07N_n5l=zHYY_1ZL<oU`P7*}OfCy_45eFhtK|~ISC;$;J zK#D=j_=>iISld7Zc(F_^h@}WpvH(OZ0ui@CVrC%LdJyXii1-R3zJUnPecX^mGoZVG zi$Ld57lCdNE#e000NpHB1iJL62y_r}5$Fh)BGA5(BGBs1BG78QBG7V{BGBw`5ol<( z2-MRk0@c<<pdz>klz~B+2XshteEcouqS8FH9RG{M22$GF6*V(4Fo2qA#k;r}7(OsF zGBVy^kiCG4ZZHU6fT0@<f)`NH4Tj<iq@x=Q>K9Pa4Tk&+l%Wqy42)@@6DWo;d4oaq z0=ei0gZTw)=mvxB1ylqI$z0|S3=E8;Bq<?qgCX|<We8jD)4ITbf<CY@7??INw{TtH z*1pJ~^MQ?lO`svFF{UM^f$0MWgOFkaa|_=G4hAb9#s=OCe5Mx}%x*|4HSl(1USyC! zupTf9HV9u~6r7-Zkx}dlqu2)~W=6RW49tvjADJ1L#Ni@b3{2b&!Yz^=#xukgh|b`@ zz^HMNQR51u#s?;UMn>Zg3?LGuMH3;G!UWS~x4><I#RW#4i;Ox~7<E1{2{S5vU=U_h z0%_2Ni)cZWU*J|)VYWi!0;BOoM&m1t#vho37>zzK2r(Lg)SJLXG~nuG7L+Z>xWK4? zkx~B&qy7gbb;cUT4-6m@q{9Fq7QzVEBez0q1;+(O!;6fDR~QXHFbOgSFn(YFksv)r z2r+~;Dl5uXWL#i0zsP8Qh0**26E~yH2L^6N8;}MIxQGng3fUEG3o0%!8eC*FxWZ`g zfk}lilJNrrhy>{{M2PtzoTa*fZAHZeMvIG#7FQT8J}|K`+J0bQVYCJ5u!M{7!R<hI z6B9^16GT14O&}3i^kD|A3`o5UL_H*EK_Yfg^E=REPL9!^@dE>h1UU#2cOc#eCzuyH z>=%SB@VLOJdy!H13Zw1^CS^wb4-CqT`XCK@a1kH4dUPw~LF(lp>LC_@L|owN(al!} zsaJ=nhnNo%L5gYg)FB4aAO_I@NgE&$BdC`za4W3{Tj6nm(exst=@mv(P_&7FVoe0( z0W-J=Qk+RINLvtbfl==wquv!pJ&=z@Kt2X*(1(k_eSCo%Jz*#@ihN*DVgze|Bnyy; z7tG5SxX}}<B1nTGL<1zLf<!zR<rw)vu>&QX7`Yi`SA>Als4O}mBhM)DfdNjqFtRa9 zZV>*!z{V(vP6%-`imp%w86b*I$gnaBeqewT0=A4|9~j_-1Cu$U*b0#k4CahtJCwl` zI#I$X1kwU0v^W`MXC!}M;AE6tkOHRA2?<U{sRhg+QK=OyU<#d(;AE7XQ4A85Tu=h0 z&<P1SkmpeeCq{Ngp$`mjLWGr3800l5A;1jsDV*Tp199Pmk`SX5sDwf$v;;w7sDuU@ zH;Iu^^aBHkTp<jhH#kG+1IZBjLU1^gk&psqlvY9*9~c-sm;#vKoMfhK1gD&-8qNWk z4^stZdof3XEe4S*m_IOp=pDrn`a(!Jl#!kVWt3Dv7#|oIoS590;G7Vq7z8JusT{#^ zW_E`f3sVfT0?MvnN&wpqB0=5;(Hl}eFo5U-CJ_2UWDJy%lMi8hU|?`$0$qR$Qucv? zA($zQ2`-b#l!M?@Fx9|05XDHO8#5oH#0-}Y41A0d3xdHEI-w*AN(iWgfdWVjm2hK} z1qs3l2Sz7GkN^_t#iR=offEUg0*t&L7~q5&D=53c2>~8Pwhs(&LXLrjr=#i;v*ZP4 l$r~)(4bCm@6I3p+h+kw8zrrGZ0fs&>Gq6ZqV3q{;m;t{|0!#n^ literal 0 HcmV?d00001 diff --git a/__pycache__/main.cpython-39.pyc b/__pycache__/main.cpython-39.pyc index 0f6447fa55eaa8f10df992e71e8b5cd526f6f6d1..d378229d7ae9fbc0df103aa88b1b69fcb108054c 100644 GIT binary patch literal 41145 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 WE(tDW4h0TZ4g(Hh4h{}}E=B+-NYG;d delta 11726 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 Vf=ii0fy0%<fJ2yrgM*)o5dime@LT`@ diff --git a/main.py b/main.py index 36e5cf9..7b8a849 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,17 @@ from datetime import datetime import pandas as pd -from my_flask_app import app +from . import app from .models.models import CustomTable, CustomColumn, Theme, CompressedDataType, Observation_Spec, RegType, RegRole from flask_sqlalchemy import SQLAlchemy from flask import jsonify, make_response, redirect, render_template, request, session, url_for, json, send_file -from sqlalchemy import ARRAY, BIGINT, BOOLEAN, DOUBLE_PRECISION, FLOAT, INT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect +from sqlalchemy import ARRAY, BIGINT, BOOLEAN, FLOAT, INT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect import pydot, base64, os, logging, io from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.dialects.postgresql.base import ischema_names from sqlalchemy.dialects.postgresql import JSONB, TSTZRANGE, INTERVAL, BYTEA, JSON, UUID, DOUBLE_PRECISION, BYTEA, ARRAY, REAL, TSTZRANGE, UUID, BYTEA, JSONB, JSON, ARRAY, FLOAT, INTEGER, TIMESTAMP, TEXT, BOOLEAN, VARCHAR, NUMERIC, REAL from sqlalchemy.dialects.sqlite import JSON, FLOAT, INTEGER, TIMESTAMP, TEXT, BOOLEAN, VARCHAR, NUMERIC, REAL from bs4 import BeautifulSoup +from sqlalchemy.exc import SQLAlchemyError # Set up database (call db.engine) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False @@ -28,12 +29,6 @@ ischema_names['_timescaledb_internal.compressed_data'] = CompressedDataType ischema_names['regtype'] = RegType ischema_names['regrole'] = RegRole -@app.errorhandler(400) -def log_bad_request(error): - logging.error(f"Bad request: {request} {request.data} {request.args}") - # You can add more request details to log here - return "Bad request", 400 - @app.route('/', methods=['POST', 'GET']) def index(): try: @@ -42,71 +37,73 @@ def index(): print("2") database_uri = request.form.get('database_uri') print(database_uri) - if database_uri != '' and database_uri != None: - print("3") - if database_uri != session.get('db_uri', ''): - session['db_uri'] = database_uri - session['target_table_names'] = [] - session['target_table_name'] = '' - session['self_defined_labels'] = [] - session['schema_selected'] = '' - session['show_all'] = False - session['object_name'] = {} - session['data_header']={'event':[], 'measurement':[], 'segment':[], 'segmentData':[]} - session['current_data_header'] = {'tablename': '', 'type': '', 'label': [], 'features_name': []} - print("4") - # Initialize inspector here, outside the inner if-else - print("4.5") - engine = create_engine(database_uri) - insp = inspect(engine) - session_factory = sessionmaker(bind=engine) - db.session = scoped_session(session_factory) - metadata_obj = MetaData() - print("5") - - else: - database_uri = session.get('db_uri', '') - engine = create_engine(database_uri) - insp = inspect(engine) - session_factory = sessionmaker(bind=engine) - db.session = scoped_session(session_factory) - print("6") - - database = database_name_from_uri(engine, database_uri) if database_uri != '' else '' - schemas = getSchema(insp) if session['db_uri'] != '' else [] - themes = getThemes() - tables_selected = [] - dropped_items = session.get('target_table_names', []) - self_labels = session.get('self_defined_labels', []) - schema_Selected = request.form.get('schema', None) - if schema_Selected != None: - session['schema_selected'] = schema_Selected - print("888" + schema_Selected) - show_all = request.form.get('show_all') == 'True' - if show_all != False: - session['show_all'] = show_all - + try: + if database_uri != '' and database_uri != None: + print("3") + if database_uri != session.get('db_uri', ''): + session['db_uri'] = database_uri + session['target_table_names'] = [] + session['target_table_name'] = '' + session['self_defined_labels'] = [] + session['schema_selected'] = '' + session['show_all'] = False + session['object_name'] = {} + session['data_header']={'event':[], 'measurement':[], 'segment':[], 'segmentData':[]} + session['current_data_header'] = {'tablename': '', 'type': '', 'label': [], 'features_name': []} + session['data_table'] = {'O':[], 'S':[], 'SD':[]} + print("4") + # Initialize inspector here, outside the inner if-else + print("4.5") + engine = create_engine(database_uri) + insp = inspect(engine) + session_factory = sessionmaker(bind=engine) + db.session = scoped_session(session_factory) + metadata_obj = MetaData() + print("5") - tables1 = importMetadata(engine, schema_Selected, None, show_all) - graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True) - image1 = generate_erd(graph_DOT1) - - print("111") - for name in dropped_items: - print(name) - if dropped_items == []: - image2 = "" - else: - tables2 = importMetadata(engine, None, dropped_items, False) - graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) - image2 = generate_erd(graph_DOT2) - print("222") + else: + database_uri = session.get('db_uri', '') + engine = create_engine(database_uri) + insp = inspect(engine) + session_factory = sessionmaker(bind=engine) + db.session = scoped_session(session_factory) + print("6") + + database = database_name_from_uri(engine, database_uri) if database_uri != '' else '' + schemas = getSchema(insp) if session['db_uri'] != '' else [] + themes = getThemes() + tables_selected = [] + dropped_items = session.get('target_table_names', []) + self_labels = session.get('self_defined_labels', []) + schema_Selected = request.form.get('schema', None) + if schema_Selected != None: + session['schema_selected'] = schema_Selected + print("888" + schema_Selected) + show_all = request.form.get('show_all') == 'True' + if show_all != False: + session['show_all'] = show_all + + tables1 = importMetadata(engine, schema_Selected, None, show_all) + graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True) + image1 = generate_erd(graph_DOT1) - # extract_ME_table(engine, 'event_data', 'E', 'time', ['col', 'machine_id'], [' col ', ' name ', ' Z_ACTUAL_ZERO_POINT '], ['value(fine, coarse)'], datetime(2022, 11, 1, 17, 5), datetime(2022, 11, 3, 0, 0)) + print("111") + for name in dropped_items: + print(name) + if dropped_items == []: + image2 = "" + else: + tables2 = importMetadata(engine, None, dropped_items, False) + graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) + image2 = generate_erd(graph_DOT2) + print("222") - return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items, self_labels=self_labels) - + return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items, self_labels=self_labels) + + except SQLAlchemyError as e: + # If there's a database error, send an error message to the frontend + return render_template('app.html', success=False, message="Failed to connect to the database. Please check the connection details.") else: # Display the form return render_template('app.html') @@ -118,24 +115,6 @@ def index(): @app.route('/handle-drop', methods=['POST']) def handle_drop(): data = request.json - # if data.get('reset') == True or data.get('reset') != None: - # print(data.get('reset')) - # session['target_table_names'] = [] - # dropped_items = [] - # graph_DOT2 = "digraph {};" - # image2 = generate_erd(graph_DOT2) - - # database_uri = session.get('db_uri', '') - # engine = create_engine(database_uri) - # insp = inspect(engine) - # schema_Selected = session.get('schema', None) - # print("999"+ str(schema_Selected)) - # show_all = session.get('show_all', False) - # tables_in_CustomTable = importMetadata(engine, schema_Selected, None, show_all) - # tables1 = list(tables_in_CustomTable.keys()) if tables_in_CustomTable != {} else [] - - # return jsonify(image2=image2, dropped_items=dropped_items, tables=tables1) - item_name = data.get('item') action = data.get('action') dropped_items = session.get('target_table_names', []) @@ -155,6 +134,7 @@ def handle_drop(): return jsonify(image2=image2) + @app.route('/reset-target-table', methods=['POST']) def reset_target_table(): print("reset") @@ -162,6 +142,7 @@ def reset_target_table(): engine = create_engine(database_uri) session['target_table_names'] = [] schema_Selected = session.get('schema_selected', None) + print(schema_Selected) show_all = session.get('show_all', False) # Regenerate ERD based on the updated dropped_items @@ -175,6 +156,7 @@ def reset_target_table(): return jsonify(image2=image2, tables=tables1) + @app.route('/get-table-data', methods=['POST']) def get_table_data(): data = request.json @@ -191,19 +173,30 @@ def get_table_data(): # Get the table columns and values table_instance = getTableInstance(engine, table_name) - table_columns = [column.name for column in table_instance.columns] - sorted_table_columns = sorted(table_columns) + table_columns = [] + for column in table_instance.columns: + if column.datatype == "varchar" or column.datatype == "numeric" or column.datatype == "string" or column.datatype == "text": + table_columns.append(column.name) + # table_columns = [column.name for column in table_instance.columns] + sorted_table_columns = sorted(table_columns) # de-nested the JSON columns from the feature_columns - feature_columns = sorted_table_columns + column_names = [] + + for column in table_instance.columns: + if column.datatype != "timestamp" and column.datatype != "uuid": + column_names.append(column.name) + + feature_columns = sorted(column_names) if check_json_column(engine, table_name) != []: json_column_names = check_json_column(engine, table_name) for column_name in json_column_names: - feature_columns.remove(column_name) - jsonKeys = handle_json_column(engine, table_name, column_name) #[('line',), ('coarse', 'fine'), ('name',), ('percent',), ('axisID', 'coarse', 'fine')] - for key in jsonKeys: - feature_columns.append( column_name + str(key) ) if len(key) > 1 else feature_columns.append( column_name + str(key).replace(',', '')) - + if column_name in feature_columns: + feature_columns.remove(column_name) + jsonKeys = handle_json_column(engine, table_name, column_name) #[('line',), ('coarse', 'fine'), ('name',), ('percent',), ('axisID', 'coarse', 'fine')] + for key in jsonKeys: + feature_columns.append( column_name + str(key) ) if len(key) > 1 else feature_columns.append( column_name + str(key).replace(',', '')) + return jsonify({ 'html_table': html_table, 'table_columns': sorted_table_columns, 'feature_columns': feature_columns }) @@ -243,6 +236,78 @@ def add_label(): return jsonify({'defined_labels': self_defined_labels}) +@app.route('/constraint-feature-columns', methods=['POST']) +def constraint_feature_columns(): + engine = create_engine(session.get('db_uri','')) + data = request.json + table_name = session.get('target_table_name', '') + label_column = data.get('label_column', '') + label = data.get('label', '') + if table_name == None or table_name == '': + print("table_name is None" + table_name) + return jsonify({}) + if check_json_column(engine, table_name) != [] and label_column != '' and label != '': + print("constraint_feature_columns") + # Get the table columns and values + table_instance = getTableInstance(engine, table_name) + # de-nested the JSON columns from the feature_columns + column_names = [] + for column in table_instance.columns: + if column.datatype != "timestamp" and column.datatype != "uuid": + column_names.append(column.name) + + feature_columns = sorted(column_names) + json_column_names = check_json_column(engine, table_name) + for column_name in json_column_names: + if column_name in feature_columns: + feature_columns.remove(column_name) + jsonKeys = get_JSON_keys(engine, table_name, column_name, label_column, label) #[('line',), ('coarse', 'fine'), ('name',), ('percent',), ('axisID', 'coarse', 'fine')] + for key in jsonKeys: + feature_columns.append( column_name + str(key) ) if len(key) > 1 else feature_columns.append( column_name + str(key).replace(',', '')) + print(feature_columns) + return jsonify({'feature_columns': feature_columns }) + + else: + print("abfhakfbdahf") + print(table_name) + print(label_column) + print(label) + return jsonify({}) + +def get_JSON_keys(engine, table_name, column_name, label_column, label): + insp = inspect(engine) + isSQLite = insp.dialect.name == 'sqlite' + + # Create a connection from the engine + with engine.connect() as conn: + # Prepare the SQL query to fetch a sample JSON object from the specified column + if insp.dialect.name == 'postgresql': + schema = getTableSchema(table_name) + query = f"SELECT DISTINCT {column_name} FROM {schema}.{table_name} WHERE {label_column} = '{label}'" + else: + query = f"SELECT DISTINCT {column_name} FROM {table_name} WHERE {label_column} = '{label}'" + + # Execute the query and fetch the result + result = conn.execute(text(query)).fetchall() + # df = pd.read_sql_query(query, engine) + conn.close() + + jsonKeys = [] + for row in result: # row is a tuple, row[0] is a dictionary + # Convert the keys to a sorted tuple for consistency + if type(row[0]) == dict: + name = tuple(sorted(row[0].keys())) + else: + name = tuple(sorted(json.loads(row[0]).keys())) + + # Append the key list if it's not already in jsonKeys + if name not in jsonKeys: + jsonKeys.append(name) + + print(jsonKeys) + return jsonKeys + + @app.route('/add-data-header', methods=['POST']) def add_data_header(): data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) @@ -275,11 +340,7 @@ def add_data_header(): obj = session.get('object_name', {}) obj[current_table] = object_column session['object_name'] = obj - return jsonify() - print(observation.to_dict()) - print("88888") - print(data_header) session['data_header'] = data_header data_header_table = generate_html_header_table() @@ -304,6 +365,7 @@ def init_data_header_table(): @app.route('/delete-data-header', methods=['POST']) def delete_data_header(): data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) + obj = session.get('object_name', {}) if data_header == {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}: data_header_table = generate_html_header_table() return jsonify({'data_header_table': data_header_table}) @@ -322,6 +384,10 @@ def delete_data_header(): data_header['segment'].remove(res) elif type == 'SD': data_header['segmentData'].remove(res) + elif type == 'O': + print("delete object", res['tablename'], res) + obj.pop(res['tablename'], None) + session['object_name'] = obj session['data_header'] = data_header data_header_table = generate_html_header_table() @@ -350,13 +416,16 @@ def get_MD_info(): elif type == 'S': time = getTimeColumns(res['tablename']) # list object = getObjectColumns(res['tablename']) # list - # index = - return jsonify({'time': time, 'object': object}) + index = getIndexColumns(res['tablename']) # list + return jsonify({'time': time, 'object': object, 'index': index}) elif type == 'SD': - time = getTimeColumns(res['tablename']) # list - object = getObjectColumns(res['tablename']) - # index = - return jsonify({'time': time, 'object': object}) + data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) + object = getObjectColumns(res['tablename']) # list + index = getIndexColumns(res['tablename']) # list + segment = [segment['label'][2] for segment in data_header['segment']] + print("Segment options") + print(segment) + return jsonify({'object': object, 'index': index, 'segment': segment}) @app.route('/get-ME-table', methods=['POST']) @@ -398,22 +467,259 @@ def get_ME_table(): return jsonify({'table_HTML': table_HTML}) +@app.route('/get-S-table', methods=['POST']) +def get_S_table(): + engine = create_engine(session.get('db_uri', '')) + data = request.json + current_data_header = session.get('current_data_header', {'tablename': '', 'type': '', 'label': [], 'features_name': []}) + table_name = current_data_header['tablename'] + type = current_data_header['type'] + object_column = data['object_column'] + optgroupLabel = data['optgroupLabel'] + object_list = [optgroupLabel, object_column] + index_column = data['index_column'] + starttime_column = data['starttime_column'] + endtime_column = data['endtime_column'] + label_list = current_data_header['label'] + + start_time = datetime.strptime(data['minDatetime'], '%Y-%m-%d %H:%M:%S') if 'minDatetime' in data else None + end_time = datetime.strptime(data['maxDatetime'], '%Y-%m-%d %H:%M:%S') if 'maxDatetime' in data else None + + print(table_name) + print(type) + print(starttime_column) + print(endtime_column) + print(object_list) + print(label_list) + + query_result = extract_S_table(engine, table_name, starttime_column, endtime_column, index_column, object_list, label_list, start_time, end_time) + table_HTML = get_ME_table_HTML(query_result) + + if start_time == None and end_time == None: + min_datetime, max_datetime = get_min_max_datetime2(engine, table_name, starttime_column, endtime_column) + return jsonify({'table_HTML': table_HTML, 'min_datetime': min_datetime, 'max_datetime': max_datetime}) + + return jsonify({'table_HTML': table_HTML}) + + +@app.route('/get-SD-table', methods=['POST']) +def get_SD_table(): + engine = create_engine(session.get('db_uri', '')) + # Get the current data header from the session + current_data_header = session.get('current_data_header', {'tablename': '', 'type': '', 'label': [], 'features_name': []}) + table_name = current_data_header['tablename'] + type = current_data_header['type'] + label_list = current_data_header['label'] + features = [] + for feature in current_data_header['features_name']: + if '(' in feature and ')' in feature: # "value('coarse', 'fine')" + feature = feature.replace("'", "") + features.append(feature) + else: + features.append(feature) + # Input data from the frontend + data = request.json # { 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'index_column': index_column, 'segment_column': segment_column} + object_column = data['object_column'] + optgroupLabel = data['optgroupLabel'] + object_list = [optgroupLabel, object_column] + index_column = data['index_column'] + segment_column = data['segment_column'] + + print("get_SD_table") + print(table_name) + print(type) + print(segment_column) + print(object_list) + print(label_list) + + index_from = int(data['index_from']) if 'index_from' in data else None + index_to = int(data['index_to']) if 'index_to' in data else None + + query_result = extract_SD_table(engine, table_name, object_list, label_list, segment_column, index_column, features, index_from, index_to) + table_HTML = get_ME_table_HTML(query_result) + + if index_from == None and index_to == None: + min_index, max_index = get_min_max_index(engine, table_name, index_column) + return jsonify({'table_HTML': table_HTML, 'min_index': min_index, 'max_index': max_index}) + + return jsonify({'table_HTML': table_HTML}) + + +@app.route('/add-data-table', methods=['POST']) +def add_data_table(): + data = request.json + selected_rows = data['selectedRowsData'] + current_row_type = data['currentRowType'] # 'E', 'M', 'S', 'SD' + data_tables = session.get('data_tables', {'O':[], 'S':[], 'SD':[]}) + + for i in selected_rows: + print(i) #{'column0': '', 'column1': '1560628', 'column2': ' label A ', 'column3': '215', 'column4': '2000-07-16 22:00:00.000000', 'column5': '2000-07-17 21:59:59.999000'} + + if current_row_type in ['E', 'M']: + data_tables['O'].extend(selected_rows) + data_tables['O'].sort(key=lambda x: datetime.strptime(x.get('column1', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f')) # Adjust 'columnTime' to your time column key + elif current_row_type == 'S': + data_tables[current_row_type].extend(selected_rows) + data_tables['S'].sort(key=lambda x: datetime.strptime(x.get('column4', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f')) # Adjust 'columnTime' to your time column key + else : + data_tables['SD'].extend(selected_rows) + data_tables['SD'].sort(key=lambda x: x.get('column2', '')) # Group data by 'label' + + session['data_tables'] = data_tables + table_html = generate_html_data_table(data_tables[current_row_type], current_row_type) if current_row_type in ['S', 'SD'] else generate_html_data_table(data_tables['O'], 'O') + + return jsonify({'table_HTML': table_html}) + + +@app.route('/reset-machine-data-table', methods=['POST']) +def reset_machine_data_table(): + + data_tables = {'O':[], 'S':[], 'SD':[]} + session['data_tables'] = data_tables + + return jsonify({}) + + +@app.route('/export-Header-to-csv', methods=['POST']) +def export_Header_to_csv(): + data = request.get_json() # This is the data sent from the frontend + if not data: + raise ValueError("No data provided for export.") + + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + if row[1] == 'O': + data.remove(row) + while len(row) < max_columns: + row.append(None) + + headers = ['type', 'label'] # List your headers here + if max_columns > (len(headers) + 1): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers)-1)]) + + # Skip the first column + df = pd.DataFrame(data) + df = df.iloc[:, 1:] + + # Rename the DataFrame headers + df.columns = headers + + # Convert the DataFrame to a CSV string + csv_output = io.StringIO() + df.to_csv(csv_output, index=False) + csv_output.seek(0) + + # Create a Flask response + response = make_response(csv_output.getvalue()) + response.headers["Content-Disposition"] = "attachment; filename=header.csv" + response.headers["Content-Type"] = "text/csv" + + return response + + @app.route('/export-to-csv', methods=['POST']) def export_to_csv(): data = request.get_json() # This is the data sent from the frontend + if not data: + raise ValueError("No data provided for export.") + + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + while len(row) < max_columns: + row.append(None) + + # Create a DataFrame from the data df = pd.DataFrame(data) + # Adjust the dataframe to skip the first column and rename the headers + headers = ['time', 'object', 'type', 'label'] # List your headers here + if max_columns > len(headers): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers))]) + + # Rename the DataFrame headers + df.columns = headers + + # Convert the DataFrame to a CSV string + csv_output = io.StringIO() + df.to_csv(csv_output, index=False) + csv_output.seek(0) + + # Create a Flask response + response = make_response(csv_output.getvalue()) + response.headers["Content-Disposition"] = "attachment; filename=observations.csv" + response.headers["Content-Type"] = "text/csv" + + return response + + +@app.route('/export-S-to-csv', methods=['POST']) +def export_S_to_csv(): + + data = request.get_json() # This is the data sent from the frontend if not data: raise ValueError("No data provided for export.") + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + while len(row) < max_columns: + row.append(None) + + # Create a DataFrame from the data + df = pd.DataFrame(data) + # Adjust the dataframe to skip the first column and rename the headers - df = df.iloc[:, 1:] # Skip the first column - headers = ['time', 'object', 'type', 'label'] # List your headers here - num_features = len(data[0]) - len(headers) - 1 - headers.extend([f'f_{i+1}' for i in range(num_features)]) - df.columns = headers # Rename the DataFrame headers + headers = ['object', 'segment', 'segment_index', 'start', 'end'] # List your headers here + if max_columns > len(headers): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers))]) + + # Rename the DataFrame headers + df.columns = headers + # Convert the DataFrame to a CSV string + csv_output = io.StringIO() + df.to_csv(csv_output, index=False) + csv_output.seek(0) + + # Create a Flask response + response = make_response(csv_output.getvalue()) + response.headers["Content-Disposition"] = "attachment; filename=segments.csv" + response.headers["Content-Type"] = "text/csv" + return response + + +@app.route('/export-SD-to-csv', methods=['POST']) +def export_SD_to_csv(): + + data = request.get_json() # This is the data sent from the frontend + + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + while len(row) < max_columns: + row.append(None) + + # Create a DataFrame from the data + df = pd.DataFrame(data) + + # Adjust the dataframe to skip the first column and rename the headers + headers = ['object', 'segment', 'segment_index', 'label'] # List your headers here + if max_columns > len(headers): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers))]) + + # Rename the DataFrame headers + df.columns = headers # Convert the DataFrame to a CSV string csv_output = io.StringIO() @@ -422,11 +728,12 @@ def export_to_csv(): # Create a Flask response response = make_response(csv_output.getvalue()) - response.headers["Content-Disposition"] = "attachment; filename=exported_data.csv" + response.headers["Content-Disposition"] = "attachment; filename=segment_data.csv" response.headers["Content-Type"] = "text/csv" return response + def readHeaderHTML(str): soup = BeautifulSoup(str, 'html.parser') tds = soup.find_all('td') @@ -434,25 +741,28 @@ def readHeaderHTML(str): res = {'tablename': '', 'type': '', 'label': [], 'features_name': []} res['tablename'] = tds[1].get_text() res['type'] = tds[2].get_text() - res['label'] = json.loads(tds[3]['data-value']) - features = [] - for td in tds[4:]: - data_value = td['data-value'] - value_list = json.loads(data_value) - if value_list[0] == '': - a = value_list[1] - features.append(a) - else: - a = value_list[0] + '(' - for i in range(len(value_list)-1): - a += "'" + value_list[i+1] + "', " - a = a[:len(a)-2] - a += ')' - if a not in features: + if res['type'] == 'O': + data_value = tds[3].get_text() + else: + res['label'] = json.loads(tds[3]['data-value']) + features = [] + for td in tds[4:]: + data_value = td['data-value'] + value_list = json.loads(data_value) + if value_list[0] == '': + a = value_list[1] features.append(a) - res['features_name'] = features # ['machine_id', "value('coarse', 'fine')"] + else: + a = value_list[0] + '(' + for i in range(len(value_list)-1): + a += "'" + value_list[i+1] + "', " + a = a[:len(a)-2] + a += ')' + if a not in features: + features.append(a) + res['features_name'] = features # ['machine_id', "value('coarse', 'fine')"] - print(features) + print(features) return res @@ -501,7 +811,7 @@ def handle_json_column(engine, table_name, column_name): if name not in jsonKeys: jsonKeys.append(name) - print(jsonKeys) + print(jsonKeys) return jsonKeys @@ -557,7 +867,7 @@ def generate_html_header_table(): for key in content.keys(): for dict_item in content[key]: table_html += f"<tr onclick='getRowData(this)'>" - table_html += "<td><button type='button' class='btn-close' aria-label='Close' data-bs-dismiss='modal' onclick='deleteRow1(event, this)'></button></td>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' onclick='deleteRow1(event, this)'></button></td>" table_html += f"<td>{dict_item.get('tablename', '')}</td>" table_html += f"<td data-id>{dict_item.get('type', '')}</td>" print("723723") @@ -584,11 +894,80 @@ def generate_html_header_table(): table_html += f"<td data-value='{data_value}'>{value}</td>" index += 1 table_html += "</tr>" + + # Add object table row to header table + obj = session.get('object_name', {}) + for key in obj.keys(): + table_html += "<tr>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' onclick='deleteRow1(event, this)'></button></td>" + table_html += f"<td>{key}</td>" + table_html += f"<td>O</td>" + table_html += f"<td>{obj[key]}</td>" + table_html += "</tr>" + table_html += "</tbody></table>" print(table_html) return table_html +def generate_html_data_table(data_tables:list, table_type:str): + # Generate column headers + table_html = "<thead><tr>" + table_html += "<th></th>" + if table_type == 'O': + table_html += "<th class='uk-table-expand'>time</th>" + table_html += "<th>object</th>" + table_html += "<th>type</th>" + table_html += "<th class='uk-table-expand'>label</th>" + max_features = max(len(row) - 5 for row in data_tables) # Assuming first 4 keys are not features + for i in range(max_features): + table_html += f"<th>f_{i+1}</th>" + elif table_type == 'S': + table_html += "<th>object</th>" + table_html += "<th class='uk-table-expand'>segment</th>" + table_html += "<th>segment_index</th>" + table_html += "<th class='uk-table-expand'>start</th>" + table_html += "<th class='uk-table-expand'>end</th>" + + elif table_type == 'SD': + table_html += "<th>object</th>" + table_html += "<th class='uk-table-expand'>segment</th>" + table_html += "<th>segment_index</th>" + table_html += "<th class='uk-table-expand'>label</th>" + max_features = max(len(row) - 5 for row in data_tables) # Assuming first 4 keys are not features + for i in range(max_features): + table_html += f"<th>f_{i+1}</th>" + + table_html += "</tr></thead><tbody>" + + # Generate table rows + if table_type == 'SD': + for row in data_tables: + table_html += f"<tr>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' onclick='deleteRow2(event, this)'></button></td>" + for i in range(len(row) - 1): + if i == 1: + table_html += f"<td>{row.get('column3', '')}</td>" + elif i == 2: + table_html += f"<td>{row.get('column4', '')}</td>" + elif i == 3: + table_html += f"<td>{row.get('column2', '')}</td>" + else: + table_html += f"<td>{row.get('column' + str(i+1), '')}</td>" + table_html += "</tr>" + else: + for row in data_tables: + table_html += f"<tr>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' data-bs-dismiss='modal' onclick='deleteRow2(event, this)'></button></td>" + for i in range(len(row) - 1): + table_html += f"<td>{row.get('column' + str(i+1), '')}</td>" + table_html += "</tr>" + + table_html += "</tbody>" + print(table_html) + return table_html + + def generate_html_table(content): if not content: return "No data found." @@ -616,6 +995,7 @@ def getTimeColumns(table_name:str) -> list: schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name columns = insp.get_columns(table_name, schema) timestamp_columns = [column['name'] for column in columns if str(column['type']) == 'TIMESTAMP'] + print("timestamp columns") print(timestamp_columns) return timestamp_columns @@ -626,12 +1006,25 @@ def getObjectColumns(table_name:str) -> list: insp = inspect(engine) schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name columns = insp.get_columns(table_name, schema) - object_columns = [column['name'] for column in columns if str(column['type']) == 'VARCHAR' or str(column['type']) == 'INTEGER' or str(column['type']) == 'NUMERIC'] + object_columns = [column['name'] for column in columns if str(column['type']) == 'VARCHAR' or str(column['type']) == 'INTEGER' or str(column['type']) == 'NUMERIC' or str(column['type']) == 'TEXT' or str(column['type']) == 'SMALLINT'] + print("object columns") print(object_columns) return object_columns +def getIndexColumns(table_name:str) -> list: + engine = create_engine(session.get('db_uri', '')) + insp = inspect(engine) + schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name + columns = insp.get_columns(table_name, schema) + index_columns = [column['name'] for column in columns if str(column['type']) == 'INTEGER' or str(column['type']) == 'NUMERIC' or str(column['type']) == 'BIGINT' or str(column['type']) == 'SMALLINT'] + print("index columns") + print(index_columns) + + return index_columns + + def query_database_for_table_content(engine, table_name, number=200): # Initialize content list content_list = [] @@ -727,6 +1120,46 @@ def get_min_max_datetime(engine, table_name, time_column, start_time=None, end_t return None, None +def get_min_max_datetime2(engine, table_name, starttime_column, endtime_column, start_time=None, end_time=None): + schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name + # Formulate the SQL query using the text function + query = text(f"SELECT MIN({starttime_column}) AS start_datetime, MAX({endtime_column}) AS end_datetime FROM {schema}.{table_name};") + + # Execute the query + with engine.connect() as connection: + row = connection.execute(query).mappings().fetchone() + + # Extract the min and max datetime values + if row: + min_datetime, max_datetime = row['start_datetime'], row['end_datetime'] + print("Minimum datetime:", min_datetime) + print("Maximum datetime:", max_datetime) + return min_datetime, max_datetime + else: + print("No datetimes found.") + return None, None + + +def get_min_max_index(engine, table_name, index_column): + schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name + # Formulate the SQL query using the text function + query = text(f"SELECT MIN({index_column}) AS start_index, MAX({index_column}) AS end_index FROM {schema}.{table_name};") + + # Execute the query + with engine.connect() as connection: + row = connection.execute(query).mappings().fetchone() + + # Extract the min and max datetime values + if row: + min_index, max_index = row['start_index'], row['end_index'] + print("Minimum index:", min_index) + print("Maximum index:", max_index) + return min_index, max_index + else: + print("No indexes found.") + return None, None + + def extract_ME_table(engine, table_name: str, type: str, time_column: str, object: list, label: list, features_name: list, start_time: datetime = None, end_time: datetime = None) -> list: conn = engine.connect() insp = inspect(engine) @@ -799,9 +1232,6 @@ def extract_ME_table(engine, table_name: str, type: str, time_column: str, objec if end_time: params['end_time'] = end_time - - # res = conn.execute(text(sql_query), params).fetchall() - # Print the query for debugging print("SQL Query:", sql_query) print("Parameters:", params) @@ -831,6 +1261,197 @@ def extract_ME_table(engine, table_name: str, type: str, time_column: str, objec return final_res +def extract_S_table(engine, table_name: str, starttime_column: str, endtime_column: str, index_column: str, object: list, label: list, start_time: datetime = None, end_time: datetime = None) -> list: + conn = engine.connect() + insp = inspect(engine) + database_name = insp.dialect.name # 'postgresql' or 'sqlite' + table_instance = getTableInstance(engine, table_name) + label_column = label[1].strip() + label_value = label[2].strip() + object_column_value = object[1].strip() + join_clause = '' + + full_table_name = f"{table_instance.schema}.{table_instance.name}" if table_instance.schema else table_instance.name + sql_columns = [] + + # Handling object_column logic + if object[0].strip() != 'self' and object[0].strip() == 'col': + print("1") + object_column = table_instance.getColumn(object_column_value) + object_column_name = object_column.fkof.table.name if object_column.fkof else '' + if object_column and object_column.fkof and object_column_name in session.get('object_name', {}): + related_table_instance = getTableInstance(engine, object_column_name) + full_related_table_name = f"{related_table_instance.schema}.{related_table_instance.name}" if related_table_instance.schema else related_table_instance.name + join_clause = f"LEFT JOIN {full_related_table_name} ON {full_table_name}.{object_column.name} = {full_related_table_name}.{object_column.fkof.name}" + sql_columns.append(f"{full_related_table_name}.{session.get('object_name').get(object_column_name)} AS {object_column_value}") + else: + sql_columns.append(f"{full_table_name}.{object_column.name}") + print("12") + # If label[0] is not 'self', add it to SQL columns + if label[0].strip() != 'self': + sql_columns.append(f"{full_table_name}.{label_column}") + print("123") + + # Adding index, starttime, endtime columns to the select clause + sql_columns.append(f"{full_table_name}.{index_column}") + sql_columns.append(f"{full_table_name}.{starttime_column}") + sql_columns.append(f"{full_table_name}.{endtime_column}") + + # Adding JSON extractions to the select clause + sql_select = ', '.join(sql_columns) + + # Constructing SQL query + sql_joins = join_clause + if label[0].strip() == 'col': + sql_where = f"WHERE {full_table_name}.{label_column} = :label_value" + if start_time: + sql_where += f" AND {full_table_name}.{starttime_column} >= :start_time AND {full_table_name}.{endtime_column} >= :start_time" + if end_time: + sql_where += f" AND {full_table_name}.{starttime_column} <= :end_time AND {full_table_name}.{endtime_column} <= :end_time" + else: + sql_where = '' + if start_time: + sql_where += f"WHERE {full_table_name}.{starttime_column} >= :start_time AND {full_table_name}.{endtime_column} >= :start_time" + if end_time: + sql_where += f" AND {full_table_name}.{starttime_column} <= :end_time AND {full_table_name}.{endtime_column} <= :end_time" + + sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {starttime_column} ASC LIMIT 500" + print("12345") + # Executing the query + params = {'label_value': label_value} + if start_time: + params['start_time'] = start_time + if end_time: + params['end_time'] = end_time + + # Print the query for debugging + print("SQL Query:", sql_query) + print("Parameters:", params) + + # Executing the query + try: + res = conn.execute(text(sql_query), params).fetchall() + except Exception as e: + print(f"Error executing query: {e}") + return [] + + # Append object and label values if necessary + final_res = [] + for row in res: + modified_row = list(row) + if object[0].strip() == 'self': + modified_row.insert(0, object_column_value) + if label[0].strip() == 'self': + label_index = 1 if object[0].strip() != 'self' else 0 + modified_row.insert(label_index, label[2]) + + final_res.append(modified_row) + + for row in final_res: + print(row) + + return final_res + + +def extract_SD_table(engine, table_name: str, object: list, label: list, segment_column: str, index_column: str, features_name: list, index_from: int = None, index_to: int = None) -> list: + conn = engine.connect() + insp = inspect(engine) + database_name = insp.dialect.name # 'postgresql' or 'sqlite' + table_instance = getTableInstance(engine, table_name) + label_column = label[1].strip() + label_value = label[2].strip() + object_column_value = object[1].strip() + join_clause = '' + + full_table_name = f"{table_instance.schema}.{table_instance.name}" if table_instance.schema else table_instance.name + sql_columns = [] + + # Handling object_column logic + if object[0].strip() != 'self' and object[0].strip() == 'col': + print("1") + object_column = table_instance.getColumn(object_column_value) + object_column_name = object_column.fkof.table.name if object_column.fkof else '' + if object_column and object_column.fkof and object_column_name in session.get('object_name', {}): + related_table_instance = getTableInstance(engine, object_column_name) + full_related_table_name = f"{related_table_instance.schema}.{related_table_instance.name}" if related_table_instance.schema else related_table_instance.name + join_clause = f"LEFT JOIN {full_related_table_name} ON {full_table_name}.{object_column.name} = {full_related_table_name}.{object_column.fkof.name}" + sql_columns.append(f"{full_related_table_name}.{session.get('object_name').get(object_column_name)} AS {object_column_value}") + else: + sql_columns.append(f"{full_table_name}.{object_column.name}") + print("12") + # If label[0] is not 'self', add it to SQL columns + if label[0].strip() != 'self': + sql_columns.append(f"{full_table_name}.{label_column}") + print("123") + # Adding index columns to the select clause + sql_columns.append(f"{full_table_name}.{index_column}") + # Handling JSON extractions + json_extractions = [] + for feature in features_name: + if '(' in feature and ')' in feature: + column_name, keys = feature[:-1].split('(') + keys = keys.split(', ') + for key in keys: + if database_name == 'postgresql': + json_extraction = f"{full_table_name}.{column_name}->>'{key}' AS {key}" + elif database_name == 'sqlite': + json_extraction = f"json_extract({full_table_name}.{column_name}, '$.{key}') AS {key}" + json_extractions.append(json_extraction) + else: + sql_columns.append(f"{full_table_name}.{feature}") + print("1234") + # Adding JSON extractions to the select clause + sql_select = ', '.join(sql_columns + json_extractions) + + # Constructing SQL query + sql_joins = join_clause + if label[0].strip() == 'col': + sql_where = f"WHERE {full_table_name}.{label_column} = :label_value" + if index_from: + sql_where += f" AND {full_table_name}.{index_column} >= :index_from" + if index_to: + sql_where += f" AND {full_table_name}.{index_column} <= :index_to" + else: + sql_where = '' + if index_from: + sql_where += f" WHERE {full_table_name}.{index_column} >= :index_from" + if index_to: + sql_where += f" AND {full_table_name}.{index_column} <= :index_to" + + sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {index_column} ASC LIMIT 500" + print("12345") + # Executing the query + params = {'label_value': label_value, 'index_from': index_from, 'index_to': index_to} + + # Print the query for debugging + print("SQL Query:", sql_query) + print("Parameters:", params) + + # Executing the query + try: + res = conn.execute(text(sql_query), params).fetchall() + except Exception as e: + print(f"Error executing query: {e}") + return [] + + # Append object and label values if necessary + final_res = [] + for row in res: + modified_row = list(row) + if object[0].strip() == 'self': + modified_row.insert(0, object_column_value) + if label[0].strip() == 'self': + label_index = 1 if object[0].strip() != 'self' else 0 + modified_row.insert(label_index, label[2]) + modified_row.insert(2, segment_column) + final_res.append(modified_row) + + for row in final_res: + print(row) + + return final_res + + def get_ME_table_HTML(data: list) -> str: # Start the HTML <body> content with an opening <table> tag html_content = "" @@ -841,7 +1462,7 @@ def get_ME_table_HTML(data: list) -> str: for cell in row: if isinstance(cell, datetime): # cell = cell.isoformat() - cell = cell.strftime('%Y-%m-%d %H:%M:%S:%f') + cell = cell.strftime('%Y-%m-%d %H:%M:%S.%f') html_content += f"<td>{cell}</td>" html_content += "</tr>\n" diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df4fecfba13b7c10abc18bb87debc8b989d42686 GIT binary patch literal 163 zcmZ3^%ge>Uz`&5%zb+L-KL!yn%m`(CW@BJrn9h*G5X_*-=(m!gh>3xL;WJ3`mzjQO zacWVqesV@;YKFc`YH@Z+et~{&Vroi!YDGy=Vsc4-k$!G|N@`BAetdjpUS>&ryk0@& oFAkgB{FKt1RJ$Tp1_lO@MaBFK3=AKb85tQrFu;f+W(EcZ0ENRQs{jB1 literal 0 HcmV?d00001 diff --git a/models/__pycache__/models.cpython-311.pyc b/models/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..780e177e25900a2bae7265a22e612d24724b20c3 GIT binary patch literal 15303 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 literal 0 HcmV?d00001 diff --git a/models/__pycache__/models.cpython-39.pyc b/models/__pycache__/models.cpython-39.pyc index 2e0a8d9d8d7c2f1d91d5104ea76cf387a4a83dbe..4dcb5a8fa921e8dc45deed97019797f6eac214e2 100644 GIT binary patch delta 3250 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 delta 3627 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 diff --git a/models/models.py b/models/models.py index f01eeb3..9c517d5 100644 --- a/models/models.py +++ b/models/models.py @@ -1,4 +1,3 @@ -from sqlalchemy import ARRAY, BIGINT, BOOLEAN, DOUBLE_PRECISION, FLOAT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect from sqlalchemy.types import UserDefinedType import re @@ -7,7 +6,7 @@ class Observation_Spec: def __init__(self, tablename:str, type:str, label:list[str], features_name:list = None): self.tablename = tablename self.type = type # 'event' or 'measurement' - self.label = label # (self-define or from column, column name, column value orself-define label) + self.label = label # (self-define or from column, column name, column value or self-define label) self.features_name = features_name if features_name else [] def add_feature(self, name): diff --git a/templates/app.html b/templates/app.html index f8e43df..44e755e 100644 --- a/templates/app.html +++ b/templates/app.html @@ -222,7 +222,7 @@ } .uk-table-hover tbody tr:hover { background-color: #5ea9e2; - color: white; + color:#add8e6; } .terminal.resizing .terminal-header { cursor: ns-resize; @@ -283,7 +283,16 @@ position: sticky; top: 0; } - + .uk-tooltip { + border-radius: 5px !important; + } + .accordion-container { + margin-top: 26px; + display: flex; + flex-direction: column; + height: 83vh; /* or adjust as necessary */ + overflow: hidden; /* This prevents the overall container from scrolling */ + } </style> </head> <body> @@ -292,14 +301,14 @@ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous"></script> <div id="sidebar1"> <!--class="uk-panel uk-panel-scrollable" --> - <legend>Database:</legend> + <legend uk-tooltip="title: Show the database name.; pos: right">Database:</legend> <div class="mb-3"> <label for="disabledTextInput" class="form-label" style="margin-top: 10px;">{{database}}.db</label> </div> <ul uk-accordion> <li> - <a class="uk-accordion-title" href="#">Database URL</a> + <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Provide a valid databsae connection here.; pos: right">Database URL</a> <div class="uk-accordion-content"> <form method="post" action="/"> @@ -315,7 +324,7 @@ <li> - <a class="uk-accordion-title" href="#">Filter</a> + <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Use these filters to refine the tables displayed below; pos: right">Filter</a> <div class="uk-accordion-content"> <div class="accordion"> @@ -333,7 +342,7 @@ <label class="form-check-label" for="showAllClick" style="margin-top: 3px;"> Primary Tables </label> - <select class="form-select" name="schema" id="schemaSelect" onchange="this.form.submit()" onclick="openTables()" style="padding: 0px 1px 2px 1px;"> + <select class="form-select" name="schema" id="schemaSelect" onchange="this.form.submit()" style="padding: 0px 1px 2px 1px;"> <option value="">Choose here</option> {% for schema in schemas %} <option value="{{ schema }}" {% if schema == schema_Selected %} selected {% endif %}>{{ schema }}</option> @@ -385,15 +394,15 @@ </li> - <li class="uk-class"> - <a class="uk-accordion-title" href="#">Tables</a> + <li class="uk-class uk-open"></li> + <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Reorder the tables by dragging the rows to the designated area below.; pos: right">Tables</a> <div class="uk-accordion-content"> <div class="border border-secondary rounded" id="table_list" style="margin-bottom: 5px; margin-top: -10px;"> <div id="table_list_source"> <div id="show_tables1" uk-sortable="group: sortable-group" class="uk-list uk-list-collapse"> {% for table in tables %} - <div id="show_tables2" class="uk-margin" style="height: 15px; margin-bottom: -4px;"> + <div id="show_tables2" class="uk-margin" style="height: 15px; margin-bottom: -4px; width: 230px;"> <div class="list-group-item-action uk-card uk-card-default uk-card-body uk-card-small " style="height: 15px; padding-top: 5px;">{{ table }}</div> </div> {% endfor %} @@ -406,27 +415,26 @@ </ul> - <legend style="margin-top: 3px;">Target Tables:</legend> + <legend style="margin-top: 3px;" uk-tooltip="title: Activate the selected table to populate the data in the scrollable terminal section and for Step 2 procedures.; pos: right">Target Tables:</legend> <div class="border border-secondary rounded" id="table_list" > <div id="dropped_items" uk-sortable="group: sortable-group" class="uk-list uk-list-collapse "> - <!-- <div > --> {% for item in dropped_items %} - <div class="uk-margin" style="height: 15px; margin-bottom: -4px;"> + <div class="uk-margin" style="height: 15px; margin-bottom: -4px; width: 230px;"> <div class="list-group-item-action uk-card uk-card-default uk-card-body uk-card-small" style="height: 15px; padding-top: 5px;">{{ item }}</div> </div> {% endfor %} - <!-- </div> --> </div> </div> - <button id="resetButton" class="btn btn-secondary headerButton">Reset</button> + <button id="resetButton" class="btn btn-secondary headerButton" uk-tooltip="title: Empty target table.; pos: bottom">Reset</button> </div> <div id="content"> <button class="uk-button uk-button-default uk-button-small custom-push-button" type="button" uk-toggle="target: #sidebar1" onclick="toggleSidebar()"></button> <ul uk-tab class="uk-flex-center" data-uk-tab="{connect:'#my-id'}" style="margin-top: 10px; z-index: 0;"> - <li><a href="#">ERD</a></li> - <li><a href="#">Create Data Header</a></li> - <li><a href="#">Create Data instance</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display a comprehensive summary of the database contents.;pos: bottom">Step1: Overview</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Construct the data header based on the selection from the prior step, to be utilized in Step 3.;pos: bottom">Step2: Create Data Header</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Generate a detailed data table corresponding to each row of the data header, with options for additional refinement.;pos: bottom">Step3: Create Data instance</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display the generated data tables, with options for exporting to csv files.;pos: bottom">Step4: Established Data Table</a></li> </ul> <!-- <div> --> @@ -476,10 +484,10 @@ <div style="flex-grow: 1; padding-left: 10px; margin-top: 3px; display: flex; flex-direction: column;"> <fieldset> - <legend style="margin: .2em .2em .4em -.4em; color:#5ea9e2; border-bottom: 1px solid gray; padding-bottom: 5px; padding-right: 3px;">Data Header</legend> + <legend style="margin: .2em .2em .4em -.4em; color:#5ea9e2; border-bottom: 1px solid gray; padding-bottom: 5px; padding-right: 3px;" uk-tooltip="title: Define the structure of the header table to be used in Step 3; pos: right">Data Header</legend> - <div class="mb-3"> - <label class="form-label" style="margin-top: 14px">Type</label> + <div class="mb-3" style="padding-bottom: 5px; border-bottom: 1px dashed black; margin-bottom: 3px;"> + <label class="form-label" style="margin-top: 8px;" uk-tooltip="title: Select the type of data header; each type requires different information to be specified; pos: left">Type</label> <div class="uk-grid-small uk-child-width-auto uk-grid"> <label><input class="uk-radio type-radio" type="radio" name="radio1" value="measurement" checked> Measurement </label><br><br> <label><input class="uk-radio type-radio" type="radio" name="radio1" value="event"> Event </label><br><br> @@ -491,13 +499,13 @@ <div class="mb-3" id="self-defined-object-selection" style="display: none; border-bottom: 1px dashed black;"> - <label class="form-label">Self-defined Object</label><br> - <input type="text" id="defined-object" class="form-control headerSelect"> - <button class="btn btn-primary headerButton" onclick="selfdefinedObject()">add</button> + <label class="form-label" uk-tooltip="title: Create a custom object name that can be used in a header instead of using predefined database table data; pos: left">Self-defined Object</label><br> + <input type="text" id="defined-object" class="form-control headerSelect" placeholder="Enter object name"> + <button class="btn btn-primary headerButton" onclick="selfdefinedObject()">add</button><span id="selfdefinedObject" style="color: red; font-size: 80%; margin-left: 2px;"></span> </div> <div class="mb-3" id="object-column-selection" style="display: none;"> - <label class="form-label">Object name</label><br> + <label class="form-label" uk-tooltip="title: Designate a specific column from the selected table to represent unique object names; pos: left">Object name</label><br> <select id="select_column_object" class="form-select headerSelect" name="column" style="margin-right: 5px; display: inline;"> {% for column in columns %} <option>Select a table first...</option> @@ -506,14 +514,14 @@ </select><span style="color: red; font-size: 80%;">*</span> </div> - <div class="mb-3" id="self-defined-label-selection"> - <label class="form-label">Self-defined Label</label><br> - <input type="text" id="defined-label" class="form-control headerSelect"> - <button class="btn btn-primary headerButton" onclick="addLabel()">add</button> + <div class="mb-3" id="self-defined-label-selection" style="border-bottom: 1px dashed black;"> + <label class="form-label" uk-tooltip="title: Create a custom label that can be used in the label selection below instead of using predefined database table columns.; pos: left">Self-defined Label</label><br> + <input type="text" id="defined-label" class="form-control headerSelect" placeholder="Enter label name"> + <button class="btn btn-primary headerButton" onclick="addLabel()">add</button><span id="addLabel" style="color: red; font-size: 80%; margin-left: 2px;"></span> </div> <div class="mb-3" id="label-selection"> - <label class="form-label">Label</label> + <label class="form-label" uk-tooltip="title: Choose a label from the available columns in the selected database table.; pos: left">Label</label> <select id="select_column" class="form-select headerSelect" name="column" onchange='handleLabelColumnClick(value)'> {% for column in columns %} <option>Select a table first...</option> @@ -538,18 +546,20 @@ </div> <div class="mb-3" id="featuresSelection"> - <label class="form-label">Feature</label><br /> - <select id="select_features" name = "value_column[]" data-placeholder="Slect a table first..." multiple class="chosen-select" tabindex="4"> - {% for column in columns %} - <option>Column select...</option> - <option value="{{label}}">{{ label }}</option> - {% endfor %} + <label class="form-label" uk-tooltip="title: Select one or multiple feature columns from the chosen table to include in the data header.; pos: left">Feature</label><br /> + <select id="select_features" name = "value_column[]" data-placeholder="Slect a table first..." multiple class="chosen-select"> </select> - <script> $(".chosen-select").chosen({tabindex: 6}); $("#select_features").css('font-size','25px'); </script> + <script> + $(document).ready(function() { + $(".chosen-select").chosen({ + tabindex: 2 + }).css('font-size', '25px'); + }); + </script> <span id="span"></span><span style="color: red; font-size: 80%;">*</span><br> </div> - <button type="submit" class="btn btn-primary headerButton" onclick="submitDataHeader()" style="margin-top: -11px;">Submit</button> + <button type="submit" class="btn btn-primary headerButton" onclick="submitDataHeader()" style="margin-top: -11px;">Submit</button><span id="submitDataHeader" style="color: red; font-size: 80%; margin-bottom: 5px;"></span> </fieldset> </div> @@ -564,7 +574,7 @@ <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetDataHeaderTable()">Reset</button> </div> <div class="uk-overflow-auto" id="data-header-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;"> - <table class='uk-table uk-table-small uk-table-hover uk-table-divider'> + <table id="H-table" class='uk-table uk-table-small uk-table-hover uk-table-divider'> <thead> <tr> <th></th> @@ -578,22 +588,22 @@ <div class="mb-3"> <h4 style="display: inline; margin-left: .2em;">Machine Data Table (LIMIT 500)</h4> <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetMachineDataTable()">Reset</button> - <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="">Add</button> - <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="exportSelectedRowsToCSV()">Export</button> + <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="addDataTable()">Add</button> + <span id="addDataTable" style="color: red; font-size: 80%; margin-left: 2px; margin-bottom: 1px;"></span> </div> <div class="uk-overflow-auto" id="machine-data-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;" > <table id="MD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> <thead> <tr class="uk-table-middle"> <th class="uk-table-shrink"> - <input class="uk-checkbox" onclick="click_all_MD_table(this)" type="checkbox" aria-label="Checkbox" style="margin-top: 7px;"> + <input class="uk-checkbox" id="click_all" onclick="click_all_MD_table(this)" type="checkbox" aria-label="Checkbox" style="margin-top: 7px;"> </th> <th data-id="time" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> time - <select id="table_select_time" class="time-object-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> - <option>Select a data header first...</option> + <select id="table_select_time" class="observation-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option value="no">Select a data header first...</option> </select> </span> </th> @@ -601,7 +611,7 @@ <th data-id="object" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> object - <select id="table_object" class="time-object-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <select id="table_object" class="observation-select segment-select segment-data-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> <option value="no">Select a data header first...</option> <optgroup id="defined_object_values" label="self-defined object"> @@ -615,15 +625,29 @@ </th> <th data-id="type" class="uk-width-small">type</th> - <th data-id="label" class="uk-table-expand">label</th> - <th data-id="segment" class="uk-table-expand">segment</th> - <th data-id="index" class="uk-table-expand">index</th> + <th data-id="label" class="uk-width-small">label</th> + <th data-id="segment" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + segment + <select id="segment-label-select" class="segment-data-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option value="no">Select a column first...</option> + </select> + </span> + </th> + <th data-id="index" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + index + <select id="index-select" class="segment-select segment-data-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option value="no">Select a column first...</option> + </select> + </span> + </th> <th data-id="starttime" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> start time - <select id="starttime-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> - <option val="no">Select a column first...</option> + <select id="starttime-select" class="segment-select" style="width: 160px; height: 20px; margin-top: 6px; margin-left: 3px;"> + <option value="no">Select a column first...</option> </select> </span> </th> @@ -631,8 +655,8 @@ <th data-id="endtime" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> end time - <select id="endtime-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> - <option val="no">Select a column first...</option> + <select id="endtime-select" class="segment-select" style="width: 160px; height: 20px; margin-top: 6px; margin-left: 3px;"> + <option value="no">Select a column first...</option> </select> </span> </th> @@ -645,6 +669,36 @@ </div> </div> </li> + <li> + <div class="accordion-container"> + <button type="submit" class="btn btn-primary uk-button-small headerButton" onclick="resetMachineDataTable2()" style="position: absolute; right: 13vh; top:32px;">Reset</button> + <button type="submit" class="btn btn-primary uk-button-small headerButton" onclick="exportSelectedRowsToCSV()" style="position: absolute; right: 1vh; top:32px;">Export</button> + <ul uk-accordion> + <li class="uk-open"> + <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Observation Table</a> + <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;"> + <!-- <h4 style="display: inline; margin-left: .2em;">Observation Table</h4> --> + <table id="ME-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> + </table> + </div> + </li> + <li> + <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Segment Table</a> + <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;"> + <table id="S-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> + </table> + </div> + </li> + <li> + <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Segment Data Table</a> + <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;"> + <table id="SD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> + </table> + </div> + </li> + </ul> + </div> + </li> </ul> <!-- </div> --> @@ -666,17 +720,16 @@ <div id="sidebar2"> <ul uk-accordion> <li> - <a class="uk-accordion-title" href="#">Filter</a> + <a class="uk-accordion-title" style="text-decoration: none;" href="#">Filter</a> <div class="uk-accordion-content"> - <div class="accordion"> <div class="accordion-item"> <h2 class="accordion-header"> - <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo"> + <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="true" aria-controls="collapse1"> Time </button> </h2> - <div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample"> + <div id="collapse1" class="accordion-collapse collapse" data-bs-parent="#accordionExample"> <div class="accordion-body"> <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <script src="//code.jquery.com/jquery-3.6.0.min.js"></script> @@ -693,6 +746,35 @@ </div> </div> </div> + <div class="accordion-item"> + <h2 class="accordion-header"> + <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2" aria-expanded="true" aria-controls="collapse2"> + Segment Index + </button> + </h2> + <div id="collapse2" class="accordion-collapse collapse" data-bs-parent="#accordionExample"> + <div class="accordion-body"> + <p style="margin: -4px -21px 5px -17px; padding: 0px;"> + <label for="fromInput">From: </label> + <input type="number" id="fromInput" min="0" step="1" style="width: 8vh;"/><br><br> + <label for="toInput">To: </label> + <input type="number" id="toInput" min="0" step="1" style="width: 8vh; margin-left: 20px;"/> + </p> + <button type="submit" class="btn btn-primary headerButton" style="margin: 10px 0px -5px -10px;" onclick="filter_SD_data_table()">Submit</button> + <script> + var fromInput = document.getElementById('fromInput'); + var toInput = document.getElementById('toInput'); + + fromInput.addEventListener('input', function() { + toInput.min = this.value; + if (parseInt(toInput.value) < parseInt(this.value)) { + toInput.value = this.value; + } + }); + </script> + </div> + </div> + </div> </div> </div> @@ -705,6 +787,8 @@ </div> <!-----------------------------------------------------------------------------------------------------> <script> + var currentRowType = null; // Defined at a higher scope, accessible globally + var jq = jQuery.noConflict(); function initializeSlider(minDatetime, maxDatetime) { // Default values if min or max datetime is not provided @@ -770,7 +854,38 @@ } - function exportSelectedRowsToCSV() { + // Function to check if the necessary information is filled in the Machine Data table + function checkBottomTableInfo() { + // Check the values of inputs/selects in the bottom table + var objectSelected = document.getElementById('table_object').value; + var timeSelected = document.getElementById('table_select_time').value; + var segmentSelected = document.getElementById('segment-label-select').value; + var indexSelected = document.getElementById('index-select').value; + var starttimeSelected = document.getElementById('starttime-select').value; + var endtimeSelected = document.getElementById('endtime-select').value; + + if (currentRowType === 'M' || currentRowType === 'E'){ + // Check if the values are not empty or not equal to the default "no" value + if (objectSelected === 'no' || timeSelected === 'no') { + return false; // Information is not complete + } + } else if (currentRowType === 'S') { + if (objectSelected === 'no' || indexSelected === 'no' || starttimeSelected === 'no' || endtimeSelected === 'no') { + return false; // Information is not complete + } + } else if (currentRowType === 'SD') { + if (objectSelected === 'no' || segmentSelected === 'no' || indexSelected === 'no') { + return false; // Information is not complete + } + } else { + console.error('Unknown type:', currentRowType); + return false; // Information is not complete + } + return true; // Information is complete + } + + + function addDataTable() { // Collect all selected rows from the Machine Data Table var selectedRowsData = []; document.querySelectorAll('#MD-table input[type="checkbox"]:checked').forEach(function(checkbox) { @@ -781,27 +896,191 @@ }); selectedRowsData.push(rowData); }); + document.getElementById("click_all").checked = false; - // Send the selected rows data to the Flask backend - fetch('/export-to-csv', { - method: 'POST', - body: JSON.stringify(selectedRowsData), - headers: { - 'Content-Type': 'application/json' + if (currentRowType != null && selectedRowsData.length > 0) { + // Check if the necessary information is filled in the Machine Data table + if (!checkBottomTableInfo()) { + alert('Please fill in the necessary information in the Machine Data table.'); + return; } - }) - .then(response => response.blob()) // Get the file blob from the response - .then(blob => { - // Create a link element and click it to download the file - var url = window.URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = url; - a.download = 'exported_data.csv'; - document.body.appendChild(a); - a.click(); - a.remove(); - }) - .catch(error => console.error('Error:', error)); + document.getElementById("addDataTable").textContent = "Add successfully!"; + setTimeout(function() { + document.getElementById("addDataTable").textContent = ""; + }, 2000); + + // Send the selected rows data to the Flask backend + fetch('/add-data-table', { + method: 'POST', + body: JSON.stringify({'selectedRowsData': selectedRowsData, 'currentRowType': currentRowType}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + // Update the Machine Data Table with the new data + if (currentRowType == 'M' || currentRowType == 'E') { + const table = document.getElementById('ME-table'); + table.innerHTML = data['table_HTML']; + } else if (currentRowType == 'S') { + const table = document.getElementById('S-table'); + table.innerHTML = data['table_HTML']; + } else if (currentRowType == 'SD') { + const table = document.getElementById('SD-table'); + table.innerHTML = data['table_HTML']; + } + }) + + } else if (currentRowType == null) { + alert('Please select a row from the Data Header table.'); + return; + } else { + alert('Please select a row from the Machine Data table.'); + return; + } + } + + + function exportSelectedRowsToCSV() { + //////////////// Collect all selected rows from the Machine Data Table -- Observation Table///////////////// + var selectedRowsData = []; + document.querySelectorAll('#ME-table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsData.push(rowData); + }); + if (selectedRowsData.length > 0) { + // Send the selected rows data to the Flask backend + fetch('/export-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsData), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'observations.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } + //////////////// Collect all selected rows from the Machine Data Table -- Segment Table///////////////// + var selectedRowsDataS = []; + document.querySelectorAll('#S-table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsDataS.push(rowData); + }); + if (selectedRowsDataS.length > 0) { + // Send the selected rows data to the Flask backend + fetch('/export-S-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsDataS), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'segments.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } + //////////////// Collect all selected rows from the Machine Data Table -- Segment Data Table///////////////// + var selectedRowsDataSD = []; + document.querySelectorAll('#SD-table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsDataSD.push(rowData); + }); + if (selectedRowsDataSD.length > 0) { + // Send the selected rows data to the Flask backend + fetch('/export-SD-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsDataSD), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'segment_data.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } + //////////////// Collect all selected rows from the Machine Data Table -- Header Table///////////////// + var selectedRowsDataHeader = []; + document.querySelectorAll('#data-header-table table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsDataHeader.push(rowData); + }); + console.log("Header csv"); + console.log(selectedRowsDataHeader); + // Send the selected rows data to the Flask backend + if (selectedRowsDataHeader.length > 0) { + fetch('/export-Header-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsDataHeader), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'header.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } } @@ -816,6 +1095,26 @@ function resetMachineDataTable() { const table = document.getElementById('MD-table'); table.querySelector('tbody').innerHTML = "" + document.getElementById("click_all").checked = false; + table.querySelectorAll('select').forEach(function(select) { + select.value = "no"; + }); + } + + + function resetMachineDataTable2() { + const tableME = document.getElementById('ME-table'); + tableME.innerHTML = ""; + const tableS = document.getElementById('S-table'); + tableS.innerHTML = ""; + const tableSD = document.getElementById('SD-table'); + tableSD.innerHTML = ""; + fetch('/reset-machine-data-table', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) } @@ -826,6 +1125,7 @@ var value = r.innerHTML; r.style.backgroundColor = "#5ea9e2"; const type = r.querySelectorAll("td")[2].innerHTML; // type: M, E, S, SD + currentRowType = type; // Store the type in the higher scope variable var numColumns = r.querySelectorAll("td").length; const table = document.getElementById('MD-table'); @@ -849,7 +1149,7 @@ }); for (let i = 1; i <= numColumns - 4; i++) { let th = tr.appendChild(document.createElement('th')); - th.classList.add("uk-table-shrink"); + th.classList.add("uk-width-small"); th.innerHTML = "F_" + i; } } else if (type == "S") { @@ -862,10 +1162,14 @@ }); for (let i = 1; i <= numColumns - 4; i++) { let th = tr.appendChild(document.createElement('th')); - th.classList.add("uk-table-shrink"); + th.classList.add("uk-width-small"); th.innerHTML = "F_" + i; } } + document.getElementById("click_all").checked = false; + table.querySelectorAll('select').forEach(function(select) { + select.value = "no"; + }); fetch('/get-MD-info', { method: 'POST', @@ -876,12 +1180,15 @@ }) .then(response => response.json()) .then(data => { - // Set all the select elements to the default value - var selectTime = document.getElementById('table_select_time'); - selectTime.value = "no"; + // Set the select element to the default value var selectObject = document.getElementById('table_object'); selectObject.value = "no"; + // Find the option with the value "no" and update its text content + var selectOption = document.querySelector('#table_object option[value="no"]'); + if (selectOption) { + selectOption.textContent = "Select a object..."; + } ////////////////////////////////////////////////////////////////////////// if (type == "M" || type == "E") { // Update the time select with the new time columns @@ -891,6 +1198,7 @@ init.value = "no"; init.textContent = "Select a column..."; selectTime.appendChild(init); + selectTime.value = "no"; data['time'].forEach(label_value => { const optionElement = document.createElement('option'); optionElement.value = label_value; @@ -907,7 +1215,100 @@ selectObject.appendChild(optionElement); }); } else if (type == "S") { + // Update the time select with the new time columns + const selectStartTime = document.getElementById('starttime-select'); + const selectEndTime = document.getElementById('endtime-select'); + selectStartTime.innerHTML = ''; // Clear existing options + selectEndTime.innerHTML = ''; // Clear existing options + + // Create initial "Select a column..." option for startTime + let initStartTime = document.createElement('option'); + initStartTime.value = "no"; + initStartTime.textContent = "Select a column..."; + selectStartTime.appendChild(initStartTime); + + // Create initial "Select a column..." option for endTime + let initEndTime = document.createElement('option'); + initEndTime.value = "no"; + initEndTime.textContent = "Select a column..."; + selectEndTime.appendChild(initEndTime); + + // Ensure both selects are set to the "no" value + selectStartTime.value = "no"; + selectEndTime.value = "no"; + + data['time'].forEach(label_value => { + // Create option element for selectStartTime + let optionElementStart = document.createElement('option'); + optionElementStart.value = label_value; + optionElementStart.textContent = label_value; + selectStartTime.appendChild(optionElementStart); + + // Create option element for selectEndTime + let optionElementEnd = document.createElement('option'); + optionElementEnd.value = label_value; + optionElementEnd.textContent = label_value; + selectEndTime.appendChild(optionElementEnd); + }); + // Update the object select with the new object columns + const selectObject = document.getElementById('table_select_object'); + selectObject.innerHTML = ''; // Clear existing options + data['object'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectObject.appendChild(optionElement); + }); + // Update the index select with the new index columns + const selectIndex = document.getElementById('index-select'); + selectIndex.innerHTML = ''; // Clear existing options + let initIndex = document.createElement('option'); + initIndex.value = "no"; + initIndex.textContent = "Select a column..."; + selectIndex.appendChild(initIndex); + data['index'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectIndex.appendChild(optionElement); + }); } else if (type == "SD") { + // Update the index select with the new time columns + const selectIndex = document.getElementById('index-select'); + selectIndex.innerHTML = ''; // Clear existing options + const initIndex = document.createElement('option'); + initIndex.value = "no"; + initIndex.textContent = "Select a column..."; + selectIndex.appendChild(initIndex); + + data['index'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectIndex.appendChild(optionElement); + }); + // Update the object select with the new object columns + const selectSegment = document.getElementById('segment-label-select'); + selectSegment.innerHTML = ''; // Clear existing options + const initSegment = document.createElement('option'); + initSegment.value = "no"; + initSegment.textContent = "Select a segment..."; + selectSegment.appendChild(initSegment); + data['segment'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectSegment.appendChild(optionElement); + }); + // Update the object select with the new object columns + const selectObject = document.getElementById('table_select_object'); + selectObject.innerHTML = ''; // Clear existing options + data['object'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectObject.appendChild(optionElement); + }); } }) .catch(error => { @@ -916,26 +1317,269 @@ }); } - + ///////////////////////////////////////OBSERVATION HEADER ROW/////////////////////////////////// // Select only the specified select elements - const time_object_selects = document.querySelectorAll('.time-object-select'); + const time_object_selects = document.querySelectorAll('.observation-select'); - function checkSelects() { - for (let select of time_object_selects) { - if (select.value === 'no') { - return false; // If any watched select is not chosen, return false + function checkSelects(selects) { + for (let select of selects) { + console.log(select.value); + if (select.value == 'no') { + return false; // If any watched select is not chosen, return false + } } - } - return true; // If all watched selects have a value, return true + return true; // If all watched selects have a value, return true } time_object_selects.forEach(select => { - select.addEventListener('change', () => { - if (checkSelects()) { + select.addEventListener('change', () => { + if (checkSelects(time_object_selects)) { + console.log("get-ME-table"); + var $ = function(id) { return document.getElementById(id); }; + // All specified selects have a value, call the fetch function + var time_column = document.getElementById('table_select_time').value; + var object_column = document.getElementById('table_object').value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(time_column); + + fetch('/get-ME-table', { + method: 'POST', + body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + + if ('min_datetime' in data && 'max_datetime' in data) { + // Initialize the slider with the min and max datetime from the server + console.log("ME: initialize slider with min and max datetime from the server"); + initializeSlider(data['min_datetime'], data['max_datetime']); + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + }); + }); + //////////////////////////////////////////////SEGMENT HEADER ROW////////////////////////////////////////// + const segment_selects = document.querySelectorAll('.segment-select'); + + segment_selects.forEach(select => { + select.addEventListener('change', () => { + if (checkSelects(segment_selects)) { + console.log("get-S-table"); + var $ = function(id) { return document.getElementById(id); }; + // All specified selects have a value, call the fetch function + var index_column = $('index-select').value; + var starttime_column = $('starttime-select').value; + var endtime_column = $('endtime-select').value; + var object_column = $("table_object").value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(index_column); + console.log(starttime_column); + console.log(endtime_column); + + fetch('/get-S-table', { + method: 'POST', + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel , 'index_column': index_column, 'starttime_column': starttime_column, 'endtime_column': endtime_column}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + + if ('min_datetime' in data && 'max_datetime' in data) { + // Initialize the slider with the min and max datetime from the server + console.log("S: initialize slider with min and max datetime from the server"); + initializeSlider(data['min_datetime'], data['max_datetime']); + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + }); + }); + //////////////////////////////////////////////SEGMENT DATA HEADER ROW////////////////////////////////////////// + const segment_data_selects = document.querySelectorAll('.segment-data-select'); + + segment_data_selects.forEach(select => { + select.addEventListener('change', () => { + if (checkSelects(segment_data_selects)) { + console.log("get-SD-table"); + var $ = function(id) { return document.getElementById(id); }; + // All specified selects have a value, call the fetch function + var index_column = $('index-select').value; + var segment_column = $('segment-label-select').value; + var object_column = $("table_object").value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(index_column); + console.log(segment_column); + + fetch('/get-SD-table', { + method: 'POST', + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel , 'index_column': index_column, 'segment_column': segment_column}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + + if ('min_index' in data && 'max_index' in data) { + // Initialize the slider with the min and max datetime from the server + console.log("SD: initialize slider with min and max datetime from the server"); + document.getElementById('fromInput').value = min_index; + document.getElementById('toInput').value = max_index; + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + }); + }); + + + function filter_ME_data_table() { + var $ = function(id) { return document.getElementById(id); }; + // Get min and max datetime from the slider + var minDatetime = $("amount1").value; + var maxDatetime = $("amount2").value; + + // All specified selects have a value, call the fetch function + console.log(currentRowType); + var isInfoComplete = checkBottomTableInfo(); + if (!isInfoComplete) { + // Information is not complete, warn the user + alert('Please complete all the required fields in the Machine Data Table before submitting.'); + } else { + var object_column = document.getElementById('table_object').value; + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + + if (currentRowType == "M" || currentRowType == "E"){ + var time_column = document.getElementById('table_select_time').value; + + fetch('/get-ME-table', { + method: 'POST', + body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'minDatetime': minDatetime, 'maxDatetime': maxDatetime}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } else if (currentRowType == "S") { + var index_column = document.getElementById('index-select').value; + var starttime_column = document.getElementById('starttime-select').value; + var endtime_column = document.getElementById('endtime-select').value; + + fetch('/get-S-table', { + method: 'POST', + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'index_column': index_column, 'starttime_column': starttime_column, 'endtime_column': endtime_column, 'minDatetime': minDatetime, 'maxDatetime': maxDatetime}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } else if (currentRowType == "SD") { + alert('This feature is not available for the Segment Data type.'); + } + } + } + + + function filter_SD_data_table() { + var index_from = document.getElementById('fromInput').value; + var index_to = document.getElementById('toInput').value; + if (currentRowType != "SD") { + alert('This feature is only available for the Segment Data type.'); + return; + } else if (index_from == "" || index_to == "") { + alert('Please fill in the index range.'); + return; + } else if (parseInt(index_to) < parseInt(index_from)) { + index_to = index_from; + } + + if (checkSelects(segment_data_selects)) { + console.log("get-SD-table"); var $ = function(id) { return document.getElementById(id); }; // All specified selects have a value, call the fetch function - var time_column = document.getElementById('table_select_time').value; - var object_column = document.getElementById('table_object').value; + var index_column = $('index-select').value; + var segment_column = $('segment-label-select').value; + var object_column = $("table_object").value; var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup @@ -944,13 +1588,10 @@ } else if (optgroupLabel == "object column") { optgroupLabel = "col"; } - console.log(optgroupLabel); - console.log(object_column); - console.log(time_column); - fetch('/get-ME-table', { + fetch('/get-SD-table', { method: 'POST', - body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel }), + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel , 'index_column': index_column, 'segment_column': segment_column, 'index_from': index_from, 'index_to': index_to}), headers: { 'Content-Type': 'application/json' } @@ -961,70 +1602,47 @@ const table = document.getElementById('MD-table'); table.querySelector('tbody').innerHTML = "" table.querySelector('tbody').innerHTML = data['table_HTML']; - - if ('min_datetime' in data && 'max_datetime' in data) { - // Initialize the slider with the min and max datetime from the server - console.log("initialize slider with min and max datetime from the server"); - initializeSlider(data['min_datetime'], data['max_datetime']); - } }) .catch(error => { // Handle any error that occurred during the fetch console.error('Error:', error); }); + } else { + alert('Please complete all the required fields in the Machine Data Table before submitting.'); } - }); - }); - - function filter_ME_data_table() { - var $ = function(id) { return document.getElementById(id); }; - // Get min and max datetime from the slider - var minDatetime = $("amount1").value; - var maxDatetime = $("amount2").value; - - // All specified selects have a value, call the fetch function - var time_column = document.getElementById('table_select_time').value; - var object_column = document.getElementById('table_object').value; - - var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option - var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup - if (optgroupLabel == "self-defined object") { - optgroupLabel = "self"; - } else if (optgroupLabel == "object column") { - optgroupLabel = "col"; - } - console.log(optgroupLabel); - console.log(object_column); - console.log(time_column); - - fetch('/get-ME-table', { - method: 'POST', - body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'minDatetime': minDatetime, 'maxDatetime': maxDatetime}), - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => response.json()) - .then(data => { - console.log(data['table_HTML']); - const table = document.getElementById('MD-table'); - table.querySelector('tbody').innerHTML = "" - table.querySelector('tbody').innerHTML = data['table_HTML']; - }) - .catch(error => { - // Handle any error that occurred during the fetch - console.error('Error:', error); - }); } function selfdefinedObject() { var object = document.getElementById('defined-object').value; var select = document.getElementById('defined_object_values'); - var opt = document.createElement('option'); - opt.value = object; - opt.innerHTML = object; - select.appendChild(opt); + // Check if the option already exists + var optionExists = Array.from(select.getElementsByTagName('option')).some(option => option.value === object); + console.log("optionExists"); + console.log(optionExists); + if (object == "" || object == null || object == " ") { + document.getElementById("selfdefinedObject").textContent = "This field is required!"; + setTimeout(function() { + document.getElementById("selfdefinedObject").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + }else if (!optionExists) { + var opt = document.createElement('option'); + opt.value = object; + opt.innerHTML = object; + select.appendChild(opt); + document.getElementById("selfdefinedObject").textContent = "Submit successfully!"; + setTimeout(function() { + document.getElementById("selfdefinedObject").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + } else { + document.getElementById("selfdefinedObject").textContent = "This object already exists!"; + setTimeout(function() { + document.getElementById("selfdefinedObject").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + } + // Clear the input field + document.getElementById('defined-object').value = ""; + } @@ -1036,10 +1654,9 @@ // var divv = document.getElementById("data-header-table"); // divv.getElementsByTagName("table")[0].deleteRow(i); - var value = r.parentNode.parentNode.innerHTML - - console.log(value); - console.log("YYYYY"); + var value = r.parentNode.parentNode.innerHTML + console.log("heyheyyouyou"); + fetch('/delete-data-header', { method: 'POST', body: JSON.stringify({ 'value': value }), @@ -1047,7 +1664,12 @@ 'Content-Type': 'application/json' } }) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) .then(data => { const table = document.getElementById('data-header-table'); table.innerHTML = data['data_header_table']; @@ -1060,6 +1682,14 @@ } + function deleteRow2(event, r) { + event.stopPropagation(); // Stop the event from bubbling up to the <tr> + var i = r.parentNode.parentNode.rowIndex; + var table = r.closest("table"); // Find the closest table ancestor + table.deleteRow(i); + } + + function resetDataHeaderTable() { fetch('/reset-data-header-table', { method: 'POST', @@ -1101,14 +1731,13 @@ function submitDataHeader() { - var $ = function(id) { return document.getElementById(id); }; var isValid = true; var radioSelect = document.querySelector('input[name="radio1"]:checked'); - var label_column = $("select_column").value; - var label = $("label_values").value; - var selectedOption = $("label_values").options[$("label_values").selectedIndex]; // Get the selected option + var label_column = document.getElementById("select_column").value; + var label = document.getElementById("label_values").value; + var selectedOption = document.getElementById("label_values").options[document.getElementById("label_values").selectedIndex]; // Get the selected option var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup if (optgroupLabel == "label column data") { optgroupLabel = "col"; @@ -1117,84 +1746,84 @@ } var label_list = ' [" ' + optgroupLabel + ' "," ' + label_column + ' "," ' + label + ' "] '; - var object_column = $("select_column_object").value; + var object_column = document.getElementById("select_column_object").value; - var tmp = $("select_features").selectedOptions; + var tmp = document.getElementById("select_features").selectedOptions; var features_list = Array.from(tmp).map(({ value }) => value); if (radioSelect.value == 'measurement') { if (label == 'Label select...' || label == null || label == "") { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; - // alert("Please select a feature"); - console.log("Feature is required"); - } + document.getElementById("span").nextElementSibling.textContent = "This field is required."; + // alert("Please select a feature"); + console.log("Feature is required"); + } return; } if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; + document.getElementById("span").nextElementSibling.textContent = "This field is required."; // alert("Please select a feature"); console.log("Feature is required"); return; } } else if (radioSelect.value == 'event') { if (label == 'Label select...' || label == null || label == "") { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; - // alert("Please select a feature"); - console.log("Feature is required"); - } + document.getElementById("span").nextElementSibling.textContent = "This field is required."; + // alert("Please select a feature"); + console.log("Feature is required"); + } return; } if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; + document.getElementById("span").nextElementSibling.textContent = "This field is required."; // alert("Please select a feature"); console.log("Feature is required"); return; } } else if (radioSelect.value == 'segment') { if (label == 'Label select...' || label == null || label == '') { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); return; } } else if (radioSelect.value == 'segmentData') { if (label == 'Label select...' || label == null || label == '') { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; - // alert("Please select a feature"); - console.log("Feature is required"); - } + document.getElementById("span").nextElementSibling.textContent = "This field is required."; + // alert("Please select a feature"); + console.log("Feature is required"); + } return; } if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; + document.getElementById("span").nextElementSibling.textContent = "This field is required."; // alert("Please select a feature"); console.log("Feature is required"); return; } } else if (radioSelect.value == 'object') { if (object_column == 'Select a table first...' || object_column == null || object_column == '') { - $("select_column_object").nextElementSibling.textContent = "This field is required."; + document.getElementById("select_column_object").nextElementSibling.textContent = "This field is required."; // alert("Please select an object column"); console.log("Object column is required"); return; } } - $("label_values").nextElementSibling.textContent = "*"; - $("span").nextElementSibling.textContent = "*"; - $("select_column_object").nextElementSibling.textContent = "*"; + document.getElementById("label_values").nextElementSibling.textContent = "*"; + document.getElementById("span").nextElementSibling.textContent = "*"; + document.getElementById("select_column_object").nextElementSibling.textContent = "*"; @@ -1208,10 +1837,20 @@ }) .then(response => response.json()) .then(data => { - if (radioSelect.value != 'object') { - const table = document.getElementById('data-header-table'); - table.innerHTML = data['data_header_table']; - } + const table = document.getElementById('data-header-table'); + table.innerHTML = data['data_header_table']; + + // Set the select elements to their default options + document.getElementById("label_values").selectedIndex = 0; + // Reset the Chosen multi-select + $("#select_features").val([]).trigger("chosen:updated"); + document.getElementById("select_column_object").selectedIndex = 0; + + // Show the success message + document.getElementById("submitDataHeader").textContent = "Submit successfully!"; + setTimeout(function() { + document.getElementById("submitDataHeader").textContent = ""; + }, 2500); // 5000 milliseconds = 5 seconds }) .catch(error => { // Handle any error that occurred during the fetch @@ -1224,6 +1863,14 @@ function addLabel() { const labelValue = document.getElementById('defined-label').value; // Get the value of the input + if (labelValue == "" || labelValue == null || labelValue == " ") { + document.getElementById("addLabel").textContent = "This field is required!"; + setTimeout(function() { + document.getElementById("addLabel").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + return; + } + // Send the value to the server with fetch API fetch('/add-self-defined-label', { method: 'POST', @@ -1242,8 +1889,13 @@ optionElement.textContent = label_value; selectElement1.appendChild(optionElement); // Append to selectElement1 }); - // Handle response data from the server - console.log(data); + + // Show the success message + document.getElementById("addLabel").textContent = "Add successfully!"; + setTimeout(function() { + document.getElementById("addLabel").textContent = ""; + }, 2500); // 5000 milliseconds = 5 seconds + }) .catch(error => { // Handle any error that occurred during the fetch @@ -1362,6 +2014,16 @@ // Function to handle the click on a dropped item function handleDroppedItemClick(itemName) { + // Change the color of the selected item + document.querySelectorAll('.list-group-item-action').forEach(item => { + if (item.textContent === itemName) { + item.style.backgroundColor = '#5ea9e2'; + } else { + item.style.backgroundColor = 'white'; + } + }); + document.getElementById('select_label_values').innerHTML = ''; + // Send a POST request to Flask backend with the item name fetch('/get-table-data', { method: 'POST', @@ -1380,7 +2042,7 @@ selectElement0.innerHTML = ''; const optionElement0 = document.createElement('option'); optionElement0.value = ''; - optionElement0.textContent = 'Column select...'; + optionElement0.textContent = 'Select column...'; selectElement0.appendChild(optionElement0); // Add the table columns as options data.table_columns.forEach(column => { @@ -1391,18 +2053,18 @@ }); // Assign data.table_columns to the select element - const selectElement = document.getElementById('select_column'); - selectElement.innerHTML = ''; + const selectColumn = document.getElementById('select_column'); + selectColumn.innerHTML = ''; const optionElement = document.createElement('option'); optionElement.value = ''; - optionElement.textContent = 'Column select...'; - selectElement.appendChild(optionElement); + optionElement.textContent = 'Select column...'; + selectColumn.appendChild(optionElement); // Add the table columns as options data.table_columns.forEach(column => { const optionElement = document.createElement('option'); optionElement.value = column; optionElement.textContent = column; - selectElement.appendChild(optionElement); + selectColumn.appendChild(optionElement); }); // Assign data.table_columns to the multi-select element @@ -1419,8 +2081,6 @@ // Trigger Chosen to update its view $(".chosen-select").trigger("chosen:updated"); - - // toggleTerminal(); // Show the terminal }) .catch(error => console.error('Error:', error)); } @@ -1495,6 +2155,7 @@ } } + document.getElementById('resetButton').addEventListener('click', function() { // event.preventDefault(); // Prevent the default form submission console.log('Reset button clicked'); @@ -1512,7 +2173,7 @@ for (let i = 0; i < data.tables.length; i++) { var item1 = document.createElement('div'); item1.className = "list-group-item-action uk-card uk-card-default uk-card-body uk-card-small"; - item1.style = "height: 15px; padding-top: 5px;"; + item1.style = "height: 15px; padding-top: 5px; width: 230px;"; item1.textContent = data.tables[i]; var item2 = document.createElement('div'); item2.className = "uk-margin"; @@ -1569,6 +2230,9 @@ document.addEventListener('DOMContentLoaded', function() { + if ('{{message}}') { + UIkit.notification('{{message}}', {status: 'success'}); + } // Show the ERD canvas showErdCanvas("erdCanvas1", "zoomInBtn1", "zoomOutBtn1", '{{ image1 }}'); showErdCanvas("erdCanvas2", "zoomInBtn2", "zoomOutBtn2", '{{ image2 }}'); @@ -1692,20 +2356,47 @@ }); + document.addEventListener('DOMContentLoaded', function() { + // Get the select elements + var labelColumn = document.getElementById('select_column'); + var labelValuesSelect = document.getElementById('label_values'); + var featureSelect = document.getElementById('select_features'); // Assume you have a select element with this ID for features + + // Listen for changes on the label select + labelValuesSelect.addEventListener('change', function() { + // Check if the selected option is from 'label column data' + var selectedOption = this.options[this.selectedIndex]; + var isLabelColumnData = selectedOption.parentNode.id === 'select_label_values'; + + // Apply constraints to the feature select based on the above condition + if(isLabelColumnData) { + const droppedItemsDiv = document.querySelector('#dropped_items'); // Get the outer div + + fetch('/constraint-feature-columns', { + method: 'POST', + body: JSON.stringify({ 'label_column': labelColumn.value, 'label': selectedOption.value}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data['feature_columns']) { + // Update the feature select element + featureSelect.innerHTML = ''; // Clear existing options + data.feature_columns.forEach(column => { + const optionElement = document.createElement('option'); + optionElement.value = column; + optionElement.textContent = column; + featureSelect.appendChild(optionElement); + }); + // Trigger Chosen to update its view + $(".chosen-select").trigger("chosen:updated"); + } + }) + } + }); + }); </script> </body> -</html> - - -<!-- For the pop up windows, I used the following code: --> - <!-- <div id="dropped_items"> - <span onclick="UIkit.modal('#my-modal').show()">Click me</span> - </div> - - <div id="my-modal" uk-modal> - <div class="uk-modal-dialog uk-modal-body"> - <h2 class="uk-modal-title">Terminal</h2> - <p>Here is the content of your "terminal".</p> - <button class="uk-modal-close" type="button">Close</button> - </div> - </div> --> \ No newline at end of file +</html> \ No newline at end of file -- GitLab