From bda0505ed6cd71e7f7339bb460b676857aa1fbb3 Mon Sep 17 00:00:00 2001 From: "RZ_MINIX\\rober" Date: Wed, 5 Nov 2025 14:13:27 -0800 Subject: [PATCH] Added support for 80 smell signatures --- .env | 2 +- 24_Robert_Zmrzli.jpg | Bin 0 -> 264902 bytes 40_Robert_Zmrzli.jpg | Bin 0 -> 359196 bytes 50_Robert_Zmrzli.jpg | Bin 0 -> 359202 bytes Dockerfile_orig | 43 + NEW.jpg | Bin 0 -> 359202 bytes after_filter_data.csv | 17281 +++++++++ before_filter_data.csv | 17281 +++++++++ before_z_data.csv | 43201 +++++++++++++++++++++ beneficiary.jpg | Bin 0 -> 347419 bytes longpresence_after_filter_data.csv | 43201 +++++++++++++++++++++ longpresence_before_filter_data.csv | 43201 +++++++++++++++++++++ longpresence_initial_data.csv | 43201 +++++++++++++++++++++ presence_after_filter_data.csv | 8641 +++++ presence_before_filter_data.csv | 8641 +++++ raw_decoded.jpg | Bin 0 -> 214188 bytes stack.yml | 2 +- temp_beneficiary.jpg | Bin 0 -> 256018 bytes well-api.py | 2335 +- well-api41-working-webhook-AIStudio.py | 21295 ++++++++++ well-api42-not-working-webhook-claude.py | 21410 ++++++++++ well-api_20250825.py | 21270 ++++++++++ well_web_files/deployment.html | 301 +- 23 files changed, 290920 insertions(+), 386 deletions(-) create mode 100644 24_Robert_Zmrzli.jpg create mode 100644 40_Robert_Zmrzli.jpg create mode 100644 50_Robert_Zmrzli.jpg create mode 100644 Dockerfile_orig create mode 100644 NEW.jpg create mode 100644 after_filter_data.csv create mode 100644 before_filter_data.csv create mode 100644 before_z_data.csv create mode 100644 beneficiary.jpg create mode 100644 longpresence_after_filter_data.csv create mode 100644 longpresence_before_filter_data.csv create mode 100644 longpresence_initial_data.csv create mode 100644 presence_after_filter_data.csv create mode 100644 presence_before_filter_data.csv create mode 100644 raw_decoded.jpg create mode 100644 temp_beneficiary.jpg create mode 100644 well-api41-working-webhook-AIStudio.py create mode 100644 well-api42-not-working-webhook-claude.py create mode 100644 well-api_20250825.py diff --git a/.env b/.env index 55e47f2..dcf1ea7 100644 --- a/.env +++ b/.env @@ -9,7 +9,7 @@ MINIO_HOST=192.168.68.70 MINIO_PORT=9000 DAILY_MAPS_BUCKET_NAME=daily-maps JWT_SECRET=Well_202502110501 -OPENAI_API_KEY=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 +OPENAI_API_KEY=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 OPENAI_API_MODEL_ENGINE=gpt-3.5-turbo REDIS_PORT="6379" MQTT_USER=well_user diff --git a/24_Robert_Zmrzli.jpg b/24_Robert_Zmrzli.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4aad921af6fa1e136e8786887d440261ae92dc8 GIT binary patch literal 264902 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
    50Vab!?r2#XM|m=`o`jbzG_fl?1>k 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�J#L< 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 literal 0 HcmV?d00001 diff --git a/40_Robert_Zmrzli.jpg b/40_Robert_Zmrzli.jpg new file mode 100644 index 0000000000000000000000000000000000000000..76f6fa5327b41f04c227d1d90bd409186488a0a7 GIT binary patch literal 359196 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%0Aw$=S^~WMMzMxchEloNOQj_%*wI3)dk1TFu%n$;< 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

    ;kefwwf}6rJy~;BZi_uWQ@7!dm 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
    a(!N+QdsqCfQjgnW_IN($>aT3-5PQ-qA_o&Xz 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 z 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
    a(!N+QdsqCfQjgnW_IN($>aT3-5PQ-qA_o&Xz zjMCJUy}SdAr9DkZsgtERx3?&%T=mBl1#Sg!07xDC)EPH7UvmVO+Z_@A06Ge;4Wu8K zsN=AqBB?f7AQGGo26NJbK2vO zx&bIrK*l!;qm?lvl$8~Fl6urjh18#vD}mTmBlEYDg?*}2Y$&sra0>ZSezjL2^*P3P z9nV2fW!w`T1xi*i)~YU%ZdZ3<#$Wn8SqTm|Z*5>pn1V_|@tmQoq=Kw-gIjRv-bcZU zQL2swY^~H<@AZ4Evut5=oORI2alT1W$lBU}eObo>p;$aVxa#d_AjyKO?e%I7BTt79 zkrg@q`g5az2M5a@!nB%qPRzPYxeGPQ-)wf?klo{1OFJ#rHcFC}#)e2CZ;-B4k5il!Q*v0 z`R09=Hl#N>7kK)9?9p5{{u=w@T$F6K+gb@|`4-h7SSta|pm1}@>J%#F*sR)y#?xts zKFOk`yS0J8ZlKmKlCp(3-~$X5Ih-hCo-xKXFI#O(of69JwjMYg{t-DTQA z5@$(5w!Yy@DN>TNle+|bz}636>(xH=f9%|wmCAhQeiC-J0LPIq6@+{}tsJ2#3P?$F z1F5Zv9H%RMmnqYY-6B5$=Ph>GHs`{GXxK%IOg2`$g#r6F<)1KcdE9fJDuY{vI$6_} z*wZ1}XJN@L$XbzgzASAa`6?s!QAXupWOV7s#%>kT>!hv?=_odsjXP;*NR-_7Qka(4 z3uz*B*IOkIyr2_=W2ZsVqU)_mX@?DOTxZEmf_W;HHcG>HZBXYRA(Z7Mn4{(EfsLaa z)X=@M#>*-_;&kaY*#gO>+GIz(>bPuqVp1Dhc!6yRSRp6p3R+TfK|X@Bn%h~UP+TC` z^mGQ(ZM3veY=V~eMuN2@kBW3CA36SV3bTXHT$=NL?M|-oP1Gr-e^x?kEJKA5DKTx$ zfDoX{Nh5NCMpk>*q-ebvc+vKTkclj|fO1x+TT;}2#h&z~k&h)-nehAdea+dIUD9Q` zvf`HaR~C6IQTq0;Oa9MWZ&o!6^M))Z94xl;7V|J*kdh9^5>v_Kj+7^(PPy8geNH_4 zpexB`i0!&wS8OB^k?r29r)WJVeoepim+HG^(Z4lu;oO>$W0;bo=4~zjrqr^m;GUW5 ziVHe5cFzyAw5ZFp+pkRCU-N%wZ*x%2wMavX`3A-wB?Vi6z&%boVzQS%3d0V#?x36? z%Ubs}8&F#WN6*W^O!k3F)JjR`6|A~kH3o%-&AIF2GW6`^DaNCCjT7SHd8cVtmpz_v zIop%##(Aw?nuLuN5#Kf)*FjnxNn1`gC-wfAl7RA0&%s+BXpg>3UzWnAr1 zW!hz2q^dXw0?V%%GGI(dS{4@`;Mu|oK>0~H&sxyE5Zo;W(ENx`NNvFz`!%cID7Z(! zS^og5r=??hbBqEA!bd#wDORVn#Cz&^w(3^WTu97vqLOe| zkOtg#8RbEC+T)hkaqSk1M3;Q_Y+8xcO12?He8w!4jm_>P@Nti71twiJRK(sb_Kis# z+~>tuTo2ca_*;}=*)2^N4gsDs)}Jp<*=`d=o5PnzT*g~P;viqm=q54!Pv=XsP+8(c zjW)@8k8M+<3WD1=>uLzflDq<4LVjMyJv-GE_G%@%`LW(@wpl8Wr=Mooagl6uaXXYu zX#k@npTZ6?Q(@VtwaUFg^HAIygVw%<22)p7R-aIX{Nk%-*>t_uJo&Nw$dlu`l%ghQ z+SUd}>;#R60F&>GRV;lubGZ+Tlc>eTEse^LKGMX69(y>#(th+ijA>8p2=k*xK_u*m z=m*;ceLnRABt6-X0^4n;rAcvMWJ^@pLmeEr$`eLB^|FAnavl5cu;XnOAymkQr;4lT3D-Gq>_-=BKH+ugiJ zL+!?D9Q7^KxTQFkT1pdul>m3B4w=WTNV!My91`79?Q&3_WJPg9Zhyv{5kOJtEI+92 z_K5Fl=Auk)SWq%xEVz`Y6Sz1C3PH#{N8YH?z)IEb^iNt)|Xo!anJIgS|nZW5@On0 zhi1AK!d2!eV*zI*6S)cs2Rx2B6`zl(WU>_9!G_&!XK^k!o_$B9708m~gsGy3)H9aW zIr~*z%J}?qB%&OK3z+#zN|2B`b^f#vOt`-ed~jjQafO@^0(c*tXXaZj^Vu=tT#(DB z{c@B40If@e+!t6=?%f@2^$r?LWRQOQhCeD*+{{!ZC~was?}`Q9(I61G>?vze zNK!@z80(zWjzbbuqmptv3}YY8qR-Zm?dc7~x6P3>9C?AT5Iw=)pXEn($9WEHW)qH( zM&g0HAC*E*kebER$$1YlPbq24qd#Lp1Eb!q7Xpp#hRVMZR-?L=aT2$@#UGdqVAX@# zZs8>*vKxp`M+An{e;{gXEhLCA)$w;0omXATP|W~AvcA=_KyXE6)gzJS1Au967bjLu z65D%;ASEP${OU@Xik6lUAu0f1DFdOXZpB>fjU_>`%2d4IoX`l&-dat?wYKYpWc5-; zFlvo9>wG4{e6{1LBRS1&r|D1Czm352n9k&{c0Vela@YYSr4AkDw2i`!l%Pk7%PAd0XahAO-pULEfTS~=Bzn|oQXhcw9B0eeo>jjm1L!Cn zDknyUzcmALQav`*wZ>A_gpdg1CadmRiJ=j-$6A*9_&Zu44}NO5<7E`>D_e?RIa$Ra zCWcFBwx@IV2<@QtCCci`+-y(C)9tjGgYP z+@yz4J<@*{+O*B-HztM1f@2hx31M+oZ;nLW9&zH`sfhLiGTT4m8FUbpE97)>%3McKpJk`99$U2V1ojsid{~)s5-eqJ{o6eX@A3LDTbQP0{SM@o8})g0|CuO4wgJN%aRmN@(;$ zGxrIHOKKM-OKo#)&=OGeaJ+e}I0g=X!bmyxpux0{{_4LfnJv~{ zZcA?hu^4Ppm;SYtd-ugNOhtWSKvE7PA1$YGkX!xUYSfC$OXH8e(j9bd03C==aamcd zEkKlvoM#C<;ET-TGT%YE+-c)vy1~JnWgqHkH2YVE*NsngVjNc+TAfxBfDg;=Phcs1w>QBq zR7TN}9+P!q0=$yTu4PV>^J5$iwd{{hwCM;5hOn41oKOen+NT^n22TX~3fow`C()V@ zO*L(L)e`q|bLN+FxJEbI z#{+@IZu@5#@@84nl^_qow_heYBEH#K0VJ)Gg>LJ$e)#C2Sb6PBuui)!|UGlgYo+}Xx6{{XEqdyCOsx8=4<4 zk+%aJbgx5)qn*7%YU4K4;?cs=7D`XC#~zgtgOef3D50@w<=qv^MiY;jM*yF_TE7g& z+TWOxv>YV?T@SSvQfg>cTQO+}dMn{U2?{_Br>;p>ew-2BvNqRc$qAD4g?rV6EktDV z(2V~8I!Q&v@-mWr6YduUHs;4tu(YdbIPd6x=UK~?^fkIe$H{MY(h2A5?rTLo3o(nz zXu6=UnK|d_L%UjR*DXGWlL#{ZivDKk+bI?pIdc?8n|G8@V$0FBA?Gza1cGF*_z z8y&_|*JCR5y>X1*wf*+%k4Pxc~{{lDLAX>?8s;|<4I2n2lkx^pqegTZK*G*Au3Br#~98& z^cfbmR5m9+F~LX~2BfO%IWQb%G8+nUdyfawpKW48Or*(I?QzNVppY%woMogWAt(fJ zNY518gv({Eq^d>5mXvyjK3cMP?oZ+;915n7rft4qT-FmkK;(QvLhyd|732jjw(>%o zP$*E!N_+d#grs2(EALL26CC`BLutp#gp2?yKR)i5vQn7|TTK!_YmPBWF|UhhSC$+? z!9ngjRQSvlg>CtQPc@p2aDyobObN-7dXSa!KBF~rWm~8~TbT+_{3fkMY_KaWHn0v9 zp|=>I&v~mv7C>;V^Nq?U@Q==w+Rp3kZyyocUNhB?`kP>}*XY#8&3X3bnwGkvVmYY*;7Qu1d!2JGzV0%}Y>f5QfMlGS0HtTK# zd5ah-NLM80wf7sVHq@mS$tgFmI)JRbJ*23P>Qh^eBRfGVBbwaVf*fQL zaz`B3nacS+jtJCIVGV)+>~q$%Q4~~w2pkTgtD`mu0Dg6A5u#KEFX}1*5_n{{h=N5ZAm)E4Jz3SK&dP*N29DOP$X^@`Rj$xL$Q4+^Qq~CUJ5_@V-Uqb|)QOCsDLgC!PB1FY z1R)u$my4=A@_JQkG0Eh2?^Y4oEu>_XdzwZuAxay$p}di|Ak$uwmOx48K7x^4qa%;4 zRyrdcp+|7d4ap7U;EZ}ymeShtRIUXD-+&4#BWI@G=8=v@abZJ_Dl?0DN(p7KvQMu{ zdf!MPM2vb>b;|xBqjLySluiObs7l5UB7BI5P(z+tyWn$Kn}xyI4ie(jv+ikR+@5tl zWBe@hw05eMsZK1CYfM!lss$;s;5k2PjS?tv&4>h%)cRE4hH>ICkdFLSlskAr1_ebfU}O?zO`lW+r}~m z2WmpL(GzAy(0=sfvy6oOO+D0z=LC$^9m`S}b6bXn6yj6~Q=p6|+JSc#;omg*XzF<5 zsjBO;+qbqn_YjpHiO)4w{po2~@)oAlIovouYR*pcpOPM0+;u~`xuq4d?b%Cxy{UAe zqtqv_f2;+2x6l23C`-*;*JR7K^nl6gHUvUGy1 z0BTSTdC-zcRaeTEKs!&~hV%U3nyS2Bp;(}3iG98 z@&N8htkvgHv2P_T{{W=Er35yH!BJAX00&c5b8fe-R<>G&1?bJl$&C+g*PTcEMd$D% zzdgCBmJ35R(IR^@f>9iXCTc`7U2ZPr1o<$~GF){;<9;}<#tjpE$n*NIQ;a_t32tg| zyu^nq$dP`T=D5|dPa|YRx3KS(g?xa8j1K&cwIWUKMW;>OBwsZeW+LE(u>9nqA0IK0 zqMw9-g#dORGm<#((zGbk)<&hojdQU)(=uN&EjlVQ=yvCDSWAv3MaZb-E&PD(&jg%; z1!N&eo}FWIafxvM0JP9b5p>imZ9l4XqXB72j}?b9mmIKa=X$HPj|zgL$>-F| zD{jlnX^v*bgLbvO4&4<9c1Jta#zq14#YwsA^y$~rZnr?Re`{l`5o}9BHG@edNDe8-5BG1s^OKb1 zZVLVolYJPhGq#IdBn>>|XMDF(nZMchT`f$#IG$B9;_>0Ohs?c%f!EMhU`Uqr<56Ao z=AdDVWxb&emOlG4JNOm(uf$+KwJaJQY{f2ZA6k}#vff;j3Dk&VW( zmOV3g@gm}NF0P*{;P5IzkulPupj=T6vnVq>?tIkHir^VUv4<1WwAzP z95{C7L`k}y&u2<^dglcw$%fa^+>zG9!`~5sS+O(xKQPSm*0k z7TcYsRl&F3e^WZr(o`ffdAG?t@r|LNLC6P@RoaTrr>-)elQ>TE|bc>_sU{ z^2k(}X$1-=A2B>}%^>a37WB95R^c00S#9?9###$ZUoCDWouK5D$zYsd4m!}R$~LQX ziMF?=SW2F4EAwO``(j%mWMxE!EuH&-&jb!S*P!WZMC&XDq@{AKmZuxq<lB7 zVM>alI7-lgw2t^ZdK#;_X|^=ppE{#XTU=bm!It)>(9R*&7SYFU0#D zso79MQ8&+cr+4eKAcZ7ygr($m3K<_t$WPFh#$=Q=swHXn8QZZ@;Y(;NV1;C*YU*%y zfyciTv2>7!<*wnWSh}xzmxU%mRup(rziQazjjZtiv;#C!>krcJz0p}P( zinER}k9xYb_^oKOA@=TwVPkm;T!v%$V;mePr18Zcv?3DD=22)hp6 z2}vC(j(B+dcXu`(d8Hr25;LxUMDa(NE;WuP!ubxgu8Q2+f&OBFhp>jWskH(Ej!KhP z+J4z=GLpN4%LwOULup!%u>^usk;$yW8M(O_%7Gd*me4mT3Y6MbMn^=I0(#WdTs}Fb zoLdEl0NS*WMhN17A>LWh%~eek3vY=kv2+wSj(t(W| zQ36%eU=Eq$q$+bs-j%%5TTT?G6cPdYlTs~u0z&0&T#0fQ_={;OB_MOy)pgfQTU8<0 z_e-0IKse+&R7cr0J%E4hC896|*^NB3p0S-mkN*HFnKx2eA8ijQ_X$_Zme-8`09wu` zMd=7{6B<;d{{X+dgZYY_(-5ILgtUi!0*F06w7?hQBeGN)O2X84V=7jEGsRTBaP#gC zGV>+2(a)GcApZb5w2?i`t*$J0aq31N)c*#*XrC5&A zmzDXb9SRjylWcJswwBnr1v*oL5^{e!g>PiQj+Wea&tFoqD4;C~ZAv>v(0dAjDk{pz z!cGrxN0%{bSla8qgjc;3tT1W~B zCmi>y*zlQJf|k&54{l9b!@Djk8_W<|ZzszjCz14^lO~HCm*WJ=!X4^B;8axFoQ?AH z`PPo-sNB?&LL}xo)h%NbzL;H zp$YAe-p@+vC@hP1wxI-{DJeh5ie-}e{-0BXIhf~_wt63N=xb}L^y(WdtfjvuLU*wy zIR%LW)dX}tsjLO>O-*&T6B%Q0ABA1(N^TP1#^9unWwGi=x~j_2Qo($r2HqncVEz?) z5$jS~gv;sJ@|1**0{fu*k4o`fDX3)FA4_u9N_cF?Si)TAvFv>fdo`{2mxv5K3Ro*q z!)sXY{>@{t{{YgvI(+I%naj(EZzz=#+E_z@3k5yfS3~ZTTx?cHvZY(C4qQ0e>x(VQ z9e)b5Q=!IvxTU8^bxBeEpG6?lYn&5vVJ*0p9a;`j%$9&qKFY3^#WhAq$;F;cIM(XyB{)rGhPMYgr?y`lPrAiGWuPO@7YQGjeSk7pdsBMGAqb+ zw@XV(*mXbM{SSKi8^S<@-xz{}xhPttHVME{Nf}4ncCRz7#G#jixu?mx9G_4nri_*( zAa7#!$Q|*nLdSpdwi0p!-n=8ZF`EN zcKBm!1|fVg4LCgZ6_1r;_uD?>x7xGCaA;*!=0tccNp;Vi3^u2E8S@z+?jK=VJrQqx zI$4+OMaz>Qg|)&|l_#i4B!T&wT}z}9HTY)JHMX8~hv2QxnGUCL4zj;OIpVzyi(^`P z$sX5ih}keEk7NH#t+W45Py-Y=cT3DOmo|# z!y42LO1U*?gBj`9XNW^hG>}4;e|N5Z`R!jyXx$h*FVpC5LybSErGTjmUf_oF`+->K zD=%m;tu2|TWw`ejkffm-N{eVc_~c}L>pLYAEpNRtA&`X=yK5_3ttk2_3Os$OG4007 zXBRl0xhC>thhn@Epa4=(@ySmgy0O>kR7+`-B`%hb@>BSbvBJOPed`lu)FU)WuWE>oWo)6x)DMm&ejwEZIh=X!@_uweQTbRplVCUHORe6@C z@c#f&TW&Fhs@f%~!-o{o015Z6V{%k>V*>lAZ8+!5BB`|phz(ki5vy&+BYta%XuUEs zt}+y&0zq02$=C5Kfr`mEM$Iv9sBee<7{SvhYU8L>+{>%KJ243aHU>KqGHXLKQ8#gr zvfr2+Y~f8QvfIcbzBdp@(z2=6j#{oUlYeP8`7)5U!~S|+x4x1=8y)@A$4vhK&aG~3 zIM#RNURN0-Ypeo&zY=+^Sd!C zro2B?Y1j0um`y`@OSfTWIXvM9_Z9Aw(E3*T_S#G93ny#14Gq&qSvM$V zD#Cox%PLUgaP9Z1GvZBJEXR?jN1W;V;_fkBgE~lRV`ph|ru6>ms%(kDwW%#HcuB&t zbK8(>C3}E}W}&qL*=;6jZlWo!#-+RMe$QNZrS+IyyG(TGEoTI3N7_RF5p6Za*+Rss~HRB%~DK89!RD znkGald8ZN(2pd)^;shZviXyLauJ;5q7)s-zIP(yCdt#!2a7}>k znbcr=)u-&h+JzM>*9LWn?7h2OI-X#CU@_Pn z^F$B190JoyFB9@5VLvJLJdsyX%Hu2X=V8im$f#1{i7*_Jhy0mE#UwYzNXK8NH0Xk% zwxyLRPDt|}Bw-~801WavRQ7ByN^NUt3UOfMt8m%}rU0tjd^v3eDpZv|e{^F2Rjsy6 z-!1e>bhivEAo+(KPf<-m&|549?LqdMNA2HM4hMdmRjsPpT8}0Bl7+qP$_=w5khK7D zkc5JH_o^tf1jQ-X$|*urHmxVGxT|QdxhVe4wMcLs*a&f9UHRGbQ`Z6kp6$A7Uu2;Y zl!LKpQ!lnsc>e%sk^HO7^<}wsYjmU|E44~c3POfUY6=JCC$?+Xt`d_WwFhjhTb6|t z6-Pv%6f$wk#Qs9Dn!&jD8w1glke3oem?!Z8&2u#$sngQ_$jiend>Bl0g)D#({#CND zLUo23L&Yj|ByQQ$HIK8%C8V!tNbCn;Tk8wKEset@^gfm5GJeOjz^WzfzA1HKIKlNj zt3bq5qqcqO6}hyOm81;S%L&{EtyK6nsMyr8&%I2K6oa1BvDhvutz*U>UtWFaA7}=x z=}`*fr2<8@5!bmROOMFG$Rdz7n5*rvNK0?B2nkkH2XRuYj}5(Z zSE_}yt3NUiP(?#cshEpVWjjKSNT*D6i^whoq-10YwvNaN2XOjUej9H5uiU;3Q!_l%B`-6&#vgJZ4#5OPv;ya$z0(_Q~l%=-R#W3iT}axvwB{OVC$is^BQsAaw1Mu_iXh=%80&H^xS}q4+X_e{ zf44ZGM#<@nn8*Mq6?Zsr2N|mQE(o(@w5FD| zJWgB#f%P;{QFAun^q<15xvF(XTHY&Lg?x0Q$dR!nDCeF@q-kZrjN|K3LfITOxqSAb zEeS$7{$jb%60GNe=}}*mk&2oSQs@NoD~zy|;8k1BoyRJ`sOykNAm=@*RBSCS$HaC@VwPbVGE*A?Hxrj9gu4OYHc zpAUmM0f@I|B1>CWs2X-~O1=rk*2VWwNK;uU7*dj$hj`?1fP3K9?@ic{xiaq## zuoZcaBremYm`+p@k}w^TFfyPsyMaP>Wx5O%-)@&lNr5%N75zk(37vIcIEij(e|Fad zZUYDHQ?Br&UX7Z?bki-WLyZS4UK_)FIMgNV0L-~Ml7;^HI9585obENfD(K?QZeC36 z)mmq1>w>H<%CczrYWRDGuYE>M>s7cC(oA^lxI>Mkl`LdraqOW`7}9M+hXNGq8Gd5g zQ@VS@rX5n4g3`fJmvU11-IeazqsIyEadD|{?cM%5<7c`e^Ky>P`@Xf27%tVP+eliA zR`!V8r#}it+^CQUJ37Gq=7fww)4TAu|? z2Djgsye|23xnJYKj@T{S^|*KkC4?bma4k)_fs#{zwBT?~F}EeV>AO9#t1YhMXInPn z@pj3fqqwG*Av>GQtD~cOLD~Tz^yC4;s+!Fr=N|6zoW$GZl676qwt5aG+~y$>d82?HR91N|_n806dC^)E7qQ6E#--e9R}k$BLt-j2Kg zKv%d?$Q)S%0wN^wsa0mTVAVWR(rDuU)xi`9Qc^@9{^nLyFywI#l#R3#%OB=AAcpO59~O_Oj+WVpDjLt8}U4GTjt%0>qV$~^O$hA~kcp*$hO5%$YvB)VI9 z1qUaB0 z%sYx(`jsG*nn_F1Qi6+A8PE3%u*_`ziI9J#R^2WZW^Jsu8%@N0!lIR!wt0;p zCme0u=6dI~64#~U`65deHzKKA?n@Fu=h&?O07_Z{*6*RNHi+sAjc~IqUxBt!A(boh zSv0o&I_pdqt`e7Rb8bTCs#28a)8$n_k7`uMRUP|1)=~~|5p0;EN46+UX&Xy-K;-u> z_NLLFl4Y{wgncp)p+yWvylGkPsN5~mpuC32N|-Jbt$*T3P#k*H`0+kA+>P)oU=3xwuWqcytSQ%Rl(Jtsb7aOtVUg+K=k?<&uKzhP=iaSR0a7<79x0 zXC#ies0~VP=>Vz5+-!r!@IR=bM2Nd{-w@1~TF368XKASzwq1@wWWHssi=-FAoo;L; z0@I1lR45KQ@sJN4D?Y>&*~wfxq}5>XxeGv7+h`xsui|Q%(HL7QVW|u)TZmG2^ME>x zdQ~jP9F|hIf`&LqQO^{t1a557@KKapfS&>IHtT(+CbnKI#Ket?^I~&E)y2Qis(}9DY?=bdsks$D3+;Gfk&zQtyzVx6>$5t4KOl*Jx!< zsZnC2jHxMF5J~*$k^?;<%bO`sw_D;%QSBi?Bz*}`saCN(LR+^u2#V)qHtJWOrW9&Q zE)eC&Q*FM51e|5HZvK@o;JzVgLj>{(JH4t!DH9~TwWUZXDLir3fp8KfwY8^`0HZ)$ zztR$%5KlEW;?ab)A%C-QjPgBdps#y{C0u*Zi9{&T3?|A_;ZujIk9smK<@KdbJCIHa zLFrQrbU49MmH{fj9nBgQu&M9bsVyfziv*G>$q5!KDnVN;`G=+(87lV8EWI}_;@Uo6 zgGyDM&SbCh6?bjFLep+xsA2KI91wc-;;h}&4$fbmBZAoDhE#v8M$#+|n$pwnA+^XG z0mcp!{K>5X=G(0iRFpzg=TbT;{olzVtK{9okwU%%A40@rgILMhw(i=O5ttA8YF8>+ zf_wD&dsNj0mXq~Uw<4xfYmW)M;FP20{{Wpu5wZ%&NJ!`AIHw0?h%1!%Lm&adQI359 zJesAB{Ks2cvQdr7J2>sznka&EjtM6TQgpcA@-gj{lzIKs)wdMMQCE%7`R%@m8sb__HZ0^ zvHq2BQRqnOQ$cbIGF}iaF1PV&8>3BSL&%jhQhv89>02q&isDOX+rxIbotw3_^zV5GyjPJ5m4pX06?PAo_c{$lM z>9a}V3{eipFCpYP<*ccvc?0D?g!)&=UIxZQtJ0cm0Q<@>!g!8Q`)4Hw*+Rac)SBww zbBO4DqYOBbpDek(9WnAN)9YV6_(f=PtmDdjbSK#Q)_5)p+vF#P`d{JoLyB!nTxGtwep1z+ zA?`P%WbhAK-s;^)79!HZq{C~R{?=L$Pun%-mU&Wc(_ULn7G&hE)V)o-OaP{$`_vCEN?FH)JS$%(E#D zDE)^uPbCVH@|;1&G5}x8)2~{?x20oWZb)Uzczv;g9>^r0sQOmc;D=+WO(d=G0O1^v zJuy=~M&&|7Pqo=tpzK%H>4^?vkKsN-lIZr{75-H=4xyUdRJSrSjAVSRxz0v=I6M7m zAzYNwT`{3KG1R2)LnmXXoSBfrwJ_ZfE^l;+ERVpig|kc4d7oN`oxFb;hy19t4% zl^U-psS`ZKC1}VYO)WT3J-O&=TPsXPico}*PD+Ma)rmK__Q%&1f}-qFi^GzqryQ*c`%FcY?_hqA!pG z?T^Htd}p<0HDp_)Li;u}MsDjZu_>6R=7oD;4oh5s9OUph;8Hn1k(5x9ug+;!Wyg_f zP14Z&WfSZ#5`IJezAC#Y4#WE3){jqWh~~!`g-?-si1z2~*T>~~7i39rA*C0Q?N+kWs zsPIBs7*fJ9f%B38_3c(|>ML!bC2DixSJ^G3niN?(lkz>ZDJcO;!h844BfeUClb>3M zy-3WZ2Ug#f7I2i~oPKnI=?VD(rASCo?VtT>cCvRbl30DTg?VfYk@F~Zs;SdcDZqrF zBqNN0jL|Q*uk7$`Ei>nb#sMnEeXBnL@E(~Z5)g>aHs%nNuRD@JIqAnFj8$zrB}qPu zsC%0VNN|v|out&6NOBxx$U;=Ka#RutBB`ai>k?aiw5dTKF-mcQK9#1l>emZvdLly} zYh|}uQgj1ZlmAI}x1k*O_Kc%Q4hvGCGzP($le7An-pr&9|o+l{rtarJ`8;D?-)teYq857JR0fCU9}xiYe=Zw>@wL zSsqyp{F2dQa_FXFH#KmhrDeBcL@DMX)#Pa#Jg0X%%irFwn0!!=%860eRt>QoAxN?lV)&->@* zSJYy)M-!2jSrRg6hYsRU`Y#O6d6SS8Dq>DXM@3*OuW?lxnJt@hiCPjD*?s<%=#6-N zqqerYdfJG$aj{>lBrQtMP8>L|JMo6Ya?z%B?;Reo* zhccE#jK!KMLm;h0tx6=1L0fwQ8{q`*Bfq6!bkChJv$;h;a;1@8hiOU}LQOE-fu#k6Dq&2#k41>1j#EfR8 zAzQ#DSw5zt6nwZeA__SQIj0<$xl3T>JdsgRx#N?{k6}$yhk#8MI?_2EDMquKY^Bea z@p}4HS+98y5EZ$7);m<^Wg(Q1N_ph*%~m@5Z>woI8Rn%TF355emw7y>@=2&}4g3RA zByB>7Nzb)M*g)$|L<91Ua83uUJqZC=2Q>cxD3p$SR9OzV#?aad$vvo`Ci!3}9E|%^ zrqjlAimGkpAg6y?oZH}#r6~01DTrl|%3B1UJ!z&=8A(!sS?rNj%6UZoBVE68ha~&b zQClP#GbXEO369UVQ%4RY4ir7fsDvqF0P#xG0SMfs6))egp$JV%ffyW6=V9cfAOW|5 ziVvC)4>c9(e=*vA^bunpN>VYNlpXmUa4Cy(c5-MAJ%r?%0>{4i!5QYO-*b*l0$i|_ z6P_w7^IL8!kG$eqX(J^{=~hAxOs-Q=T!WVSi6kBede2>TOj~$ZRF=?`kC|#$Pp7H& ztgfTg_Y3vOn>Ce(WJrHLTxJ0-ynRpbr_`Fb(mF25r*7=Os4o`jx`4E~K~r7=1OS3^ zge6DzuY9=){{ZOMUr(dI%14~l`j*GG1$L;j8{<~SE-y`hCgd}1jGfc&&8%$l(%Jk8 zB}3*tx>n;wcy^5!9yB>oA>A*qM)vKBQj=t`@TCVOxH24c4y5%wXQ8Je=`O_;T9(=+ z*<;FkoC27G)1LZ;N1zBEf=(pPKRy)Kdo|d-*_Wc7L@(Zr{ zi2_XdC@L5UN|aJ~AY(m6c43AwJl0)AvOZarv|OJ}$586Ko~JC^$y9mT-Q;3qkQegC zwKkPu2|GyK2YldQ3cHy;ZN}dY!3R$*b7!S4+B9nqVEI`Xn{;>{2RYbC=K%M`J(|mQ z)jG-RyAxnt@9rO>Nf-$){pYiEfp2k!`w3 zKNE6VKkL0sBYKCH(MkDZV5gi9PNdc5LqDZ-T>Fj5S7}$cw~Mu*fW+N*D<7*YZ0%7I z?fVo1uMar#dK`B(WU}fZzWg-a?V4ukAO+7#Tz64w*%(sxAdi`*h)FxR81Is3m+eBv zoK!d$C>H70sM%GwL&0zxWuOCvN}l2g3Q+2E&pk4D@`-*RYNqu&n$i{CMBB4*7ZTxo zRCb)s?4+@2Z9xa+3qL;{&j6Ybrn^vGF0H$5x9S^w2c%jW3bN=Xr6tRKg|xKT58R$O zQq%{`0`rr*2j;AQ{UR}Hhj7$Uu6GNK*5;)~nW|A8Ul}bxsQfLt1BRNye;)%l^cbkS z&z+{;TM}bNx<#7L$4ZAG!uV2@;VmxU;8EP^DBb1f92$?LU2{=HUB=;Zw?vGPls5F2 zLn~59Ck?fXr6Z;~^q`d+1uCzPoPLAfI9*0+ZE`9SQiZul{MiRwQml@v`CaQ+=5C)Rm_bU z*N5tBoz2jH5+8!wAz$!bV9~_|X_z)OM;#y~Ib@I0{8W8=|Inhl%rJ&VBoAQjMu?Qz6|vtbPy+ zi9B*gTGD9os6SN7j|r<~&RdcKkj0x=$hXK!QVJF2hfa2aGmQ1Ct0t7WNV%{g-lR^9 z;(~;2J*w8?hj4tSfB@&UT}h~6A)lGs-xBLoc4S4INuH%i;X21cEmq+Pf=WJQH3OOtAF z91`SdK_IqHN!msRdseY_^JV{i&DlMWDQSb19JcF^(ziv7V)gA9hTdl4KCfO|ONhG1SbdT!i&ppal z{RLJ_xN2DeDsug&D3RI+4Z~V2R?z+Pynv_PQ#~IZD`e2 zgh%8C<=w7v%8ql$LP_sXZSi#E>1?cN{Wqu-P7AR$4cI9?FizqD&(gIJ6Mu$;O^aj- z>RfI8YZ3MRvNArL*1FdwMcO)Fe%j1sf%7HZPZSaV0Fr@`2TAFrF%joQg2dum=*0V^ z(2z5bGLTeH*V2nA)UCi@(>7~cDgOXj3hQupC({Ih)~T*3b%^sm16Nq>z?C?(PSyxn zQOP(PQcrvVjyqN|J<8{Db?D2~Cg9Tq9nfy?yBS1)df;cTrA1g->W$rF7Qkms!<4k= z4kg>B?uWV@^}wpzo~^${NSPYRE*tKR>uNRn;=)o-QMFkWm}b)!GahVd1rpczix*kQ zSMnf!RC{)szD$%BbsYHZbA-Qml2Cu~lTk#H)2L_NVxil_c`Sl5V>>j;AfC7?NdExV zw3fX^5z9-B!It!ShXG-*Tgp#zGmh0)Z=ZU$KvP!>Rnhek!MEJu&TRcroz*4+*qIS4 zF$`n4m3fF$FMZN@s1Rb@8}}TkMDk5Va(rlRr{a}{)Oo4Tp@o!`t>%)UyY`KPk8Fe1 zrN^{eABI%>sFK;|Xj(#t*+PMl0(8rJtta46ykjM0TpzKj$5a5xIrgK-Y6=$kx2^Do zcJB@#qzva6JcHA*s^%RRB^4>R5|nd=Cm+mIP~^rU#+dKz-cvXqDGA;DsjgaJrO(?h z;#%ZwLW&9Z?~0E5VPld}=uZdjngUSsE{~iLM@(e?d8rh8nj=jqw`2#F!pBz2%Eo@; zv+|#_LMmg=hKF;6rd+UquTG=AX^{a}GL$X7$v?bQNAxt?u7*Of@iSDEC}9a+;uG({ zCV`Bunk_SOx<)p5t-7#{(wKSIbF`do=sDbZ#av2{aZ=WTLky5MD`5-Atu{@K>q<&u zON8kh1)n&DeXuBn`L4E*?!}S9yrzm$2d+wu0-2X!+?jPvxgn=ceo|5fez>USHPM`@ zQAx-8#DYdELh=DtQ~@CNIi@ZZ&g$8Y%d13mf<^}v)j$f=7V-;(6t><9$>~>-oMqO@ z3rWXMTE-1MbZ%1Hktr<4z{-VbE%sI{%mp&*vUi-T2_*aDG*DS+$cU*4X~|7Awf+=1 zyl(ud&8i}$ki^JOr5vd-<}BcQfmixOM0#sVdewd?LW+{~6vs-?zJUD1oDO?cgsHFm zM30DveRd~Qw>1%vpfS)C{{Ww@Nfu<7n%t86Z4_jKoSC-3EhSFV-rxfbbovZEn)HigC7kMFTlJiS#TlTPx z|9Wv1LS`4GB}&a!e+b`zL)Lj(Z;XsIns`;-boKP!fOMH);1dx-dFQP?wriyF;gw!q4H)6dfHsxYtH5pBUq89HD6` z^BckIxg8aX^%t-3SzBzP>brbbNGf{X(q%#){gIMR2d^A+M0i5T@NX0CE?6vo7FNU2 z-T_QVi12bdz?^SvgU8aL_#R=sq34CR9AR>n(~BR9GruWHzc1@Y@xxBrbo3d|TfP+$ zpGq9tZFHDxR(~21pV^b$BfSg30Jd~T95pq<3yp7m)S=^0UcgC&;%Dh=*)Zsg~T;OC`F(mH0>Xj_*DZSftr zEEBb<$z^{~rGkAa**-|NnN8W8X^3-MYF6AS2zViR@9kF#m?sw#Gkk}Tc`rA$L+G^i zsCtV~El(w+$G*79&Rdf)nN6qI5)yIr6x}r=_07^OB{1|d>WBgC000@zK_eVhV-L89 zzD`y$QTR)zBoD1N&vA<(X((yO06&BQpUBnx=r)Ltl$)emhQ(n>fhD(`N%ScPtyW$f zj$yd`Sh4jCHc##T0X|&l;B_i*smBx((O)xEMSgUs%)$L^<0wgb*uwrEb71qm12|9| zbft}{EEVT^;aynys3}p8Fm>)Le{+Es&Nku|X1Gt2_rs zUAgROlQgZ#cU2|-04Ce2QjN??Wa1U}K05jZ;*gTj%k|Sw+#Nz;2V6YGl=vS9dW?8b zPym3eXWpbOjX1q5uls4yQeHuh;{>W^leq8elA*Pdr72s7Tmjdt3wr_mD)Unub=ymB zA2|;4^{L-bJ134iR(dp>&Xc>Duv}^R?lPu}Ph>*AL_dkLItKtff69PHcUjmaz+LiV zav65)i2g?ry7xJg9*15%d2WLZK6|3%Tim~HiaMJ z9X`}q8dlA#--9+fTw%DU&4szrm*6>(y@i54cFqX;Roj;HZ)r`bX>AHSl!newPy4;8 zdK!ScAv2Vaw)V+nVWRRCl>4Ebb&YZrfJo zN7M;v}G?TE+2ZLfqMNux!UwGU3nUNBP!{ z-Qq5qxV9Sjhhe%qJkq-TYd0!Dn6R-GG0%O9Q6JAVHzLN6Q|BOj#{`5A&aL-~{h#T- zhvnQdsPdb56UM{I6(w?9wq=(R2qW+jn)AqZ`F1&Jk!g~a+(`-zx{S8JsH-;hEyYg% z0M{|n)2is>WR#zvCyKrkQYoG$x-NtW@?Sn{d2L-nXD{?}b+&t9tw>;z2R*H)=RFEfh9j|g4Wl#A^>K#xg^Tj!x z4`_#!HrtCm)T38nDsu@@9CuORQkSjpl9v84gdsnR<^vzjjj$r*ENGVY0uqQU?M?<1 zHxvBoH2Zz6;H0Y69k)+?rxXLIEeg6ukLM-E-d@6#tOB)QpOj;h$4b%4v-#mFi{+Lv zfQ68S59f-N7?E=^ax`s%Ypt*0l+=cfQN2o10nes!Sxrl%J{3h@0k{QjFSp8Z$0yRg zHf@n8U@2~f))n{^as2uks+5_Q>xMnff~@Yot3@kJTc^0WQCcwNIrAu^l;;De`&AsB6{{yMFtxbD`(8(x zw3LF@M&W`89A>lo48PyxvV`13p|@Uj%qJ>wDQt1+j(Moi^`(|!6v(g{FAz>UY$ z;~1?}ZwT+td6FHu$As2$ljN5$G29)a*S$)C;G~k_a%JKagec^xF&kv|{{VWKY1rQK zF%uIV#Vjr(N^O1HQioqoYb|xKTH1ZTfN99D5LBYKiAnV9Ud?y#=iwSrOyJ{411tTS z$0Mc&4SDvU(F>4|IT4f=-acd~jE;H1{{Sk7iJ`s3%jKy4!>v$zr7rrbo! zgWfsc{%1sN2Xrj%z<~l3H=d%#{bw3EB`<{SIo7=od95}; zL1EjBhQmqdFxD~lIH$)3rOFc{PCPT!yg9QsV$-NX+e;H4JJuFHWqKt2Ju7av-}2C> z98lfLRlE*Da&@9muY`^qQhudJXs*Os zQ8{u=vv2XuxQ5DZEeUOqqz{R0SWrBCq;pwqaS7YimUB`XWS0Wee57qjB$J$Htz>5E z*)2K#n`XI}5AQOlq>rk1gZWgZwJY*tc|a|sejSe|3kTe0xq7Ch6RD$%jFUX(%mfw{ z9U!EkWh&2d4Qs6K`GFbWlZvC432)q8X$cMG&FWY6HGOT{7D56?m=b#8zI~`wGwFIy z(Hje9K^Xd0vee>TY@f*1S_;70Q<0warM9C9A2%blWoJzmjiiItrMA&n2AdfHY3;{6 z)RYjD=qjPoIi|~M+)uShQh`YIps7IOh=aF-)|m}M_JTeA=#91%a877gCyf2*t@P_s zF~bO6+~jtt?BPQkR4{J3CfLENSBgEXmcl|k~e*;H>hGj6TMWlz z;;zqSaH1_+7>L*zREZ>x5|Qg#iyy$-Y*Q?7-c8+?A5v~Eq_(ft(h%ZGSHF^$l2gXg z1~b;X+I=dDws}2fxyiW4qJ5eThebeC#@~6!EB%qUw3Q;`dX$x9Hxwl0EP@7A(B}rA z+24Bis=TDz<7ypJjL2ruy(+d?S{xT&QU3A4o=30PQ{w6QH@S1Jvo5KN1~QWpexMC2 z5%QoB5&{NrM=2x&*Cw>iUG$!qVjY#c9?OTqxP~;vw6^TWmO)r{;Pz6app_$nNya!m zE2$WydFjc^mUZ5L>glp|G~3Dzz><@FeFJMi^=>OifO`xMm6JuCcaYNLi56=e(m`Y3 zSY9ue=ah1i;)ug-Elcis!5HK!uk>!7yy{I<)yMUGJN3>RjkNri08N)qv`E}gg{TKs zl!8bHr~q?{yu54c9*xr%$=Xs!>hp1Mezw=Ll39~*R^_Q|z>2<@+>y9#100S5gOwST zUmLc`w&~e6STUsA$$5<;+R)v$)=~~8T%-b0^0uHW2a-5EsMWpF=OWu`y4dnQl1JzfW|j;z%@^H(Tv)W9$m`WBG+Jjvm)K37V^k{5~OhAlD8Y@896!pvstZObG68cDoa|+LSK~)dsQX<7`C29IwvSj(}A3J z;C*ToS!JY0y4x*Qc<4j-TZ7~ffVn!5N=ZJOTlsraBVOOMr2HGLM|DHwB<(rv$mD&i z6{hrSo+ZvuWzQ-DPX7Sfp~LbSCp|)I-1J|7aqaP3OwDI|dBQ8892wGfw*s_P?4>LRG~uCgV` zb0^_%*5$#FoOa%f0)4@zGW^XcZdzdKE7hhK%7nK(s%^6w{5T<7LG`N_E&641Ux=wU zPYZB(ybPT?aOSb#oaU%kjnUQ6o(`Ka3ejzP{gYyBC`BonBrF4DF zdCuPz0$*I+pkcs$ej$VW>5F$&+I0Ky^zO7bc<$sbJUg3Hiqrf)RDqm(3a*B)CFQ~E z_0Cq?W{!z%K|G)BPn#wakD@r@$D16S_HFRej`vZ!=L)5bE2@l=uvEwy@ zA9CfOlC`LW^)4fhNCuf7Q22eTb_d+hth(U=E1zMV9VaAy`%3voze*^SdNY>mrLHfW zVEC5}yb|FtV%!=}({E~$ol`P=1fkCnr^LX(X6fOgU05FK2_V%SyG&`j1Rpjn(?+qq zIsX8ub-eg#J--W?sqk)hXwX6+>R8Q09E77;d5Ql3#-(SqK_eyFqTEp%^^U5D`!1F3 zlOI5-lA@e$2g^w3k;PF@(AJNLQf)UMF~ucBhcsNJDL@WR0O^BM=gri2xXv>g?8Ju~ zSaG>2@hl*uV1*?q?VdBwVN`09soa{i{{U`o7L_)TJ_)}NgP}z{DO}pnjm04Z2Y-`IVE&l*dz>O&i z0ZyV5ke)u<)4}@E&>72?ptI@qm*>lEX+0Av49Ke=Ufrp&d_cB88OZ_!z>;u-Z@Dtz z`s{Te(eA&g-JXTRm8)$?IVGqUrrv9l#t`Z{0md^>uNrvXp{W+h*TqV~!ahC0}yc=Br-TZK_J6TBJ1VNhB-Gj_L^i0LGD6$Kl;{ z`2*miscGu#@tkb`0OeI!#lN_w(5GiK!^8cWmwaq0hm`SM4gM8B(>1sg?}(= z0I*zTso)%1h7TZdR&BPX*a}e#YdG8wf6lAarM8etxlrqxtW*AEm8DBViQ{q#Mnz2l zZ8#E_4)%%yK}Z1PpH8)!kv{zE!GubS9yWS5*eWCIlUA~sYvHn|8$wVxZ)YbRJJwQF z(K1lylHgX;f)l|Mz-s>hS31l;Jm8=Y#G_Io$UugpxhmrXWbye{7RzgW*M24BN|wrw zQj(%E?TYjWRzHi{`!>0YB>^f+Y&!5G-6J%#1$!4Hmr7&p9dZkR0rw`SCCE7ViB}6j zf3tILg+KXHPxPy%yf=w@0WRr@cJcn*HWvbq-9S~wpYYyRpM7pwpVUyC2_(A4mqN;b z>jR@9Xqb6?w472M~}s=K#{;$N-{s)O;thvan3bnid-s2OrzMs&np8Ch)O83!4m#%;+`wy>2i2bDI0qxqVsx8{4R z{j+SkulBBmtv~RCTGu7J3f$B<%PU&27V^GSk6e0CG16a_mcu?)#3!em?sN7P)h!bY zWhJ&lSp)Gl(OSv+n(AIapJ{&IagOUPH1(-*vR}2zIC>JliKF}I^!BB*--4yN3Fv5= z@lTkjfZ-Pgd#$6M90RIo4~8y!dNlYlh1Lk-G}lv!aRv@qcy}E2K9!v|WgBW)ZPmt8 zD)Sm!O2XK4Ir9*GGupZxnEEvuw#plc6Hw{vlagSzyvLf6i7=KrwEBIzbJn8^sx?)$ z<#|oPyQpSVX1I?qkM)Qjx})5G^l931^)}%p#$}c*+L>tE=MjiEgbsy6qFn50!pj~@ zl9vUQ$B`fAn`l|f(U1E^{{V>@{HnPw2TaR|-w4ZUjzNy45uAP#o>HOfQ>>RZBTFte zxe;G%8N0{m`yd^G_;VbkC9=`Nw^KGha*-}|}A}eLbTV!SS zz~rd|s)w+uIxaF^H#;8lW1Zw7lmw)0kib$y{Qe~%{uO%CcoN*iXoMD;QZBIfxWg`t zHF1Pb?Gm5jDI+{|s9O5{A5q#jFu+7|?vc2C(kAIhPOk)4&O^y8>g zXw=qrr1VHs;e+=Hz^iK%9L9C|P2b&VNyB$7IXXPFBoIN|*&DHg+OCGGjilU58j?lE z1W*nQ;>8|pA;&_d(pAnoXRS%P+H`Hk6BnD1gQ8^>EZX&kj!F0|;2|O841}vCS?Szl zb5J!NUwF|`=3As+tv41m6`2dew;Mx7J>C+qN{j4{2u~dK_QhrWvxVoKCM zv)&+D)3uc=AKXnxI47aczIe&4<Y3F;BN^eY-&nTl>Fcy zn3KulBLvdlbffx~*?5&>wKo#{+Cnu}gn1ceRxnjA5^{U;Fmuz`lp=}RO5!z5%5~Du zY>8^|0wM}8`c2#jSLprTU>-LSlgD$&!rx~ z!}mo7qaiWc2LMWSAp_VI9+VP@dVNx6L_~$LAt+zW3fUpXzFd%{4m%!d>6i*zdzCH6 zlz0Jr5lmhNM0KrT z@!bkMQj^TnB-}RENQAH#n$U~XTujjR@IjrVYRyz3QpXmT}x!{ z<39C&Z@Nl-w78V8xXustYu?j?oZ3rHyU9~!^)<-_Xni0KJCT~e zH>>pYl#?3Os^+N&l*O=}qo3kag013snFc#4U};@OfP1`%Z6C=`Ro?7K*&=MVABgKN zGc__>b0i(Ac$w!72qx0#XeR<-)bj>Cxj z*;>D)Lkf?1WWR5?H8CJ(C~#zbM^Qv&89qsrT8{-Htrl6bT{9tXUT_o;LHxl4Q(l=sj1iqP00p>h+lON}^?NyDQCuX}5Pi0<-I z-mmOkraNkRB~cm$4zQIJ?&AOx>HTUm(Ini}6_%q}tk%fL*@=pA)U1COkkIFhbvdm3 zTbfmx2GP()A4W+*^en*>sU_ zQom?Xazd1m{_3y^s{4p+UDpAXNp3)E;siNJ*!Pv6$Bu-1isfXJXHN`LV4j{_UeUsj z8>;HS!k*p8*p5_veXA(@$cnUu921NKR+h;NaD)d6NjW$*%2M%(Oq=HW#wWzmE-q_7zXzE3$(WqhU0nZg?hQgZNkny`xh02n8Z!mFa>O+rR_>rPs+*R3%s!33PrJg}kbQZfu3$m`7+4kbw({Mo97 z$So*inmZ5fAnwPtAp1*F@02szhNW;!%X`aL%19ox!!rrWPu`g_HkWIB0fj68I*if>(n!ZS@99~`2yo=6 zpK6}N`%o}@bf!|WDvQKChQJ|MBcZ6yzKIIz3C%=pCZrf^a7|u z>Emc18aiYQ4r(l2rGliXTys(0StmQb^d!t1c{r%fOG=5s;()3pp=2E7(LBIMBo6gf zJ!)2okfb;MTEK8R^9&qDr&x6%gRB@u%B9@ovB%3P>R@8xkor^gZ*lbfGc3~RDQ`%|}WWI9L+UVNt1 zpp`sFWahC}3uXTR#4Sej(#Rs-(UiAjASxB1pq#C0Qh9NBNjT@*iY2d3T{SiQPP@AM z(w|rCqDE|oHui0_W93c`2|o4dDYRR(wY4JrmM%qq`H;cbK5X;rxYV6Lr_PMh?Zmnzvu=*ykj|27d2PwN)x^2v=j9$+NJ$vT z$7;iBj}aS*sbgIqzR11mms&Re06mzS1SAyUD1esF%f{ebz#aOW)1tO|z2@OH*euy@ z3_>#5VK2qdwq=6~00>D-mnD*wekFpTo^jOXyxqNSnL7C@ZRT%RmF{U5=?QRJENUtN zOu>w_kg@|{1%uBACn~D#UuKnQ(XwTUQeB+;$$CT&h*DcAA23{}kN2u7+I9of8uKkZ za@4xD--3q)zdiV7Je#t&p~h5C1%#zoO0oOg_2Z>k2=3rk=~?I=A!?g^X=XHr-IR$g zDdTHG@h0NoQ6DfD2JuJOh9MSGP60 z(jEf{a(w%Y$#SJX^DWn-scT==UHy@i75ablb+%d=dPub1W-d@ETkx$>B$B0NL1eGyINSWiPt*PpPScM# z5vlw|hQicT8Hc6VVJQ26d)J{c;nX>VOSi*~a&-dx7LfMwbfTxuYY+K;OlKAe!GKUR`T_5bT|Z?rOslJk+yZsZ+>GqJu0D> zmoi98OqKQluu1%=`*_E))tXW+Y#Ko@?UCVIB-vv|WCWr-r1%gMolS?UVMbwVv-PB!-Ig>eFfGb;C=~3jpV@m!EQRSFcX8 z-JVlTH>dS@bvR60tA7J?1Q0?}?g7Z>^Q#K2sOXpmL@kj#T-yFJEnxBxeBeSWo))V9g4L}mn=4!g5JYk3V$ zii^}l=N%TGoDMyE{cG1RFjT~KB)xYHGy6iD%tXja0XR5XPE7+(NqO{aY-!p16_N1L zU5fGR!39KjtW(j&^lyf(t`D7~M7Bn`1})bXz9q6ZM{W1*PbVWIExb1L!TZ#!jfQRO zW9&*xPcjJ#n-LPgV(xBmb|C<@B-@}o-5iJFJR%fe9WcPqxEff?NS zaJ3JIOge-hoP?Di6Zft(MCF01wAchX~{U02dVx4o`u-*!+|h>l&3M zQY6LKS9Ft$j)pS^3s)5^qwJ zE^?&9Li&r&sZ^Fg_e(&EpJ)9aV>vP`>%Oef%%w`+rs3KoOH-YQB#a+Qc=dagizGcaV$Y@ObGKM;@{NeA!%Nyb4ZpFvv3tvb(2!zH+S@&U)=_~`eRPt`c6 zZ|F-R!kcz}fl}0xo%3|mmXvZZPEvS2^;vB2S5QR#u9Us$Wrm1z;VrtBY*k3x(E%TbiHUyI5pfUG4t0tc>;TP_5TV)8e0kRDUBsE6{JP z$wGz)?koZ;Reqh~{f0Urxoo>dLXdy#k3zX5fJZ+%kN*HOUUKtN-&&5MxVdUTKPo2Z z;-uts<&psVRWidR;CSSV@@SDVwsxoYj24_%@~BbnlG~K1?H1nK)h}8eMt+AN zRaMFR92f~&?v}T>%4A1wOjTq3)gCIU0&m)iN)xBG5`GwdG{10slk~teyCgpN{F|uO z%~3V?9Ell^B=Q<>PB|!~ecb;5DjQmAL$cIv32VeZ!-KmP!-tC_j#jXJQ0a*UEN|;W@n6*C$rNsJq0z{b(F1pffV+O4#vw)nLz#OhmpoPbUx>Jrv{vHYqdS}XOY z6zo_MayTy9%j*N!lUKTD!*;FH55|_GMR4ck(;LE5$GOSPI>g0oe2jBl%)Chu*p)iq zr5&kq7OPj3Ptvw9^tXr?1`_JxUg06603EmLWE8pXQWc!lOuGe^426~@>8W`?H=n0m zhQGRn35`5uK97X5X-kV`Unbkz(bfp$6UXwS+GJW@LfkFdo$%scov+eS^6ig*ZwId< zCpDP4=uJU-Y;3vf0KGs$zh$RCIxd=>w72;M16G=ilMY&%lW)?I$*ZqB*xR_IQ&-ko^?t&JpzxWC|KkplKC?z@R=4@Uo1wB+^4hi2}_c)qB@-LPu`x7 z4enOZfiV{eYEj%vvXK>(j-wzP;2#vV`^ag#b3wtiLK7Zys`{+`DfcJRiHZ2AX>GmP z4$56dW1ghotw-)_O42q}_X!mZLwfRr zwQcj~6=R6$4JW6ntEg>ad3a(%g%Jh&RV zApNvcoCanm)1q>C{Odz+oowCk_el%2vhsk9e2$!|3*Wk29or|b{{S;Vx?S%&ds01R zx1E5(1{m;HB*#bPnI;JY~O^IC&( zVfc||uH?d=Py*e5#$wG!b|nwpS8STj*CmMY?-De-TUB)uODaMNl!ngcB#zD_(DpTZ z`aFD1v%^cn@*grZtSg*M?eo5G`o9Ul+byv4st*R)55HoaBH%=UI&>YdWs-!%sH) z1Z5VaWPGl)IYAj9dseSnOKRDu1GfuJs$HAOOJWR-P8d;8&&`H;Ptvs41t!n?Hp5HU z#7QY-#BW5E==WZ1tJ86C=j&PP)6pPqV(nwGN-N+lMY(YtQ1;5m2CJ-YTW&}T)7l{; zYD$&*gIl`es2I=pZXA>BDm4E95N+2rs>up>_OOt&OVc`yl8LRTkM%DScPMkyo;amr zli=yrtm08;Nt`Hs7~q4U7+PVbyOL#Q2ET67N&&zd!*+jNCzYNQjES! z%ciuMsid2V;lg5L=t?BqY^ob40IctEtaUl({#6+fT8@+D>~DYWr+`n>rAIop#d3Ki zPuj`lBlUb|;i#Wpo%EvEqg0Ll;ZpmHonoNNz@U?*=MC?Frb zXra9tYaUoWN)UIb1tCX{-!MIY&Yt|HBSLY%C5Jx=Ny?Bx@9jf!BG!*JrpW{hhSnBA z`cyCmw%|1?M249Wko$$h2SK8XT{Hst|rW-RvQEF2bqQ)?e%!KOQ6i>T0IJ@Ecu z{3w#8Fra;oYN72jH&3Xu^PK(@A$!mVpimV69odPBt>}`Z$W};5B?=$$3W(*|g-U6s zrX`tfXJTC?D_%Vn(Dmt3#iHc_mfn|Xkd{_{Sxo}_AAP*1?@Kj>ONqB)8(NeIdMicd zazBX~$|xKo$qL$}km1Kwj4MB0^fkKNbe}nCKs^f1Kb>?NJCNKmJ|PX29Oac48a|mx z8&M@? zDmW+ZqgPft9E3OV4a~I_tZ>uP0v!j~DLDRhMATLzIMS5DmW1Q>YYJ~XeJUoize9ni zq)E12XKCx0akMm-5{Z!91z;4c6lEXpXMy?Dcmr7?{N{>Bbb!LY@H~uS znR|yC^`@dlfgur{;Z4H19uR$5dpwRi*PWe1a=6235}1z?TK-Qh#Rc1>kN3&>GupJ) zy)kn(Ng8pe5g|yBu%&5><_iyHX$o>2uy`jNCpgK?76e8SHHk@!cZi=ogOVRam)yKo!mbHRGug@jDMN!B#zuMPlg(7>Ro-;!8nynYm2Qt12LAw6 z-p`z(0!hO!q~P@@K7z7$Sr=_i!Xv*8?NQjaW3ELA+Tox0-<8t1?>Dqw;%Wt%mE<0ooK(|&t zz>`wdBsNgsjpw7gPd4gOexzX2rq_{nreqfGpJ{1oESrVZNICO`ic=juijIFe*IQfD zFCnQfG^>Nnf=oB`i_a^MMJ(gDr9xJ$OgSYB)B1g+pTeguQkf(A)cans_>`&}v9!Z4 zxS_GD7+3HkKAowzKaqAKhIR7B)!oSpwMm9l3Q&~Vo`!&&BytDKo(IrzS?lC` zR-jw@zDo)|VIzxH3Aa9S_=}N!XH#G+JX+YD;^DG|NL;kp}@Tw<$55 z9_M!o{#C1ysoaRuLCYNbY_*J(JhoJp{{Z7k$vFHOxbKyfyw)MV!&0*CeFdt?cLi-Z!mkkvIxCzmaYSRMINbC0bK_o!dG#bt@eQ(`}vd zUAkmzb5ddjwxlmBQ0KR$XEeu&66{Yh{lY8r*h&0INCv$=@g-6fB@Uy^X?Y3-Jab+l zdD&yRO^5`B0$dA2h$TqaKz7y+PeL1h?F>pn_FD)|}7%-&*)1w0? zg?HfA8R?F`pVSppkEreP(zS%CT-Bmp^413I*NV5|gjgzP&O^#s19Fg%K>l^Jw(A(z zBsSz_l9SXZ^H`^a#O>kLMt)C0M6d@?X;k=F;Nr7wT+*O0ta|38B6i`Bc&x519Z0nK zEgk9Bnumz!YB$6OTO9LAl1E%sbXr+7@mD$fQ54}ybI?^Il9s~8-fCNOejy+$j8uUk zP6ey2B9@=SQJp&mcYUddbg1%az&naYa1KT*gT)+zPtt&(I^=WOhzdhq^(aD1GQ)}F zkVj+Ej)_4D!8KSFgPip3NVhP0^G=9#I$VV~yk`QTEd^YEN^;azz7>ig7*DlGjEi zHLD(-&!eB8Squr;Z*iR-)fdm2_-uAZRV(N#3#fG*$>~DVH8z+5L>8M)`A-i-fPP=C z9Z(o^8gPo8;;!Mo|&!UqBVEZNKU7W zG|jd5T5;_w5h-2nb0N}FWETol%0Ff^w2m{*a4Np{sP5BXr7#|1(`scQh<2!`1{7or z;Na(bpE1extb9FTT`>-b8E=kXPNYL+{4tubUdB5R>j(i&3{TK@n^ zcxN~TKi#K}wNZS%T=gBjs}u&AX4@ISwwYtxNF zepTV86dGyrpC(1cMB@la;igBgr9`q`=IPaZNRts4P_=|5EH?aj%^ZM);Nfl#K4L12 zM!MN;Z!;S67ASpV&UiJtQ>h&6+*EcWIN%KN)}W?i9$TgP?Y$wq2IIDXkPdm>{2&f< z-kQjuT8PcM$U!jcXaz^a%deo*(7ZdF7Yxy?$dXrMwZC3cHDT!wIv@sq&PI5sYEqy*? zUZ0};KV~((2(#K^YLqyCytUt>q^pj(1pO+S(C(O<5vW67*`Ub?Qk7@dIz^@ty(9(h zduR#c7#Sy^^{FyE6=P8>K726KdYB|&T(kzD5QAcjitRmtZZ@$Fi1bAsTGDAT?bmM{v~QLaR=IEt*iGR%Ki!|0ag2^Vd)8Lt;#Bw#OKvxh zk8Wrm^nF30zI^S-1xiAXab0|R$X6#LCX#K^XQthvXs0m!%G01FIf0eiFYd%YP}sof zSnKe$nB_M6Tt-+(`B2oiBq#GHn)8=Db-G-hb%U%l#PGbQ{S!qzw-4KB6gfJAT-KDk zmY|y@yc2^3xJ`ln;(7e(rK^u$m3xw*9teH{#V6c0{~;m!KzCG)HPC(%Q0O?j56I|YOl)r(UuQeW-4y~|@KZ$m8Dm0ZS5wOVwd1wR-leBUV zKQ%$L+wam?H)i}f(0bYvXv$@1JJRB*4k1I0z0Nx&Iq8$ytZn`xHDrc}gHT^|gqL@d zW$^;usc_sSSpg1!3Q{>4KGmK%XBT!^EOD2ELv+@bma?nIhc3o(1qnu^@YZ4`Ms<5m z<#|AY+HyJ&dgDDRUYNP)uMrsvx9ZDIt6GW*tyjiHzTnwVz(N#py;glP07}r5`e$i>7NoXS`^}s?xfRAj*QHU`oa&>zZC{HqeiKJ zVsx&BzB)J$HluHlzj8CdtV5Pd#*KH%vg$t+^mt(5aW3|Qbb<&xgp?c<_6MAfYNqqz zZPr%iCC~8E^0e?@y;&zglCSr$oCEi+gf+02eV8SqlwTbl$gK#wm_pvrS1WqKhNX3I3{$a*o5mdu~m6O}A7=ZHE^lO?Y_bc`J#3l8|t7 z!g`9OgW|oxTAE}_OL`&`ZLND(I$XlroRjjeJ^N;?`-uFCwC~C5Y|DDo`V`CUB|wbq zLfR+!;+r8dXUKikNOjWc_Y^qdSB&)|cUsHaHD;&0%xza4I3z&91??qvVf^&LOKT*b zp{*st^`C@SQ}Lo{z_L%u z)#daIcHs1_2kHX~++Un-4W6M5tqzZ-26Oq=dQ6*1coTD<6$7hoSeCr~#agx%eq>p? zYD%M*BV9DaMdLnFuWmT6%WYA|xlgpa76%Z$a{0%-GN`Enm*jjFS zj$th>{{X5aF@v!&tn9F^M;|WUIqg`~+!V+zM0;Ph(fTc9-E_Fi2nfP14%PoStGoZNY(1V z{7p~O>jZtmd8=kMW{8&B5O2CYN>}eou{af<_qOp&@xiW&)TX?GFWVeKRL!E~mrp{8 zjIBBQ^H8P0kn5rODVwvRP~?Osk4`GuFF_zbLoa3xuT)N`JqJCpu(0O;17j#**%<8#T~pU9D3k#SA31XQ|E%&@LhpzDY~TvZI4 zyhmG7;9zZK3etl~@)5RAovSu|N9k`_c@p0rI-&*mJ_N_ImNW+Hf4K~{W3FMWl zAC+6%Zeb-GljWsfykn`rKGkgP&fFeIUFB>HWT;tOQ2s)n9thowERv#33sYeS{${SCSne|!QC^>x>Han@NI#IxXA^Xa`nJcuLs1wp~> z8fuw8ku{;>is+?IMYFbP%tzXrHlb(N2usQSS*m-2bmg%rUBYXz<&KG!WK)D6s1<0_ z$XNkx>KJ(a*cTy>=1o^zq)2J-n2%l@S79q^)%ntUXm4^wI$gEQ-ne%AM6=YoyDAc+ z{{V?OsB@ybJ=CSB$ljy)W3ZNmTt7bKzm;j4rTSqY)qAD&D5c2dr5>Jh$sf?rp%fdL z4o#itdw0S^YV4^q&fokX)OjqZCof62xBeK-6%+TRkr66+O}MzhK!d?5;(}o+L+K2u z$H4v8(X@VMtx5?A`7u&r#VAlmn7rh9!;i|PKX`gWU^%%hr|_kO1dql}+M-!t9YjPd-A!w~7<$TF)D&;^A-9438O)gcvre1Z=J}bW?bAaf7*}6L) zdhpjo%`G!`&KpAVqJxDa+pkl8PWqYDBkQj|X@{F40C^wmsX-9os*ZY`ZHw+f-<|`YQP! zQ$xHvceXmLN5AJUG*9}8Y3?zePZeQfSHj4ZHq9NC86$PqOTMr4S?KQMbNj9ixD~2) zZO+T68?)RM9^ZPFkJvr{Rl4@hIt=g@eLYXTVzmv8<{?Ru0xRUhQ8MkIZIcj>-)|jp z?NznOD7Nt)6h-KSUF_0VaI`5Uu$c)z69Iqh-5#BR{{Ya}mwu1s0tTZ}N0``^+NlqG9>bXlb*!js-d|OBAhVez=?g5gp%Y&^;>Y>80&|f3;dnrJ0d;DOn6zXT2y4uqrwOPDDv^0?--112%5>h$F?Ytg8kr-BO@~jh5Vua`o zVzmDNqdy#jXS<;E0F0z(KhLLXzO!9bI^iNsjv6$sw+yIXbX?@(-*!juIUJHw3W4By zlh*}CF3GAjH`cDvZ>-6cJ%}2av4*d(KoM?jo}OSFe8c|$pK7A9+gOpQ3L`c&X0CnA zF5M|4TdspYF+#dz=Z~#ThWm0MhNHz>aSsk!Wpb$Jx>9|+Q>5P|F##60;l{t(QlhVb zxDbb%$j9uR>XT8~1-$JS15e#m9e07GFOMv`s~!CwWu{JeB@W{W2d`hXbYFneFy4y@ zX~->=mtS_vtkR~`a(-_qM~6+022`oD%oM1jP{QO%XP_zNB(ZKCuLdgIFTaV9!sb2 z{Pi0q;wOrzjiB`X&7>+dpGj(ro?~0Xw&_Ekw={R`Z)t~=Y?lj$l+Om+s$dxr zN3vYO$oHtCQ0b9SrdITuW&Y6_p}0zbS(w6ypikne{Vh5srhJH(h;J6q7N#emP^1CC z=R6KbA6l_>+q+iOy(SH+V%DByX`7zKXV=SCG4!h4C#hh^lOi?mR?4(IoRme>wv;^* zNGF6BMnTvm9W1trG1Yg zv@lq<*EvzcDpA-D@99;o>T7^_{8I`urc7Xo5)?D>kyK;8Px#wB6YW&) zYuf~?k++9%^1ljZ##0ZEzeDz^E?dycn~6$!pc7CNI0g%)jD$U z%A+?LIJ0R)jDLDcpgLxv&wmvMgU40aDq~h003td36kDW+s&37B;+`q=os`+t2J2L zwm#ss-5Zk`Sw9LMSIhK5`5#KApE1~T8)8Er-Y*GRQD_c08R&b|pf4asLy*xb8wP8^ z@|8Ch=E}lRgn|1dkgmDuSF)1RjieSPvgzesWwBu&{t+h?lZzr8xel==!nWUQq?s|B zT2w#6vB{}M9J!$>iFQ<0l1J=PVu>7^y{4`SYf)@fr75(vy8U?+`5ht!}3tp3Xx7EAhqFvr82kJ)^ zJpm(Bc!Q{P{gzC-)cDt?q`HvIi^iGQqz%9k=P*D?$2mRgIU89ubAZb+qdn!cF}KY! zna7~qcP6(}uT!POQVoUJnB^?mu7XMz{{RZAkSVvDaSO3UWEFXl*J#xRLV@vF5Y!G_~sIbU+fMM}iJ`gz-VfIM3nlUXQ3OkXTDD-kN^e zRDxs8lG0>1ka7}M**M9noiQFO=6NXcN5Mn_Xn*>C~98!8zg|@N*A6w9Q#+W=!b1_;GS@OSFu!g5Z7##0T3VDxt_QH}5eMcg$H2$K8rkrBnjWsqL z7M@C;lc(M(6Odr=o|I#yd3SZ%nV>uqjA$8vm?fPa+{ zmKwM3TS_csa-<}JKHaJ&i`BP35+KTTsdk%$hMZ9m)X&1)(w+$30K&My$n>iQA9->g zX5$pda1L2oiXKSrLH+8r;*61Gb4m}etoV+r9mSTXL|fMNCu-!5saL2U*0%8VJ(^2t zTQ^p4u8C5BHRn<^>&roY))#n$kW28fi7h9(lAqqkpsY@+VT)d)YHfu`oo8*P-4j-o zd46{#O}O6*0AmO5STbl-j@}PdmO4+9>9!S?gQ##i(de}FsXr2&j2iLH1Fzg0XZx;@hNP7K7os*ZT3i^Z%sGCTWHBjh>n zQ4;z<9Q~-Mcq^&EtkiE^6HJD{+O=~~<~&lIwtp%`sV;Gm$9j+DsR|iZN3BSsqAk!$ zoH_P2X3_IN!Q!Bn$pfCWMz^zo4Kz^gM}C)2fD zBV)4S)<{+bJ#zK{f_l*W?DRMk$65+dSK>aj5J~ui?8a&%%N+Aj)Eq*Tr7Pv^C~I&j zLvo5mI*o~A-H2V}Skf=2-(nzEfSsEw&>SP4kUB-U3~Y9{Qq*x9zR zaDGdzIIoCsq%h9>6OQEiR{R)T+051IqlORUqSCiHTF+fhlJ(8IEv_sQgxy$-UwzW7 ze!&17ry#2&bj4l;w*A^Ik4{5jpronLsf!3{I0UK3)W5{8Gm58v;O3jNT-dSMS;xjI zd-IzM^H7!G930>$n$-Aj;o`5XzjKnqbD*}B70Q-DhR7+(mZcxYcAlQ&+PwrZs46^0 zZ1T$P$oA;cboAuh+F^gLlCaadp7z(s$_VKRgZQzY6g}$p6z8nhhF7PU+b!jx$+!E1 zA^VJ?I;EhahO?8%>BprPRm+0>n?2H1(naCu?R?jH>fITftA=66z&Xa=YBr|0%ZYMs z-EWO5-5K-+d~59yI#}iSkuTpP1!SOcVZG~ll6~83U4S8_lAQD1SGy7LL>+4iZ>I;60l$e)P#C}Q~ZBE(P zDh@3Nkhk0?2{`JIbI(ek)i>+@s6l<1h+Awq<+TDi326Wff|TbT**x{ApfuN+7RWZV zHq7Y*XYD1;QonB@VE*#*HY3bpVc8JeO?@@KaDaB@~zVYL$He5`@|t8HcQkrOt!%W`EpvQw0lo_m3wD}8Lxv$U1B z$(q-S496qPQ<`lImeaSMN>5IE)b>po?K#H}^1A?{W**NAq-F(C@b zQbzRvRc7T+b@hdZ&k40DYE#RWG&%Ck-6n05}J@YhW^>PA0U=uKx_KJPq*h)_WuAJozu!(XZ81qQSpSS7dMO!jKfStnF~|M} zk}DG$L)WPV<+e+C$x0hi&OV&gQedvx2=#j;lCE+806bTy=nDk-R?{YL>*rwg;w{e46)pzi z+qH&`Dh`dQT6ZWXfu5%wvx?u?d^_*KqSlWl(WkXD8%mWg zMqLak(GsMbyYS};Cvd+ikaz;D zrfX(38j`~2Xzg0I0Ycc+@E2^jCke(p#N#UMpIQu!0d&$&MZ3V!@7&_Jg%>XV8Q!*F zgVW0W?B_gjS&dDstP&=+-NNfOE9zNoF?Ge6USd=_?r{MAW`Zcv>AMu?r7r1hx9Uwh z6ytPFJ*q>|(5;{xZshU_>rKDQ)~4XM;@q`Euuu{ZiLl~Hb9On&B;;gPRtAF5cVek` zIQ|;lGtL{;a^MmF0096BEbTGjyfBnP(;gkXe}x+IQpootDxq*T)XA;u-w`6anKrBI z@zQuyTNDH+{m2zLj~8`&t4tL=e@koR#<^+uZYKbYq=cmBIjXPJIv7O?dfDM*G=bCm zZ!t?BVO6m7?}u(nQMOLga4skCxoXrB)B*nhd1O@E`4?(5^RD*U?RipcnmQ}(;FZm{ zJ8FC=9x&n#1xSS}YvtD$R>rw_hoL31)7J}OK7ppYI@Q?xwqU586W-)Lk6BXB^TH@Me#zO- zb9UO{=ek_tt(zLw_JHJV0^mX9I~!KSbtmXDD;;Uqx|N71Z&BIhp@k3v^%ae|Op-sw zl#KQ3RM-Ci5ba`cWA#Ue5|)2;+eEfc{#9bNEljA+@I>_&En2oC2jVm>nZvpn<4W8C`oRI3k@N zMoba^0Qz3=<}T7cSz3F2fpCu!^!2zYE53MvZ3CjNdVIP^*X1=1 zN9hM?wZ>Xgt)R;|#FNW2>M{A|#7zyzE0CY(RKbo0qpet{J|uNE}Tj0M~E zGplfnq(;*(=Lex1k&5#sd{FUamlYYS=ZG;Dm2JxOlMVrc$jDA=e9O0p7r1BRb=Qdw zJiG-m#*_$FI41=-!NA6U`V`-E$oo`!Q$91>WgtH$_o`&883>D~0(11p6;$_*Hs+=& z=HU37ASeBWnV^sUR%_0z{v0afA;+$=ylIr6ua&mJhBSKpd+j}&b2)RvK9@fvKEbZ$klu%dtQ zRF*s_x4#kjjo3V3Qb66AHwN2L`T9vC#g`I-d4BBDLBP)k13sO4(*4Na zY>BGz8sQnO&iHGs+hmM~69p?c_w=eS`bU#>o*0LsH6m6|Iz00iKWex+HWufZz}Rs+{x2eqZe&Z14%P=u`eke3EODo3hgeYz}|zUpgpL(TB( zQ%p(xD~TndkLXQAb6md)Xhq{jYdB=`8;*wDRz8^(NamlhTVy3IXj}cYr1WWekxNMX zZmS1VWqFX6^yJ2fAG>&7{{YW4t5U>zbWeB1Z95O=Qgv;-j;Why4gvMsnp#D#F^m&u93-{p#X2g}2En^L#kd@l>DQdsZx??g6R$!B&&{dXxG(O9VeP z`*TU}=Uosn^}?z&$=Wv9-sv~pG~0>m<;xE&eStOSZfUDyF?PIsKCytJHpYF~Zvmb% zLFTKj8g?Ub&|~VzuCVCHZri5{w|x{%FWoju(wDd26*MeIRuO-3=lUc=x4NKy`*B!X z-;9Zl(!L$3D`@mwk{U_-X1d{_n3LtS;lbs^)34J*O-4%~o;-Q{HOPohby z%}-@bvBK2U6XmQrR>}FAJ)K56b~U07xGH>>mus$_xao(IxKd^56}kSGV{{+ZT6$PL zStp&j&)e=RFRk3X>Ev7HM7fEV0_p@i+K_bqeg0O5^`Pl_Y$b@d2B9wM z^GPX)^D&g`p(nNnC+KSV&k_7Yff?7$>s)DwXc_Ykk!ewqJ<5i0??}c-iKiNYsBbbD zX|E17o6?p85eyeC_Xic!r2%QrKzbbgst~iaa7_DLNUuef$pTM@+KxaXOJ{M+Cl$_c zlgJ!np44d?iYC6c2eo`?I;uNu5H$^%l(BSDgm-TjlLx=UIlbN5^HbwsO zn$OMhox~AuqGqP2x^yx@lvi8skx?4|VEdDQ)_B?xIitFpM;@{xC zsHXm*zDNP9cDl7Sz9g_n0TR%j2e~{9bHJ%L>vOhi$HiUaOwyAaR`2Zwn*F7|Pi^G@ zU}vrIkhT4a!Ww%=~uTlhn{IvAv(yy?Yv<9 z>K)ra>ODanxi-1Ff5Z@#1)*@{qAkHcQIS&qacY9ry}aYN3R5^ipjy$ zDAjV?TQGEu{nX$Vu22yYMm;xR9M!en-yNBzWKAV>u_Z}LTuqtfN@!!g(c2`Nk`YCD z)Y^*eFd_MN%})&{YGuc5P^PG7sRUpjYAmf!tF0v{x6Id!Lm!2!wXwU5TzUn9tPjek zNskiYAO5MnTds4GdA467Dm%TqoO4i^L}e2d-dX};9ew2QANjAL!Q|D z3|^z6GwB|ivNC<@&PwCjB0DUu=@SXaUAq#QSNu8Y^rQkDjU%NZT9hJLBa^g8QQCPPpL(HAkV}ORw{`f~{hPeSax~Qc0K_tqY6JSgcZryZ0^N5~c?mL1 zDWLj*PBZO_w;m<665hDi>&^bi*x@YRt)g6iqFrof6ajZ{P2LO@vl`(kaf#28`yxw2 z=z9MEIwX6TBsU?pJkk5isI-6ZsN`38;!6RAU**ctPF(b z-}=-r10z)!?XG3f1(uR`BIN*YMl;dG9z|-c^Fxq}v7|8cc`4oJ#`)vBpW;tr>M5~f zvhqUI zd@q`UFtioNJu{qi6?0>-x~o!~RlYOsyy0`q(mIevaXg+&PK*_FHAxyo_g_L8b9?Go z9(}RIt0OqcZCTIKwyi<7N=s&4`V@x}KWK~Ik#QS%{{U!$GxZfT0iCos%n|V}>Nb=H z5^u0$%0T+K$lo5tE2bZU`UmEa zQ(VTY(>I9Q;Ldk&{{Ue>%jf;loYbB72@7&9t*fe@UP4oCy|&O-{4Xa5=~c5dZo6#a z{cqHb$LfViw6QUqd$`X^5Cvt$xE1EyS0yU^EX$6S40|mnH745B7*4I&@nk`DLkfQ1 zJ;0&v5VE9$_NvQnpRwEJyCu7ON}lKAd8eFZdH(?HYB&`-Y&YI*Ewwr<5=Y~>po9;q zlS~BbwxBZD5z(skU_CZTwMUwP^xMz!s7u!N9WiPx+;yavlmR8F8XZXI{rq53M$mJm zt8{4Ee^yKAmo)yd_(zfUR?(WZYr_3VSwJLB z)2fpXZ?Ynx@OEWH?)kdrR`rKT!eNdRu;*tkC^&T+}> zK&-}JG=0+JIz;p^Grl^>86_DHE$%;3~c z&Ts4$R^#kX2AbHHZVtBWkYmMcjf?V>yZ!2o3`_NuEK z3`cFW7VhdFh1(30+WK`uRu4YaOGBbk?IP-2Y8%bSEfj}ZNcqxE4=FvxQEEFwcE?;~ zI)$wV94n`_X?(|i17bNGAxD$2#7VO(uyd(xm@P}s>- zDPIpUzpG^OYSjs1OGULPm2LcEu4$>$DR>tnFwC`%N(l|Q_C4yIw858F`EX>*LTquH z)Ds^G^PtI4iE?BlfL7Yh0)X_*Y^S>-bQa^w3Y1y{$#K-|lHNNL`_<+PgSuSQN}jY?t_X5VfPA*wnt~>!Aarff!t`AK$i+NXB`r*0$5=o22KwW<*rD<-rzIWgjj|c7haweSHmO&8Jxr z)Y9tZf%bYhxFxv>Wly)~5D0H@-H%*VfS14-!S9;zyG6QAtMSH7*||$@HJ;xY<)tM2 z;YbN1>5_9^z?mLux9<5>(2DaJ@H|qZ+-Ws&&kHYOKa>MU0{{Em|9j~VGz z&MXbeQm_Y8npv~L93XM+S;sy*TLKTo^=09#5>G9&~Qw*o&^zUj^e2S7;4Aa@nJ z1_zPRgHfrDDBY1Qvn{%>R8P3-87@K4T3xX=>mdn0=`I31xXB}`maK3&z&-I;-4~$l z9xUoxuZTL0Y15XO?y2j2xuTaR$w^k$T8_~fQb528$2iCs=CwDw&9lTut)3m|Qq!6q z#+M~ayIj(?Vq9jS3UO}8a|DLQI7u9z@PyHzc$;p~fplXkqkTaPbNzQBuQlkQhB(zD!|F9M&pph%ji*_LsV-m9Cf#eeKvaM1><8A}!Qa{d43eaj zgp{W{eaA{Pn1M%snQWyfU8_D|7BPEll)c=D?dZv^;UQAw(4dM;OnpD^p>Cq?)<9>)Mhv zu2bx#BGaxc0U^BPE9cM4lj-lz6*a)rWVGLaH!FJ)?hicu&SaGkBqAc82q7e#1T6l1 zdRC(ArQ&I*@msmi(~%}AK3?nonz^HLak&7HqC2RQ>D#Srpm;zg!$hcB4^8T5>2x}q zj)>F_MpJ4@IQa_jNY6puq9SYhHPpwK;*P%1u8t8OQ$YP=9Qy-~MJ*4yxG_uc`B(Cm zVXwjKxNzV?k8zg{Li79~O9dqTYee3LxcF??mHmv>{v0S2Iq~{d?ryMDukO^hn^RKv=)!M4TvnDUzWZ2XMUHbyR@=0QQqZc(V-QkrwjOZC7HTNGU)mN&V0Qv(9=| zou)L6DxZpHNO-B?>iX={l2dYk4nGkGJB2bwiSBregAQBX9(bnFNo}-GShd|b=Lu4P z+Ia0icSOt5Y$fz)9};wXZjwTDMx;#idh(o*2iAljEkWZW%3E!};)SmI@lZ&%YfTK} zWCOqoTWy@2;;?snyE@fbc0Yu!7{@NE6gkmPp(o`7zC9~+&a}QjZNn1d;`^yj20he{5zYJI~^hw17UsrcctJ^*hhMDPl(7 zkuA6yN`K-#KD2a(qSEPivuQ__9Zs|so;PpCPy5IEim}tTOFXuuOLcx9NL%SjJiSMK zkXArB0|VtxUes+ZanROXHxbLMy&|wz<+${w7j%-7kQKlf;n zB$n;>%^V;wp}-@~Rxo;WUWkkfZI6v_`cf$eH@BjcFnyYe^32?-bybGUy{1Q`k5i#t7-@kwJ6A^u212cbB7^ zOUXx=i|2<=;eYQ01C0HvIRnMJorYAaW8NdfzqF8~3onS42NF?|I8)r5fyPP4UMdk3 zY+cjNr7lT@;oGsDP(StehMIv3Nm`S&MYfczql|C|de1*`*7|Ivk*&47I5LP=!|IJB zZY3c6yHvh$$ml;V^+_X8X}Py$&TGS+J8+Yp9@6@p#@KV5gk%i;s?G+Z_^X3!WB!=z z4ktKGvU+Dm9r|SF7^X;yAkWo$T0O-mz3RO-WIctxWau}WaYKMrf;#Yi^$i-M!+T}P zZpYL%RV93-G4x{8rFx7h1Y;Qd>(SWO8nu+4{+;|ldM{0~^gwyQJ=7CbGH;TukBALP z*Z0MeR9O8)#582`bGQOIso;t+GheXYTr%x&Wy)otxF#l(X`JKJBl%R=TF=f(S(3)I zZZP>$+d5J)Yx4sptmNLU5ZqL0S}#>9Qh6wob80w0_*FrLAvdQKs5%Py9Vq-uV^yQf z1Jor)nm8R2Z8Ef_;qryt;WFY*414vJ*p46gfW==;(2=5+=h`h7RvL2&RLwneZfsR# zfJ)d|BmvaovQn@2C|~t+SN&6Hj80V>QbB5K1oOw3oSap(Zw~(eUE9A{Ga4k)-c+d? zMsaRZPpN2*4oy@?^qQ>$sU)e#wBvey{{Sg2B`9H+RcN_|57g>Y;TK0xwtl5!6u zcQtD4NC50OKp;flQcJf{ND~*`|;tx{Y)uDO-Y{w6x&|ukL~T zsh_RgdTDj&dZDHj&zQY3p^^UphNPoU-Gp&{t7$qh<599>4@dxO|lBOb;&wpt$6wrRXSeK*9in$JX^M@ z#hi%OT@>g}N$6FKR;{`EmdP}@)bAA`ZzOM#Yi;m8yc|{U_N!h-_+>-Z`ocqwEQN>k z%WtI#$L}B~BOK(=*8(1V)s>jCu_{-Dmx#UBDiiVCBmNZeM0ZB$i^IDckKvS6tMMhR zMC3L4Y%MnL7T6@^LM+(w4tkz*L%L4C-4>Yb9~l}QFyh!}Kk)-t z=-;CjuX<2@%_FJd#Ovh3)=Vg4(o607fuE2-50Ie zhF^&TTL!?%dgZ8F-}S(BVxUCR8oy&A<7?E@8(GHW>RqG{UK}-z3exu#DG~e-Ei4=t zX6pwyKSFBrPic3Y%A;uL5gR9l*}ex9KIDoCNU@)y^&81PPLm;2TBJe!||3Y%|jM%0=V=Ri9YXH94&cHHL083JRJ0!b61o(79-c&@`oBu)_&|RAr`DoA ze!+Hc9b&IQYVhmcTsE=wzHk8nxrK~Z_|Ariw$)0ZYkWkH-BPvx4IF5_{tgn3f# z5XyQcRBhmWat&4_O~Y`9#@VP9Risy8eq?zI-LF|ARv{o5_BON4!E zV{#U2gwZ9fK4PRE8o4+f{Kj!YoqU$lUQ{JD5w&sT!HkrH{HPv`^{YW?%)DQb?T0o(AW z^P~XflWk{X%TC}q3Hgn6R7us=$kLlko0NFKs{suH;=h(_r~=ym0Hp6X{F{qWB5HsG zjLC+y{4M2zGL;T~S8@LU&!#+ISZT+jtV(6IF)5-#n^3_%sXpSpNKDF9`8X{vp(XRl;1*HN~3KXleu(g|Y*!CNSG^ zvpDAW8c^!%XVCTHvF^;9e!CcJLS#W@pr&qM{P`z_mY)1~tLQhEWw8YU`Tm-e`I>)J zv-l*Wf8C|0JQ4VL=lRro5>SIJ%af7qQWo)YOLCmr(wFK8>N)LOIw|b2g6M|IBQd5f zHxPmN;a}Ug06m6x8l2qMf}UNwQEF{8_R<6^jonk@WbgspPI}dat$JrrMl7=z!>iP> zz-83F+=crG2L7-x3*O4kCvow6kTU$l0W2%t>)5-Z=BjxGq=~YO%BDU>lMb7%n z>w`bePW;TgKf=silufrJw3Z%Ris^2W3t_vj}m>)!yBLky$!rMLfQ4t%Ax@J3M3omta4A8Z1HBvbbv|!0IPMe z^3EC%1@7xs$_gK(=;Zzc|&Vb98zLUQ1vMt4Mn+G^#+RqN^M$- z#@|yW#uX{1$F4JseQM(Sp)VSRkgJ!3nrBkBqm|rrbmX$UdW@WtRq<__LqaX4;p?q; z;a$LvBHy5`_@|~o2^b^Xnqnxga^)vk6eiuSZCZ}u-ZlOo!9EXkm3K8EaIacy`@p0%F@%MryH&M&fcb+KEZ0^HQJ)@Y_Kwkw;v5B>E_;9 z-*nQmgs9p6FD)klI0CcRO@%$#lqE4{u$xw0`bA2*}NUQM!TkWz-lcq zY;i!Bw7ta>BVhLl>yND<7f}_aHC!agha*gD5~SfVF6|i8QSHAO2a01_Z4eW-J%d&0 zl^o>T(fZO3K8nv2=z2)B;$(LQF?coSdEM&Q*+c;QLeulX|mEQH>tv z%Sb#((|0ZEP~)A1fM_O&l3;11%JOc?(es{H@7(O`x4)lt3=Gg4(2I{SOn`2-yK)p1 zr?jQ$tpz^2PHH}?@qYNjjoV$-7kI2KBla57!rs*$;5d*@J?Yk16Q)37YP7G!#VR|m z8a77sB9B{ z3WIXe59x?u`v~|JTwdk05+%WN>NE1vc_h+33Ogu6kyhQXsQH_j9}x-D@6Q0yB1Ycx z(_D#lvOJ`KnYvseM0Y>lWRt~b^wsKo6_(^$Q)mjt_u-(u4Sz;~Bz-Gq3|emm34?F6 z+$5-Ba%{H;ZPIh=v3=@CGSh4}r{o9cJ8!s42Plyu1CS*B_8excoS3#?B0Fy0+;K`u zQi~=1rk0RM1nqMqjbT zk~5w?XaXY|u4@sNl(lQRAqQ%jyxP;B{At0XTd%OA0VmQ&VGSUAnX|n{<*!0}@&IXU`*{;|Hx2 z03Fu1mlTNeWuP)vac;fEORgkwj68rp`HC*0c4LuPTw64T6mWm)^!!3rKioMq1UDnY z>8nf3T%tDF+N3`eyBFK`m?N?8LY=GU>ItcG+X4jgRsGGj+oC`J_=ofSy|GRWSQ49bblTsL(Fa*ty9|t~}Gu1Nc-tw=+_NGcsY^;kx73El9hBZVFqv!bsW2`$5)@ z$!<$*stfQWxbH;33c@|qO<$^H_Tzx-Q&Al~3gMXu$Mc~sW3m^AJ9ZS6aP~r6byzB0a|L`ugp>B@)O!g-HUD zqD8&9(~P1s_Zf0o$`(~I#7&AwSK&g*+&KQVo4Lq#1jf?Jl9qA@nw4%r>Ne*;)}1V0 z;#}k^Tu2Vvxh<*^l!c^m_>!TIUX@Dy)&iuw#Dyfu+_eTgvfGIpff-5sLkFm;B#UBa zmcwmNl3N8RTFSPOfldkD=xfgSb*npE=Y!g&`E9zqqXi?T1xApBd8taWeQK6~%v>(* zJ8=-MGvY{fT%{KjsT;k6gMd%HY}Y;<<|4qI4nvT_b~zSuvg-@%sPX~n+*V&xT4CH9 zOLH1ai2cZL_2Pgn$HSaf7Ya9?_P%67Hh?!SNS&&hErKwO>tt5r6k~FCUj6Z&we`h>t1IP zB%6J~0(&zpHz;5d^I!Jca!;AnkFV-Or)vng*$g-TDR5=Tw+3 zJhxsbVZE%WYRBON@YU7b7IyAZBD-yK!(B|`6jBO&-92_z#3?1GcWqDN=V;>~b;zi4 zrLxpyF)lNfOQXqCPI-ViZkV1hQ0F^U!K(ns_<3+ohW;? zE?KHX!lDT^DXFSZz&OWx2BEP_(=yv?;K#wb>UX0m%Wez~Qm*5g90;-`G#&_2nIwR; zjl=m@o}Q9Po~|1B$z7I>$y28TlS=E!WyGaVww0@*O1aHbIVGtpdK(v5&?P73Dw1|> zKSNoCXgj=Vi$X3FYIyeL6$Vem%{v~H4 zt@dc*OVk?Z^zRMzUYYS;*!nFOH4CE&1#kmOE`7a6xTx1;>u494R?AL^xm{#RbIN7F zP}^l#&&;B5o}EqwdGrlA4^2vv?wVZN9;&&Qm$n0lX^TuRGl1)fIV#(Ok5SKRq_}AL z*H|*|Q+1u)Hn_?S#gycSJ|g1xLK;T=<30ZX^{4wdl)q4)XRKW;H`^q2-26npS#%|} z4ka!|jPRX3#GboS*-+`nw_H{V<>CptST0&?LR+M1ohfj6YM*v}d+wKJ&l?nfbRK?D z#tHRSb6Q8jYin~1xv^CI#E&qx`-)eVKZ(LlKGmbRv||wR6g#&dyGGj$7Eg`b1l z0n-z5Qztf_NOz#9M~5ics>6v6T!g2X`p?f@pH|b-L9VhF`fi z@a^)Ls2_;>kPkn4b14OS*y+gXV@x*k-15UtT%;Y+9#297k10v#rDm=6jaPkjPsyI- zSk8523Q$V@$j>M1TI(&9T9WPL+9LV8P8Ry|me4&uwY!6%W~tWL{bh_h zWNz+|P*amvs4!P@MUvsJY}R)at*rjL(`#h~DkpCBtAXDtCl%=yJs~oDKPE4Sz83hI z2`K=%r(`u2U!s&$tl;$MMh{Na7B>jMTRbSd=-Yr@!$fMmCud1n{cWRf&dWjC0)n<~ zQ3yV&$I`X7`-?iYmg8CV{+-paQg9$^cv~GOs801H6`y{UNqIU$MN3mJ#PL7D=M=L6 zpYe3gYAS?l$p-hL4c0g78_$GR9a|Mg{{X6f9^6V*{_t%I`HexhTrIYF4YLcxpAvjD zN^p?}R@t1F6c6DYfFAvYYA;$ZMzGh#tHC{Wq%I3SYA!koaGZo;K<-W#wLeY2PR6Jt z`0aMm3iwEVH(qUMY!n+tyV5Il@)P)@T)A9u8^u%3jBjG39JSo@lmyOfIskdC0 z*psmEYbkW_ZT%w(T#G)4y4u@Pg0p4u0U;-hWDFjd?rRf$@iN_fo}oHFgtvB-qD+Ka zWW;q`=Ol!u6g4^F-NgY5(V9*}y!`fi+{F5R?;igE%As~y!66G=R@8~@_>9sNFsB;U z4X?L+kPQWJU^-Hk+_<{Q=$oa*Qk?tqibXRP=oq%x^m}lkGMpEZx10evC-E%%p0!}c znul*~K5Vjpo{hFT9^*9ZS`iHfBkhFCG4ML29GXU*l-sO>8btR`BqrYEun)3ANTS1t ziPAzN+}w`ae~86~xlV)5K2SZYWonr_Px)>?cJW2{#AGJXYHg&BqoKj1{t`rMTMHY^ zw|DVIc8*&=vk?CPu^~QJ?*Kp^{l#gKJzgaS7;D5o54=iBR$FE!udw_CgoOd}GOh_K zP#p(au4`G7e?yD$YgA~+Pc$4K+MPHgEouh@9B_ILeX*Lldd#h8Qn=S24?IdiB_wVA zTXI3SnkczHERJFl7&^tlX`6yZn8=gV&+J4O#|o+>oG ze(K?yLmn*Y{c?kZ{0_IYI>NxlHkGUKA98b=s<~LF=_v&(gRQh2a0yVityc(X^*kO$ zTiOD3ot(D|#9a-j)Sd$xuGk^I4oSvwpS>s%UVz=XNtEV@yIGJxE4g@eeBOn7`FX$| z)Cu~A2kL1GUbem}8W0JKtu)b;QO*wM0!~LDf1OVL#ND*h9~J)qN0O&7rDOG`hQ04X zvNO31gpxc~rWaGb1)H?&m8&;US%nZxZ z9vxpACkb-hDa&iB{{Vmy+qE|DqqKdtQVbm>s&vWVCT5=08_-X&T8XXI;zpR&v1CYF zo)+Aea-z9V-Gd>2O}OW!P)D&^quL#TZdQVb+lwx(3B5~N@^CsG*aMCQO_4=3+ja4# zcG|DrCrmiuGId)lBz}0R%WjyzPmrbFu8C!|f=X|i(756D!VW*3Bl;@ViRl8}rfwMF z$0MQnAfIj6%}AHwta;e|TMleLRKT$9PuP=+VM$Q*^jBt-Da|1nkP3YFuCg6*XB-6e z2Q_+~H>BQ*sCP!Rd^X+rJ~ms=Hco$qW4&jz{{V;a8GTGW1H=W&=MFIv5J30Aty@-e zYkT#4nNwd-UU7JW(J1F25Rr~40GR^KrfqJah{;QCG^3T;E=qX|80m%Q6gRDr8b_TW zqErZ0T2W=fM^Bh{_p1n+Nk#QPZ--%?>iJuZ$@q7{`WymkWLef`o)K=El5Lt$;77Q@ z_?U|J^AX7U=AjcsBK0Mr`Q)n@?Hq3O;?(U7Qd~XIj9M2z#le9R5bAEgl-ZXSuf`$#Syl z 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%0AU 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%0D^UCiI3JZQ*Nezf@Dd5Y0;QK! zrQnV|Gl!7g)>q*x4E!@pB>ws`PxGj*YFmt}fpSL0v_~N$2Q}?s|Sg zs=N@?wvP+^M`ux8H9GCFEu^FMQ!PB}lTx%0QZP29Jq}cQfyYBvF9xn~Sz(kQOH<0; z_gteLdGrGn$~AmucRO%pN$c>E-f$%J7(Hs*9l9JKXpAos_5CZGhOisS-~LDxfQpHR)8~(qO$RCDSrSf3m=Imf-3G6 z!74!cxTIF&L#{SLxu#c)fK5$x)U6wd8K@WwSSKB5B0AfAj2f8JWd|mhX{;+C@l!Fp z0sQC_A8qnLPiiOQqEOhhf-%ALqdek`$d2VbVgY>Ovw}aoBVpWY2c8&Ns>q^F& zFtc5Cmm5M5Tq-=Cz

    h2InsZ5pw(K~P?=p$~(9^TR{UwFC+WrfgJf^a= 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 z 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 literal 0 HcmV?d00001 diff --git a/well-api.py b/well-api.py index 8389119..c35fa16 100644 --- a/well-api.py +++ b/well-api.py @@ -83,6 +83,8 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) + + location_names = {-1:"All",0:"?",5:"Office",6:"Hallway",7:"Garage",8:"Outside",9:"Conference Room",10:"Room",34:"Kitchen", 56:"Bedroom",78:"Living Room",102:"Bathroom",103:"Dining Room",104:"Bathroom Main",105:"Bathroom Guest", 106:"Bedroom Master", 107:"Bedroom Guest", 108:"Conference Room", 109:"Basement", 110:"Attic", 200:"Other"} @@ -100,6 +102,7 @@ Loc2Color = {"Bedroom":((16,255,16),0),"Bedroom Master":((0,255,0),0),"Bedroom G s_table = ["temperature", "humidity", "pressure", "light", "radar", "voc0", "voc1", "voc2", "voc3", "voc4", "voc5", "voc6", "voc7", "voc8", "voc9"] # derived smells_table = ["s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9"] # derived +s_table_80 = ["temperature", "humidity", "pressure", "light", "radar"] + [f"s{i}" for i in range(80)] s_table_temp = [] @@ -186,16 +189,43 @@ user_id_2_user = {} smell_min = 1 no_smell = 102400000 smell_max = no_smell - 1 -sensor_legal_values = {"radar": (0,1000, 1), "co2": (smell_min, smell_max, 31), "humidity": (1,99, 31), "light": (0, 4095, 1), - "pressure": (0, 10000, 5), "temperature": (1, 60, 31), "voc": (smell_min, smell_max, 31), "voc0": (smell_min, smell_max, 31), - "voc1": (smell_min, smell_max, 31), "voc2": (smell_min, smell_max, 31), "voc3": (smell_min, smell_max, 31), "voc4": (smell_min, smell_max, 31), - "voc5": (smell_min, smell_max, 31), "voc6": (smell_min, smell_max, 31), "voc7": (smell_min, smell_max, 31), "voc8": (smell_min, smell_max, 31), "voc9": (smell_min, smell_max, 31), - "s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), - "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} +sensor_legal_values = { + "radar": (0, 1000, 1), + "co2": (smell_min, smell_max, 31), + "humidity": (1, 99, 31), + "light": (0, 4095, 1), + "pressure": (0, 10000, 5), + "temperature": (1, 60, 31), + "voc": (smell_min, smell_max, 31), + # Keep your existing voc0-voc9 for backward compatibility + "voc0": (smell_min, smell_max, 31), "voc1": (smell_min, smell_max, 31), + "voc2": (smell_min, smell_max, 31), "voc3": (smell_min, smell_max, 31), + "voc4": (smell_min, smell_max, 31), "voc5": (smell_min, smell_max, 31), + "voc6": (smell_min, smell_max, 31), "voc7": (smell_min, smell_max, 31), + "voc8": (smell_min, smell_max, 31), "voc9": (smell_min, smell_max, 31) +} -smell_legal_values = {"s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), - "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} +sensor_legal_values = { + "radar": (0, 1000, 1), + "co2": (smell_min, smell_max, 1), + "humidity": (1, 99, 1), + "light": (0, 4095, 1), + "pressure": (0, 10000, 1), + "temperature": (1, 60, 1), + "voc": (smell_min, smell_max, 1), + # Keep your existing voc0-voc9 for backward compatibility + "voc0": (smell_min, smell_max, 1), "voc1": (smell_min, smell_max, 1), + "voc2": (smell_min, smell_max, 1), "voc3": (smell_min, smell_max, 1), + "voc4": (smell_min, smell_max, 1), "voc5": (smell_min, smell_max, 1), + "voc6": (smell_min, smell_max, 1), "voc7": (smell_min, smell_max, 1), + "voc8": (smell_min, smell_max, 1), "voc9": (smell_min, smell_max, 1) +} +#extend to s0-79 +for i in range(80): + sensor_legal_values[f"s{i}"] = (smell_min, smell_max, 1) + +smell_legal_values = {f"s{i}": (smell_min, smell_max, 1) for i in range(10)} def format_address_component(component, component_type): """ @@ -2077,8 +2107,8 @@ def StoreDeployment2DB(parameters, editing_deployment_id): VALUES ({new_deployment_id}, {CleanObject(parameters.get('beneficiary_id'), "n")}, {CleanObject(parameters.get('caretaker_id'), "n")}, {CleanObject(parameters.get('owner_id'), "n")}, {CleanObject(parameters.get('installer_id'), "n")}, '{CleanObject(parameters.get('address_street'))}', '{CleanObject(parameters.get('address_city'))}', '{CleanObject(parameters.get('address_zip'))}', '{CleanObject(parameters.get('address_state'))}', - '{CleanObject(parameters.get('address_country'))}','{CleanObject(parameters.get('wifis'))}','{CleanObject(parameters.get('devices'))}','{CleanObject(parameters.get('lat'))}', - '{CleanObject(parameters.get('lng'))}','{CleanObject(parameters.get('gps_age'))}'); + '{CleanObject(parameters.get('address_country'))}','{CleanObject(parameters.get('wifis'))}','{CleanObject(parameters.get('devices'))}','{CleanObject(parameters.get('lat'), "n")}', + '{CleanObject(parameters.get('lng'), "n")}','{CleanObject(parameters.get('gps_age'), "n")}'); """ logger.debug(f"sql= {sql}") # Execute update query @@ -3008,6 +3038,7 @@ def GetUsersFromDeployments(deployments): """ with conn.cursor() as cur: cur.execute(sql) + print(sql) deployments_dets = cur.fetchall()#cur.fetchone() except Exception as err: logger.error("GetUsersFromDeployments "+str(err) +" "+sql) @@ -3428,8 +3459,7 @@ def GetLastDurationMinutes(deployment_id, selected_devices, filter, ddate): data_type = "z-graph" time_zone_s = GetTimeZoneOfDeployment(deployment_id) - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided - + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) @@ -3738,7 +3768,7 @@ def GetSensorsDetailsFromDeployment(deployment_id, ddate, filter_minutes, fast=F logger.error("GetSensorsDetailsFromDeployment "+str(err) +" "+sql) time_zone_s = GetTimeZoneOfDeployment(deployment_id) - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) #Which sensor is in: Bathroom, Kitchen, Bedroom ? @@ -4021,6 +4051,10 @@ def GetSensorsDetailsFromDeployment(deployment_id, ddate, filter_minutes, fast=F report = {} try: + if before_last_present_device != None: + before_last_location = dev_id_to_location[before_last_present_device] + else: + before_last_location = "" report = {"user_id":deployments_dets[0], "name":deployments_dets[1] + " " + deployments_dets[2], @@ -4037,7 +4071,7 @@ def GetSensorsDetailsFromDeployment(deployment_id, ddate, filter_minutes, fast=F "bedroom_delayed": [13, 16], "last_location": dev_id_to_location[last_present_device], "last_detected_time": last_present_time, - "before_last_location": dev_id_to_location[before_last_present_device], + "before_last_location": before_last_location, "last_present_duration": last_present_duration, "wellness_score_percent": wellness_score_percent, "wellness_descriptor_color": "bg-green-100 text-green-700", @@ -4240,7 +4274,7 @@ def GetProximityList(deployment_id, epoch_from_file_s): LIMIT 1 ) AS latest_deployment """ - #print (sqlr) + print (sqlr) with conn.cursor() as cur: devices_string = ReadCleanStringDB(cur, sqlr) @@ -4724,7 +4758,7 @@ def save_to_minio(image, filename, bucket_name="daily-maps", content_type="image return False -def SaveImageInBlob(file_name, arr_stretched, labels = []): +def SaveImageInBlob(file_name, arr_stretched, labels = [], metadata=None): #labels=[(caption,(x,y),font,scale,color,thickness,line_type)] try: @@ -4753,11 +4787,19 @@ def SaveImageInBlob(file_name, arr_stretched, labels = []): image_bytes = encoded_image.tobytes() AddToLog(f"len(image_bytes)={len(image_bytes)}") + + # Prepare metadata (all values must be strings) + user_metadata = {} + if metadata: + user_metadata = {k: str(v) for k, v in metadata.items()} + miniIO_blob_client.put_object( DAILY_MAPS_BUCKET_NAME, file_name, io.BytesIO(image_bytes), - len(image_bytes)) + len(image_bytes), + metadata=user_metadata + ) return True except Exception as e: AddToLog(f"{traceback.format_exc()}") @@ -4938,29 +4980,43 @@ def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_so """ Optimized version of array filling from TimeScaleDB data. Uses vectorized operations for significant speed improvement. + Now handles mtype field with multiple records per minute. """ # Convert start time to timezone-aware datetime start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') - #start_time = start_time.replace(tzinfo=timezone.utc) # Create device index mapping device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} # Define column mappings (sensor type to position in record) columns = { - 'avg_temperature': 2, - 'avg_humidity': 3, - 'pressure_amplitude': 4, - 'max_light': 5, + 'temperature': 2, + 'humidity': 3, + 'pressure': 4, + 'light': 5, 'radar': 6 } - # Add sensor columns dynamically + + # Add sensor columns dynamically (s0-s9) cols_len = len(columns) for i in range(10): - columns[f'sensor_min_s{i}'] = i + cols_len #smell * 10 + 5 + columns[f's{i}'] = i + cols_len + + # For mtype mode, we need to store all mtype records differently + # Check if we have mtype data + has_mtype_data = len(day_data) > 0 and len(day_data[0]) > 16 + + if has_mtype_data: + # Store mtype data separately for later processing + global mtype_data_cache + mtype_data_cache = [] + for record in day_data: + if record[0] and record[1] and record[16] is not None: # time, device_id, mtype + minute = int((record[0] - start_time).total_seconds() / 60) + if 0 <= minute < arr_source.shape[1]: + mtype_data_cache.append((minute, record[1], record[16], record)) # Pre-process data into a more efficient structure - # Group by device_id to reduce lookup operations device_data = defaultdict(list) for record in day_data: if record[0] and record[1]: # If time and device_id exist @@ -4970,7 +5026,6 @@ def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_so for device_id, records in device_data.items(): if device_id not in device_to_index: continue - base_idx = device_to_index[device_id] * len(columns) # Convert records to numpy array for faster processing @@ -4978,7 +5033,6 @@ def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_so # Calculate all minute deltas at once times = records_array[:, 0] - #print(times[0], start_time, (times[0] - start_time).total_seconds()) minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) # Filter valid minute deltas @@ -4999,11 +5053,29 @@ def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_so if not np.any(valid_values): continue - # Update array in bulk - arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + # Update array in bulk (for non-mtype mode, use first valid value per minute) + if not has_mtype_data: + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + else: + # For mtype mode, we'll aggregate differently + # Group by minute and use mean/min/max as appropriate + for minute in np.unique(minute_deltas[valid_values]): + minute_mask = minute_deltas == minute + minute_values = values[valid_values & minute_mask] + if len(minute_values) > 0: + if col_name in ['temperature', 'humidity', 'pressure']: + arr_source[row_idx, minute] = np.mean(minute_values) + elif col_name == 'light': + arr_source[row_idx, minute] = np.max(minute_values) + elif col_name.startswith('s'): + # For sensor values, use minimum of positive values + positive_vals = minute_values[minute_values > 0] + if len(positive_vals) > 0: + arr_source[row_idx, minute] = np.min(positive_vals) return arr_source + def fast_fill_smell_array_from_timescale(day_data, time_from_str, device_to_index, arr_source, timezone_str="Europe/Berlin"): """ Optimized version of array filling from TimeScaleDB data. @@ -5647,6 +5719,8 @@ def ShowArray(arr, threshold, filename="histogram.png", title="Histogram Plot", print(f"Plot saved to: {filename}") #plt.show() + + def AddLimits_optimized(arr_source, devices_c, sensors_c, percentile): """ Vectorized version of AddLimits that processes all sensors at once. @@ -5695,6 +5769,93 @@ def AddLimits_optimized(arr_source, devices_c, sensors_c, percentile): return arr_source +def GenerateTestPattern(arr_source, devices_c): + + #smell_min = 1 + #no_smell = 102400000 + #smell_max = no_smell - 1 + #sensor_legal_values = { + #"radar": (0, 1000, 1), + #"co2": (smell_min, smell_max, 31), + #"humidity": (1, 99, 31), + #"light": (0, 4095, 1), + #"pressure": (0, 10000, 5), + #"temperature": (1, 60, 31), + #"voc": (smell_min, smell_max, 31), + length = 1440 + measurements_per_device = 85 #THPLR S' + dev_count = len(devices_c[0]) + for d in range(dev_count): + for r in range(measurements_per_device): + y = d * measurements_per_device + r + up_down = r + 1 + if r == 0: #T + upper = 30 + lower = 20 + elif r == 1: #H + upper = 30 + lower = 20 + elif r == 2: #P + upper = 30 + lower = 20 + elif r == 3: #L + upper = 30 + lower = 20 + elif r == 4: #R + upper = 30 + lower = 20 + else: + upper = 250 + lower = 150 + + for x in range(0, length, 2 * up_down): + for z in range(d+10): + arr_source[y, z] = 0 + z = z + 1 + arr_source[y, z+x:min(z+x + up_down, length)] = upper + # Lower part + arr_source[y, min(z+x + up_down, length):min(z+x + 2 * up_down, length)] = lower + + return arr_source + +def AddLimits_optimized_80(arr_source, devices_c, percentile): + """ + Updated AddLimits for 80-sensor unwrapped format. + arr_source shape: (devices_c * 85, 1444) - 85 = 5 other measurements + 80 sensors + """ + total_rows = arr_source.shape[0] + measurements_per_device = 85 # 5 other + 80 sensors + + # Create sensor type mapping for each row using the simple s_table_80 + sensor_types = [] + for device_idx in range(devices_c): + sensor_types.extend(s_table_80) # temperature, humidity, pressure, light, radar, s0, s1, ..., s79 + + sensor_types = np.array(sensor_types[:total_rows]) + + # Get legal values for each sensor type + min_vals = np.array([sensor_legal_values.get(t, [-0.001, 10000, 1])[0] for t in sensor_types]) + max_vals = np.array([sensor_legal_values.get(t, [-0.001, 10000, 1])[1] for t in sensor_types]) + windows = np.array([sensor_legal_values.get(t, [-0.001, 10000, 1])[2] for t in sensor_types]) + + # Process rows that need cleaning (window > 2) + clean_mask = windows > 2 + if np.any(clean_mask): + for window in np.unique(windows[clean_mask]): + rows_to_clean = np.where(clean_mask & (windows == window))[0] + for row_idx in rows_to_clean: + arr_source[row_idx, :1440] = clean_data_vectorized( + arr_source[row_idx, :1440], + window, + percentile + ) + + # Set min/max values + arr_source[:, 1440] = min_vals + arr_source[:, 1441] = max_vals + return arr_source + + def AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile): """ Vectorized version of AddLimits that processes all sensors at once. @@ -5834,26 +5995,662 @@ def process_chunk(args): return result +def FillImage_backward_compatible(scaled_day, devices_c, arr_stretched_template, bw): + """ + Backward compatible visualization that handles both mtype 0 and mtype 17 formats + """ + measurements_per_device = 85 -def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw): + #for device_idx in range(devices_c): + #device_offset = device_idx * measurements_per_device + + #if device_idx == 0: # Debug first device only + #print(f"\nDevice {device_idx} smell sensor debug (minute 0):") + #for sensor_idx in range(10): + #row_idx = device_offset + 5 + sensor_idx + #if row_idx < scaled_day.shape[0]: + #value = scaled_day[row_idx, 0] + #min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + #max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + #print(f" s{sensor_idx}: row={row_idx}, value={value}, min={min_val},max={max_val}") + + + minutes = scaled_day.shape[1] - 4 + + vocs_scaled = {} + + # Process each device + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + device_y_offset = device_idx * 150 + + # Fill visualization for all minutes + for minute in range(minutes): + # Environmental sensors (5 stripes * 10 pixels each) + for env_idx in range(5): + row_idx = device_offset + env_idx + value = scaled_day[row_idx, minute] if row_idx < scaled_day.shape[0] else -0.001 + + # Calculate color with bounds checking + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + if bw: + color_val = max(0, min(255, int(normalized * 255))) + color = [color_val, color_val, color_val] + else: + if env_idx == 0: # Temperature + temp_f = value * 9/5 + 32 + color = list(GetTemperatureColor(temp_f)) + else: + color_val = max(0, min(1279, int(normalized * 1279))) + color = list(BestColor(color_val)) + + # Ensure all color values are valid uint8 + color = [max(0, min(255, int(c))) for c in color] + else: + color = [0, 0, 0] + + # Fill 10-pixel stripe + for stripe_y in range(10): + pixel_y = device_y_offset + env_idx * 10 + stripe_y + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + # Smell sensors - handle both old format (mtype 0/17) and unwrapped format + # For old format: s0-s9 are directly at positions 5-14 (device_offset + 5 through 14) + # These were already filled by unwrapping, so we just read them directly + for group_idx in range(10): + # For mtype 0 data, after unwrapping, each s0-s9 sensor was duplicated + # across 8 positions. We can just read the first occurrence. + # Position for s0 is at device_offset + 5 + (0*10 + 0) = device_offset + 5 + # Position for s1 is at device_offset + 5 + (0*10 + 1) = device_offset + 6, etc. + sensor_row = device_offset + 5 + group_idx + + if sensor_row < scaled_day.shape[0]: + value = scaled_day[sensor_row, minute] + + if value >= 0: + min_val = scaled_day[sensor_row, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[sensor_row, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + # Invert like original VOC sensors + normalized = 1 - normalized + + if bw: + color_val = max(0, min(255, int(normalized * 255))) + color = [color_val, color_val, color_val] + else: + color_val = max(0, min(1279, int(normalized * 1279))) + color = list(BestColor(color_val)) + + # Ensure valid uint8 values + color = [max(0, min(255, int(c))) for c in color] + else: + color = [0, 0, 0] + + # Fill 10-pixel stripe + for stripe_y in range(10): + pixel_y = device_y_offset + (5 + group_idx) * 10 + stripe_y + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + # Create vocs_scaled array (empty for now) + vocs_scaled_array = np.array([]) + + return arr_stretched_template, vocs_scaled_array + +def FillImage_old_format(scaled_day, devices_c, arr_stretched_template, bw, group_by): + """ + Old format visualization with group_by support + """ + minutes = scaled_day.shape[1] - 4 + measurements_per_device = 85 + sensors_c = 15 # 5 environmental + 10 smell + + # +++ 1. DEFINE A CONSTANT FOR THE STRIPE HEIGHT +++ + STRIPE_HEIGHT = 8 + + vocs_scaled = {} + + # Collect VOC data + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + voc_rows = [] + for sensor_idx in range(10): + sensor_row_idx = device_offset + 5 + sensor_idx + if sensor_row_idx < scaled_day.shape[0]: + voc_rows.append(sensor_row_idx) + + if voc_rows: + voc_data = scaled_day[voc_rows, :minutes] + vocs_scaled[device_idx] = voc_data + + # Calculate pixel positions based on group_by mode + def get_pixel_y(device_idx, sensor_idx, stripe_offset): + """Calculate Y pixel position based on grouping mode""" + if group_by == "sensortype": + # All devices' sensor 0 together, then all sensor 1, etc. + return (sensor_idx * devices_c + device_idx) * STRIPE_HEIGHT + stripe_offset + else: + # All sensors for device 0, then all for device 1, etc. + return (device_idx * sensors_c + sensor_idx) * STRIPE_HEIGHT + stripe_offset + + # Fill visualization + for minute in range(minutes): + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + + # Environmental sensors (5 stripes × 10 pixels) + for env_idx in range(5): + row_idx = device_offset + env_idx + if row_idx < scaled_day.shape[0]: + value = scaled_day[row_idx, minute] + + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + if bw: + color = list(GrayColor(int(normalized * 255))) + else: + if env_idx == 0: + temp_f = value * 9/5 + 32 + color = list(GetTemperatureColor(temp_f)) + else: + color = list(BestColor(int(normalized * 1279))) + else: + color = [0, 0, 0] + + for stripe_y in range(STRIPE_HEIGHT): + pixel_y = get_pixel_y(device_idx, env_idx, stripe_y) + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + # Smell sensors (10 stripes × 10 pixels) + for smell_idx in range(10): + row_idx = device_offset + 5 + smell_idx + if row_idx < scaled_day.shape[0]: + value = scaled_day[row_idx, minute] + + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + normalized = 1 - normalized + + if bw: + color = list(GrayColor(int(normalized * 255))) + else: + color = list(BestColor(int(normalized * 1279))) + else: + color = [0, 0, 0] + + for stripe_y in range(STRIPE_HEIGHT): + pixel_y = get_pixel_y(device_idx, 5 + smell_idx, stripe_y) + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + if vocs_scaled: + all_voc_data = [vocs_scaled[device_idx] for device_idx in sorted(vocs_scaled.keys())] + vocs_scaled_array = np.vstack(all_voc_data) + else: + vocs_scaled_array = np.array([]) + + return arr_stretched_template, vocs_scaled_array + + +def FillImage_new_format(scaled_day, devices_c, arr_stretched_template, bw, group_by): + """ + New format visualization with group_by support + group_by == "sensortype": Groups all devices by sensor type + group_by != "sensortype": Groups all sensors by device + """ + + ENV_STRIPE_HEIGHT = 8 + ENV_SENSOR_COUNT = 5 + SMELL_SENSOR_COUNT = 80 + + ENV_BLOCK_HEIGHT = ENV_SENSOR_COUNT * ENV_STRIPE_HEIGHT # 5 * 8 = 40 + DEVICE_BLOCK_HEIGHT = ENV_BLOCK_HEIGHT + SMELL_SENSOR_COUNT # 40 + 80 = 120 + + minutes = scaled_day.shape[1] - 4 + measurements_per_device = 85 + + vocs_scaled = {} + + # Collect VOC data + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + voc_rows = [] + for decade in range(8): + for voc_sensor in [5, 6, 7, 8, 9]: + sensor_row_idx = device_offset + 5 + (decade * 8 + voc_sensor) + if sensor_row_idx < scaled_day.shape[0]: + voc_rows.append(sensor_row_idx) + + if voc_rows: + voc_data = scaled_day[voc_rows, :minutes] + vocs_scaled[device_idx] = voc_data + + # Calculate pixel positions based on group_by mode + def get_env_pixel_y(device_idx, env_idx, stripe_offset): + """Environmental sensor Y position (8 pixels tall)""" + if group_by == "sensortype": + # This logic also needs to use the new height + return env_idx * devices_c * ENV_STRIPE_HEIGHT + device_idx * ENV_STRIPE_HEIGHT + stripe_offset + else: + # CORRECTED: Use the new DEVICE_BLOCK_HEIGHT constant (120) + return device_idx * DEVICE_BLOCK_HEIGHT + env_idx * ENV_STRIPE_HEIGHT + stripe_offset + + def get_smell_pixel_y(device_idx, sensor_idx): + """Smell sensor Y position (1 pixel tall)""" + if group_by == "sensortype": + # CORRECTED: Use the new ENV_BLOCK_HEIGHT constant + env_total_height = ENV_SENSOR_COUNT * devices_c * ENV_STRIPE_HEIGHT + return env_total_height + sensor_idx * devices_c + device_idx + else: + # CORRECTED: Use the new constants + # This is equivalent to your original correct line, but more readable + return device_idx * DEVICE_BLOCK_HEIGHT + ENV_BLOCK_HEIGHT + sensor_idx + + # Fill visualization + for minute in range(minutes): + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + # Environmental sensors (5 stripes × 10 pixels) + for env_idx in range(5): + row_idx = device_offset + env_idx + if row_idx < scaled_day.shape[0]: + value = scaled_day[row_idx, minute] + + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + if bw: + color = list(GrayColor(int(normalized * 255))) + else: + if env_idx == 0: + temp_f = value * 9/5 + 32 + color = list(GetTemperatureColor(temp_f)) + else: + color = list(BestColor(int(normalized * 1279))) + else: + color = [0, 0, 0] + + for stripe_y in range(8): + pixel_y = get_env_pixel_y(device_idx, env_idx, stripe_y) + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + # Smell sensors (80 individual pixels) + for sensor_idx in range(80): + row_idx = device_offset + 5 + sensor_idx + if row_idx < scaled_day.shape[0]: + pixel_y = get_smell_pixel_y(device_idx, sensor_idx) + value = scaled_day[row_idx, minute] + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + normalized = 1 - normalized + + if bw: + color = list(GrayColor(int(normalized * 255))) + else: + color = list(BestColor(int(normalized * 1279))) + + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + else: + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = [0, 0, 0] + + if vocs_scaled: + all_voc_data = [vocs_scaled[device_idx] for device_idx in sorted(vocs_scaled.keys())] + vocs_scaled_array = np.vstack(all_voc_data) + else: + vocs_scaled_array = np.array([]) + + return arr_stretched_template, vocs_scaled_array + +def FillImage_mixed_formats(scaled_day, devices_c, arr_stretched_template, bw, device_formats): + """ + Handle mixed old/new format devices in same image + device_formats: dict mapping device_idx -> 'old' or 'new' + """ + + minutes = scaled_day.shape[1] - 4 + measurements_per_device = 85 + + vocs_scaled = {} + + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + device_y_offset = device_idx * 150 + device_format = device_formats.get(device_idx, 'old') + + print(f"\nDevice {device_idx} (format={device_format}):") + print(f" device_offset={device_offset}, device_y_offset={device_y_offset}") + print(f" Expected Y range: {device_y_offset} to {device_y_offset + 149}") + + + for minute in range(minutes): + + if minute == 0 and device_idx < 2: # First 2 devices only + for env_idx in range(5): + row_idx = device_offset + env_idx + value = scaled_day[row_idx, minute] + pixel_y_start = device_y_offset + env_idx * 10 + print(f" Env sensor {env_idx}: row_idx={row_idx}, value={value:.2f}, pixel_y={pixel_y_start}-{pixel_y_start+9}") + + # Environmental sensors (same for both formats) + for env_idx in range(5): + row_idx = device_offset + env_idx + value = scaled_day[row_idx, minute] if row_idx < scaled_day.shape[0] else -0.001 + + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + if bw: + color_val = max(0, min(255, int(normalized * 255))) + color = [color_val, color_val, color_val] + else: + if env_idx == 0: + temp_f = value * 9/5 + 32 + color = list(GetTemperatureColor(temp_f)) + else: + color_val = max(0, min(1279, int(normalized * 1279))) + color = list(BestColor(color_val)) + + color = [max(0, min(255, int(c))) for c in color] + else: + color = [0, 0, 0] + + for stripe_y in range(10): + pixel_y = device_y_offset + env_idx * 10 + stripe_y + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + # Smell sensors - format-specific handling + if device_format == 'old': + # Old format: read s0-s9 directly from positions 5-14 + for group_idx in range(10): + sensor_row = device_offset + 5 + group_idx + + if sensor_row < scaled_day.shape[0]: + value = scaled_day[sensor_row, minute] + + if value >= 0: + value = 102400000 - value if value <= 102400000 else 0 + min_val = scaled_day[sensor_row, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[sensor_row, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + normalized = 1 - normalized + + if bw: + color_val = max(0, min(255, int(normalized * 255))) + color = [color_val, color_val, color_val] + else: + color_val = max(0, min(1279, int(normalized * 1279))) + color = list(BestColor(color_val)) + + color = [max(0, min(255, int(c))) for c in color] + else: + color = [0, 0, 0] + + for stripe_y in range(10): + pixel_y = device_y_offset + (5 + group_idx) * 10 + stripe_y + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + else: # device_format == 'new' + # New format: aggregate 8 sensors per group from 80 unwrapped sensors + for group_idx in range(10): + group_values = [] + for i in range(8): + sensor_idx = group_idx * 8 + i + if sensor_idx < 80: + row_idx = device_offset + 5 + sensor_idx + if row_idx < scaled_day.shape[0]: + val = scaled_day[row_idx, minute] + if val >= 0: + group_values.append(val) + + if group_values: + avg_value = sum(group_values) / len(group_values) + ref_row = device_offset + 5 + group_idx * 8 + + if ref_row < scaled_day.shape[0]: + min_val = scaled_day[ref_row, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[ref_row, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = max(0, min(1, (avg_value - min_val) / (max_val - min_val))) + else: + normalized = 0.5 + + normalized = 1 - normalized + + if bw: + color_val = max(0, min(255, int(normalized * 255))) + color = [color_val, color_val, color_val] + else: + color_val = max(0, min(1279, int(normalized * 1279))) + color = list(BestColor(color_val)) + + color = [max(0, min(255, int(c))) for c in color] + else: + color = [128, 128, 128] + else: + color = [0, 0, 0] + + for stripe_y in range(10): + pixel_y = device_y_offset + (5 + group_idx) * 10 + stripe_y + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + + return arr_stretched_template, np.array([]) + +def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw, use_mtype_mode=False, day_data=None): """ Optimized version of FillImage function that fills the stretched array with colored sensor data. + Now supports mtype-based positioning for new sensor layout. Parameters: scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings devices_c: number of devices sensors_c: number of sensors per device - arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + arr_stretched: 3D array to fill with RGB values group_by: grouping strategy ("sensortype" or other) bw: boolean flag for black and white output + use_mtype_mode: boolean flag for new mtype-based positioning + day_data: raw data from database (needed for mtype values) Returns: arr_stretched: Filled array with RGB values - and vocs_scaled aray from 0 to 1280 + and vocs_scaled array from 0 to 1280 + """ + stripes = devices_c * sensors_c + minutes = arr_stretched.shape[1] + + if use_mtype_mode and day_data: + # New mtype mode: process each mtype record individually + return process_mtype_data(day_data, devices_c, sensors_c, arr_stretched, minutes, bw) + else: + # Old mode: use existing logic + return process_legacy_data(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw, minutes) + +def process_mtype_data(day_data, devices_c, sensors_c, arr_stretched, minutes, bw): + """ + Process data with mtype-based positioning. + """ + # Parse start time from first record to calculate minute offsets + if not day_data: + return arr_stretched, np.array([]) + + start_time = day_data[0][0].replace(hour=0, minute=0, second=0, microsecond=0) + + # Group data by minute and mtype + minute_mtype_data = defaultdict(dict) + + for record in day_data: + if record[0] and record[1] and record[16] is not None: # time, device_id, mtype + minute = int((record[0] - start_time).total_seconds() / 60) + if 0 <= minute < minutes: + mtype = record[16] + if 100 <= mtype <= 170: + if minute not in minute_mtype_data: + minute_mtype_data[minute] = {} + minute_mtype_data[minute][mtype] = record + + # Color calculation functions + def best_color_vectorized(vals): + vals = np.clip(vals, 0, 1279).astype(np.int32) + r = np.zeros_like(vals, dtype=np.int32) + g = np.zeros_like(vals, dtype=np.int32) + b = np.zeros_like(vals, dtype=np.int32) + + mask1 = vals < 256 + r[mask1] = 255 + g[mask1] = vals[mask1] + + mask2 = (vals >= 256) & (vals < 512) + r[mask2] = 511 - vals[mask2] + g[mask2] = 255 + + mask3 = (vals >= 512) & (vals < 768) + g[mask3] = 255 + b[mask3] = vals[mask3] - 512 + + mask4 = (vals >= 768) & (vals < 1024) + g[mask4] = 1023 - vals[mask4] + b[mask4] = 255 + + mask5 = vals >= 1024 + r[mask5] = vals[mask5] - 1024 + b[mask5] = 255 + + return r, g, b + + def gray_color_vectorized(vals): + vals = np.clip(vals, 0, 255).astype(np.int32) + return vals, vals, vals + + color_func = gray_color_vectorized if bw else best_color_vectorized + + # Collect all sensor values for min/max calculation + all_sensor_values = [] + voc_values = [] + + for minute_data in minute_mtype_data.values(): + for mtype, record in minute_data.items(): + # Extract sensor values (s0-s9 are at indices 7-16) + for sensor_idx in range(10): + sensor_val = record[7 + sensor_idx] # s0-s9 at positions 7-16 + if sensor_val is not None and sensor_val > 0: + all_sensor_values.append(sensor_val) + # VOC sensors are typically s5-s9 + if sensor_idx >= 5: + voc_values.append(sensor_val) + + # Calculate global min/max for normalization + if all_sensor_values: + global_min = np.min(all_sensor_values) + global_max = np.max(all_sensor_values) + if global_max > global_min: + scale_factor = (1279 if not bw else 255) / (global_max - global_min) + else: + scale_factor = 1 + global_min = 0 + else: + global_min = 0 + global_max = 1 + scale_factor = 1 + + # Process each minute + for minute, mtype_data in minute_mtype_data.items(): + for mtype, record in mtype_data.items(): + if 100 <= mtype <= 170: + base_y = mtype - 100 + + # Process each sensor (s0-s9) + for sensor_idx in range(10): + sensor_val = record[7 + sensor_idx] # s0-s9 at positions 7-16 + if sensor_val is not None and sensor_val > 0: + # Calculate y position + y_pos = base_y + sensor_idx + if 0 <= y_pos < arr_stretched.shape[0]: + # Normalize the sensor value + normalized_val = scale_factor * (sensor_val - global_min) + + # Invert VOC sensors (s5-s9) + if sensor_idx >= 5: + normalized_val = (1279 if not bw else 255) - normalized_val + + # Convert to color + r, g, b = color_func(np.array([normalized_val])) + arr_stretched[y_pos, minute] = [r[0], g[0], b[0]] + + # Return normalized VOC values + if voc_values: + voc_normalized = [(scale_factor * (v - global_min)) for v in voc_values] + voc_inverted = [(1279 if not bw else 255) - v for v in voc_normalized] + return arr_stretched, np.array(voc_inverted) + else: + return arr_stretched, np.array([]) + + + +def process_legacy_data(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw, minutes): + """ + Process data using the original logic for mtype=17 or non-mtype data. """ stripes = devices_c * sensors_c stretch_by = arr_stretched.shape[0] // stripes - minutes = arr_stretched.shape[1] # Pre-calculate VOC rows mask if group_by != "sensortype": @@ -5871,35 +6668,29 @@ def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_b else: row_indices = np.arange(stripes)[:, np.newaxis] * stretch_by + np.arange(stretch_by) - # Optimize color calculation functions + # Color calculation functions def best_color_vectorized(vals): - """Vectorized version of BestColor that matches the original implementation exactly""" vals = np.clip(vals, 0, 1279).astype(np.int32) r = np.zeros_like(vals, dtype=np.int32) g = np.zeros_like(vals, dtype=np.int32) b = np.zeros_like(vals, dtype=np.int32) - # Region 0-255 mask1 = vals < 256 r[mask1] = 255 g[mask1] = vals[mask1] - # Region 256-511 mask2 = (vals >= 256) & (vals < 512) r[mask2] = 511 - vals[mask2] g[mask2] = 255 - # Region 512-767 mask3 = (vals >= 512) & (vals < 768) g[mask3] = 255 b[mask3] = vals[mask3] - 512 - # Region 768-1023 mask4 = (vals >= 768) & (vals < 1024) g[mask4] = 1023 - vals[mask4] b[mask4] = 255 - # Region 1024-1279 mask5 = vals >= 1024 r[mask5] = vals[mask5] - 1024 b[mask5] = 255 @@ -5907,7 +6698,6 @@ def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_b return r, g, b def gray_color_vectorized(vals): - """Vectorized version of GrayColor that matches the original implementation exactly""" vals = np.clip(vals, 0, 255).astype(np.int32) return vals, vals, vals @@ -5957,6 +6747,51 @@ def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_b return arr_stretched, normalized_vals[voc_rows] +def FillSmellImage_80_sensors(scaled_day, arr_stretched, y_offset, device_to_index): + """ + Renders only the 80 smell sensor data as 1-pixel high lines for each device. + This is the new replacement for FillSmellImage_optimized. + """ + minutes = 1440 + devices_c = len(device_to_index) + measurements_per_device = 85 # 5 environmental + 80 smell sensors + x_offset = 200 # Assuming the same x_offset as your original function + + for minute in range(minutes): + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + + # Loop through all 80 smell sensors + for sensor_idx in range(80): + # Calculate the source row in the scaled_day array + # The +5 is crucial to skip the 5 empty environmental sensor slots + row_idx = device_offset + 5 + sensor_idx + + # Calculate the destination Y pixel on the final image + # This stacks each 80-pixel block for each device + pixel_y = y_offset + (device_idx * 80) + sensor_idx + + # --- This logic is copied directly from the working 'FillImage_new_format' --- + value = scaled_day[row_idx, minute] + if value >= 0: + min_val = scaled_day[row_idx, 1442] + max_val = scaled_day[row_idx, 1443] + + if max_val > min_val: + normalized = (value - min_val) / (max_val - min_val) + else: + normalized = 0.5 + + # Invert for display (higher values = hotter colors) + normalized_inverted = 1 - normalized + final_normalized = np.clip(normalized_inverted, 0, 1) + color = BestColor(final_normalized * 1279) + + # Draw the pixel onto the canvas + if pixel_y < arr_stretched.shape[0] and (x_offset + minute) < arr_stretched.shape[1]: + arr_stretched[pixel_y, x_offset + minute] = color + + return arr_stretched def FillSmellImage_optimized(scaled_day, arr_stretched, y_offset): """ @@ -5969,9 +6804,8 @@ def FillSmellImage_optimized(scaled_day, arr_stretched, y_offset): Returns: arr_stretched: Filled array with RGB values """ - stretch_by = 20 x_offset = 200 - + stretch_by = 8 def best_color_vectorizedS(vals): """Vectorized version of BestColor that matches the original implementation exactly""" vals = np.clip(vals, 0, 1279).astype(np.int32) @@ -6040,9 +6874,11 @@ def FillSmellImage_optimized(scaled_day, arr_stretched, y_offset): # Calculate the y position for this stripe y_pos = y_offset + row_idx * stretch_by - # Place the stripe into arr_stretched - print(stretch_by, stripe.shape, arr_stretched.shape, y_pos) - arr_stretched[y_pos:y_pos+stretch_by, x_offset:x_offset+1440, :] = stripe + end_y = y_pos + stretch_by + end_x = x_offset + 1440 + + if end_y <= arr_stretched.shape[0] and end_x <= arr_stretched.shape[1]: + arr_stretched[y_pos:end_y, x_offset:end_x, :] = stripe return arr_stretched @@ -6895,30 +7731,76 @@ def create_humidity_optimized_heatmap(my_data, bw, fields, wave_m, device_to_ind return wave_m -def create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields, device_to_index, base_minute, timezone_st, smell_component_stretch_by, selected_date, y_offset): +def create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields_s, device_to_index, base_minute, time_zone_s, smell_component_stretch_by, selected_date, y_offset, has_new_format): """ - Create a heatmap with the exact blue-cyan-green-yellow-red-violet spectrum - matching Image 2, with green at position 40 - """ - if len(my_data) < 1: + HYBRID version that detects the data format and calls the appropriate visualization pipeline. + - If mtype 100-170 is found, it uses the new 80-sensor, 1-pixel line method. + - Otherwise, it defaults to the original 10-sensor, stretched stripe method. + """ + + if not my_data: return - minutes = 1440 - devices_c = len(device_to_index) - sensors_c = len(fields) - stripes = devices_c * sensors_c #2 for upper maxes, lower mins - arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) - st = time.time() - time_from_str, time_to_str = GetLocalTimeForDate(selected_date, timezone_st) - st = time.time() - arr_source = fast_fill_smell_array_from_timescale(my_data, time_from_str, device_to_index, arr_source_template, timezone_st) - arr_source = AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) - scaled_day = CalcExtremes(arr_source, minutes, stripes) + # --- 2. SELECT THE CORRECT PIPELINE --- - arr_stretched = FillSmellImage_optimized(scaled_day, arr_stretched, y_offset) + if has_new_format: + # + # --- EXECUTE THE NEW 80-SENSOR PIPELINE --- + # + print("New smell format detected. Running 80-sensor visualization.") - return + devices_c = len(device_to_index) + devices_list = list(device_to_index.keys()) + measurements_per_device = 85 + minutes = 1440 + stripes = devices_c * measurements_per_device + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + # a. Unwrap data using the specialized smell-only unwrapper + unwrapped_data = unwrap_smell_only_data(my_data, devices_list, time_from_str) + + # b. Interpolate data correctly + interpolated_data = fast_interpolate_unwrapped_data(unwrapped_data) + + # c. Fill the source array This are real measurements, so 5+80 + arr_source_template = np.full((stripes, minutes + 4), -0.001, dtype=float) + arr_source = fast_fill_array_from_unwrapped(interpolated_data, devices_list, arr_source_template, time_from_str) + + # d. Add limits (using the 80-sensor version) + arr_source = AddLimits_optimized_80(arr_source, devices_c, percentile=100) + + # e. Calculate min/max + scaled_day = CalcExtremes(arr_source, minutes, stripes) + + # f. Render the image (using the new 80-sensor renderer with the OverflowError fix) + arr_stretched = FillSmellImage_80_sensors(scaled_day, arr_stretched, y_offset, device_to_index) + + else: + # + # --- EXECUTE THE ORIGINAL 10-SENSOR PIPELINE --- + # + print("Old smell format detected. Running original 10-sensor visualization.") + + minutes = 1440 + devices_c = len(device_to_index) + sensors_c = len(fields_s) # Use fields_s for old format + stripes = devices_c * sensors_c + + # a. Fill the source array (using the original function) + arr_source_template = np.full((stripes, minutes + 4), -0.001, dtype=float) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + arr_source = fast_fill_smell_array_from_timescale(my_data, time_from_str, device_to_index, arr_source_template, time_zone_s) + + # b. Add limits (using the original function) + arr_source = AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + + # c. Calculate min/max + scaled_day = CalcExtremes(arr_source, minutes, stripes) + + # d. Render the image (using the original renderer) + arr_stretched = FillSmellImage_optimized(scaled_day, arr_stretched, y_offset) + + return arr_stretched # Return the modified array def create_optimized_heatmap_simple(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val, max_val): @@ -7768,7 +8650,7 @@ def CreateFullLocationMap(location_image_file, devices_list, selected_date, cur.execute(sql) my_data = cur.fetchall()#cur.fetchone() #print(result) - if my_data == None: + if my_data == None or my_data == []: return False @@ -8133,7 +9015,7 @@ def CreateFullLocationMap(location_image_file, devices_list, selected_date, #print("stop") #print(r,g,b) SaveObjectInBlob(image_file+".bin", present_at) - SaveImageInBlob(image_file, arr_stretched) + SaveImageInBlob(image_file, arr_stretched, []) def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_date, map_type, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s): @@ -8209,7 +9091,7 @@ def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_d humidity_stripe_width = 15 humidity_stretch_by = humidity_stripe_width + alarm_stripe_width - smell_component_stretch_by = 20 + smell_component_stretch_by = 8 text_dimensions = get_text_dimensions("TEST", label_font, label_font_scale, label_font_thickness) text_height = text_dimensions["height"] @@ -8340,7 +9222,7 @@ def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_d cur.execute(sql) my_data = cur.fetchall()#cur.fetchone() #print(result) - if my_data != None: + if my_data != None and my_data != []: device_to_index = {device: idx for idx, device in enumerate(ids_list)} # Calculate base minute @@ -8361,7 +9243,7 @@ def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_d arr_stretched[title_labels_height+y, 200:] = rgb_row print(time.time()-st) - vertical_offset = vertical_offset + title_labels_height + stripes*radar_stretch_by + vertical_offset = vertical_offset + title_labels_height + stripes*radar_stretch_by ######################################## LIGHT ################################################################## if show_light: title_label_text = "LIGHT" @@ -8719,22 +9601,8 @@ def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_d cnt = 0 temp_ids_list = [] # Create a separate list for temperature section for details in devices_list: - dev_id = details[0] temp_ids_list.append(details[1]) - descriptor = details[2] - if details[3] != None and details[3] != "": - descriptor = descriptor + " " + details[3] - if details[6] != None and details[6] != "": - descriptor = descriptor + " " + details[6] - text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) - text_height = text_dimensions["height"] - # Position labels in the temperature section - y_pos = vertical_offset + title_labels_height +80+ text_height + cnt * smell_component_stretch_by * 10 - #y_pos = vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by) - labels.append((descriptor, (10, y_pos), label_font, label_font_scale, - label_font_color, label_font_thickness, label_font_line)) - cnt += 1 # Get smell data sql = get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, smell_offset) @@ -8748,11 +9616,31 @@ def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_d device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} base_minute = ConvertToBase(time_from_str, time_zone_s) + has_new_format = any(len(rec) > 12 and 100 <= rec[12] <= 170 for rec in my_data if rec[12] is not None) # Create the heatmap data - create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields_s, device_to_index, base_minute, time_zone_s, smell_component_stretch_by, selected_date, vertical_offset + 18 + title_text_height) + arr_stretched = create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields_s, device_to_index, base_minute, time_zone_s, smell_component_stretch_by, selected_date, vertical_offset + 18 + title_text_height, has_new_format) + for details in devices_list: + dev_id = details[0] + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + if has_new_format: + y_pos = vertical_offset + title_labels_height +20+ text_height + cnt * 80 + else: + y_pos = vertical_offset + title_labels_height +20+ text_height + cnt * smell_component_stretch_by * 10 + + #y_pos = vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by) + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 SaveImageInBlobLabelsOut(image_file, arr_stretched, labels, title_labels) print("stop") @@ -8928,13 +9816,13 @@ def CreateDailyLocationMap(location_image_file, devices_list, selected_date, fil arr_stretched_sorted[y, :] = rgbsorted_row #print("stop") #print(r,g,b) - SaveImageInBlob(image_file, arr_stretched) - SaveImageInBlob(image_file[:-4]+"S.png", arr_stretched_sorted) + SaveImageInBlob(image_file, arr_stretched, []) + SaveImageInBlob(image_file[:-4]+"S.png", arr_stretched_sorted, []) def GenerateFullLocationMap(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) st = time.time() @@ -8945,7 +9833,7 @@ def GenerateFullLocationMap(map_file, deployment_id, ddate, recreate_or_not, cha def GenerateFullLocationMapLabelsOut(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) st = time.time() @@ -8954,158 +9842,578 @@ def GenerateFullLocationMapLabelsOut(map_file, deployment_id, ddate, recreate_or else: print(ddate, time.time() - st) -def CreateMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by): - global Id2MACDict, s_table_temp +def fast_interpolate_unwrapped_data(unwrapped_data): + """ + Corrected version that interpolates data for each device independently to prevent data corruption. + """ + if not unwrapped_data: + return unwrapped_data + + # 1. Group all records by their device_id first + data_by_device = defaultdict(list) + for record in unwrapped_data: + device_id = record[1] + data_by_device[device_id].append(record) + + all_interpolated_records = [] + + # 2. Process each device's data in its own isolated group + for device_id in data_by_device: + device_records = data_by_device[device_id] + + # Sort by time to ensure correct interpolation order (important!) + device_records.sort(key=lambda x: x[0]) + + # --- The following logic is your original code, but now it only runs on one device at a time --- + sensor_data = [] + other_data = [] + for record in device_records: + other_data.append(record[:7]) + sensor_row = [record[i] if i < len(record) and record[i] is not None else np.nan for i in range(7, 87)] + sensor_data.append(sensor_row) + + sensor_array = np.array(sensor_data, dtype=float) + + for col in range(sensor_array.shape[1]): + values = sensor_array[:, col] + valid_mask = ~np.isnan(values) + if not np.any(valid_mask): + continue + + valid_indices = np.where(valid_mask)[0] + if len(valid_indices) > 1: + values = np.interp(np.arange(len(values)), valid_indices, values[valid_indices]) + else: + valid_value = values[valid_indices[0]] + values = np.full_like(values, valid_value) + sensor_array[:, col] = values + + # Reconstruct this device's data and add it to the final list + for i, other_record in enumerate(other_data): + full_record = list(other_record) + sensor_array[i].tolist() + all_interpolated_records.append(tuple(full_record)) + + # 3. Return the combined list of all correctly interpolated records + return all_interpolated_records + + +def FillImage_hybrid(scaled_day, devices_c, arr_stretched_template, bw): + """ + Hybrid visualization: + - Environmental sensors (temp, humidity, pressure, light, radar): 10-pixel tall stripes + - Smell sensors (s0-s79): Individual pixels + + Total height: 5*10 + 80 = 130 pixels per device + """ + minutes = scaled_day.shape[1] - 4 + measurements_per_device = 85 # 5 environmental + 80 smell sensors + + # Environmental sensor indices (first 5 measurements per device) + env_sensor_names = ["temperature", "humidity", "pressure", "light", "radar"] + + vocs_scaled = {} + + # Process each device + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + + # Collect VOC data (smell sensors s5, s15, s25, etc.) + voc_rows = [] + for decade in range(8): + for voc_sensor in [5, 6, 7, 8, 9]: + sensor_row_idx = device_offset + 5 + (decade * 10 + voc_sensor) + if sensor_row_idx < scaled_day.shape[0]: + voc_rows.append(sensor_row_idx) + + if voc_rows: + voc_data = scaled_day[voc_rows, :minutes] + vocs_scaled[device_idx] = voc_data + + # Fill the visualization + for minute in range(minutes): + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + device_y_offset = device_idx * 130 # 130 pixels per device (5*10 + 80) + + # Fill environmental sensors (10 pixels each) + for env_idx in range(5): + row_idx = device_offset + env_idx + if row_idx < scaled_day.shape[0]: + value = scaled_day[row_idx, minute] + + # Calculate color for this environmental sensor + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = (value - min_val) / (max_val - min_val) + normalized = max(0, min(1, normalized)) + else: + normalized = 0.5 + + # Convert to color using your existing functions + if bw: + color = list(GrayColor(int(normalized * 255))) + else: + # For temperature, use your special temperature scaling + if env_idx == 0: # Temperature is first environmental sensor + # Convert to Fahrenheit for your temperature scaling + temp_f = value * 9/5 + 32 # Assuming value is in Celsius + color = list(GetTemperatureColor(temp_f)) + else: + # Other environmental sensors use standard color mapping + color_val = int(normalized * 1279) + color = list(BestColor(color_val)) + + # Fill 10 pixels for this environmental sensor + for stripe_y in range(10): + pixel_y = device_y_offset + env_idx * 10 + stripe_y + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + else: + # No data - black pixels + for stripe_y in range(10): + pixel_y = device_y_offset + env_idx * 10 + stripe_y + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = [0, 0, 0] + + # Fill smell sensors (1 pixel each, 80 total) + for sensor_idx in range(80): + row_idx = device_offset + 5 + sensor_idx # +5 for environmental sensors + if row_idx < scaled_day.shape[0]: + pixel_y = device_y_offset + 50 + sensor_idx # +50 for environmental stripes + + value = scaled_day[row_idx, minute] + if value >= 0: + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = (value - min_val) / (max_val - min_val) + normalized = max(0, min(1, normalized)) + else: + normalized = 0.5 + + # Invert smell sensor colors (like original VOC behavior) + normalized = 1 - normalized + + if bw: + color = list(GrayColor(int(normalized * 255))) + else: + color_val = int(normalized * 1279) + color = list(BestColor(color_val)) + + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = color + else: + # No data - black pixel + if pixel_y < arr_stretched_template.shape[0]: + arr_stretched_template[pixel_y, minute] = [0, 0, 0] + + return arr_stretched_template, vocs_scaled + + +# Remove this function - we'll use your existing BestColor() and GrayColor() functions + + +def CreateMapFast_hybrid(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by): + """Updated CreateMapFast with hybrid visualization""" + global Id2MACDict, s_table_temp st = time.time() + error_string = "" if radar_part == "s28": radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" try: - - #stretch_to_min_max = True - - #current_date_p = selected_date.replace("-", "_") - #current_date_s = selected_date - lower_than200 = 0 larger_than200 = 0 ids_list = [] for details in devices_list[0]: - dev_id = details[0] + dev_id = details[0] ids_list.append(details[1]) if dev_id < 200: lower_than200 += 1 else: larger_than200 += 1 - if lower_than200 > 0 and larger_than200 > 0: - return False, [] - + error_string = "Mixed types of devices are not allowed" + return False, [], error_string if larger_than200 > 0: - sensors_c = len(s_table_temp) - else: #old sensors not supported - return False, [] - + #sensors_c = 80 + measurements_per_device = 85 + else: + error_string = "No new devices (well_id > 300) are found" + return False, [], error_string devices_c = len(devices_list[0]) devices_list_str = ",".join(map(str, devices_list[1])) image_file = map_file time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) temp_offset = -10 - if sensors_c > 1: - sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) - else: - sql = get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, s_table_temp[0]) + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) print(sql) - #print(sql) - #st = time.time() with get_db_connection() as conn: with conn.cursor() as cur: cur.execute(sql) - day_data = cur.fetchall()#cur.fetchone() - #print(result) + day_data = cur.fetchall() + if day_data == None: + error_string = "No data found" + return False, [], error_string + + # Detect format in the data + has_old_format = any(record[17] in [0, 17] for record in day_data if len(record) > 17 and record[17] is not None) + has_new_format = any(100 <= record[17] <= 170 for record in day_data if len(record) > 17 and record[17] is not None) + + #print(f"Data format detected: old={has_old_format}, new={has_new_format}") + + # Don't support mixed formats + #if has_old_format and has_new_format: + # error_string = "Mixed formats in single deployment not supported" + # AddToLog(error_string) + # return False, [], has_old_format, error_string + + # Unwrap and interpolate + unwrapped_data = unwrap_sensor_data(day_data, devices_list[1], time_from_str) + + interpolated_data = fast_interpolate_unwrapped_data(unwrapped_data) + + # Setup arrays based on format + minutes = 1440 + stripes = devices_c * measurements_per_device + + total_height = devices_c * 120 #15 stripes × 8 pixels + + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) + arr_stretched_template = np.zeros((total_height, minutes, 3), dtype=np.uint8) + + arr_source = fast_fill_array_from_unwrapped(interpolated_data, devices_list[1], arr_source_template, time_from_str) + + #arr_source = GenerateTestPattern(arr_source, devices_list) + + arr_source = AddLimits_optimized_80(arr_source, devices_c, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + + #if has_old_format: + # arr_stretched, vocs_scaled = FillImage_old_format(scaled_day, devices_c, arr_stretched_template, bw, group_by) + #else: + arr_stretched, vocs_scaled = FillImage_new_format(scaled_day, devices_c, arr_stretched_template, bw, group_by) + + metadata = { + 'hasoldformat': has_old_format + } + SaveImageInBlob(image_file, arr_stretched, [], metadata) + return True, vocs_scaled, has_old_format, error_string + + except Exception as e: + error_string = traceback.format_exc() + AddToLog(error_string) + return False, [], has_old_format, error_string + + +def CreateMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by): + global Id2MACDict, s_table_temp + st = time.time() + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + try: + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + for details in devices_list[0]: + dev_id = details[0] + ids_list.append(details[1]) + if dev_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + if lower_than200 > 0 and larger_than200 > 0: + return False, [] + if larger_than200 > 0: + sensors_c = 80 # 80 sensors after unwrapping + measurements_per_device = 85 # 5 other + 80 sensors + else: #old sensors not supported + return False, [] + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + image_file = map_file + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + temp_offset = -10 + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + day_data = cur.fetchall() if day_data == None: return False, [] - stretch_by = 10 + # Unwrap the sensor data into consistent s0-s79 format + unwrapped_data = unwrap_sensor_data(day_data, devices_list[1], time_from_str) + + unwrapped_data = fast_interpolate_unwrapped_data(unwrapped_data) + + # Setup arrays for 80-sensor format minutes = 1440 - stripes = devices_c * sensors_c #2 for upper maxes, lower mins + stripes = devices_c * measurements_per_device # Each device has 85 measurements + total_height = 80 # 80 pixels for 80 sensors + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) - arr_stretched_template = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) # 3 for RGB channels - arr_source = fast_fill_array_from_timescale(day_data, time_from_str, devices_list[1], arr_source_template, time_zone_s) - arr_source = AddLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + arr_stretched_template = np.zeros((total_height, minutes, 3), dtype=np.uint8) + + # Fill array using the fixed function + arr_source = fast_fill_array_from_unwrapped(unwrapped_data, devices_list[1], arr_source_template, time_from_str) + + # INVERT BME680 sensor values (rows 5-84 for each device = smell sensors) + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + for sensor_idx in range(80): # All 80 smell sensors + row_idx = device_offset + 5 + sensor_idx + if row_idx < arr_source.shape[0]: + # Invert all values in this row + for minute in range(arr_source.shape[1] - 4): # Don't touch the metadata columns + val = arr_source[row_idx, minute] + if val > 0: + arr_source[row_idx, minute] = 102400000 - val + + # Use the updated AddLimits function for 80 sensors + arr_source = AddLimits_optimized_80(arr_source, devices_c, percentile=100) + + # Calculate extremes scaled_day = CalcExtremes(arr_source, minutes, stripes) - arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) - SaveImageInBlob(image_file, arr_stretched) + + # Use the fixed uniform processing function + arr_stretched, vocs_scaled = FillImage_uniform(scaled_day, devices_c, arr_stretched_template, bw) + + SaveImageInBlob(image_file, arr_stretched, []) return True, vocs_scaled except Exception as e: AddToLog(traceback.format_exc()) return False, [] +def FillImage_uniform(scaled_day, devices_c, arr_stretched_template, bw): + """ + Fill image array for uniform 80-sensor data. + scaled_day shape: (devices_c * 85, minutes+4) + """ + minutes = scaled_day.shape[1] - 4 # Subtract the 4 extra columns + measurements_per_device = 85 + + # Collect all VOC data into a single array + all_voc_data = [] + + # Process each device + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + + # Extract VOC sensor rows (every 10th sensor starting from 5: 5,6,7,8,9,15,16,17,18,19,etc.) + voc_rows = [] + for decade in range(8): # 8 decades of 10 sensors each + for voc_sensor in [5, 6, 7, 8, 9]: # VOC sensors in each decade + sensor_row_idx = device_offset + 5 + (decade * 10 + voc_sensor) # +5 for other measurements + if sensor_row_idx < scaled_day.shape[0]: + voc_rows.append(sensor_row_idx) + + # Collect VOC data for this device + if voc_rows: + voc_data = scaled_day[voc_rows, :minutes] + all_voc_data.append(voc_data) + + # Convert to single numpy array + if all_voc_data: + vocs_scaled = np.vstack(all_voc_data) + else: + vocs_scaled = np.array([]) + + # Fill the image array (80 pixels tall) + for minute in range(minutes): + for device_idx in range(devices_c): + device_offset = device_idx * measurements_per_device + + # Fill sensor data (80 pixels for 80 sensors) + for sensor_idx in range(80): + row_idx = device_offset + 5 + sensor_idx # +5 for other measurements + if row_idx < scaled_day.shape[0]: + pixel_y = sensor_idx # Direct mapping + + value = scaled_day[row_idx, minute] + if value >= 0: # Valid data + # Normalize value using min/max from columns 1442, 1443 + min_val = scaled_day[row_idx, 1442] if scaled_day.shape[1] > 1442 else 0 + max_val = scaled_day[row_idx, 1443] if scaled_day.shape[1] > 1443 else 1 + + if max_val > min_val: + normalized = (value - min_val) / (max_val - min_val) + normalized = max(0, min(1, normalized)) # Clamp to [0,1] + else: + normalized = 0.5 + + if bw: # Black and white + intensity = int(normalized * 255) + arr_stretched_template[pixel_y, minute] = [intensity, intensity, intensity] + else: # Color mapping + if normalized < 0.5: + # Blue to green + r = 0 + g = int(normalized * 2 * 255) + b = int((1 - normalized * 2) * 255) + else: + # Green to red + r = int((normalized - 0.5) * 2 * 255) + g = int((1 - (normalized - 0.5) * 2) * 255) + b = 0 + + arr_stretched_template[pixel_y, minute] = [r, g, b] + else: + # No data - black pixel + arr_stretched_template[pixel_y, minute] = [0, 0, 0] + + return arr_stretched_template, vocs_scaled + def get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset): """ - Generate a TimeScaleDB query for sensor and radar readings based on device IDs. - - Parameters: - devices_list_str (str): Comma-separated string of device IDs - time_from_str (str): Start time for the query - time_to_str (str): End time for the query - ids_list (list): List of device IDs in priority order for sorting - radar_part (str): Radar column name, defaults to 'radar' - - Returns: - str: Generated SQL query + Simple query to get raw sensor data without complex interpolation. """ - - # Generate the CASE statement for ordering based on the provided ids_list case_statements = [] for index, device_id in enumerate(ids_list, start=1): case_statements.append(f"WHEN {device_id} THEN {index}") - case_order = "\n ".join(case_statements) sql = f""" SELECT COALESCE(sr.minute, rr.minute) as minute, COALESCE(sr.device_id, rr.device_id) as device_id, - sr.avg_temperature+ {temp_offset} as avg_temperature, - sr.avg_humidity, - sr.pressure_amplitude, - sr.max_light, + sr.temperature+ {temp_offset} as temperature, + sr.humidity, + sr.pressure, + sr.light, rr.radar, - sr.min_s0 as sensor_min_s0, - sr.min_s1 as sensor_min_s1, - sr.min_s2 as sensor_min_s2, - sr.min_s3 as sensor_min_s3, - sr.min_s4 as sensor_min_s4, - sr.min_s5 as sensor_min_s5, - sr.min_s6 as sensor_min_s6, - sr.min_s7 as sensor_min_s7, - sr.min_s8 as sensor_min_s8, - sr.min_s9 as sensor_min_s9 + sr.s0, sr.s1, sr.s2, sr.s3, sr.s4, sr.s5, sr.s6, sr.s7, sr.s8, sr.s9, + sr.mtype FROM ( SELECT time_bucket('1 minute', time) AS minute, device_id, - AVG(temperature) AS avg_temperature, - AVG(humidity) AS avg_humidity, - AVG(pressure) AS pressure_amplitude, - MAX(light) AS max_light, - MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, - MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, - MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, - MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, - MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, - MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, - MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, - MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, - MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, - MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 - FROM - sensor_readings - WHERE - device_id IN ({devices_list_str}) + mtype, + AVG(temperature) AS temperature, + AVG(humidity) AS humidity, + AVG(pressure) AS pressure, + MAX(light) AS light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS s9 + FROM sensor_readings + WHERE device_id IN ({devices_list_str}) AND time >= '{time_from_str}' AND time < '{time_to_str}' - GROUP BY - minute, - device_id + AND mtype IN (0, 17, 100, 110, 120, 130, 140, 150, 160, 170) + GROUP BY minute, device_id, mtype ) sr FULL OUTER JOIN ( SELECT time_bucket('1 minute', time) AS minute, device_id, MAX({radar_part}) AS radar - FROM - radar_readings - WHERE - device_id IN ({devices_list_str}) + FROM radar_readings + WHERE device_id IN ({devices_list_str}) AND time >= '{time_from_str}' AND time < '{time_to_str}' - GROUP BY - minute, - device_id + GROUP BY minute, device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute), + sr.mtype; + """ + return sql + +def get_deployment_query_80(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset): + """ + Query to get sensor data with s0-s79 columns mapped from mtype values. + Maps mtype values to column ranges: + - mtype 0, 17, 100 -> s0-s9 + - mtype 110 -> s10-s19 + - mtype 120 -> s20-s29 + - ... up to mtype 170 -> s70-s79 + """ + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # Generate column mappings for each mtype range + column_mappings = [] + + # S0-S9 (mtype = 0, 17, or 100) + for i in range(10): + column_mappings.append(f"MIN(CASE WHEN mtype IN (0, 17, 100) AND s{i} > 0 THEN s{i} END) AS s{i}") + + # S10-S79 (mtype = 110 to 170) + for mtype_offset in range(11, 18): # 110, 120, 130, 140, 150, 160, 170 + mtype_value = mtype_offset * 10 + base_col = (mtype_offset - 10) * 10 + for i in range(10): + source_col = i + target_col = base_col + i + column_mappings.append(f"MIN(CASE WHEN mtype = {mtype_value} AND s{source_col} > 0 THEN s{source_col} END) AS s{target_col}") + + columns_sql = ",\n ".join(column_mappings) + + # Generate the s0-s79 column list for the outer SELECT + outer_columns = [] + for i in range(80): + outer_columns.append(f"sr.s{i}") + outer_columns_str = ", ".join(outer_columns) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.temperature + {temp_offset} as temperature, + sr.humidity, + sr.pressure, + sr.light, + rr.radar, + {outer_columns_str} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS temperature, + AVG(humidity) AS humidity, + AVG(pressure) AS pressure, + MAX(light) AS light, + {columns_sql} + FROM sensor_readings + WHERE device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + AND mtype IN (0, 17, 100, 110, 120, 130, 140, 150, 160, 170) + GROUP BY minute, device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM radar_readings + WHERE device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY minute, device_id ) rr ON sr.minute = rr.minute AND sr.device_id = rr.device_id ORDER BY @@ -9116,6 +10424,249 @@ def get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, """ return sql +def unwrap_smell_only_data(day_data, devices_list, time_from_str): + """ + A specialized version of unwrap_sensor_data that handles a data format + containing ONLY smell sensor data (s0-s9) and mtype. + It outputs a standardized 87-column record with placeholders for environmental data. + """ + if not day_data: + return [] + + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + data_by_minute = defaultdict(list) + + for record in day_data: + if record[0] and record[1]: + minute = int((record[0] - start_time).total_seconds() / 60) + data_by_minute[minute].append(record) + + unwrapped_data = [] + + for minute in range(1440): + minute_records = data_by_minute.get(minute, []) + if not minute_records: + continue + + records_by_device = defaultdict(list) + for record in minute_records: + device_id = record[1] + records_by_device[device_id].append(record) + + for device_id, device_records in records_by_device.items(): + sensors_80 = [None] * 80 + + for record in device_records: + # --- MODIFICATION #1: Adjust indices for the new data format --- + sensor_values = record[2:12] # s0-s9 are at indices 2 through 11 + mtype = record[12] if len(record) > 12 and record[12] is not None else 17 + + # The unwrapping logic for old vs. new format is still valid + if mtype == 17 or mtype == 0: + for decade in range(8): + for sensor_idx in range(10): + if sensor_idx < len(sensor_values) and sensor_values[sensor_idx] is not None: + target_pos = decade * 10 + sensor_idx + sensors_80[target_pos] = sensor_values[sensor_idx] + elif 100 <= mtype <= 170: + base_pos = mtype - 100 + for sensor_idx in range(10): + if sensor_idx < len(sensor_values) and sensor_values[sensor_idx] is not None: + target_pos = base_pos + sensor_idx + if target_pos < 80: + sensors_80[target_pos] = sensor_values[sensor_idx] + + minute_time = start_time + datetime.timedelta(minutes=minute) + + # --- MODIFICATION #2: Create a standard record with placeholders --- + # The downstream functions expect [time, dev_id, temp, hum, pres, light, radar, s0-s79] + unwrapped_record = [ + minute_time, + device_id, + None, # temp placeholder + None, # humidity placeholder + None, # pressure placeholder + None, # light placeholder + None, # radar placeholder + ] + sensors_80 + + unwrapped_data.append(tuple(unwrapped_record)) + + return unwrapped_data + +def unwrap_sensor_data(day_data, devices_list, time_from_str): + """Debug version to see what happens during unwrapping""" + if not day_data: + return [] + + # Check device distribution in raw data + device_ids_in_raw = {} + for record in day_data: + device_id = record[1] + device_ids_in_raw[device_id] = device_ids_in_raw.get(device_id, 0) + 1 + + #print(f"Raw data device distribution: {device_ids_in_raw}") + + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + data_by_minute = defaultdict(list) + + for record in day_data: + if record[0] and record[1]: + minute = int((record[0] - start_time).total_seconds() / 60) + data_by_minute[minute].append(record) + + unwrapped_data = [] + device_ids_in_unwrapped = {} + + for minute in range(1440): + minute_records = data_by_minute.get(minute, []) + if not minute_records: + continue + + # Group by device_id FIRST - this is probably missing! + records_by_device = defaultdict(list) + for record in minute_records: + device_id = record[1] + records_by_device[device_id].append(record) + + # Process each device separately + for device_id, device_records in records_by_device.items(): + sensors_80 = [None] * 80 + temperature_vals, humidity_vals, pressure_vals = [], [], [] + light_vals, radar_vals = [], [] + + # vvvvvvvvvvvvvvvv START OF THE FIX vvvvvvvvvvvvvvvv + + # 1. Check which mtypes are present for this specific device and minute + mtypes_present = {rec[17] for rec in device_records if len(rec) > 17 and rec[17] is not None} + + # 2. Decide if this is a "new format" minute. If any new mtype is found, we treat it as such. + is_new_format_minute = any(100 <= m <= 170 for m in mtypes_present) + + # ^^^^^^^^^^^^^^^^^^^ END OF THE FIX ^^^^^^^^^^^^^^^^^^^ + + + for record in device_records: + mtype = record[17] if len(record) > 17 and record[17] is not None else 17 + + # vvvvvvvvvvvvvvvv START OF THE FIX vvvvvvvvvvvvvvvv + + # 3. If this is a new format minute, skip any old format records to prevent data corruption. + if is_new_format_minute and (mtype == 0 or mtype == 17): + continue # Ignore this old-format record + + # ^^^^^^^^^^^^^^^^^^^ END OF THE FIX ^^^^^^^^^^^^^^^^^^^ + + # Collect other measurements + if record[2] is not None: temperature_vals.append(record[2]) + if record[3] is not None: humidity_vals.append(record[3]) + if record[4] is not None: pressure_vals.append(record[4]) + if record[5] is not None: light_vals.append(record[5]) + if record[6] is not None: radar_vals.append(record[6]) + + # Process sensor values + sensor_values = record[7:17] + + if mtype == 17 or mtype == 0: + # Old format: duplicate across all decades + for decade in range(8): + for sensor_idx in range(10): + target_pos = decade * 10 + sensor_idx + if sensor_values[sensor_idx] is not None: + sensors_80[target_pos] = sensor_values[sensor_idx] + + elif 100 <= mtype <= 170: + # New format: map to specific positions + base_pos = mtype - 100 + for sensor_idx in range(10): + target_pos = base_pos + sensor_idx + if target_pos < 80 and sensor_values[sensor_idx] is not None: + sensors_80[target_pos] = sensor_values[sensor_idx] + + # Create unwrapped record for this device and minute + minute_time = start_time + datetime.timedelta(minutes=minute) + + avg_temp = sum(temperature_vals) / len(temperature_vals) if temperature_vals else None + avg_humidity = sum(humidity_vals) / len(humidity_vals) if humidity_vals else None + avg_pressure = sum(pressure_vals) / len(pressure_vals) if pressure_vals else None + avg_light = max(light_vals) if light_vals else None + avg_radar = sum(radar_vals) / len(radar_vals) if radar_vals else None + + unwrapped_record = [ + minute_time, device_id, avg_temp, avg_humidity, + avg_pressure, avg_light, avg_radar + ] + sensors_80 + + unwrapped_data.append(tuple(unwrapped_record)) + + # Track device distribution in unwrapped data + device_ids_in_unwrapped[device_id] = device_ids_in_unwrapped.get(device_id, 0) + 1 + + #print(f"Unwrapped data device distribution: {device_ids_in_unwrapped}") + return unwrapped_data + +def fast_fill_array_from_unwrapped(unwrapped_data, devices_list, arr_source, time_from_str): + """ + Fill array from unwrapped sensor data with proper row structure: + For each device: [temperature, humidity, pressure, light, radar, s0, s1, ..., s79] + """ + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + #print(f"Device mapping: {device_to_index}") + + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + measurements_per_device = 85 # 5 other + 80 sensors + #records_processed = 0 + #values_written = 0 + #device_data_count = {} + + for record in unwrapped_data: + device_id = record[1] + if device_id not in device_to_index: + #print(f"Skipping device_id {device_id} - not in device_to_index") + continue + + # Calculate minute from timestamp + record_time = record[0] + minute = int((record_time - start_time).total_seconds() / 60) + + if minute < 0 or minute >= arr_source.shape[1]: + #print(f"Skipping minute {minute} - out of bounds") + continue + + #records_processed += 1 + #if records_processed <= 5: # Debug first 5 records + # print(f"Processing record {records_processed}: device_id={device_id}, minute={minute}") + + + device_idx = device_to_index[device_id] + device_offset = device_idx * measurements_per_device + + # Fill the 5 other measurements first + other_measurements = record[2:7] # temperature, humidity, pressure, light, radar + for i, value in enumerate(other_measurements): + if value is not None: + row_idx = device_offset + i + if row_idx < arr_source.shape[0]: + arr_source[row_idx, minute] = value + #values_written += 1 + + # Fill the 80 sensor values + sensor_values = record[7:87] # s0-s79 + for i, value in enumerate(sensor_values): + if value is not None: + row_idx = device_offset + 5 + i # +5 to skip other measurements + if row_idx < arr_source.shape[0]: + arr_source[row_idx, minute] = value + #values_written += 1 + + #print(f"Data distribution: {device_data_count}") + #print(f"devices_list: {devices_list}") + #print(f"Total records processed: {records_processed}") + #print(f"Total values written: {values_written}") + return arr_source + def get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, sensor_in): """ Generate a TimeScaleDB query for a single sensor reading based on device IDs. @@ -10113,49 +11664,41 @@ def get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str # SQL query with added alarm_level calculation sql = f""" + SELECT - minute, - device_id, - min_s0, - min_s1, - min_s2, - min_s3, - min_s4, - min_s5, - min_s6, - min_s7, - min_s8, - min_s9 + COALESCE(sr.minute) as minute, + COALESCE(sr.device_id) as device_id, + sr.s0, sr.s1, sr.s2, sr.s3, sr.s4, sr.s5, sr.s6, sr.s7, sr.s8, sr.s9, + sr.mtype FROM ( SELECT time_bucket('1 minute', time) AS minute, device_id, - MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, - MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, - MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, - MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, - MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, - MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, - MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, - MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, - MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, - MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 - FROM - sensor_readings - WHERE - device_id IN ({devices_list_str}) + mtype, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS s9 + FROM sensor_readings + WHERE device_id IN ({devices_list_str}) AND time >= '{time_from_str}' AND time < '{time_to_str}' - GROUP BY - minute, - device_id - ) rr - + AND mtype IN (0, 17, 100, 110, 120, 130, 140, 150, 160, 170) + GROUP BY minute, device_id, mtype + ) sr ORDER BY - CASE device_id + CASE COALESCE(sr.device_id) {case_order} END, - minute + COALESCE(sr.minute), + sr.mtype; + """ return sql @@ -10634,7 +12177,7 @@ def CreateDailyCSV(csv_file, devices_list, selected_date, vocs_scaled, time_zone devices_list_str = ",".join(map(str, devices_list[1])) time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) if consolidated_by == "by_minute_rc": - sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + sql = get_deployment_query_80(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) elif consolidated_by == "by_deca_rd": sql = get_deployment_deca_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset) elif consolidated_by == "by_minute_rd": @@ -10672,8 +12215,8 @@ def GetBlob(file_name, bucket_name="daily-maps"): file_name (str): Name of the file to retrieve from blob storage Returns: - tuple: (image_bytes, content_type) - Returns None, None if image not found or error occurs + tuple: (image_bytes, content_type, metadata) + Returns None, None, None if image not found or error occurs """ logger.debug(f"GetBlob({file_name})") try: @@ -10687,14 +12230,17 @@ def GetBlob(file_name, bucket_name="daily-maps"): data_bytes = data.read() #logger.debug(f"len(data_bytes)={len(data_bytes)}") + stat = miniIO_blob_client.stat_object(bucket_name, file_name) + metadata = stat.metadata if stat.metadata else {} + if bucket_name == "daily-maps": - return data_bytes, 'image/png' + return data_bytes, 'image/png', metadata else: - return data_bytes, 'application/zip' + return data_bytes, 'application/zip', metadata except Exception as e: logger.error(f"Error: {traceback.format_exc()}") - return None, None + return None, None, None def GetJPG(file_name, bucket_name="user-pictures"): """ @@ -10805,7 +12351,7 @@ def ReadCandles(file, sensor, period, time_from, time_to): result = QuerrySql(file, sqlr) return result -def ReadSensor(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): +def ReadSensor(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, temp_offset, bucket_size="no"): time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) # Format in ISO 8601 format with timezone @@ -10823,12 +12369,28 @@ def ReadSensor(device_id, sensor, time_from_epoch, time_to_epoch, data_type, rad radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" elif sensor[0] == "s": - sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + + if len(sensor) <= 2: + sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + #we need to re-map s_index to s0-9 and mtype + s_i = int(sensor[1:]) + smell_index = s_i % 10 + mtype = 100 + 10 * int(s_i / 10) + sqlr = f"SELECT time, s{smell_index} AS smell FROM sensor_readings WHERE device_id = {device_id} AND s{smell_index} >= '{legal_min}' AND s{smell_index} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype={mtype} ORDER BY time ASC" else: if sensor == "temperature": - sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + #sqlr = f"SELECT time, {sensor} - 16 from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + #sqlr = f"SELECT time, {sensor} + {temp_offset} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + sqlr = f"SELECT time, {sensor} + {temp_offset} FROM (SELECT time, {sensor}, ROW_NUMBER() OVER (ORDER BY time) as rn FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '1' AND {sensor} <= '99' AND time >= '{time_from_str}' AND time <= '{time_to_str}') subquery WHERE rn % 8 = 1 ORDER BY time" + + elif sensor == "humidity": + #sqlr = f"SELECT time, {sensor} - 16 from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + #sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + sqlr = f"SELECT time, humidity FROM (SELECT time, humidity, ROW_NUMBER() OVER (ORDER BY time) as rn FROM sensor_readings WHERE device_id = {device_id} AND humidity >= '1' AND humidity <= '99' AND time >= '{time_from_str}' AND time <= '{time_to_str}') subquery WHERE rn % 8 = 1 ORDER BY time" + else: - sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype=17 ORDER BY time ASC" + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" logger.debug(f"sqlr = {sqlr}") with get_db_connection() as conn: @@ -10877,6 +12439,10 @@ def ReadSensorDeltas(device_id, sensor, time_from_epoch, time_to_epoch, data_typ def ReadSensor3(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + + if time_to_epoch < time_from_epoch: + time_to_epoch, time_from_epoch = time_from_epoch, time_to_epoch + # Convert epoch to datetime and format as ISO 8601 strings with timezone time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") @@ -11093,7 +12659,7 @@ def ReadSensor3(device_id, sensor, time_from_epoch, time_to_epoch, data_type, ra AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' - AND mtype = 17 + AND mtype > 4 GROUP BY time_bucket('{bucket_interval}', time) ORDER BY time ASC; """ @@ -11178,18 +12744,19 @@ def ReadRadarDetail(device_id, sensor, time_from_epoch, time_to_epoch, alt_key_s return result -def check_and_parse(data_str): - # Remove whitespace to handle cases with spaces - cleaned = data_str.strip() - # Check if second character is '[' - is_list_of_lists = cleaned[1] == '[' - if cleaned[0] == '[': - # Parse the string regardless of type - parsed = json.loads(cleaned) - else: - #parsed = cleaned.split(",") - parsed = SmartSplit(cleaned) - return is_list_of_lists, parsed +def check_and_parse(devices_str): + """Parse devices string and determine if it's nested""" + try: + # Try parsing as Python literal first (handles single quotes) + devices = ast.literal_eval(devices_str) + except (ValueError, SyntaxError): + # Fallback to JSON if that fails + devices = json.loads(devices_str) + + # Check if nested: first element is a list + is_nested = isinstance(devices, list) and len(devices) > 0 and isinstance(devices[0], list) + + return is_nested, devices def clean_data_with_rolling_spline(line_part_t, window=5, threshold=2.0): """ @@ -11880,13 +13447,13 @@ def GeneratePresenceHistory(filename, recreate_in, deployment_id, filter_minutes if force_recreate: - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) - image_bytes, content_type = GetBlob(filename_day) + image_bytes, content_type, metadata = GetBlob(filename_day) if image_bytes != None: image_stream = io.BytesIO(image_bytes) image = Image.open(image_stream) @@ -12112,7 +13679,7 @@ def CreateDailyLocationChart(filename_chart_image_day, locations): all_images.append(footer_image) final_image = np.vstack(all_images) #this needs to write straight to MinIo ! - SaveImageInBlob(filename_chart_image_day, final_image) + SaveImageInBlob(filename_chart_image_day, final_image, []) result = True #cv2.imwrite(filename_chart_image_day, final_image) #print(rooms_count) @@ -12234,7 +13801,7 @@ def GeneratePresenceHistoryChart(filename, recreate_in, deployment_id, filter_mi if force_recreate: - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list_a, device_ids = GetProximityList(deployment_id, timee) CreateDailyLocationMap(filename_day, devices_list_a, ddate, filter_minutes, time_zone_s, stretch_by) locations_list_s = ReadObjectMinIO("daily-maps", filename_chart_data_day) @@ -12289,15 +13856,15 @@ def GeneratePresenceHistoryChart(filename, recreate_in, deployment_id, filter_mi if force_recreate: - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) #here we need to rotate and resize to: - image_bytes, content_type = GetBlob(filename_day) - image_bytes_s, content_type_s = GetBlob(filename_day[:-4]+"S.png") + image_bytes, content_type, metadata = GetBlob(filename_day) + image_bytes_s, content_type_s, metadata = GetBlob(filename_day[:-4]+"S.png") if image_bytes != None: image_stream = io.BytesIO(image_bytes) image = Image.open(image_stream) @@ -12381,7 +13948,7 @@ def GeneratePresenceHistoryChart(filename, recreate_in, deployment_id, filter_mi numpy_image = np.array(result_image_cv2) - success = SaveImageInBlob(filename, numpy_image) + success = SaveImageInBlob(filename, numpy_image, []) #SaveImageInBlob(filename, result_image) # Save directly to MinIO instead of local file #if success: @@ -12429,7 +13996,7 @@ def GeneratePresenceHistoryFiles(filename, recreate_in, deployment_id, filter_mi if force_recreate: - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, 10) @@ -12982,7 +14549,7 @@ def RunCommand(commmand, args_dictionary, deployment_id): filename_4w = f"/{deployment_id}/{deployment_id}_{maps_dates[0]}_{maps_dates[-1]}_{filter_minutes}_{stretch_by}_4w_locations.png.bin" for ddate in maps_dates: - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) Id2Location = {} @@ -13106,7 +14673,7 @@ def RunCommand(commmand, args_dictionary, deployment_id): current_time = datetime.datetime.now(datetime.timezone.utc) last_location = details["last_location"] temperature = int(details["temperature"]) - if "America" in time_zone_s: + if "America" in time_zone_s or "US/" in time_zone_s: temperature_sentence = f"{int(CelsiusToFahrenheit(temperature))} degrees Farenhight" else: temperature_sentence = f"{temperature} degrees Celsius." @@ -15885,7 +17452,7 @@ def CreateSensorsMapFast(map_file, devices_list, selected_date, bw, time_zone_s, if day_data == None: return False, [] - stretch_by = 10 + stretch_by = 8 minutes = 1440 stripes = devices_c * sensors_c arr_source_template = np.full((stripes, minutes + 4), -0.001, dtype=float) @@ -15903,8 +17470,9 @@ def CreateSensorsMapFast(map_file, devices_list, selected_date, bw, time_zone_s, arr_source = AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_table, percentile=100) scaled_day = CalcExtremes(arr_source, minutes, stripes) + arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) - SaveImageInBlob(image_file, arr_stretched) + SaveImageInBlob(image_file, arr_stretched, []) return True, vocs_scaled except Exception as e: @@ -16433,7 +18001,7 @@ class WellApi: return elif get_function_name == "get_image_file": #image represents day in local time - + st = time.time() deployment_id = req.params.get('deployment_id') time_zone_s = GetTimeZoneOfDeployment(deployment_id) ddate = req.params.get("date") @@ -16461,22 +18029,24 @@ class WellApi: force_recreate = True #ddate is in Local Time - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 #Get end of day + #midnight + 5 sec of day begining on date #time that describes new devices in deployment_history is in UTC therefore timee is in UTC st = time.time() vocs_scaled = {} #file_date is in Local time, so we are comparing that and current Local (to install) Date + devices_list = GetProximityList(deployment_id, timee) if force_recreate: st = time.time() vocs_scaled = {} - devices_list = GetProximityList(deployment_id, timee) s_table_temp = s_table - stored, vocs_scaled = CreateMapFast(filename, devices_list, ddate, bw, time_zone_s, radar_part, group_by) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + stored, vocs_scaled, has_old_format, error_string = CreateMapFast_hybrid(filename, devices_list, ddate, bw, time_zone_s, radar_part, group_by) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der if stored != True: AddToLog("Map not created") #logger.warning("Map not created") - resp.media = package_response("Map not created", HTTP_401) + resp.status = falcon.HTTP_400 + resp.media = package_response("Map not created: "+error_string, HTTP_400) return else: AddToLog("Map created") @@ -16486,7 +18056,7 @@ class WellApi: #print(time.time() - st) #lets read and send image from blob - image_bytes, content_type = GetBlob(filename) + image_bytes, content_type, metadata = GetBlob(filename) if debug: resp.media = package_response(f'Log: {debug_string}', HTTP_200) else: @@ -16500,6 +18070,14 @@ class WellApi: resp.content_type = content_type resp.data = image_bytes resp.status = falcon.HTTP_200 + toreport = "0" + if "x-amz-meta-hasoldformat" in metadata: + if metadata["x-amz-meta-hasoldformat"] == "True": + toreport = "1" + resp.set_header('X-Image-Metadata', toreport) + resp.set_header('X-Processing-Time', time.time() - st) + resp.set_header('X-Image-Info', json.dumps({'width': 1440, 'height': len(devices_list[0]) *120})) + return elif get_function_name == "get_photo": #image represents day in local time @@ -16563,8 +18141,7 @@ class WellApi: force_recreate = True # Convert date to UTC epoch for device queries - timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 5 # add so date boundary is avoided - + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 st = time.time() vocs_scaled = {} @@ -16616,7 +18193,7 @@ class WellApi: MQSendL("/" + unique_identifier, json_data) # Read and send image from blob - image_bytes, content_type = GetBlob(filename) + image_bytes, content_type, metadata = GetBlob(filename) if debug: resp.media = package_response(f'Log: {debug_string}', HTTP_200) else: @@ -16744,7 +18321,7 @@ class WellApi: GenerateFullLocationMap(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast,time_zone_s, filter_minutes) #lets read and send image from blob - image_bytes, content_type = GetBlob(filename) + image_bytes, content_type, metadata = GetBlob(filename) if image_bytes is None: raise falcon.HTTPNotFound( @@ -16815,7 +18392,7 @@ class WellApi: filename = filename.replace('\\','/') if chart_type == 6: #"AI Locations": - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) st = time.time() @@ -16824,7 +18401,7 @@ class WellApi: else: print(ddate, time.time() - st) #lets read and send image from blob - image_bytes, content_type = GetBlob(filename) + image_bytes, content_type, metadata = GetBlob(filename) if image_bytes is None: raise falcon.HTTPNotFound( @@ -16847,6 +18424,8 @@ class WellApi: date_from = date_from.replace("_","-") date_to = date_to.replace("_","-") consolidated_by = req.params.get("consolidated_by") + if consolidated_by == None: + consolidated_by = "by_minute_rc" force_recreate = req.params.get("re_create") == "true" radar_part = req.params.get("radar_part") zip_filename = f"/{deployment_id}/{deployment_id}_{date_from}_{date_to}_{consolidated_by}_data.zip" @@ -16869,7 +18448,7 @@ class WellApi: for ddate in dates: force_recreate_csv = force_recreate csv_dayly_filename = f"/{deployment_id}/{deployment_id}_{ddate}_{consolidated_by}_data.csv" - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 if not force_recreate_csv: #time that describes new devices in deployment_history is in UTC therefore timee is in UTC file_exists, time_modified_utc = check_file_exists(csv_dayly_filename, bucket_name="data-downloads") @@ -16910,7 +18489,7 @@ class WellApi: #pack CSV files from BLOB into ZIP #lets read and send image from blob - zip_bytes, content_type = GetBlob(zip_filename, bucket_name="data-downloads") + zip_bytes, content_type, metadata = GetBlob(zip_filename, bucket_name="data-downloads") if debug: resp.media = package_response(f'Log: {debug_string}', HTTP_200) else: @@ -17052,6 +18631,17 @@ class WellApi: #with get_db_connection() as db_conn: privileges = GetPriviledgesOnly(user_name) + #Dissallow access to deployment + deployment_id = form_data.get('deployment_id') + + if deployment_id != "" and deployment_id != None: + if privileges != "-1" and deployment_id not in privileges: + payload = {'ok': 0, 'error': "not allowed"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + # Handle credentials function - most common case if function == "credentials": @@ -17211,7 +18801,7 @@ class WellApi: stdev_range = int(form_data.get("stdev_range")) - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) selected_date = FindCalibrationDate(device_ids, ddate) @@ -17448,7 +19038,10 @@ class WellApi: all_slices = {} cleaned_values = {} - line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + + temp_calib, humid_calib = GetCalibMaps([device_id]) + temp_offset = ExtractTempOffset(temp_calib[device_id]) + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, temp_offset) st = time.time() cleaned_values = [ (line_part[i][0], (line_part[i][0] - line_part[i-1][0]).total_seconds() * 1000) @@ -17569,17 +19162,21 @@ class WellApi: selected_date, date_to = date_to, selected_date devices_list = form_data.get('devices_list') + #devices_list = devices_list.replace("'",'"') + radar_details = {} #devices_list = '[267,560,"?",null,"64B70888F6F0"]' #devices_list = '[[267,560,"?",null,"64B70888F6F0"],[268,561,"?",null,"64B70888F6F1"]]' sensor_list_loc = [form_data.get('sensor_list')] - is_nested, device_details = check_and_parse(devices_list) - if not is_nested: - device_ids_list = [device_details[1]] - well_ids_list = [device_details[0]] - else: - device_ids_list = list(map(lambda x: x[1], device_details)) - well_ids_list =list(map(lambda x: x[0], device_details)) + device_ids_list, well_ids_list, device_details = extract_device_and_well_ids(devices_list) + + #is_nested, device_details = check_and_parse(devices_list) + #if not is_nested: + #device_ids_list = [device_details[1]] + #well_ids_list = [device_details[0]] + #else: + #device_ids_list = [device[1] for device in device_details] + #well_ids_list = [device[0] for device in device_details] data_type = form_data.get('data_type') epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< @@ -17591,18 +19188,22 @@ class WellApi: days = (epoch_to_utc - epoch_from_utc) / (60 * 1440) well_id = well_ids_list[0] + device_detail = device_details[0] all_slices = {} radar_part = "" - if len(device_details) > 4: - device_id2_mac = {device_details[1]: device_details[4]} + if len(device_detail) > 4: + device_id2_mac = {device_detail[1]: device_detail[4]} #device_id2_mac = {device_details[1]: device_details[3]} #epoch_to = '1730592010' #smal sample to test #radar_part = form_data.get('radar_part') we need to find what radar part is configured in device settings - if len(device_details) > 5: - radar_part_all = device_details[5] + if len(device_detail) > 5: + radar_part_all = device_detail[5] if type(radar_part_all) == str: - radar_part_all = ["s3_max",int(radar_part_all)] + if "," in radar_part_all: + radar_part_all = json.loads(radar_part_all) + else: + radar_part_all = ["s3_max",int(radar_part_all)] elif type(radar_part_all) == int: radar_part_all = ["s3_max",radar_part_all] elif type(radar_part_all) == list: @@ -17620,18 +19221,18 @@ class WellApi: if "_" in radar_part: radar_parts = radar_part.split("_") radar_part = radar_parts[0] - radar_details[device_details[1]] = radar_part_all + radar_details[device_detail[1]] = radar_part_all #devices = GetVisibleDevices(deployment_id) temp_calib, humid_calib = GetCalibMaps(device_ids_list) for device_id in device_ids_list: - + temp_offset = ExtractTempOffset(temp_calib[int(device_id)]) sensor_data = {} for sensor in sensor_list_loc: st = time.time() if days < 3: - line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, temp_offset) elif days < 14: bucket_size = "1m" line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) @@ -17639,10 +19240,11 @@ class WellApi: bucket_size = "10m" line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) - #Lets apply calibration: - if sensor == "temperature": - temperature_calib = temperature_offset #float(temp_calib[device_id].split(",")[2]) - line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + #already done above + ##Lets apply calibration: + #if sensor == "temperature": + #temperature_calib = temperature_offset #float(temp_calib[device_id].split(",")[2]) + #line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] if sensor == "humidity": line_part = [(timestamp, value + humidity_offset) for timestamp, value in line_part] @@ -17705,7 +19307,7 @@ class WellApi: sensor_data[sensor] = cleaned_values - if len(device_details) > 4: + if len(device_detail) > 4: all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only else: all_slices[device_id] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only @@ -17737,6 +19339,10 @@ class WellApi: # chart_data with rooms : [list] deployment_id = form_data.get('deployment_id') selected_date = form_data.get('date') + date_to = form_data.get('to_date') + if date_to == None: + date_to = selected_date + sensor = form_data.get('sensor') # one sensor radar_part = form_data.get('radar_part') buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] @@ -17746,14 +19352,15 @@ class WellApi: data_type = form_data.get('data_type') time_zone_s = GetTimeZoneOfDeployment(deployment_id) - epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) + epoch_from_utc, epoch_to_utc_ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) + epoch_from_utc_, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) # obtain devices_list for deployment_id selected_date = selected_date.replace("_","-") devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) sensor_data = {} units = "°C" - if "America" in time_zone_s: + if "America" in time_zone_s or "US/" in time_zone_s: units = "°F" # see https://www.w3schools.com/cssref/css_colors.php sensor_props = {"temperature": ["red", units], @@ -17814,7 +19421,7 @@ class WellApi: compressed_readings = CelsiusToFahrenheitList(compressed_readings) sensor_data[sensor] = compressed_readings - chart_data.append({'name': location_name, 'data': compressed_readings}) + chart_data.append({'name': loc_and_desc, 'data': compressed_readings}) result_dictionary['chart_data'] = chart_data payload = result_dictionary resp.media = package_response(payload) @@ -18043,13 +19650,15 @@ class WellApi: radar_part = form_data.get('radar_part') well_id = well_ids_list[0] all_slices = {} + temp_calib, humid_calib = GetCalibMaps(device_ids_list) #device_id2_mac = {device_details[1]: device_details[4]} for device_id in device_ids_list: #device_id2_mac + temp_offset = ExtractTempOffset(temp_calib[device_id]) sensor_data = {} for sensor in sensor_list: st = time.time() - line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, temp_offset) window = sensor_legal_values[sensor][2] #print("@1", time.time() - st) #first = 3300 @@ -18650,16 +20259,23 @@ class WellApi: deployment_id = form_data.get('deployment_id') map_type = form_data.get('map_type') print(f"$1 ----{time.time() - st}") - maps_dates, positions_list, timezone_s = GetDeploymentDatesBoth(deployment_id) - print(f"$2 ----{time.time() - st}") + datee = form_data.get('date') + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = datee.replace("_","-") + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 #Get end of day + devices_list = GetProximityList(deployment_id, timee)[0] + + maps_dates, _, timezone_s = GetDeploymentDatesBoth(deployment_id) + if maps_dates != []: if datee == "2022-4-2": #that one is default in HTML so disregard datee = maps_dates[0] locations_desc_map = {} - for details in positions_list: + for details in devices_list: well_id = details[0] location = details[2] if details[3] != None and details[3] != "": @@ -18674,10 +20290,10 @@ class WellApi: dataa = {} dataa['Function'] = "deployments_maps_report" - dataa['proximity'] = positions_list + dataa['proximity'] = devices_list maps_dates.sort(reverse = True) dataa['maps_dates'] = maps_dates - dataa['device_count'] = len(positions_list) + dataa['device_count'] = len(devices_list) dataa['time_zone'] = timezone_s dataa['map_type'] = map_type @@ -18691,7 +20307,7 @@ class WellApi: #for i in range(len(MACs_list)): # MACs_map[devices_list[i]] = MACs_list[i][0] - id = positions_list[0][0] + id = devices_list[0][0] #dataa['MACs_map'] = MACs_map dataa['locations_desc_map'] = locations_desc_map #proximity_list = proximity.split(",") @@ -18726,8 +20342,8 @@ class WellApi: checked_or_not = " checked" - for index in range(len(positions_list)): - details = positions_list[index] + for index in range(len(devices_list)): + details = devices_list[index] device_id = details[0] location = details[2] if details[3] != None and details[3] != "": @@ -19277,7 +20893,7 @@ class WellApi: data_type = "presence" time_zone_s = GetTimeZoneOfDeployment(deployment_id) - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) if device_id_in_s != None: #lets remove other devices, since asking for one @@ -19560,7 +21176,7 @@ class WellApi: data_type = "z-graph" time_zone_s = GetTimeZoneOfDeployment(deployment_id) - timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) @@ -19958,13 +21574,19 @@ class WellApi: cnt = 0 for deployment in all_deployments: + #print(deployment) + #if deployment['deployment_id'] == 40: + # print("stop") cnt += 1 if cnt >= first: + #print (deployment['beneficiary_id']) if deployment['beneficiary_id'] in user_id_2_user.keys(): + #print(user_id_2_user[deployment['beneficiary_id']]) caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": user_id_2_user[deployment['beneficiary_id']][3], "first_name": user_id_2_user[deployment['beneficiary_id']][5], "last_name": user_id_2_user[deployment['beneficiary_id']][6]} - else: - caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": deployment['beneficiary_id'][3], "first_name": deployment['beneficiary_id'][5], "last_name": deployment['beneficiary_id'][6]} - result_list.append(caretaker_min_object) + result_list.append(caretaker_min_object) + #else: + # caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": deployment['beneficiary_id'][3], "first_name": deployment['beneficiary_id'][5], "last_name": deployment['beneficiary_id'][6]} + if cnt > last: break @@ -20471,7 +22093,7 @@ class WellApi: elif function == "activities_report_details": deployment_id = form_data.get('deployment_id') - timezone_str = GetTimeZoneOfDeployment(deployment_id) + time_zone_s = GetTimeZoneOfDeployment(deployment_id) filterr = form_data.get('filter') if filterr == None: filterr = 6 @@ -20479,8 +22101,8 @@ class WellApi: filterr = int(filterr) refresh = form_data.get('refresh') == "1" - ddate = current_date_at_tz(timezone_str) - timee = LocalDateToUTCEpoch(ddate, timezone_str)+5 #add so date boundary is avoided + ddate = current_date_at_tz(time_zone_s) + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 24 * 3600 - 1 devices_list, device_ids = GetProximityList(deployment_id, timee) #Here we need to add per day: (all based on Z-graph data!) @@ -20507,9 +22129,9 @@ class WellApi: other_location = location_ot #weekly - week_dates = get_week_days_and_dates(7, timezone_str) - month_dates = get_week_days_and_dates(30, timezone_str) - six_months_dates = get_week_days_and_dates(180, timezone_str) + week_dates = get_week_days_and_dates(7, time_zone_s) + month_dates = get_week_days_and_dates(30, time_zone_s) + six_months_dates = get_week_days_and_dates(180, time_zone_s) other_color = Loc2Color[other_location][0] rgb_string = f"rgb({other_color[0]}, {other_color[1]}, {other_color[2]})" @@ -20528,10 +22150,10 @@ class WellApi: for day_activity in six_months_dates: datee = day_activity[0] - hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, time_zone_s, radar_threshold_group_st) if hours > 18: - print("Too long 6m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + print("Too long 6m!!!", device_id, well_id, datee, filterr, refresh, time_zone_s, radar_threshold_group_st) data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} data.append(data_record) @@ -20550,7 +22172,7 @@ class WellApi: for day_activity in week_dates: datee = day_activity[0] - hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, time_zone_s, radar_threshold_group_st) data_record = { "title": day_activity[1], "events": events_count, "hours": hours} data.append(data_record) @@ -20568,11 +22190,11 @@ class WellApi: for day_activity in month_dates: datee = day_activity[0] - hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, time_zone_s, radar_threshold_group_st) #if datee == "2025-05-20" and device_id == 572: # print(hours) if hours > 18: - print("Too long m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + print("Too long m!!!", device_id, well_id, datee, filterr, refresh, time_zone_s, radar_threshold_group_st) data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} data.append(data_record) @@ -20874,7 +22496,7 @@ class WellApi: if details != {}: details["units"] = "°C" - if "America" in details["time_zone"]: + if "America" in details["time_zone"] or "US/" in details["time_zone"]: details["temperature"] = CelsiusToFahrenheit(details["temperature"]) details["units"] = "°F" devices_list, device_ids = GetProximityList(deployment_id, date_s) @@ -20918,7 +22540,7 @@ class WellApi: details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr, False) details["units"] = "°C" - if "America" in details["time_zone"]: + if "America" in details["time_zone"] or "US/" in details["time_zone"]: details["temperature"] = CelsiusToFahrenheit(details["temperature"]) details["bedroom_temperature"] = CelsiusToFahrenheit(details["bedroom_temperature"]) details["units"] = "°F" @@ -21162,6 +22784,99 @@ class WellApi: #$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ +def parse_devices_field(devices_str): + """ + Parse the devices field which may be a stringified JSON array. + """ + if not devices_str: + return [] + + try: + # Try direct parse + return ast.literal_eval(devices_str) + except (ValueError, SyntaxError): + try: + # Try unescaping if it's a JSON string + import json + return json.loads(devices_str) + except: + # Last resort: manually unescape + unescaped = devices_str.replace('\\"', '"') + return ast.literal_eval(unescaped) + +def normalize_input(data): + """ + Normalize input to a list of rows (each row is a list). + Handles: + - Single row as list: [215,510,"Bedroom",...] + - Single row as string: '215,510,"Bedroom",...' + - Multiple rows: [[215,510,...], [218,521,...]] + """ + # If it's a string, parse it as a single row + if isinstance(data, str): + # Simple CSV parsing (good enough for this format) + import re + # Match quoted strings or non-comma sequences + parts = re.findall(r'"[^"]*"|\[.*?\]|[^,]+', data) + parts = [p.strip().strip('"') for p in parts if p.strip()] + return [parts] + + # If it's a list + if isinstance(data, list): + if not data: + return [] + + # Check if it's a single row (first element is not a list) + if not isinstance(data[0], list): + return [data] + + # It's already multiple rows + return data + + return [] + + +def extract_device_and_well_ids(devices_list): + """ + Extract device_ids_list, well_ids_list, and devices from various input formats. + + Returns: + tuple: (device_ids_list, well_ids_list, devices) + - devices: list of parsed device arrays from index 5 of each row + """ + # Normalize input to list of rows + rows = normalize_input(devices_list) + + device_ids_list = [] + well_ids_list = [] + devices = [] + + for row in rows: + # Ensure we have enough elements + if len(row) < 3: + continue + + # Extract well_id (index 0) and device_id (index 1) + well_id = int(row[0]) if isinstance(row[0], (int, str)) and str(row[0]).isdigit() else row[0] + device_id = int(row[1]) if isinstance(row[1], (int, str)) and str(row[1]).isdigit() else row[1] + + well_ids_list.append(str(well_id)) + device_ids_list.append(str(device_id)) + + # Parse the devices field (index 5) + #devices_field = row[5] if len(row) > 5 else "" + #parsed_devices = parse_devices_field(devices_field) + devices.append(row) + + return device_ids_list, well_ids_list, devices + +def ExtractTempOffset(calib_str): + parts = calib_str.split(",") + if len(parts) > 2: + return parts[2] + else: + return "-10" + def read_bounded_stream_data(request): """Read data from bounded stream using stream-specific properties""" diff --git a/well-api41-working-webhook-AIStudio.py b/well-api41-working-webhook-AIStudio.py new file mode 100644 index 0000000..1c0b353 --- /dev/null +++ b/well-api41-working-webhook-AIStudio.py @@ -0,0 +1,21295 @@ +#!/usr/bin/env python3 +#Vesion 2.0.1 +import os +import sys +import ast +from ast import literal_eval +import falcon +from falcon import HTTP_200, HTTP_400, HTTP_401, HTTP_500 +import json +import logging +from dotenv import load_dotenv +import calendar +import io +import datetime +from datetime import timedelta, timezone +import jwt +import psycopg2 +import html +import re +import fnmatch +import traceback +import time +import pytz +from PIL import Image, ImageDraw, ImageFont +import paho.mqtt.client as mqtt +import ssl +import hashlib +import itertools +from collections import defaultdict, deque +import warnings +from io import BytesIO +import zipfile +from minio import Minio +from minio.error import S3Error +import numpy as np +import cv2 +from sklearn.mixture import GaussianMixture +import openai +from openai import OpenAI +from typing import List, Tuple +import redis +import base64 +import requests +import uuid +import csv +import random + +base_url = "http://192.168.68.70:5050" + +# Try to import the module +try: + from filter_short_groups import filter_short_groups_c + print("Successfully imported filter_short_groups_c") +except ImportError as e: + print(f"Error importing module: {e}") + exit(1) + + +device_lookup_cache = {} +threshold_cache = {} +humidity_offset = 34 +temperature_offset = -10 +st = 0 +if True: + + #from scipy import interpolate + from scipy.optimize import curve_fit + from scipy import stats + import pandas as pd + #from scipy.signal import savgol_filter + +EnablePlot = False #True + +if EnablePlot: + import matplotlib + matplotlib.use('Agg') # Set the backend before importing pyplot + import matplotlib.pyplot as plt + from matplotlib.colors import LinearSegmentedColormap + import matplotlib.dates as mdates + +# Configure logging +logging.basicConfig( + level=logging.DEBUG,# .ERROR, + format='%(asctime)s [%(levelname)s] %(message)s' +) +logger = logging.getLogger(__name__) + +location_names = {-1:"All",0:"?",5:"Office",6:"Hallway",7:"Garage",8:"Outside",9:"Conference Room",10:"Room",34:"Kitchen", + 56:"Bedroom",78:"Living Room",102:"Bathroom",103:"Dining Room",104:"Bathroom Main",105:"Bathroom Guest", + 106:"Bedroom Master", 107:"Bedroom Guest", 108:"Conference Room", 109:"Basement", 110:"Attic", 200:"Other"} + +#Loc2Color = {"?":(0,0,0),"Office":(255,255,0),"Hallway":(128,128,128),"Garage":(128,0,0),"Outside":(0,0,0),"Conference Room":(0,0,128), + #"Room":(64,64,64),"Kitchen":(255,0,0),"Bedroom":(16,255,16),"Living Room":(160,32,240),"Bathroom":(0,0,255), + #"Dining Room":(255,128,0),"Bathroom Main":(16,16,255), "Bedroom Master":(0,255,0),"Bathroom Guest":(32,32,255), + #"Bedroom Guest":(32,255,32), "Basement":(64,64,64), "Attic":(255,165,0), "Other":(192,192,192)} + +Loc2Color = {"Bedroom":((16,255,16),0),"Bedroom Master":((0,255,0),0),"Bedroom Guest":((32,255,32),0),"Bathroom":((0,0,255),1), + "Bathroom Main":((16,16,255),1),"Bathroom Guest":((32,32,255),1),"Kitchen":((255,0,0),2),"Dining Room":((255,128,0),3),"Dining":((255,128,0),3), + "Office":((255,255,0),4),"Conference Room":((0,0,128),5),"Conference":((0,0,128),5),"Room":((64,64,64),6),"Living Room":((160,32,240),7),"Living":((160,32,240),7),"Hallway":((128,128,128),8), + "Garage":((128,0,0),9),"Basement":((64,64,64), 10),"Attic":((255,165,0), 11),"Other":((192,192,192),12),"?":((0,0,0),13),"Outside":((0,0,0),14)} + + +s_table = ["temperature", "humidity", "pressure", "light", "radar", "voc0", "voc1", "voc2", "voc3", "voc4", "voc5", "voc6", "voc7", "voc8", "voc9"] # derived +smells_table = ["s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9"] # derived + +s_table_temp = [] + +Consolidataed_locations = {"?":"Room","Office":"Office","Hallway":"Hallway","Garage":"Garage","Outside":"Outside","Conference Room":"Office", + "Room":"Room","Kitchen":"Kitchen","Bedroom":"Bedroom","Living Room":"Living Room","Bathroom Guest":"Bathroom", + "Dining Room":"Dining Room","Bathroom":"Bathroom", "Bathroom Main":"Bathroom","Bedroom Master":"Bedroom", + "Bedroom Guest":"Bedroom", "Basement":"Basement", "Attic":"Attic", "Other":"Room"} + +AveragePercentPerLocation = {"Bedroom":[29, 37.5], "Bathroom":[2, 4], "Office":[10, 40],"Hallway":[0.1, 0.2],"Garage":[2, 3],"Outside":[5, 10], + "Room":[5, 10],"Kitchen":[5, 12.5], "Living Room":[5, 10], + "Dining Room":[5, 10], "Basement":[0, 0.2], "Attic":[0, 0.2]} + +location_indexes = {} + +for i in location_names: + location_indexes[location_names[i]] = i + + + +# HTTP Status codes +HTTP_200 = falcon.HTTP_200 +HTTP_201 = falcon.HTTP_201 +HTTP_400 = falcon.HTTP_400 +HTTP_401 = falcon.HTTP_401 +HTTP_404 = falcon.HTTP_404 +HTTP_500 = falcon.HTTP_500 + +load_dotenv() + +DB_NAME = os.getenv('DB_NAME') +DB_USER = os.getenv('DB_USER') +DB_PASSWORD = os.getenv('DB_PASSWORD') +DB_HOST = os.getenv('DB_HOST') +DB_PORT = os.getenv('DB_PORT') +MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') +MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY') +MINIO_HOST = os.getenv('MINIO_HOST') +MINIO_PORT = os.getenv('MINIO_PORT') +DAILY_MAPS_BUCKET_NAME = os.getenv('DAILY_MAPS_BUCKET_NAME') +JWT_SECRET = os.getenv('JWT_SECRET') +MASTER_ADMIN = os.getenv('MASTER_ADMIN') +MASTER_PS = os.getenv('MASTER_PS') + +OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') +model_engine = os.getenv('OPENAI_API_MODEL_ENGINE') + +# Redis Configuration +REDIS_HOST = os.getenv('REDIS_HOST', 'localhost') +REDIS_PORT = int(os.getenv('REDIS_PORT')) +REDIS_DB = int(os.getenv('REDIS_DB', 0)) +REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', None) + +ENABLE_AUDIO_PLAYBACK = True +OPT_IN_KEYWORD = "WELLNUOJOIN" +DEFAULT_TTS_VOICE = "female" +DEFAULT_TTS_LANGUAGE = "en-US" +CLIENT_STATE_PREFIX = "app_state" +TELNYX_API_KEY = os.getenv('TELNYX_API_KEY') +TELNYX_API_BASE_URL = os.getenv("TELNYX_API_BASE_URL") + +logger.debug(f"REDIS_PORT: {REDIS_PORT}") +logger.debug(f"TELNYX_API_KEY: {TELNYX_API_KEY}") +logger.debug(f"TELNYX_API_BASE_URL: {TELNYX_API_BASE_URL}") + +redis_host = os.getenv('REDIS_HOST', '192.168.68.70') +redis_host = '192.168.68.70' + +use_pdb = True +debug = False +debug_string = "" +logger.debug(f"Environment variables: {os.environ}") +filesDir = "/home/app/well_web_storage" #os.path.dirname(os.path.realpath(__file__)) +min_io_address = MINIO_HOST + ":" + MINIO_PORT + +miniIO_blob_client = Minio(min_io_address, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False) + +user_id_2_user = {} +smell_min = 1 +no_smell = 102400000 +smell_max = no_smell - 1 +sensor_legal_values = {"radar": (0,1000, 1), "co2": (smell_min, smell_max, 31), "humidity": (1,99, 31), "light": (0, 4095, 1), + "pressure": (0, 10000, 5), "temperature": (1, 60, 31), "voc": (smell_min, smell_max, 31), "voc0": (smell_min, smell_max, 31), + "voc1": (smell_min, smell_max, 31), "voc2": (smell_min, smell_max, 31), "voc3": (smell_min, smell_max, 31), "voc4": (smell_min, smell_max, 31), + "voc5": (smell_min, smell_max, 31), "voc6": (smell_min, smell_max, 31), "voc7": (smell_min, smell_max, 31), "voc8": (smell_min, smell_max, 31), "voc9": (smell_min, smell_max, 31), + "s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), + "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} + +smell_legal_values = {"s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), + "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} + + +def format_address_component(component, component_type): + """ + Apply proper capitalization rules based on component type + """ + if not component: + return "" + + component = component.strip() + + if component_type == 'street_number': + # Street numbers should remain as-is + return component + + elif component_type in ['street_name', 'city', 'country']: + # Title case with special handling for common patterns + return title_case_address(component) + + elif component_type == 'state': + # States should be uppercase if abbreviation, title case if full name + if len(component) == 2: + return component.upper() + else: + return title_case_address(component) + + elif component_type == 'zip_code': + # ZIP codes remain as-is + return component + + elif component_type in ['apt', 'unit', 'suite']: + # Apartment/unit numbers remain as-is + return component + + else: + # Default to title case + return title_case_address(component) + + +def title_case_address(text): + """ + Apply proper title case with address-specific rules + """ + # Words that should remain lowercase (unless at start) + lowercase_words = { + 'and', 'at', 'by', 'for', 'in', 'of', 'on', 'to', 'up', 'via', 'with' + } + + # Words that should be uppercase + uppercase_words = { + 'ne', 'nw', 'se', 'sw', 'n', 's', 'e', 'w', # Directions + 'st', 'nd', 'rd', 'th', # Ordinal suffixes + 'po', 'llc', 'inc', 'corp' # Business suffixes + } + + # Street type abbreviations that should be title case + street_types = { + 'st': 'St', 'ave': 'Ave', 'rd': 'Rd', 'dr': 'Dr', 'ln': 'Ln', + 'ct': 'Ct', 'pl': 'Pl', 'blvd': 'Blvd', 'pkwy': 'Pkwy', + 'hwy': 'Hwy', 'way': 'Way', 'circle': 'Circle', 'court': 'Court', + 'drive': 'Drive', 'lane': 'Lane', 'place': 'Place', 'road': 'Road', + 'street': 'Street', 'avenue': 'Avenue', 'boulevard': 'Boulevard' + } + + words = text.lower().split() + result = [] + + for i, word in enumerate(words): + # Remove punctuation for comparison + clean_word = word.rstrip('.,;:') + punctuation = word[len(clean_word):] + + if clean_word in uppercase_words: + result.append(clean_word.upper() + punctuation) + elif clean_word in street_types: + result.append(street_types[clean_word] + punctuation) + elif i > 0 and clean_word in lowercase_words: + result.append(clean_word + punctuation) + else: + # Handle special cases like "McDonald", "O'Connor" + formatted = format_special_cases(clean_word) + result.append(formatted + punctuation) + + return ' '.join(result) + + +def format_special_cases(word): + """ + Handle special capitalization cases + """ + # Handle names with apostrophes (O'Connor, D'Angelo) + if "'" in word: + parts = word.split("'") + return "'".join(part.capitalize() for part in parts) + + # Handle hyphenated words + if "-" in word: + parts = word.split("-") + return "-".join(part.capitalize() for part in parts) + + # Handle Mc/Mac prefixes + if word.startswith('mc') and len(word) > 2: + return 'Mc' + word[2:].capitalize() + elif word.startswith('mac') and len(word) > 3: + return 'Mac' + word[3:].capitalize() + + # Default capitalization + return word.capitalize() + + +def recreate_address(parsed_address): + """ + Recreate a properly formatted address string from parsed components + + Args: + parsed_address (dict): Dictionary containing parsed address components + OR list of tuples: [('value', 'type'), ...] + + Expected keys/types: + - street_number/house_number + - street_name/road + - apt/unit/suite (optional) + - city + - state + - zip_code/postcode + - country + + Returns: + str: Properly formatted address string + """ + + # Handle both dict and list of tuples formats + if isinstance(parsed_address, list): + # Convert list of tuples to dict + addr_dict = {} + for value, addr_type in parsed_address: + # Map the parser's field names to our expected names + if addr_type == 'house_number': + # Only use house_number if street_number doesn't already exist + if 'street_number' not in addr_dict: + addr_dict['street_number'] = value + elif addr_type == 'street_number': + # street_number takes priority over house_number + addr_dict['street_number'] = value + elif addr_type == 'road': + addr_dict['street_name'] = value + elif addr_type == 'postcode': + addr_dict['zip_code'] = value + else: + addr_dict[addr_type] = value + parsed_address = addr_dict + + # Format each component + street_number = format_address_component( + parsed_address.get('street_number', ''), 'street_number' + ) + + street_name = format_address_component( + parsed_address.get('street_name', ''), 'street_name' + ) + + # Handle apartment/unit/suite + apt_unit = '' + for key in ['apt', 'apartment', 'unit', 'suite', 'ste']: + if parsed_address.get(key): + apt_value = format_address_component(parsed_address[key], 'apt') + if key.lower() in ['apt', 'apartment']: + apt_unit = f"Apt {apt_value}" + elif key.lower() == 'unit': + apt_unit = f"Unit {apt_value}" + elif key.lower() in ['suite', 'ste']: + apt_unit = f"Suite {apt_value}" + break + + city = format_address_component( + parsed_address.get('city', ''), 'city' + ) + + state = format_address_component( + parsed_address.get('state', ''), 'state' + ) + + zip_code = format_address_component( + parsed_address.get('zip_code', ''), 'zip_code' + ) + + country = format_address_component( + parsed_address.get('country', ''), 'country' + ) + + # Build the address string - detect format based on available components + if country and not state: + # International format (like Croatian): Street Number, PostalCode, City, Country + address_parts = [] + + # Street address line + street_parts = [street_number, street_name] + street_line = ' '.join(filter(None, street_parts)) + + if apt_unit: + street_line += f", {apt_unit}" + + if street_line: + address_parts.append(street_line) + + # Add postal code, city, country as separate parts + if zip_code: + address_parts.append(zip_code) + if city: + address_parts.append(city) + if country: + address_parts.append(country) + + return ', '.join(address_parts) + + else: + # US format: Street\nCity, State ZIP + address_parts = [] + + # Street address line + street_parts = [street_number, street_name] + street_line = ' '.join(filter(None, street_parts)) + + if apt_unit: + street_line += f", {apt_unit}" + + if street_line: + address_parts.append(street_line) + + # City, State ZIP line + city_state_zip = [] + if city: + city_state_zip.append(city) + + if state and zip_code: + city_state_zip.append(f"{state} {zip_code}") + elif state: + city_state_zip.append(state) + elif zip_code: + city_state_zip.append(zip_code) + + if city_state_zip: + address_parts.append(', '.join(city_state_zip)) + + return '\n'.join(address_parts) + + +def GetRedisInt(key_name): + try: + result = int(redis_conn.get(key_name).decode('utf-8')) + except: + result = None + return result + +def GetRedisFloat(key_name): + try: + result = float(redis_conn.get(key_name).decode('utf-8')) + except: + result = None + + return result + +def GetRedisString(key_name): + try: + result = redis_conn.get(key_name).decode('utf-8') + except: + result = None + return result + +def GetRedisMap(key_name): + try: + result_bytes = redis_conn.hgetall(key_name) + result = {k.decode('utf-8'): v.decode('utf-8') for k, v in result_bytes.items()} + except: + result = {} + return result + + + +def read_file(file_name, source = "LOCAL", type_ = "TEXT", bucket_name="daily-maps"): + + blob_data = "" + if source == "MINIO": + blob_data = ReadObjectMinIO(bucket_name, file_name) + elif source == "LOCAL": + login_file = os.path.join(filesDir, file_name) + login_file = login_file.replace("\\","/") + logger.debug(f"Full file path: {login_file}") + logger.debug(f"File exists: {os.path.exists(login_file)}") + #print(login_file) + if type_ == "TEXT": + with open(login_file, encoding="utf8") as f: + blob_data = f.read() + else: + with open(login_file, 'rb') as f: + blob_data = f.read() + + elif source == "AZURE": + try: + blob_data = ""#container_client.download_blob(file_name).readall() + except Exception as err: + logger.error("Not reading Azure blob "+str(err)) + blob_data = "" + return blob_data + else: + pass + return blob_data + + +def match_with_wildcard(string, pattern): + return fnmatch.fnmatchcase(string, pattern) + +def extract_differing_part(string, pattern): + regex_pattern = re.escape(pattern).replace(r'\*', r'(.+)') + match = re.match(regex_pattern, string) + if match: + return match.group(1) + else: + return None + +def get_db_connection(): + + #when new_table created: + #GRANT ALL PRIVILEGES ON TABLE public.new_table TO well_app; + #GRANT ALL PRIVILEGES ON TABLE public.new_table TO postgres; + return psycopg2.connect(dbname=DB_NAME, user=DB_USER, password=DB_PASSWORD, host=DB_HOST, port=DB_PORT) + + +def generate_token(username): + expiration = datetime.datetime.now(timezone.utc) + timedelta(hours=24) + token = jwt.encode({"username": username, "exp": expiration}, JWT_SECRET, algorithm="HS256") + return token + + +def verify_token(token): + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) + return payload + except jwt.ExpiredSignatureError: + return None + except jwt.InvalidTokenError: + return None + +def SmartSplit(data_string): + """ + Splits a comma-separated string into a list, properly handling nested structures + and converting values to appropriate Python types using only the ast library. + """ + if not data_string: + return [] + + # Remove trailing comma if present + data_string = data_string.rstrip(',') + + items = [] + current_item = "" + bracket_count = 0 + in_quotes = False + quote_char = None + + i = 0 + while i < len(data_string): + char = data_string[i] + + # Handle quotes + if char in ('"', "'") and (i == 0 or data_string[i-1] != '\\'): + if not in_quotes: + in_quotes = True + quote_char = char + elif char == quote_char: + in_quotes = False + quote_char = None + + # Track brackets only when not in quotes + if not in_quotes: + if char in '[{(': + bracket_count += 1 + elif char in ']}': + bracket_count -= 1 + + # Split on comma only when not inside brackets/quotes + if char == ',' and bracket_count == 0 and not in_quotes: + items.append(current_item.strip()) + current_item = "" + else: + current_item += char + + i += 1 + + # Add the last item + if current_item.strip(): + items.append(current_item.strip()) + + # Convert each item using ast.literal_eval when possible + result = [] + for item in items: + if item == '': + result.append(None) + else: + try: + # Try to evaluate as Python literal + converted = ast.literal_eval(item) + result.append(converted) + except (ValueError, SyntaxError): + # If it fails, keep as string + result.append(item) + + return result + + +def SaveObjectInBlob(file_name, obj): + """ + Saves a Python object to MinIO blob storage using JSON serialization + + Args: + file_name (str): Name of the file to save in blob storage + obj: Python object to serialize and save + """ + try: + # Convert object to JSON string + json_str = json.dumps(obj) + # Convert string to bytes + json_bytes = json_str.encode('utf-8') + + # Save to MinIO + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(json_bytes), + len(json_bytes) + ) + return True + except Exception as e: + logger.error(f"Error saving object to blob: {traceback.format_exc()}") + return False + +def SaveGenericObjectInBlob(bucket_name, file_name, obj): + """ + Saves a Python object to MinIO blob storage using JSON serialization + + Args: + file_name (str): Name of the file to save in blob storage + obj: Python object to serialize and save + """ + try: + # Convert object to JSON string + json_str = json.dumps(obj) + # Convert string to bytes + json_bytes = json_str.encode('utf-8') + + # Save to MinIO + miniIO_blob_client.put_object( + bucket_name, + file_name, + io.BytesIO(json_bytes), + len(json_bytes) + ) + return True + except Exception as e: + logger.error(f"Error saving object to blob: {traceback.format_exc()}") + return False + + + +def ReadObjectMinIO(bucket_name, file_name, filter_date=None): + """ + Read object from MinIO with optional date filtering. + + Args: + bucket_name (str): Name of the MinIO bucket + file_name (str): Name of the file/object + filter_date (str, optional): Date string in format "YYYY-MM-DD". + If provided, returns empty string if object + was modified before or on this date. + + Returns: + str: Object content as string, empty string if filtered out, or None on error + """ + try: + # If date filtering is requested, check object's last modified date first + if filter_date: + try: + # Get object metadata to check last modified date + stat = miniIO_blob_client.stat_object(bucket_name, file_name) + last_modified = stat.last_modified + + # Parse filter date (assuming format YYYY-MM-DD) + target_date = datetime.datetime.strptime(filter_date, "%Y-%m-%d").date() + + # If object was modified before or on target date, return empty string + if last_modified.date() <= target_date: + return None + + except S3Error as e: + logger.error(f"Error getting metadata for {file_name}: {e}") + return None + except ValueError as e: + logger.error(f"Invalid date format '{filter_date}': {e}") + return None + + # Retrieve the object data + response = miniIO_blob_client.get_object(bucket_name, file_name) + # Read the data from response + data_bytes = response.read() + # Convert bytes to string + data_string = data_bytes.decode('utf-8') + # Don't forget to close the response + response.close() + response.release_conn() + return data_string + + except S3Error as e: + logger.error(f"An error occurred while reading {file_name}: {e}") + return None + except: + logger.error(f"An error occurred while decoding {file_name}") + return None + +#def ReadObjectMinIO(bucket_name, file_name): + #try: + ## Retrieve the object data + #response = miniIO_blob_client.get_object(bucket_name, file_name) + + ## Read the data from response + #data_bytes = response.read() + + ## Convert bytes to string and then load into a dictionary + #data_string = data_bytes.decode('utf-8') + + ## Don't forget to close the response + #response.close() + #response.release_conn() + + #return data_string + + #except S3Error as e: + #logger.error(f"An error occurred: {e}") + #return None + + +def package_response_C(payload, status_code=HTTP_200): + """Package response in a standard format""" + if status_code == HTTP_200: + return {"status": "success", "data": payload} + else: + return {"status": "error", "message": payload, "code": status_code} + +def package_response(content, status=falcon.HTTP_200): + """ + Format the HTTP response. + + :param content: The content to be returned in the response. + :param status: HTTP status code (default is 200 OK). + :return: A dictionary containing the formatted response. + """ + if isinstance(content, str): + # If content is a string, try to parse it as JSON + try: + response = json.loads(content) + except json.JSONDecodeError: + # If it's not valid JSON, use it as message + response = {"message": content} + elif isinstance(content, dict): + # If content is a dictionary, serialize it with datetime handling + try: + # First serialize to JSON string with datetime handling + json_str = json.dumps(content, default=datetime_handler) + # Then parse back to dict + response = json.loads(json_str) + except TypeError as e: + response = {"message": f"Serialization error: {str(e)}"} + else: + # For any other type, convert to string and use as message + response = {"message": str(content)} + + # Add status code to the response + response["status"] = status + + # Handle specific status codes + if status == falcon.HTTP_400: + response["error"] = "Bad Request" + elif status == falcon.HTTP_401: + response["error"] = "Unauthorized" + elif status == falcon.HTTP_500: + response["error"] = "Internal Server Error" + + return response + +def GetPriviledges(conn, user_name, password): + sql = "SELECT key, access_to_deployments, user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None and result != []: + if result[0][0] == password: + return result[0][1], result[0][2] + else: + return "0", "0" + else: + return "0", "0" + +def GetPriviledgesOnly(user): + with get_db_connection() as conn: + if isinstance(user, (int)) or user.isdigit(): + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_id = " + user + else: + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_name = '" + user + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetUserId(user_name): + with get_db_connection() as conn: + sql = "SELECT user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetNameFromUserId(user_id): + with get_db_connection() as conn: + sql = f"SELECT user_name, first_name, last_name FROM public.person_details WHERE user_id = {user_id}" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0] + else: + return None + + + +def ListDeployments(priviledges, user_id): + + global user_id_2_user + + conn = get_db_connection() + + if priviledges == "-1": + sql = "SELECT * FROM public.deployments ORDER BY deployment_id ASC;" + else: + sql = f"SELECT * FROM public.deployments WHERE deployment_id IN ({priviledges}) OR user_edit = {user_id} ORDER BY deployment_id ASC;" + + try: + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + complete_result = [] + else: + deployment_ids = [] + deployment_records_dict = {} + for record in result: + deployment_id = record[0] + deployment_ids.append(deployment_id) + deployment_records_dict[deployment_id] = record + + sql = f"SELECT * FROM public.deployment_details WHERE deployment_id IN ({','.join(map(str, deployment_ids))}) ORDER BY deployment_id ASC;" + cur.execute(sql) + details_result = cur.fetchall() + + beneficiary_ids = [] + + for record_details in details_result: + if record_details[1] != None and record_details[1] not in beneficiary_ids: + beneficiary_ids.append(record_details[1]) + + sql = f"SELECT * FROM public.person_details WHERE user_id IN ({','.join(map(str, beneficiary_ids))});" + + + cur.execute(sql) + user_id_2_user = {} + users = cur.fetchall()#cur.fetchone() + for usr_record in users: + user_id_2_user[usr_record[0]] = usr_record + + complete_result = [] + if details_result != None: + for record_details in details_result: + deployment_record = deployment_records_dict[record_details[0]] + complete_record = {'deployment_id': record_details[0], 'beneficiary_id': record_details[1], 'caretaker_id': record_details[2], + 'owner_id': record_details[3], 'installer_id': record_details[4], + 'address_street': record_details[6], 'address_city': record_details[7], 'address_zip': record_details[8], + 'address_state': record_details[9], 'address_country': record_details[10], + 'devices': record_details[5], 'wifis': record_details[11], 'persons': deployment_record[4], 'gender': deployment_record[5], + 'race': deployment_record[6], 'born': deployment_record[7], 'pets': deployment_record[8], 'time_zone': deployment_record[3] + } + complete_result.append(complete_record) + except: + logger.debug(f"Error: {traceback.format_exc()}") + return complete_result + +def ListCaretakers(privileges, user_name): + + conn = get_db_connection() + if privileges == "-1": + sql = "SELECT * FROM public.person_details WHERE role_ids LIKE '%2%' ORDER BY last_name;" #2 is caretaker + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + else: + #we need to check if + sql = f"SELECT * FROM public.person_details WHERE user_name = '{user_name}';" #2 is caretaker + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + + pass + return result + +def ListBeneficiaries(privilidges, user_info): + + conn = get_db_connection() + with conn.cursor() as cur: + + if (privilidges == "-1"): + sql = "SELECT * FROM public.person_details WHERE role_ids LIKE '%1%' ORDER BY last_name;" #1 is beneficiary + else: + #we need to find beneficiaries from list of deployments + sql = f"SELECT beneficiary_id FROM public.deployment_details WHERE deployment_id IN ({privilidges}) ORDER BY deployment_id ASC;" + cur.execute(sql) + result1 = cur.fetchall()#cur.fetchone() + if result1 == None: + result = [] + return result + beneficiaries = ",".join(str(x[0]) for x in result1) + sql = f"SELECT * FROM public.person_details WHERE user_id IN ({beneficiaries}) OR user_edit = {user_info} AND role_ids LIKE '%1%' ORDER BY last_name;" #1 is beneficiary + logger.debug(f"sql= {sql}") + + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + + return result + +def UserDetails(user_id): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'person_details';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.person_details WHERE user_id = "+user_id + + caretaker_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + caretaker_record[field[0]] = result[cnt] + cnt += 1 + + return caretaker_record + +def DeviceDetails(mac): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_mac = '" + mac + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + + return device_record + +def GetDeviceDetailsSingle(device_id): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_id = '" + device_id + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + + return device_record + +def GetDeviceDetailsSingleFromMac(device_mac): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_mac = '" + device_mac + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + else: + #device is not in DB so first lets find it in + pass + return device_record + +def DeploymentDetails(deployment_id): + + deployment_record = {} + + conn = get_db_connection() + + with conn.cursor() as cur: + + sql = "SELECT * FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'deployments';" + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.deployments WHERE deployment_id = '" + deployment_id + "'" + + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + deployment_record[field[3]] = result[cnt] + cnt += 1 + + sql = "SELECT * FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'deployment_details';" + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.deployment_details WHERE deployment_id = '" + deployment_id + "'" + + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + deployment_record[field[3]] = result[cnt] + cnt += 1 + + + return deployment_record + +def ValidUser(user_name, password): + + + if use_pdb: + with get_db_connection() as db_conn: + priviledges, user_id= GetPriviledges(db_conn, user_name, password) + return priviledges, user_id + + else: + pass + #container = GetReference("/MAC") + #try: + ## We can do an efficient point read lookup on partition key and id + ##response = container.read_item(item="64B708896BD8_temperature_2024-01-01_00", partition_key="64B708896BD8") #OK + ##items = query_items(container, '64B708896BD8') #Too slow + ##AddToLog("1!") + #privileges = GetCaretakers(container, email, password) + #return privileges + + #except Exception as err: + #AddToLog("Error !1 "+str(err)) + + +def GetMaxRole(user_name): + + with get_db_connection() as db_conn: + + sql = "SELECT role_ids FROM public.person_details WHERE user_name = '" + user_name + "'" + + with db_conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None and result != []: + return str(result[0]) + return "" + + +def SelectOption(html_code, select_id, selected_item): + """ + Modifies HTML code to set the selected attribute for a specific option in a select element. + + Args: + html_code (str): Original HTML code + select_id (str): ID of the select element to modify + selected_item (str or int): Value of the option to be selected + + Returns: + str: Modified HTML code with the selected attribute added + """ + # Convert selected_item to string for comparison + selected_item = str(selected_item) + + # Find the select element with the given ID + select_pattern = rf']*id=[\'"]?{select_id}[\'"]?[^>]*>(.*?)' + select_match = re.search(select_pattern, html_code, re.IGNORECASE | re.DOTALL) + + if not select_match: + return html_code # Return unchanged if select element not found + + select_content = select_match.group(0) + select_content_orig = select_content + # Remove any existing selected attributes + select_content = re.sub(r'\s+selected(?=[>\s])', '', select_content, flags=re.IGNORECASE) + + # Add selected attribute to the matching option + def replace_option(match): + value = re.search(r'value=[\'"]?([^\'">\s]+)', match.group(0)) + if value and value.group(1) == selected_item: + # Add selected attribute before the closing > + return match.group(0).rstrip('>') + ' selected>' + return match.group(0) + + modified_select = re.sub( + r']*>', + replace_option, + select_content + ) + + # Replace the original select element with the modified one + return html_code.replace(select_content_orig, modified_select) + +def FillFields(blob_data, record, form_type): + """ + Fill in the input fields in the HTML blob_data with values from the caretaker dictionary. + + :param blob_data: str - The initial HTML string containing empty or placeholder input fields. + :param caretaker: dict - The dictionary containing values to populate the fields. + :return: str - The HTML string with the input fields filled with the appropriate values. + """ + # Ensure blob_data is a string + #blob_data = str(blob_data) + + # Populate the fields + for field in record: + logger.debug(f"field= {field}") + if field == "user_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_user_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "deployment_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_deployment_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "device_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_device_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "user_name": + if record[field] != None: + escaped_string = html.escape(record[field]) + pattern = rf'(]+id="new_user_name"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="new_user_name"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + elif field == "location": + if record[field] != None: + blob_data = SelectOption(blob_data, 'location', record[field]) + + elif field == "gender": + if record[field] != None: + blob_data = SelectOption(blob_data, 'gender', record[field]) + + elif field == "race": + if record[field] != None: + blob_data = SelectOption(blob_data, 'race', record[field]) + + elif field == "time_zone_s": + if record[field] != None: + blob_data = SelectOption(blob_data, 'time_zone_s', record[field]) + + elif field == "time_edit" or field == "user_edit": + pass + else: + if record[field] != None: + escaped_string = html.escape(str(record[field])) + pattern = rf'(]+id="{field}"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="{field}"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + return blob_data + +def StoreThresholds2DB(device_id, TR, BR, TLIFE, BLIFE): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + + if device_id == None or device_id == 0: + return 1 + + try: + + + sql = f""" + UPDATE public.devices + SET + radar_threshold = '[{TR},{BR},{TLIFE},{BLIFE}]' + WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql}") + # Execute update query + print(sql) + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + logger.debug("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + + +def StoreBeneficiary2DB(parameters, editing_user_id, user_id): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + if editing_user_id == None or editing_user_id == "": + editing_user_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_user_id != "0": + sql = f""" + UPDATE public.person_details + SET + email = '{CleanObject(parameters.get('email'))}', + user_name = '{CleanObject(parameters.get('new_user_name'))}', + first_name = '{CleanObject(parameters.get('first_name'))}', + last_name = '{CleanObject(parameters.get('last_name'))}', + address_street = '{CleanObject(parameters.get('address_street'))}', + address_city = '{CleanObject(parameters.get('address_city'))}', + address_zip = '{CleanObject(parameters.get('address_zip'))}', + address_state = '{CleanObject(parameters.get('address_state'))}', + address_country = '{CleanObject(parameters.get('address_country'))}', + time_edit = {current_epoch_time}, + user_edit = {user_id}, + role_ids = '{CleanObject(parameters.get('role_ids'))}', + phone_number = '{CleanObject(parameters.get('phone_number'))}', + picture = '{CleanObject(parameters.get('picture'))}', + key = '{CleanObject(parameters.get('key'))}' + WHERE user_id = {editing_user_id}; -- replace 34 with the actual person_id you want to update + """ + + else: + sql = f""" + INSERT INTO public.person_details + (role_ids, email, user_name, first_name, last_name, address_street, address_city, address_zip, address_state, address_country, time_edit, user_edit, phone_number, picture, key) + VALUES + ('{CleanObject(parameters.get('role_ids'))}', '{CleanObject(parameters.get('email'))}', '{CleanObject(parameters.get('new_user_name'))}', + '{CleanObject(parameters.get('first_name'))}', '{CleanObject(parameters.get('last_name'))}', '{CleanObject(parameters.get('address_street'))}', + '{CleanObject(parameters.get('address_city'))}', '{CleanObject(parameters.get('address_zip'))}', '{CleanObject(parameters.get('address_state'))}', + '{CleanObject(parameters.get('address_country'))}', {current_epoch_time}, {user_id}, '{CleanObject(parameters.get('phone_number'))}', + '{CleanObject(parameters.get('picture'))}', '{CleanObject(parameters.get('key'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1, error_string + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + return 0, error_string + +def is_valid_email_strict(email): + """ + More comprehensive email validation with additional checks. + + Args: + email (str): Email address to validate + + Returns: + bool: True if email is valid, False otherwise + """ + if not isinstance(email, str) or len(email) > 254: + return False + + # Check for basic structure + if email.count('@') != 1: + return False + + local, domain = email.split('@') + + # Check local part (before @) + if not local or len(local) > 64: + return False + + # Check domain part (after @) + if not domain or len(domain) > 253: + return False + + # Comprehensive regex pattern + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + + # Additional checks + if email.startswith('.') or email.endswith('.'): + return False + + if '..' in email: + return False + + return bool(re.match(pattern, email)) + +def StoreDisclaimer2DB(parameters): + + email = CleanObject(parameters.get('email')) + if not is_valid_email_strict(email): + error_string = "email has wrong format" + #AddToLog(error_string) + return 0, error_string + + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + + sql = f""" + INSERT INTO public.disclaimers + (email, user_name, first_name, last_name, devices, date, policy_version) + VALUES + ('{CleanObject(parameters.get('email'))}', '{CleanObject(parameters.get('user_name'))}', + '{CleanObject(parameters.get('firstName'))}', '{CleanObject(parameters.get('lastName'))}', '{CleanObject(parameters.get('devices'))}', + '{CleanObject(parameters.get('agreementDate'))}', '{CleanObject(parameters.get('privacyPolicyVersion'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1, error_string + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + return 0, error_string + + +def AccountExists(user_name): + conn = get_db_connection() + + sql = "SELECT * FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + if len(result) > 0: + return True + else: + return False + return False + +def DeleteRecordFromDB(form_data): + + caretaker = form_data['user_name'] + privileges = GetPriviledgesOnly(caretaker) + + if privileges != "-1": + AddToLog("Forbidden!") + return 0 + + conn = get_db_connection() + cur = conn.cursor() + function = form_data.get('function') + if function == "deployment_delete": + user_id = form_data['user_id'] + editing_deployment_id = form_data['editing_deployment_id'] + priviledges = form_data['priviledges'] + if editing_deployment_id == None or editing_deployment_id == "" or editing_deployment_id == "0": + AddToLog("deployment_id is not defined") + cur.close() + conn.close() + return 0 + try: + if user_id == "-1": + sql = f""" + DELETE FROM public.deployments WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + sql = f""" + DELETE FROM public.deployment_details WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + else: + #lets check if user_edit = user_id + sql = f"SELECT user_edit FROM public.deployments WHERE deployment_id = '{editing_deployment_id}'" + cur.execute(sql) + result = cur.fetchone() + if priviledges != "-1": + if result[0] != int(user_id): + cur.close() + conn.close() + return 0 + + sql = f""" + DELETE FROM public.deployments WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + sql = f""" + DELETE FROM public.deployment_details WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + + elif function == "device_delete": + user_id = form_data['user_id'] + editing_device_id = form_data['editing_device_id'] + priviledges = form_data['priviledges'] + if editing_device_id == None or editing_device_id == "" or editing_device_id == "0": + AddToLog("editing_device_id is not defined") + cur.close() + conn.close() + return 0 + try: + if user_id == "-1": + sql = f""" + DELETE FROM public.deployments WHERE device_id = {editing_device_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + #else: + ##lets check if user_edit = user_id + #sql = f"SELECT user_edit FROM public.deployments WHERE deployment_id = '{editing_deployment_id}'" + #cur.execute(sql) + #result = cur.fetchone() + #if priviledges != "-1": + #if result[0] != int(user_id): + #cur.close() + #conn.close() + #return 0 + + #sql = f""" + #DELETE FROM public.deployments WHERE device_id = {editing_device_id} + #""" + #logger.debug(f"sql= {sql}") + ## Execute update query + #cur.execute(sql) + + #conn.commit() + + ## Close the cursor and connection + #cur.close() + #conn.close() + + #AddToLog("Deleted!") + #return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + else: + + #user_id = form_data['user_id'] + editing_user_id = form_data['delete_user_id'] + if editing_user_id == None or editing_user_id == "" or editing_user_id == "0": + AddToLog("user_id is not defined") + cur.close() + conn.close() + return 0 + try: + if privileges == "-1": #user_id == "-1": + sql = f""" + DELETE FROM public.person_details WHERE user_id = {editing_user_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + else: + #lets check if user_edit = user_id + sql = f"SELECT user_edit FROM public.person_details WHERE user_id = '{editing_user_id}'" + cur.execute(sql) + result = cur.fetchone() + if result[0] != int(user_id): + cur.close() + conn.close() + return 0 + + sql = f""" + DELETE FROM public.person_details WHERE user_id = {editing_user_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + +def StoreCaretaker2DB(parameters, editing_user_id, user_id): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + + if editing_user_id == None or editing_user_id == "": + editing_user_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_user_id != "0": + sql = f""" + UPDATE public.person_details + SET + role_ids = '{parameters.get('role_ids')}', + access_to_deployments = '{parameters.get('access_to_deployments')}', + email = '{parameters.get('email')}', + user_name = '{parameters.get('new_user_name')}', + first_name = '{parameters.get('first_name')}', + last_name = '{parameters.get('last_name')}', + address_street = '{parameters.get('address_street')}', + address_city = '{parameters.get('address_city')}', + address_zip = '{parameters.get('address_zip')}', + address_state = '{parameters.get('address_state')}', + address_country = '{parameters.get('address_country')}', + time_edit = {current_epoch_time}, + user_edit = {user_id}, + phone_number = '{parameters.get('phone_number')}', + picture = '{parameters.get('picture')}', + key = '{parameters.get('key')}' + WHERE user_id = {editing_user_id}; -- replace 34 with the actual person_id you want to update + """ + + else: + sql = f""" + INSERT INTO public.person_details + (role_ids, access_to_deployments, email, user_name, first_name, last_name, address_street, address_city, address_zip, address_state, address_country, time_edit, user_edit, phone_number, picture, key) + VALUES + ('{parameters.get('role_ids')}', '{parameters.get('access_to_deployments')}', '{parameters.get('email')}', '{parameters.get('new_user_name')}', '{parameters.get('first_name')}', + '{parameters.get('last_name')}', '{parameters.get('address_street')}', '{parameters.get('address_city')}', '{parameters.get('address_zip')}', '{parameters.get('address_state')}', + '{parameters.get('address_country')}', {current_epoch_time}, {user_id}, '{parameters.get('phone_number')}', '{parameters.get('picture')}', '{parameters.get('key')}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + return ok + +def StoreFlow2DB(user_name, time_s, flow_json): + + conn = get_db_connection() + cur = conn.cursor() + + query = f""" + INSERT INTO public.node_reds (user_name, last_activity, flow) + VALUES ('{user_name}', {time_s}, '{flow_json}') + ON CONFLICT (user_name) + DO UPDATE SET + last_activity = EXCLUDED.last_activity, + flow = EXCLUDED.flow + """ + logger.debug(f"sql= {query}") + + try: + #cur.execute(query, (user_name, time_s, flow_json)) + cur.execute(query) + conn.commit() + logger.debug(f"OK!") + return True + except Exception as e: + conn.rollback() + print(f"Error storing flow: {e}") + logger.debug(f"Error storing flow: {e}") + return False + finally: + cur.close() + conn.close() + logger.debug(f"Closing!") + +def StoreAlarms2DB(deployment_id, device_id, deployment_alarms_json, device_alarms_json): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + + try: + sql = f""" + UPDATE public.deployments SET alarm_details = '{CleanObject(deployment_alarms_json)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + + sql1 = f""" + UPDATE public.devices SET alert_details = '{CleanObject(device_alarms_json)}' WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def StoreAlarms2DBSimple(deployment_id, device_id, deployment_alarms_json, device_alarms_json): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + + try: + if deployment_id != 0: + sql = f""" + UPDATE public.deployments SET alarm_details = '{CleanObject(deployment_alarms_json)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + + if device_id != 0: + + sql1 = f""" + UPDATE public.devices SET alert_details = '{CleanObject(device_alarms_json)}' WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def GetAlarmSimple(deployment_id, device_id): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + deployment_alarms_json = "" + device_alarms_json = "" + + try: + if deployment_id != 0: + sql = f""" + SELECT alarm_details FROM public.deployments WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + deployment_alarms_json = cur.fetchone()[0] + except Exception as err: + pass + + + try: + if device_id != 0: + + sql1 = f""" + SELECT alert_details FROM public.devices WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + device_alarms_json = cur.fetchone()[0] + + + cur.close() + conn.close() + + return deployment_alarms_json, device_alarms_json + except Exception as err: + pass + return deployment_alarms_json, device_alarms_json + + +def CleanObject(object_in, typee = "s"): + + if typee == "n": + res = 0 + if object_in == None or object_in == "": + return 0 + if isinstance(object_in, str): + try: + res = object_in.replace("'", '"') + except: + pass + return res + else: + res = "" + if object_in == None: + return "" + if isinstance(object_in, str): + try: + res = object_in.replace("'", '"') + except: + pass + return res + return object_in + +def StoreDeployment2DB(parameters, editing_deployment_id): + conn = get_db_connection() + cur = conn.cursor() + + if editing_deployment_id == None or editing_deployment_id == "": + editing_deployment_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_deployment_id != "0": + sql = f""" + UPDATE public.deployments + SET + persons = {CleanObject(parameters.get('persons'), "n")}, + gender = {CleanObject(parameters.get('gender'), "n")}, + race = {CleanObject(parameters.get('race'), "n")}, + born = {CleanObject(parameters.get('born'), "n")}, + pets = {CleanObject(parameters.get('pets'), "n")}, + time_zone_s = '{CleanObject(parameters.get('time_zone_s'))}', + user_edit = {CleanObject(parameters.get('user_id'), "n")}, + time_edit = {current_epoch_time} + WHERE deployment_id = {CleanObject(editing_deployment_id, "n")}; + """ + + else: + sql = f""" + INSERT INTO public.deployments + (persons, gender, race, born, pets, time_zone_s, user_edit, time_edit) + VALUES + ({CleanObject(parameters.get('persons'), "n")}, {CleanObject(parameters.get('gender'), "n")}, {CleanObject(parameters.get('race'), "n")}, + {CleanObject(parameters.get('born'), "n")}, {CleanObject(parameters.get('pets'), "n")}, '{CleanObject(parameters.get('time_zone_s'))}', + {CleanObject(parameters.get('user_id'), "n")}, {current_epoch_time}) + RETURNING deployment_id; + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + if editing_deployment_id == "0": + new_deployment_id = cur.fetchone()[0] + # Commit the changes to the database + conn.commit() + + if editing_deployment_id != "0": + sql = f""" + UPDATE public.deployment_details + SET + beneficiary_id = {CleanObject(parameters.get('beneficiary_id'), "n")}, + caretaker_id = {CleanObject(parameters.get('caretaker_id'), "n")}, + owner_id = {CleanObject(parameters.get('owner_id'), "n")}, + installer_id = {CleanObject(parameters.get('installer_id'), "n")}, + address_street = '{CleanObject(parameters.get('address_street'))}', + address_city = '{CleanObject(parameters.get('address_city'))}', + address_zip = '{CleanObject(parameters.get('address_zip'))}', + address_state = '{CleanObject(parameters.get('address_state'))}', + address_country = '{CleanObject(parameters.get('address_country'))}', + + wifis = '{CleanObject(parameters.get('wifis'))}', + devices = '{CleanObject(parameters.get('devices'))}', + lat = {CleanObject(parameters.get('lat'), "n")}, + lng = {CleanObject(parameters.get('lng'), "n")}, + gps_age = {CleanObject(parameters.get('gps_age'), "n")} + + WHERE deployment_id = {editing_deployment_id}; + """ + + else: + sql = f""" + INSERT INTO public.deployment_details + (deployment_id, beneficiary_id, caretaker_id, owner_id, installer_id, address_street, address_city, address_zip, address_state, address_country) + VALUES + ({new_deployment_id}, {CleanObject(parameters.get('beneficiary_id'), "n")}, {CleanObject(parameters.get('caretaker_id'), "n")}, {CleanObject(parameters.get('owner_id'), "n")}, {CleanObject(parameters.get('installer_id'), "n")}, + '{CleanObject(parameters.get('address_street'))}', '{CleanObject(parameters.get('address_city'))}', '{CleanObject(parameters.get('address_zip'))}', '{CleanObject(parameters.get('address_state'))}', + '{CleanObject(parameters.get('address_country'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + + + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + return ok + +def StoreDevice2DB(parameters, editing_device_id): + import uuid + import time + call_id = str(uuid.uuid4())[:8] + + logger.debug(f"[{call_id}] StoreDevice2DB ENTRY - editing_device_id: {editing_device_id}") + logger.debug(f"[{call_id}] Parameters: {parameters}") + print(f"[{call_id}] StoreDevice2DB ENTRY - editing_device_id: {editing_device_id}") + + # Database connection + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + logger.debug(f"[{call_id}] Converted editing_device_id to: {editing_device_id}") + + try: + current_utc_time = datetime.datetime.now(timezone.utc) + + if editing_device_id != "0": + # Update specific device by device_id + logger.debug(f"[{call_id}] UPDATE mode - editing device_id: {editing_device_id}") + sql = f""" + UPDATE public.devices + SET + device_mac = '{CleanObject(parameters.get('device_mac'))}', + well_id = '{CleanObject(parameters.get('well_id'))}', + description = '{CleanObject(parameters.get('description'))}', + location = '{CleanObject(parameters.get('location'))}', + close_to = '{CleanObject(parameters.get('close_to'))}', + group_id = {CleanObject(parameters.get('group_id'))}, + radar_threshold = '{CleanObject(parameters.get('radar_threshold'))}', + temperature_calib = '{CleanObject(parameters.get('temperature_calib'))}', + humidity_calib = '{CleanObject(parameters.get('humidity_calib'))}' + WHERE device_id = {editing_device_id}; + """ + else: + # Use PostgreSQL's UPSERT with ON CONFLICT to handle duplicates safely + logger.debug(f"[{call_id}] INSERT/UPSERT mode") + well_id = CleanObject(parameters.get('well_id')) + device_mac = CleanObject(parameters.get('device_mac')) + + # Debug CleanObject results + logger.debug(f"[{call_id}] CleanObject results:") + logger.debug(f"[{call_id}] device_mac: '{device_mac}'") + logger.debug(f"[{call_id}] well_id: '{well_id}'") + logger.debug(f"[{call_id}] description: '{CleanObject(parameters.get('description'))}'") + logger.debug(f"[{call_id}] location: '{CleanObject(parameters.get('location'))}'") + logger.debug(f"[{call_id}] close_to: '{CleanObject(parameters.get('close_to'))}'") + logger.debug(f"[{call_id}] radar_threshold: '{CleanObject(parameters.get('radar_threshold'))}'") + logger.debug(f"[{call_id}] temperature_calib: '{CleanObject(parameters.get('temperature_calib'))}'") + logger.debug(f"[{call_id}] humidity_calib: '{CleanObject(parameters.get('humidity_calib'))}'") + logger.debug(f"[{call_id}] group_id: '{CleanObject(parameters.get('group_id'))}'") + + # Validate required fields + if not well_id or not device_mac: + logger.error(f"[{call_id}] Missing required fields: well_id={well_id}, device_mac={device_mac}") + raise ValueError("well_id and device_mac are required") + + # Use UPSERT with composite key (well_id, device_mac) + sql = f""" + INSERT INTO public.devices + (device_mac, well_id, description, location, close_to, radar_threshold, temperature_calib, humidity_calib, group_id) + VALUES + ('{device_mac}', '{well_id}', '{CleanObject(parameters.get('description'))}', + '{CleanObject(parameters.get('location'))}', '{CleanObject(parameters.get('close_to'))}', '{CleanObject(parameters.get('radar_threshold'))}', + '{CleanObject(parameters.get('temperature_calib'))}', '{CleanObject(parameters.get('humidity_calib'))}', {CleanObject(parameters.get('group_id'))}) + ON CONFLICT (well_id, device_mac) + DO UPDATE SET + description = EXCLUDED.description, + location = EXCLUDED.location, + close_to = EXCLUDED.close_to, + radar_threshold = EXCLUDED.radar_threshold, + temperature_calib = EXCLUDED.temperature_calib, + humidity_calib = EXCLUDED.humidity_calib, + group_id = EXCLUDED.group_id; + """ + + logger.debug(f"[{call_id}] Using UPSERT with composite key (well_id, device_mac)") + + logger.debug(f"[{call_id}] SQL: {sql}") + print(f"[{call_id}] SQL: {sql}") + + + logger.debug(f"[{call_id}] SQL: {sql}") + print(f"[{call_id}] SQL: {sql}") + + # === ADD THIS NEW DEBUGGING BLOCK === + final_well_id = CleanObject(parameters.get('well_id')) + final_device_mac = CleanObject(parameters.get('device_mac')) + logger.debug(f"[{call_id}] FINAL KEY VALUES FOR SQL:") + logger.debug(f"[{call_id}] well_id: {repr(final_well_id)} (Type: {type(final_well_id)})") + logger.debug(f"[{call_id}] device_mac: {repr(final_device_mac)} (Type: {type(final_device_mac)})") + + # Execute update/insert query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + logger.debug(f"[{call_id}] StoreDevice2DB SUCCESS - returning 1") + print(f"[{call_id}] StoreDevice2DB SUCCESS - returning 1") + AddToLog("Written/updated!") + return 1 + + except Exception as err: + logger.error(f"[{call_id}] Error in StoreDevice2DB: {traceback.format_exc()}") + AddToLog(traceback.format_exc()) + # Make sure to close connections in case of error + try: + if cur: + cur.close() + if conn: + conn.close() + except: + pass + logger.debug(f"[{call_id}] StoreDevice2DB ERROR - returning 0") + print(f"[{call_id}] StoreDevice2DB ERROR - returning 0") + return 0 + +def StoreGroupToDevice(editing_device_id_or_mac, group_id, user_name): + + max_wait = 30 + + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_group", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'group_id': group_id + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + + record = { + 'function': "set_group", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'group_id': group_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + logger.error(f"{(st + max_wait)-time.time()}: {queue_length}") + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.error(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.1) + + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + + try: + + #current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + #current_epoch_time = current_utc_time.timestamp() + + if editing_device_id != "0" and editing_device_id != None: + sql = f"UPDATE public.devices SET group_id = {CleanObject(group_id)} WHERE device_id = {editing_device_id}" + else: + cur.close() + conn.close() + return 0 + + logger.debug(f"sql= {sql}") + + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + + AddToLog("Written/updated!") + return str(record) + except Exception as err: + AddToLog(traceback.format_exc()) + return "" + +def StoreWellIdToDevice(editing_device_id_or_mac, well_id, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_well_id", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'well_id': well_id + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + + record = { + 'function': "set_well_id", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'well_id': well_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + logger.error(f"{(st + max_wait)-time.time()}: {queue_length}") + + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + + try: + + #current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + #current_epoch_time = current_utc_time.timestamp() + + if editing_device_id != "0": + sql = f"UPDATE public.devices SET well_id = {CleanObject(well_id)} WHERE device_id = {editing_device_id}" + else: + cur.close() + conn.close() + return 0 + + logger.debug(f"sql= {sql}") + + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + AddToLog("Written/updated!") + return str(record) + except Exception as err: + AddToLog(f"{traceback.format_exc()} {err}") + return "" + + +def GetDeviceLive(editing_device_id_or_mac, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "get_device_live", + 'user_name': user_name, + 'mac': editing_device_id_or_mac + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + record = { + 'function': "get_device_live", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + if editing_device_id != 0: + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + + return str(record) + else: + return("Error: Device not found!") + + +def StoreNetworkIdToDevice(editing_device_id_or_mac, network_id, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_network_id", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'network_id': network_id + } + MAC = editing_device_id_or_mac + else: + record = { + 'function': "set_network_id", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'network_id': network_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + logger.info(f"Cleared REDIS: from_alerter_{MAC}") + redis_conn.delete(f'from_alerter_{MAC}') + logger.info(f"Pushing to REDIS: send_requests {record_json}") + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + if queue_length > 0: + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + return str(record) + + +def DeviceReboot(editing_device_id_or_mac, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "reboot", + 'user_name': user_name, + 'mac': editing_device_id_or_mac + } + mac = editing_device_id_or_mac + else: + record = { + 'function': "reboot", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac + } + mac = DeviceId2MAC(editing_device_id_or_mac) + + try: + + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.delete(f'from_alerter_{mac}') + logger.info(f"Pushing to REDIS: send_requests {record_json}") + redis_conn.lpush('send_requests', record_json) + + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{mac}') + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{mac}') + logger.info(f"Received in REDIS: from_alerter_{mac} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + AddToLog(str(record)) + return str(record) + except Exception as err: + AddToLog(traceback.format_exc()) + return "" + +def ShowAge(seconds): + minutes = int(seconds // 60) + hours = int(minutes // 60) + days = int(hours // 24) + + if seconds >= 0: + hours = int(hours - (days * 24)) + minutes = int(minutes - (days * 24 * 60) - (hours * 60)) + seconds = int(seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60)) + + if days > 0: + report = f"{int(days)} d {int(hours)} h {int(minutes)} m {int(seconds)} s" + elif hours > 0: + report = f"{int(hours)} h {int(minutes)} m {int(seconds)} s" + elif minutes > 0: + report = f"{int(minutes)} m {int(seconds)} s" + else: + report = f"{int(seconds)} s" + + return report + else: + return "0 s" + +def UpdateDevicesTable(html_string, devices, users): + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + table_rows_string = "" + for device in devices: + #result = next(item for item in users if item[0] == device[6]) + + deployment_name = "?" + for item in users: + if item[0] == device[6]: + result = item + deployment_name = result[1] + if result[2] != None: + deployment_name = deployment_name + " " + result[2] + break + + + + + mac = device[2] + if mac == "64B7088903B4": + print("stop") + mac_row_string = f' {mac}\n' + age = time.time() - device[3] + + if (age < 300): + row_sting = f' \n {device[0]}\n' + else: + row_sting = f' \n {device[0]}\n' + + row_ending = f' \n \n' + for col_cnt in range(1, len(device)): + column_value = device[col_cnt] + #print(column_value) + if col_cnt == 2: + col_string_template = mac_row_string + elif col_cnt == 3: + if column_value > 0: + col_string_template = f' {ShowAge(age)}\n' + else: + col_string_template = f' No data\n' + elif col_cnt == 4: + col_string_template = f' {column_value}\n' + elif col_cnt == 5: + if column_value == None: + col_string_template = f' \n' + else: + col_string_template = f' {column_value}\n' + elif col_cnt == 6: + col_string_template = f' {column_value}\n'; + else: + if column_value == None: + column_value = "" + col_string_template = f' {column_value}\n' + row_sting = row_sting + col_string_template + row_sting = row_sting + row_ending + table_rows_string = table_rows_string + row_sting + #print(table_rows_string) + + html_string = html_string.replace("###ROWS###",table_rows_string) + return html_string + +def UpdateDeploymentsSelector(html_string, deployments, include_all=True, selected="1"): + # + if include_all: + selector_string = f' \n' + else: + selector_string = '' + + for deployment in deployments: + first_name = "" + last_name = "" + if deployment[1] != None: + first_name = deployment[1] + if deployment[2] != None: + last_name = deployment[2] + if deployment[0] == int(selected): + choice_string = f' \n' + else: + choice_string = f' \n' + selector_string = selector_string + choice_string + #print(selector_string) + + html_string = html_string.replace("###INSTALLS###",selector_string) + return html_string + +def GetDeviceDetails(cur, deployment_ids, location_id): + + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + macs = [mac for _, mac in deployment_ids] + #macs = list(deployment_ids.keys()) + macs_string_nq = ",".join(macs) + macs_string = "'" + "','".join(macs) + "'" + + if location_id == -1: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) + ORDER BY om.position; + """ + else: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) AND location = {location_id} + ORDER BY om.position; + """ + + cur.execute(sql) + print(sql) + devices_ids_records = cur.fetchall() + all_details = [] + + + devices_ids_list = [x[0] for x in devices_ids_records] + device_ids_string = ",".join(map(str, devices_ids_list)) + #sql = f"SELECT device_id, MAX(time) as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) GROUP BY device_id" #to slow + sql = f"SELECT DISTINCT ON (device_id) device_id, time as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) AND time > now() - INTERVAL '1 day' ORDER BY device_id, time DESC" + cur.execute(sql) + print(sql) + devices_times = cur.fetchall()#cur.fetchone() + found_device_details = {} + for device_record in devices_times: + device_id, last_message_time = device_record + found_device_details[device_id] = last_message_time + cnt = 0 + for device_table_record in devices_ids_records: + if len(devices_times) > 0: + device_id = device_table_record[0] + if device_id in found_device_details: + last_message_time = found_device_details[device_id] + last_message_epoch = int(last_message_time.timestamp()) + else: + try: + last_message_time = int(device_table_record[14]) + except: + last_message_time = 0 + last_message_epoch = last_message_time + else: + last_message_time = 0 + last_message_epoch = 0 + + #print(last_message_epoch) + #print(type(last_message_epoch)) + device_id = device_table_record[0] + mac = device_table_record[1] + well_id = device_table_record[2] + description = device_table_record[3] + if description == None: + description = "" + if device_table_record[5] != None: + if device_table_record[5] != "": + description = description + " Close to " + device_table_record[5] + location_id = device_table_record[4] + if location_id == None: + location_id = 0 + try: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + except: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + cnt += 1 + all_details.append(row_data) + + return all_details + +def GetDeviceDetailsComplete(cur, deployment_ids, location_id): + + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + macs = [mac for _, mac in deployment_ids] + #macs = list(deployment_ids.keys()) + macs_string_nq = ",".join(macs) + macs_string = "'" + "','".join(macs) + "'" + + if location_id == -1: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) + ORDER BY om.position; + """ + else: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) AND location = {location_id} + ORDER BY om.position; + """ + + cur.execute(sql) + print(sql) + devices_ids_records = cur.fetchall() + all_details = [] + + + devices_ids_list = [x[0] for x in devices_ids_records] + device_ids_string = ",".join(map(str, devices_ids_list)) + #sql = f"SELECT device_id, MAX(time) as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) GROUP BY device_id" #to slow + sql = f"SELECT DISTINCT ON (device_id) device_id, time as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) AND time > now() - INTERVAL '1 day' ORDER BY device_id, time DESC" + cur.execute(sql) + print(sql) + devices_times = cur.fetchall()#cur.fetchone() + found_device_details = {} + for device_record in devices_times: + device_id, last_message_time = device_record + found_device_details[device_id] = last_message_time + cnt = 0 + for device_table_record in devices_ids_records: + if len(devices_times) > 0: + + if device_id in found_device_details: + last_message_time = found_device_details[device_id] + last_message_epoch = int(last_message_time.timestamp()) + else: + try: + last_message_time = int(device_table_record[14]) + except: + last_message_time = 0 + last_message_epoch = last_message_time + else: + last_message_time = 0 + last_message_epoch = 0 + + #print(last_message_epoch) + #print(type(last_message_epoch)) + device_id = device_table_record[0] + mac = device_table_record[1] + well_id = device_table_record[2] + description = device_table_record[3] + alarm_details = device_table_record[16] + if description == None: + description = "" + if device_table_record[5] != None: + if device_table_record[5] != "": + description = description + " Close to " + device_table_record[5] + location_id = device_table_record[4] + if location_id == None: + location_id = 0 + #try: + # row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0], alarm_details] + #except: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0], alarm_details] + cnt += 1 + all_details.append(row_data) + + return all_details + +def GetVisibleDevices(deployments): + + devices_details = [] + stt = time.time() + with get_db_connection() as conn: + with conn.cursor() as cur: + #list all devices that user has access to + if deployments == "-1": + sql = "SELECT device_mac FROM public.devices ORDER BY device_id ASC"# SELECT deployment_id, devices FROM public.deployment_details" + macs_group = [] + deployment_ids = [] + print(sql) + cur.execute(sql) + macs_records = cur.fetchall()#cur.fetchone() + for record in macs_records: + deployment_ids.append((0, record[0])) + devices_details = GetDeviceDetails(cur, deployment_ids, -1) + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + print(sql) + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if "[" not in dev_group: + if "," not in dev_group: + dev_group = '["' + dev_group + '"]' + else: + dev_group = dev_group.replace(" ", "") + dev_group = dev_group.replace(",", '","') + dev_group = '["' + dev_group + '"]' + + macs_group = literal_eval(dev_group) + + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + else: + print(f"Deployment {deployment_id} has dev_group empty") + devices_details = GetDeviceDetails(cur, deployment_ids, -1) + #devices_details.append(devices_detail) + + return devices_details + +def GetVisibleDevicesPerLocation(deployments, location): + + devices_details = [] + + with get_db_connection() as conn: + #list all devices that user has access to + if deployments == "-1" or deployments == "0": + sql = "SELECT deployment_id, devices FROM public.deployment_details" + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + with conn.cursor() as cur: + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if dev_group[0] == "[": + macs_group = literal_eval(dev_group) + else: + macs_group = dev_group.split(',') + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + + devices_details = GetDeviceDetails(cur, deployment_ids, location_indexes[location]) + #devices_details.append(devices_detail) + + return devices_details + +def GetVisibleDevicesPerLocationComplete(deployments, location): + + devices_details = [] + + with get_db_connection() as conn: + #list all devices that user has access to + if deployments == "-1" or deployments == "0": + sql = "SELECT deployment_id, devices FROM public.deployment_details" + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + with conn.cursor() as cur: + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if dev_group[0] == "[": + macs_group = literal_eval(dev_group) + else: + macs_group = dev_group.split(',') + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + + devices_details = GetDeviceDetailsComplete(cur, deployment_ids, location_indexes[location]) + #devices_details.append(devices_detail) + + return devices_details + +def GetUsersFromDeployments(deployments): + #list all devices that user has access to + deployments_dets = [] + with get_db_connection() as conn: + try: + if deployments == "-1": + sql = f""" + SELECT dd.deployment_id, pd.first_name, pd.last_name + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + ORDER BY dd.deployment_id; + """ + else: + sql = f""" + SELECT dd.deployment_id, pd.first_name, pd.last_name + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + WHERE dd.deployment_id IN ({deployments}) + ORDER BY dd.deployment_id; + """ + with conn.cursor() as cur: + cur.execute(sql) + deployments_dets = cur.fetchall()#cur.fetchone() + except Exception as err: + logger.error("GetUsersFromDeployments "+str(err) +" "+sql) + + return deployments_dets + +def GetPreviousDate(current_date): + date_obj = datetime.datetime.strptime(current_date, "%Y-%m-%d") + # Subtract one day + previous_date = date_obj - timedelta(days=1) + # Convert back to string format + previous_date_str = previous_date.strftime("%Y-%m-%d") + return(previous_date_str) + +def CovertToIsoTime(date_s, n_minute): + + hours = n_minute // 60 # Integer division + minutes = n_minute % 60 + + base_date = datetime.datetime.strptime(date_s, "%Y-%m-%d") + final_datetime = base_date + timedelta(hours=hours, minutes=minutes) + iso_timestamp = final_datetime.isoformat() + return iso_timestamp + + +def sleep_length(presence_list, short_absence_threshold=15): + """ + Calculate the total sleep duration and wake time based on presence data. + + This function correctly interprets the presence_list to determine sleep duration by: + 1. Properly aggregating the total sleep time from all significant in-bed periods + 2. Considering short absences as part of the same sleep session + 3. Determining the wake time when the main sleep session ended + + Args: + presence_list (list): List of tuples indicating bed presence/absence + short_absence_threshold (int, optional): Maximum duration in decas to consider + an absence "short" and still count as sleep. + Default is 15 (2.5 minutes) + + Returns: + tuple: (sleep_duration_minutes, wake_time_minutes) + sleep_duration_minutes: Total sleep duration in minutes + wake_time_minutes: Minute in the day when person was determined to be + done sleeping (minutes since midnight) + """ + # Extract in-bed periods and out-of-bed periods + in_bed_periods = [] + out_bed_periods = [] + + # First process the raw data into periods + for i in range(len(presence_list)): + deca_index, deca_count = presence_list[i] + + # Skip separator tuples where deca_count is 0 + if deca_count == 0: + continue + + if deca_count > 0: # In bed + # Special case for the midnight (first) tuple + if i == 0 and deca_index == 0: + # This is time in bed before midnight + start_deca = -deca_count # Negative because it's before midnight + end_deca = 0 # Midnight + else: + start_deca = deca_index + end_deca = deca_index + deca_count + + in_bed_periods.append({ + 'start': start_deca, + 'end': end_deca, + 'duration': deca_count + }) + else: # Out of bed + out_bed_periods.append({ + 'start': deca_index, + 'end': deca_index + abs(deca_count), + 'duration': abs(deca_count) + }) + + # Sort periods to ensure chronological order + in_bed_periods.sort(key=lambda p: p['start']) + out_bed_periods.sort(key=lambda p: p['start']) + + # Merge in-bed periods that are separated by short absences + merged_periods = [] + current_period = None + + for period in in_bed_periods: + # If we're at the start or after a long break, begin a new period + if current_period is None: + current_period = period.copy() + else: + # Check if this period starts shortly after the previous one ends + gap = period['start'] - current_period['end'] + + # If the gap is negative, the periods overlap (data error), treat as continuous + if gap < 0: + gap = 0 + + # If the gap is short enough, merge the periods + if gap <= short_absence_threshold: + # Extend the current period + current_period['end'] = period['end'] + current_period['duration'] += period['duration'] + gap # Include gap + else: + # Gap too long, add the completed period and start a new one + merged_periods.append(current_period) + current_period = period.copy() + + # Add the last period if there is one + if current_period is not None: + merged_periods.append(current_period) + + # Find significant sleep periods (at least 30 minutes) - REMOVED night-time restriction + significant_sleep_threshold = 180 # 30 minutes (180 decas) + significant_periods = [p for p in merged_periods if p['duration'] >= significant_sleep_threshold] + + if significant_periods: + # Find the LATEST significant sleep period (for multi-day data) + main_sleep_period = max(significant_periods, key=lambda p: p['start']) + + # Calculate total sleep duration + sleep_duration_minutes = round(main_sleep_period['duration'] / 6) # Convert to minutes + + # Wake time is when this period ended + # Handle multi-day scenarios by taking modulo 24 hours + wake_time_deca = main_sleep_period['end'] + wake_time_minutes = round(wake_time_deca / 6) + + # If wake time is beyond 24 hours, wrap it to the current day + if wake_time_minutes >= 1440: # 1440 = 24 hours in minutes + wake_time_minutes = wake_time_minutes % 1440 + + return (sleep_duration_minutes, wake_time_minutes) + + # No significant sleep periods found + return (0, 0) + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + +def filter_short_groups_c_wc_old(presence_list, filter_size, device_id_str, from_date, to_date, time_zone_s, refresh = False): + + #days = presence_list + #for from_date, to_date + + tz = pytz.timezone(time_zone_s) + # Get current time in that timezone + current_time = datetime.datetime.now(tz) + # Return just the date part as string + now_date_str = current_time.strftime("%Y-%m-%d") + + start_date = datetime.datetime.strptime(from_date, "%Y-%m-%d") + end_date = datetime.datetime.strptime(to_date, "%Y-%m-%d") + + last_offset = 0 + #if to_date == now_date_str: + # last_offset = 1 + + # Loop through each date (including end_date) + current_date = start_date + dates_list = [] + days_difference = 1 + (end_date - start_date).days + whole_result = [0] * 6 * 1440 * (days_difference) + + is_long = False + if len(presence_list)/(6 * 1440) > (days_difference): #long version + is_long = True + + while current_date <= end_date: + current_date_str = current_date.strftime("%Y-%m-%d") + print(current_date_str) + dates_list.append(current_date_str) + current_date += timedelta(days=1) + + for day in range(1, days_difference-last_offset+1): + print(day) + end_index = (1 + day) * 6 * 1440 + if end_index > len(presence_list): + end_index = len(presence_list) + + if is_long: + start_index = end_index - 2 * 6 * 1440 + else: + start_index = end_index - 6 * 1440 + + current_date_str = dates_list[day-1] + filename_day_presence = f"/{device_id_str}/{device_id_str}_{current_date_str}_{filter_size}_presence.bin" + filtered_day_str = None + if refresh == False: + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence) + + if filtered_day_str == None: + filtered_day = filter_short_groups_c(presence_list[start_index:end_index], filter_size, device_id_str, from_date) + SaveGenericObjectInBlob("filtered-presence", filename_day_presence, filtered_day) + else: + filtered_day = json.loads(filtered_day_str) + whole_result[start_index:end_index] = filtered_day + + if current_date_str != to_date: + end_index = len(presence_list) + start_index = end_index - 2 * 6 * 1440 + filtered_day = filter_short_groups_c(presence_list[start_index:end_index], filter_size, device_id_str, from_date) + whole_result[start_index:end_index] = filtered_day + + return whole_result + +def filter_short_groups_c_wc(presence_list, filter_size, device_id_str, from_date, to_date, time_zone_s, refresh=False): + """ + Filter out short groups across multiple days. + For each target day, processes [previous_day + target_day] and extracts only the target_day result. + """ + # Setup timezone and current time + tz = pytz.timezone(time_zone_s) + current_time = datetime.datetime.now(tz) + now_date_str = current_time.strftime("%Y-%m-%d") + + start_date = datetime.datetime.strptime(from_date, "%Y-%m-%d") + end_date = datetime.datetime.strptime(to_date, "%Y-%m-%d") + + # Build dates list + current_date = start_date + dates_list = [] + while current_date <= end_date: + current_date_str = current_date.strftime("%Y-%m-%d") + print(current_date_str) + dates_list.append(current_date_str) + current_date += timedelta(days=1) + + days_difference = len(dates_list) + + # Handle current day limitation + samples_per_day = 6 * 1440 + total_samples = samples_per_day * days_difference + + # If today is the last day, limit the data + effective_total_samples = total_samples + if to_date == now_date_str: + current_minute_of_day = current_time.hour * 60 + current_time.minute + current_sample_of_day = min(current_minute_of_day * 6, samples_per_day) + effective_total_samples = (days_difference - 1) * samples_per_day + current_sample_of_day + print(f"Today detected: limiting to {current_sample_of_day} samples for last day") + + # Initialize result - use effective total samples + whole_result = [0] * effective_total_samples + + # Process each day (0-indexed to avoid confusion) + for day_idx in range(days_difference): + current_date_str = dates_list[day_idx] + print(f"Processing day {day_idx + 1}: {current_date_str}") + + # Calculate result array indices for this day + result_start_idx = day_idx * samples_per_day + result_end_idx = (day_idx + 1) * samples_per_day + + # For the last day, if it's today, limit the end index + if day_idx == days_difference - 1 and to_date == now_date_str: + result_end_idx = result_start_idx + current_sample_of_day + + # Skip if this day's range is beyond our result array + if result_start_idx >= len(whole_result): + break + + # Ensure we don't exceed result array bounds + result_end_idx = min(result_end_idx, len(whole_result)) + + # Calculate input data range - FIXED LOGIC + if day_idx == 0: + # First day: no previous day available in our data, process only current day + input_start_idx = 0 + input_end_idx = min(len(presence_list), samples_per_day) + has_previous_day = False + else: + # Other days: use previous day + current day + input_start_idx = (day_idx - 1) * samples_per_day + input_end_idx = min(len(presence_list), (day_idx + 1) * samples_per_day) + has_previous_day = True + + # Skip if no input data available + if input_start_idx >= input_end_idx or input_start_idx >= len(presence_list): + print(f"No input data available for {current_date_str}") + continue + + # Try to load cached data + filename_day_presence = f"/{device_id_str}/{device_id_str}_{current_date_str}_{filter_size}_presence.bin" + filtered_day_str = None + + if not refresh: + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence, current_date_str) + + if filtered_day_str is not None and filtered_day_str != "": + has_larger = bool(re.search(r'\b(?:[2-9]|\d{2,})\.\d+\b', filtered_day_str)) + if has_larger: + filtered_day_str = None + + if filtered_day_str is None or filtered_day_str == "": + # Filter the input data + input_data = presence_list[input_start_idx:input_end_idx] + print(f"Input range: {input_start_idx}:{input_end_idx}, length: {len(input_data)}, has_previous_day: {has_previous_day}") + + filtered_data = filter_short_groups_c(input_data, filter_size, device_id_str, from_date) + + # Calculate how much data we need for this day + needed_samples = result_end_idx - result_start_idx + + # Determine extraction offset - FIXED LOGIC + if has_previous_day and len(filtered_data) >= 2 * samples_per_day: + # We processed [previous_day + current_day], take the second day (current_day) + day_data_start = samples_per_day # Skip the first day (previous day context) + print(f"Extracting day 1 from 2-day filtered result: [{day_data_start}:{day_data_start + needed_samples}]") + elif has_previous_day and len(filtered_data) >= samples_per_day: + # We have previous day context but less than 2 full days + # Take from the portion that corresponds to current day + available_current_day_samples = len(filtered_data) - samples_per_day + day_data_start = samples_per_day + needed_samples = min(needed_samples, available_current_day_samples) + print(f"Extracting partial day 1: [{day_data_start}:{day_data_start + needed_samples}]") + else: + # First day or single day processing, take from beginning + day_data_start = 0 + print(f"Extracting day 0 (first/single day): [{day_data_start}:{day_data_start + needed_samples}]") + + day_data_end = day_data_start + needed_samples + + # Extract the day's portion, ensuring we don't exceed bounds + if day_data_start < len(filtered_data): + filtered_day = filtered_data[day_data_start:min(day_data_end, len(filtered_data))] + else: + filtered_day = [] + + print(f"Filtered data length: {len(filtered_data)}") + print(f"Extracted day data: start={day_data_start}, end={day_data_end}, length={len(filtered_day)}") + + # Cache the result + SaveGenericObjectInBlob("filtered-presence", filename_day_presence, filtered_day) + else: + filtered_day = json.loads(filtered_day_str) + + # Copy to result array + copy_length = min(len(filtered_day), result_end_idx - result_start_idx) + if copy_length > 0: + whole_result[result_start_idx:result_start_idx + copy_length] = filtered_day[:copy_length] + + print(f"Completed {current_date_str}: copied {copy_length} samples") + + return whole_result + + +def GetLastDurationMinutes(deployment_id, selected_devices, filter, ddate): + + global threshold_cache, device_lookup_cache + + max_sleep = 0 + max_device_id = 0 + max_woke_up = 0 + presence_list = [] + to_date = ddate + + + date_obj = datetime.datetime.strptime(ddate, "%Y-%m-%d") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + # Convert back to string + prev_date = previous_day.strftime("%Y-%m-%d") + + data_type = "z-graph" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + + + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Lets filter bedrooms only + just_selected_devices = [] + for device_details in devices_list: + if device_details[1] in selected_devices: + just_selected_devices.append(device_details) + + devices_list = just_selected_devices + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + #time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + #epoch_time = calendar.timegm(time_from_z.utctimetuple()) + time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from.utctimetuple()) + + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + # Calculate the difference in days + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + + if data_type == "all" or data_type == "z-graph" or data_type == "multiple": + + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + threshold_str = details[5] + if "," in threshold_str: + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + else: + if is_number(threshold_str): + threshold_lst = ["s3",float(threshold_str)] + + print(threshold_lst) + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + if len(devices_list) > 0: + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #print(sql) + if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zsql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + cur.execute(zsql) + myz_data = cur.fetchall() + + #if my_data != None: + + #device_id_2_threshold = {} + #device_id_2_location = {0: "Outside"} + #row_nr_2_device_id = {} + #cnt = 0 + #row_nr_2_device_id[0] = 0 + + ##presence_map['longpresence'] and temporary_map_day_plus are similar, except one is used for Z-graph, and another for multiple persons detection + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'] = {} + #presence_map['longpresence'] = {} + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'] = {} + + #for details in devices_list: + ##(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + #well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'][well_id] = [0] * 6 * 1440 * days_difference + + + ##presence_map[][well_id] = zeros_list + #cnt += 1 + #row_nr_2_device_id[cnt] = well_id + + #if radar_threshold_group_st == None: + #radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + #if len(radar_threshold_group_st) > 8: + #radar_threshold_group = json.loads(radar_threshold_group_st) + #else: + #radar_threshold_group = ["s3",12] + + #device_id_2_location[well_id] = location_name + #device_id_2_threshold[well_id] = radar_threshold_group + #if len(my_data) > 1: + + #start_time_ = my_data[0][0] + #parsed_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + #) + + #presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + presence_map['longpresence'] = {} + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + + print(deployment_id) + print(time_from_z_str) + print(devices_list) + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + start_time = datetime.datetime( + parsed_time.year, + parsed_time.month, + parsed_time.day, + parsed_time.hour, # Adjust for UTC-7 + parsed_time.minute, + parsed_time.second, + tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + ) + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + presence_map['longpresence'][well_id] = temporary_map_day_plus[well_id] + + for device_id in ids_list: + device_id_str = str(device_id) + if data_type == "presence" or data_type == "all" or data_type == "z-graph": + if filter > 1: + #presence_list = filter_short_groups_numpy(presence_map["presence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + #presence_list = filter_short_groups_c_wc(presence_map["presence"][id2well_id[device_id]], filter, device_id_str, ddate, to_date, time_zone_s) + #presence_map["presence"][id2well_id[device_id]] = presence_list + #longpresence_list = filter_short_groups_numpy(presence_map["longpresence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s, True) + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + + max_sleep = 0 + max_device_id = 0 + max_woke_up = 0 + for device_id in ids_list: + #print(device_id_2_threshold[id2well_id[device_id]]) + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + sleep_minutes, woke_up = sleep_length(z_graph) + if sleep_minutes > max_sleep: + max_sleep = sleep_minutes + max_device_id = device_id + max_woke_up = woke_up + presence_map = {} + return max_sleep, max_device_id, max_woke_up, z_graph + +def is_number(s): + try: + float(s) + return True + except ValueError: + return False + +def GetTempOffset(device_id): + result = 0 + sql = f""" + SELECT temperature_calib + FROM public.devices + WHERE device_id = {device_id}; + """ + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + try: + if isinstance(result, tuple): + if "," in result[0]: + string_nr = result[0].split(',')[-1].strip() + if is_number(string_nr): + return float(string_nr) + else: + return temperature_offset + else: + if is_number(result[0]): + return float(result[0]) + else: + return temperature_offset + else: + return temperature_offset + except: + return temperature_offset + + +def GetTemperature(bedroom_device_id): + result = 0 + sql = f""" + SELECT * + FROM public.sensor_readings + WHERE device_id = {bedroom_device_id} and temperature > 0 + ORDER BY "time" DESC + LIMIT 1; + """ + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + + if result == None: + return 0 + else: + return result[2] + +def GetSensorsDetailsFromDeployment(deployment_id, ddate, filter_minutes, fast=False): + #list all devices that user has access to + deployments_dets = [] + with get_db_connection() as conn: + try: + sql = f""" + SELECT pd.user_id, pd.first_name, pd.last_name, pd.address_street, pd.picture + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + WHERE dd.deployment_id ={deployment_id}; + """ + with conn.cursor() as cur: + cur.execute(sql) + deployments_dets = cur.fetchone() + except Exception as err: + logger.error("GetSensorsDetailsFromDeployment "+str(err) +" "+sql) + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Which sensor is in: Bathroom, Kitchen, Bedroom ? + bathrooms = [] + kitchens = [] + bedrooms = [] + dev_id_to_location = {0: "Outside/?"} + for device in devices_list: + dev_id_to_location[device[1]] = device[2] + if Consolidataed_locations[device[2]] == "Bathroom": + bathrooms.append(device[1]) + elif Consolidataed_locations[device[2]] == "Kitchen": + kitchens.append(device[1]) + elif Consolidataed_locations[device[2]] == "Bedroom": + bedrooms.append(device[1]) + + #we need to determine where user is seen last, and user sensor data from there... + locations_file = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + logger.debug(f"locations_file1 ={locations_file}") + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + force_recreate = False + file_exists1, file_modified_utc1 = check_file_exists(locations_file+".bin") + if file_exists1: + file_modified_local = file_modified_utc1.astimezone(pytz.timezone(time_zone_s)) + file_modified_date_local = file_modified_local.date() #local date + file_modified_date_utc = file_modified_utc1.date() + file_date_utc = MapFileToDate(locations_file) #locations_file is UTC + #if file_modified_date_local < file_date_utc: + if file_modified_utc1.date() < file_date_utc: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - file_modified_local + if time_passed.seconds > 30: #recreate if older than 5 minutes + force_recreate = True + else: + force_recreate = True + + + logger.debug(f"force_recreate={str(force_recreate)}") + + if force_recreate: + CreateLocationsStripe(locations_file, time_zone_s) + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + + last_present_device = 0 + last_present = 0 + last_bathroom = 0 + last_kitchen = 0 + last_bedroom = 0 + last_bathroom_date = ddate + last_kitchen_date = ddate + last_bedroom_date = ddate + before_last_present_device = 0 + last_present_duration = 0 + + if locations_list_s is not None: + locations_list = json.loads(locations_list_s) + + if len(locations_list) > 1: + if locations_list[-1][0] > 0: + last_present_device = locations_list[-1][0] + last_present = locations_list[-1][1] + locations_list[-1][2] + before_last_present_device, last_present_duration = FindPreviousLocation(locations_list, last_present_device, -1) + #before_last_present_device = locations_list[-3][0] + #last_present_duration = locations_list[-1][2] + + else: + last_present_device = locations_list[-2][0] + last_present = locations_list[-2][1] + locations_list[-2][2] + before_last_present_device, last_present_duration = FindPreviousLocation(locations_list, last_present_device, -2) + #before_last_present_device = locations_list[-4][0] + #last_present_duration = locations_list[-2][2] + + elif len(locations_list) == 1: + last_present_device = locations_list[0][0] + + #Lets find last bathroom presence time + + if len(locations_list) > 0 and len(bathrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bathrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bathroom: + last_bathroom = loc_time[1] + loc_time[2] + last_bathroom_date = ddate + + + #Lets find last kitchen presence time + + if len(locations_list) > 0 and len(kitchens) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in kitchens: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_kitchen: + last_kitchen = loc_time[1] + loc_time[2] + last_kitchen_date = ddate + + + #Lets find last bedroom presence time + + if len(locations_list) > 0 and len(bedrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bedrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bedroom: + last_bedroom = loc_time[1] + loc_time[2] + last_bedroom_date = ddate + + + if last_bathroom == 0 or last_kitchen == 0 or last_bedroom == 0: + ddate = GetPreviousDate(ddate) + + locations_file = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + logger.debug(f"locations_file2 ={locations_file}") + + force_recreate = False + file_exists1, file_modified_utc1 = check_file_exists(locations_file+".bin") + logger.debug(f"file_exists1={str(file_exists1)}") + logger.debug(f"file_modified_utc1={str(file_modified_utc1)}") + #file_exists1, file_modified_utc1 + if file_exists1: + file_modified_local = file_modified_utc1.astimezone(pytz.timezone(time_zone_s)) + file_modified_date_local = file_modified_local.date() + file_date_utc = MapFileToDate(locations_file) + logger.debug(f"file_modified_utc1={str(file_modified_utc1.date())} file_date_utc={str(file_date_utc)}") + if file_modified_utc1.date() < file_date_utc: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - file_modified_local + logger.debug(f"current_time={current_time} file_modified_local={file_modified_local} time_passed={time_passed}") + if time_passed.seconds > 30: #recreate if older than 5 minutes + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + CreateLocationsStripe(locations_file, time_zone_s) + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + + + logger.debug(f"locations_list_s={locations_list_s}") + if (locations_list_s is not None): + + locations_list = json.loads(locations_list_s) + + if last_present_device == 0: + if len(locations_list) > 1: + if locations_list[-1][0] > 0: + last_present_device = locations_list[-1][0] + else: + last_present_device = locations_list[-2][0] + + elif len(locations_list) == 1: + last_present_device = locations_list[0][0] + + if last_bathroom == 0: + if len(locations_list) > 0 and len(bathrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bathrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bathroom: + last_bathroom = loc_time[1] + loc_time[2] + last_bathroom_date = ddate + + if last_kitchen == 0: + if len(locations_list) > 0 and len(kitchens) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in kitchens: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_kitchen: + last_kitchen = loc_time[1] + loc_time[2] + last_kitchen_date = ddate + + if last_bedroom == 0: + if len(locations_list) > 0 and len(bedrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bedrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bedroom: + last_bedroom = loc_time[1] + loc_time[2] + last_bedroom_date = ddate + + + + last_bathroom_time = "2023-01-01T00:00:00" + if last_bathroom > 0: + last_bathroom_time = CovertToIsoTime(last_bathroom_date, last_bathroom) + + last_kitchen_time = "2023-01-01T00:00:00" + if last_kitchen > 0: + last_kitchen_time = CovertToIsoTime(last_kitchen_date, last_kitchen) + + last_bedroom_time = "2023-01-01T00:00:00" + if last_bedroom > 0: + last_bedroom_time = CovertToIsoTime(last_bedroom_date, last_bedroom) + + last_present_time = "2023-01-01T00:00:00" + if last_present > 0: + last_present_time = CovertToIsoTime(ddate, last_present) + + # debug for 48h bug + if last_bathroom_time == "2023-01-01T00:00:00" or last_kitchen_time == "2023-01-01T00:00:00" or last_bedroom_time == "2023-01-01T00:00:00": + #last_bathroom_time = "48h" if last_bathroom_time == "2023-01-01T00:00:00" else f"{last_bathroom-last_bathroom_time}" + #last_kitchen_time = "48h" if last_kitchen_time == "2023-01-01T00:00:00" else f"{last_kitchen-last_kitchen_time}" + #last_bedroom_time = "48h" if last_bedroom_time == "2023-01-01T00:00:00" else f"{last_bedroom-last_bedroom_time}" + + logger.debug(f"48h-> deployment_id={str(deployment_id)}, ddate={str(ddate)}") + logger.debug(f"48h-> force_recreate={force_recreate}") + logger.debug(f"48h-> last_bathroom_time={last_bathroom_time}|last_kitchen_time={last_kitchen_time}|last_bedroom_time={last_bedroom_time}") + logger.debug(f"48h-> devices_list={str(devices_list)}") + logger.debug(f"48h-> bathrooms={str(bathrooms)}") + logger.debug(f"48h-> kitchens={str(kitchens)}") + logger.debug(f"48h-> bedrooms={str(bedrooms)}") + logger.debug(f"48h-> locations_list_s={str(locations_list_s)}") + + + # wellness_score_percent + wellness_score_percent = 90 + + sleep_filter_minutes = 5 + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + + sleep_bathroom_visit_count = 0 + # bedroom_co2 + bedroom_co2 = 500 + device_detail = None + bedroom_temperature = 0 + sleep_hours = 0 + if fast == False: + + if len(bedrooms) > 0: + sleep_minutes, bedroom_device_id, woke_up, presence_list = GetLastDurationMinutes(deployment_id, bedrooms, sleep_filter_minutes, ddate) + sleep_hours = sleep_minutes/ 60 + # bedroom_temperature + temp_offset = GetTempOffset(bedroom_device_id) + + bedroom_temperature = GetTemperature(bedroom_device_id) + temp_offset + + # sleep_bathroom_visit_count + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + if sleep_minutes < woke_up: # went to sleep after midnight + date_sleep = ddate + to_sleep = woke_up - sleep_minutes + else:# went to sleep before midnight + to_sleep = 1440 + woke_up - sleep_minutes + + # Convert string to datetime object + previous_day = date_obj - timedelta(days=1) + date_sleep = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + + # shower_detected_time + shower_detected_time = last_bathroom_time + + # breakfast_detected_time + breakfast_detected_time = 0 + + # living_room_detected_time + living_room_time_spent = 0 + + # outside_hours + outside_hours = 0 + + #lets find last time seen at Bathroom, Kitchen, Bedroom pd.first_name, pd.last_name, pd.address_street, pd.picture + + picture_url = deployments_dets[4] + + temp_offset = GetTempOffset(last_present_device) + temperature = GetTemperature(last_present_device) + temp_offset + + + report = {} + + try: + + report = {"user_id":deployments_dets[0], + "name":deployments_dets[1] + " " + deployments_dets[2], + "address":deployments_dets[3], + "time_zone":time_zone_s, + "picture":picture_url, + "bathroom_at": last_bathroom_time, + "kitchen_at": last_kitchen_time, + "bedroom_at": last_bedroom_time, + "temperature": temperature, + "smell": "clean", + "bathroom_delayed": [6, 12], + "kitchen_delayed": [6, 12], + "bedroom_delayed": [13, 16], + "last_location": dev_id_to_location[last_present_device], + "last_detected_time": last_present_time, + "before_last_location": dev_id_to_location[before_last_present_device], + "last_present_duration": last_present_duration, + "wellness_score_percent": wellness_score_percent, + "wellness_descriptor_color": "bg-green-100 text-green-700", + "bedroom_temperature": round(bedroom_temperature, 2), + "sleep_bathroom_visit_count": sleep_bathroom_visit_count, + "bedroom_co2": bedroom_co2, + "shower_detected_time": shower_detected_time, + "breakfast_detected_time": breakfast_detected_time, + "living_room_time_spent": round(living_room_time_spent, 2), + "outside_hours": round(outside_hours, 2), + "wellness_descriptor": "Great!", + "last_seen_alert": "Alert = None", + "last_seen_alert_colors": "bg-green-100 text-green-700", #https://tailwindcss.com/docs/colors + "most_time_spent_in": "Bedroom", + "sleep_hours": round(sleep_hours, 2) + } + except Exception as e: + print(traceback.format_exc()) + return report + +def FindPreviousLocation(locations_list, last_present_device, start_index): + """ + Find the previous device_id (not last_present_device and not 0) counting backwards from start_index. + Also sum the minutes for all entries except the one being reported. + + Args: + locations_list: List of [device_id, start_time, minutes] entries + last_present_device: Device ID to exclude from search + start_index: Index to start searching backwards from + + Returns: + tuple: (device_id, total_minutes) or (None, total_minutes) if no valid device found + """ + total_minutes = 0 + found_device = None + end_index = -len(locations_list) - 1 + print(locations_list[-4:]) + # Count backwards from start_index + for i in range(start_index, end_index, -1): + device_id = locations_list[i][0] + minutes = locations_list[i][2] + + # If we haven't found our target device yet, check if this one qualifies + if found_device is None and device_id != last_present_device and device_id != 0: + found_device = device_id + break + else: + # Add minutes for all entries except the one we're reporting + total_minutes += minutes + + return found_device, total_minutes + + +def ToList(input_data): + # If input is already a list + if isinstance(input_data, list): + return [str(x).strip() for x in input_data] + + # If input is string + if isinstance(input_data, str): + # Remove outer brackets if present + cleaned = input_data.strip('()') + cleaned = cleaned.strip('[]') + # Remove extra quotes + cleaned = cleaned.replace('"', '').replace("'", '') + # Split by comma and clean each element + return [x.strip() for x in cleaned.split(',')] + + raise ValueError(f"Unsupported input type: {type(input_data)}") + +def MACsToWellIds(cur, macs_list): + + device_ids = [] + device_list = [] + + macs_string = ",".join(f"'{mac}'" for mac in macs_list) + if macs_string != "'None'": + sqlr = f"SELECT well_id, device_mac, device_id, location, description, radar_threshold, close_to FROM public.devices WHERE device_mac IN ({macs_string})" + + print (sqlr) + macs_map = {} + cur.execute(sqlr) + proximitys_list = cur.fetchall() + for well_id, mac, device_id, location, description, radar_threshold, close_to in proximitys_list: + if location == None: + location = -1 + if radar_threshold == None: + radar_threshold = "50" + if close_to == None: + close_to = "" + + macs_map[mac] = (well_id, device_id, location_names[location], description, mac, radar_threshold, close_to) + + + for mac in macs_list: + if mac != "": + print(mac) + device_ids.append(macs_map[mac][1]) + device_list.append(macs_map[mac]) + + return device_ids, device_list + +def MACsToDeviceId(MAC): + + sqlr = f"SELECT device_id FROM public.devices WHERE device_mac = '{MAC}'" + device_id = 0 + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchone() + if result != None: + device_id = result[0] + + + return device_id + +def DeviceId2MAC(device_id): + + try: + + with get_db_connection() as conn: + + sqlr = f"SELECT device_mac FROM public.devices WHERE device_id ='{device_id}'" + with conn.cursor() as cur: + + cur.execute(sqlr) + mac = cur.fetchone()[0] + return mac + except: + return "" + + +def WellId2Details(well_ids): + + try: + + with get_db_connection() as conn: + #sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + #device_ids_string = ",".join(map(str, devices_ids_list)) + sqlr = f"SELECT well_id, device_id, device_mac, location, description FROM public.devices WHERE well_id IN ({well_ids})" + print(sqlr) + with conn.cursor() as cur: + + cur.execute(sqlr) + details = cur.fetchall() + return details + except: + return "" + + + +def MACsStrToDevIds(cur, macs): + + device_ids = [] + #we need to repcakage string to contain ' + macs_list = ToList(macs) + macs_string = ",".join(f"'{mac}'" for mac in macs_list) + if macs_string != "'None'": + + sqlr = f"SELECT device_mac, device_id FROM public.devices WHERE device_mac IN ({macs_string})" + print (sqlr) + macs_map = {} + cur.execute(sqlr) + proximitys_list = cur.fetchall() + for mac, device_id in proximitys_list: + device_ids.append((mac, device_id)) + + return device_ids + + + +def ReadCleanStringDB(cur, sql): + cur.execute(sql) + temp_string = cur.fetchone() + if temp_string == None: + return "" + else: + return str(temp_string[0]).strip() + +# obtain device_list, device_ids for deployment_id on time as epoch_from_file_s (usually today) +# it tries first overridden/newly-installed (FROM public.deployment_history) +# then if none found there searches FROM public.deployment_details +def GetProximityList(deployment_id, epoch_from_file_s): + + #both are valid: + #64B70888FA84,64B70888F6F0,64B70888F860,64B70889062C,64B70888FAB0,64B708896BDC,64B708897428 + #['64B70888FA84', '64B70888F6F0', '64B70888F860', '64B70889062C', '64B70888FAB0', '64B708896BDC', '64B708897428'] + + #result_list = [] + #well_ids = [] + with get_db_connection() as conn: + + sqlr = f""" + SELECT * FROM ( + SELECT proximity + FROM public.deployment_history + WHERE deployment_id = {deployment_id} + AND time <= {epoch_from_file_s} + ORDER BY time DESC + LIMIT 1 + ) AS latest_deployment + """ + #print (sqlr) + with conn.cursor() as cur: + devices_string = ReadCleanStringDB(cur, sqlr) + + if devices_string == "": + sqlr = f"SELECT devices from public.deployment_details WHERE deployment_id ={deployment_id}" + #print (sqlr) + devices_string = ReadCleanStringDB(cur, sqlr) + + + if devices_string == "": + return [], [] + + macs_list = ToList(devices_string) + device_ids, device_list = MACsToWellIds(cur, macs_list) + return device_list, device_ids + +def FilterList(to_filter: str, allowed: str) -> str: + # Convert comma-separated strings to sets + filter_set = set(to_filter.split(',')) + allowed_set = set(allowed.split(',')) + + # Find intersection and sort the result + filtered = sorted(filter_set.intersection(allowed_set), key=int) + + # Join back to comma-separated string + return ','.join(filtered) + + +def GetMatchingDevices(privileges, group, deployment, location): + + global LocationsMap + + results=[] + if privileges != "-1": + if deployment == "" or deployment == "0": + deployment = privileges + + privileges_list = privileges.split(',') + if deployment != "0": + if "," in deployment: + deployment = FilterList(deployment, privileges) + else: + if deployment not in privileges_list: + return results + else: + if deployment == "0": + deployment = "-1" + + devices = GetVisibleDevicesPerLocation(deployment, location) + return devices + +def GetMatchingDevicesComplete(privileges, group, deployment, location): + + global LocationsMap + + results=[] + if privileges != "-1": + if deployment == "" or deployment == "0": + deployment = privileges + + privileges_list = privileges.split(',') + if deployment != "0": + if "," in deployment: + deployment = FilterList(deployment, privileges) + else: + if deployment not in privileges_list: + return results + else: + if deployment == "0": + deployment = "-1" + + devices = GetVisibleDevicesPerLocationComplete(deployment, location) + return devices + +def getOldestDeploymentHistoryFromBeneficiary(deployment_id): + #this will return oldest entry as well as last proximity (devices) + st = time.time() + print(f"*0 ----{time.time() - st}") + results=[] + well_ids_last = [] #this needs to be list of tuples (well_id, Location_st, Description) + oldest_time = None + try: + print(f"*0a ----{time.time() - st}") + with get_db_connection() as conn: + sqlr = f""" + SELECT * FROM ( + SELECT time, proximity + FROM public.deployment_history + WHERE deployment_id = {deployment_id} + ORDER BY time ASC + ) AS latest_deployment + """ + print (sqlr) + print(f"*1 ----{time.time() - st}") + with conn.cursor() as cur: + cur.execute(sqlr) + print(f"*2 ----{time.time() - st}") + results = cur.fetchall() + print(f"*3 ----{time.time() - st}") + #lets find which of historical sets has data in DB + if results == None or results == []: #look in deployment_details + sqlr = f"SELECT devices from public.deployment_details WHERE deployment_id ={deployment_id}" + #print (sqlr) + print(f"*4 ----{time.time() - st}") + devices_string = ReadCleanStringDB(cur, sqlr) + print(f"*5 ----{time.time() - st}") + macs_list = ToList(devices_string) + print(f"*6 ----{time.time() - st}") + device_ids_last, device_alls_last = MACsToWellIds(cur, macs_list) + sql_query = """ + SELECT device_id, first_seen_at + FROM device_first_seen + WHERE device_id = ANY(%s) + GROUP BY device_id; + """ + print(f"*7 ----{time.time() - st}") + try: + cur.execute(sql_query, (device_ids_last,)) + results1 = cur.fetchall() + if results1 == []: + try: + # Find the oldest timestamp for each device from sensor_readings + oldest_query = """ + SELECT device_id, MIN(time) as first_seen_at + FROM sensor_readings + WHERE device_id = ANY(%s) + GROUP BY device_id + """ + + cur.execute(oldest_query, (device_ids_last,)) + oldest_results = cur.fetchall() + + if oldest_results: + # Insert the oldest records into device_first_seen + insert_query = """ + INSERT INTO device_first_seen (device_id, first_seen_at) + VALUES (%s, %s) + ON CONFLICT (device_id) DO NOTHING + """ + + # Insert each device's first seen timestamp + for device_id, first_seen_at in oldest_results: + cur.execute(insert_query, (device_id, first_seen_at)) + + # Commit the inserts + conn.commit() + + # Set oldest_time to the earliest timestamp found + oldest_time = min(result[1] for result in oldest_results) + + AddToLog(f"Inserted {len(oldest_results)} device first seen records") + else: + # No sensor readings found for these devices + AddToLog("No sensor readings found for the specified devices") + oldest_time = None + + except Exception as e: + AddToLog(f"Error determining oldest records: {traceback.format_exc()}") + AddToLog(str(e)) + oldest_time = None + # Rollback in case of error + conn.rollback() + else: + oldest_time = results1[0][1] + except Exception as e: + AddToLog(traceback.format_exc()) + AddToLog(str(e)) + print(f"*8 ----{time.time() - st}") + else: + history_entry = results[-1] + macs_list = ToList(history_entry[1]) + print(f"*9 ----{time.time() - st}") + device_ids_last, device_alls_last = MACsToWellIds(cur, macs_list) + + + for history_entry in results: + macs_list = ToList(history_entry[1]) + print(f"*10 ----{time.time() - st}") + device_ids, device_alls = MACsToWellIds(cur, macs_list) + #print(f"*11 ----{time.time() - st}") + #sql_query = """ + #SELECT time as oldest_record_time + #FROM sensor_readings + #WHERE device_id = ANY(%s) + #ORDER BY time ASC + #LIMIT 1; + #""" + print(f"*12 ----{time.time() - st}") + print("Getting oldest record time for devices:", device_ids_last) + + #print(sql_query, device_ids_last) + #try: + #cur.execute(sql_query, (device_ids_last,)) + #results1 = cur.fetchall() + #oldest_time = results1[0][0] + #if oldest_time != None: + #break + + + #except Exception as e: + #print(str(e)) + + try: + oldest_time = get_oldest_record_time_optimized(cur, device_ids_last) + if oldest_time is not None: + break + except Exception as e: + print(str(e)) + + print(f"*13 ----{time.time() - st}") + + except Exception as e: + print(f"*0b ----{time.time() - st}") + AddToLog(traceback.format_exc()) + + print(f"*14 ----{time.time() - st}") + return oldest_time, device_alls_last + +def get_oldest_record_time_optimized(cur, device_ids): + """ + Get the oldest record time for devices, using device_first_seen cache + and only querying sensor_readings for devices not in cache. + """ + if not device_ids: + return None + + # First, check which devices already have cached first_seen times + device_ids_tuple = tuple(device_ids) + cache_query = """ + SELECT device_id, first_seen_at + FROM device_first_seen + WHERE device_id = ANY(%s) + """ + + cur.execute(cache_query, (device_ids,)) + cached_results = cur.fetchall() + cached_device_times = {device_id: first_seen for device_id, first_seen in cached_results} + + # Find devices that are NOT in cache + uncached_device_ids = [device_id for device_id in device_ids if device_id not in cached_device_times] + + # For uncached devices, query sensor_readings and populate cache + for device_id in uncached_device_ids: + try: + # Query oldest record for this specific device + oldest_query = """ + SELECT time as oldest_record_time + FROM sensor_readings + WHERE device_id = %s + ORDER BY time ASC + LIMIT 1; + """ + + cur.execute(oldest_query, (device_id,)) + result = cur.fetchone() + + if result and result[0] is not None: + oldest_time = result[0] + + # Insert into cache table (use ON CONFLICT in case of race conditions) + insert_cache_query = """ + INSERT INTO device_first_seen (device_id, first_seen_at) + VALUES (%s, %s) + ON CONFLICT (device_id) DO NOTHING; + """ + cur.execute(insert_cache_query, (device_id, oldest_time)) + + # Add to our local cache dict + cached_device_times[device_id] = oldest_time + + except Exception as e: + print(f"Error processing device_id {device_id}: {str(e)}") + continue + + # Return the earliest time among all devices that have data + valid_times = [time for time in cached_device_times.values() if time is not None] + return min(valid_times) if valid_times else None + + +def getLastEditedBeneficiary(beneficiary): + + #lets generate token here to elliminate issues with outdated token... + token = generate_token(beneficiary) + url = 'https://well-api.azurewebsites.net/api/well_api' + params = { + "name": "beneficiary_detail", + "beneficiary": beneficiary, + "token": token + } + #{"id": "user_beneficiary_bernhard@wellnuo.com", "MAC": "BENEFICIARY", "email": "bernhard@wellnuo.com", "edit_date": "Fri Aug 16 06:45:01 2024", "c_password": "bern1", "first_name": "Bernhard", "last_name": "Knigge", "address": "776 Dubanski Dr.", "address_city": "San Jose", "address_state": "CA", "address_zip": "95123", "address_country": "United States", "phone_number": "4087055709", "persons": "2", "gender": "M", "race": "W", "born": "1972", "pets": "1", "creds": "", "devs": "[[203, 'Living Room', '', '64B708890B14'], [251, 'Bathroom', '', '64B7088909E8'], [252, 'Bedroom', '', '64B708890734'], [204, 'Bathroom', 'Guest', '64B708890288'], [201, 'Kitchen', 'toaster', '64B708890584'], [202, 'Kitchen', 'stove', '64B7088906D8'], [205, 'Office', '', '64B708897018']]", "tzone": "America/Los_Angeles", "ttl": -1, "_rid": "R60hANIG-K+qTQIAAAAAAg==", "_self": "dbs/R60hAA==/colls/R60hANIG-K8=/docs/R60hANIG-K+qTQIAAAAAAg==/", "_etag": "\"3500a0ae-0000-0800-0000-66bef56d0000\"", "_attachments": "attachments/", "_ts": 1723790701} + response = requests.get(url, params=params) + if response.status_code == 200: + + text = response.text + #print(text) + if text == "Log-Out": + return text + if text[0] == "{": + data = json.loads(response.text) + date_string = data["edit_date"] + parsed_date = datetime.datetime.strptime(date_string, '%c') + # Convert the datetime object to a timestamp (epoch time) + epoch_str = str(time.mktime(parsed_date.timetuple())) + devices = data["devs"] + return(epoch_str, devices) + else: + return text,"" + else: + logger.debug((f"Failed to retrieve the data, status code: {response.status_code}")) + + return "","" + +def GetDeploymentNameFromId(Id): + + con = sqlite3.connect(main_db) + con.text_factory = str + cur = con.cursor() + results=[] + SQL = "SELECT name FROM deployments WHERE id =" + Id + df = cur.execute(SQL) + results = cur.fetchall() + if len(results) > 0: + return results[0][0] + else: + return "" + +def GetTimeZoneOfDeployment(deployment_id): + time_zone_st = 'America/Los_Angeles' + with get_db_connection() as conn: + with conn.cursor() as cur: + sqlr = f"SELECT time_zone_s from public.deployments WHERE deployment_id ={deployment_id}" + time_zone_st = ReadCleanStringDB(cur, sqlr) + return time_zone_st + +def StringToEpoch(date_string, time_zone_s): + """ + Convert a date string to epoch timestamp for start of day (midnight) in specified timezone + + Args: + date_string (str): Date in 'YYYY-MM-DD' format + time_zone_s (str): Timezone string (e.g. 'America/Los_Angeles') + + Returns: + float: Epoch timestamp in seconds + """ + # Parse the date string + date_format = '%Y-%m-%d' + naive_date = datetime.datetime.strptime(date_string, date_format) + + # Get the timezone + timezone = pytz.timezone(time_zone_s) + + # Localize the date to midnight in the specified timezone + local_date = timezone.localize(naive_date) + + # Convert to epoch timestamp + epoch_time = local_date.timestamp() + + return epoch_time + +def LocalDateToUTCEpoch(local_date_str, time_zone_s): + """ + Convert a date string to epoch timestamp for start of day (midnight) in UTC + + Args: + local_date_str (str): Date in 'YYYY-MM-DD' format + time_zone_s (str): Timezone string (e.g. 'America/Los_Angeles') + + Returns: + float: Epoch UTC timestamp in seconds + """ + timezone = pytz.timezone(time_zone_s) + # Parse the date string + date_format = '%Y-%m-%d' + local_datetime = datetime.datetime.strptime(local_date_str, date_format) + local_datetime = timezone.localize(local_datetime) + + utc_datetime = local_datetime.astimezone(pytz.UTC) + epoch_time = int(utc_datetime.timestamp()) + + return epoch_time + +def GetDeploymentDatesBoth(deployment_in): + + #when looking at the date, date is defined in TZ where device is! + #Lets take oldest data from first member of deployment + st = time.time() + date_list = [] + print(f"&0 ----{time.time() - st}") + time_zone_st = GetTimeZoneOfDeployment(deployment_in) + print(f"&1 ----{time.time() - st}") + oldest_date_dt_utc, devices_all = getOldestDeploymentHistoryFromBeneficiary(deployment_in) + print(f"&2 ----{time.time() - st}") + if oldest_date_dt_utc != None: + #get date in local time zone from UTC datetime + + #oldest_date_dt + # Get today's date + local_timezone = pytz.timezone(time_zone_st) # Replace with your local timezone + oldest_date_dt_local = oldest_date_dt_utc.astimezone(local_timezone) + today_date = datetime.datetime.now(local_timezone) + + # Generate a list of date strings from oldest_date to today in inverted order + date_list = [(today_date - timedelta(days=x)).strftime('%Y-%m-%d') for x in range((today_date - oldest_date_dt_local).days + 1)] + print(f"&3 ----{time.time() - st}") + return date_list, devices_all, time_zone_st + +def check_file_exists(file_name, bucket_name="daily-maps"): + try: + # Try to get the object's stats - this will raise an exception if the object doesn't exist + stat_result = miniIO_blob_client.stat_object(bucket_name, file_name) + last_modified_utc = stat_result.last_modified + return True, last_modified_utc + except S3Error as e: + if e.code == 'NoSuchKey': + return False, 0 + # Re-raise if it's a different error + raise + +def get_text_dimensions(text, font, font_scale, thickness): + (width, height), baseline = cv2.getTextSize(text, font, font_scale, thickness) + return { + 'width': width, + 'height': height, + 'baseline': baseline, + 'total_height': height + baseline + } + +def save_to_minio(image, filename, bucket_name="daily-maps", content_type="image/png"): + """ + Save a PIL Image directly to MinIO + + Args: + image (PIL.Image): Image to save + filename (str): Filename to use in MinIO + bucket_name (str): MinIO bucket name + content_type (str): Content type of the file + + Returns: + bool: True if successful, False otherwise + """ + logger = logging.getLogger(__name__) + try: + # Convert PIL image to bytes + img_byte_arr = io.BytesIO() + image.save(img_byte_arr, format='PNG') + img_byte_arr.seek(0) # Move to start of the BytesIO buffer + + # Upload to MinIO + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + filename, + img_byte_arr, + length=len(img_byte_arr.getvalue()), + content_type=content_type + ) + return True + + except Exception as e: + logger.error(f"Error saving to MinIO: {traceback.format_exc()}") + return False + + +def SaveImageInBlob(file_name, arr_stretched, labels = []): + + #labels=[(caption,(x,y),font,scale,color,thickness,line_type)] + try: + + image_with_text = arr_stretched.copy() + + for label in labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + # Encode the image to a memory buffer using imencode + success, encoded_image = cv2.imencode('.png', image_with_text) + AddToLog(f"success={success}") + if not success: + raise Exception("Could not encode image!") + + #AddToLog(f"DAILY_MAPS_BUCKET_NAME={DAILY_MAPS_BUCKET_NAME}") + + image_bytes = encoded_image.tobytes() + AddToLog(f"len(image_bytes)={len(image_bytes)}") + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(image_bytes), + len(image_bytes)) + return True + except Exception as e: + AddToLog(f"{traceback.format_exc()}") + logger.error(f"{traceback.format_exc()}") + return False + +def SaveImageInBlobLabelsOut(file_name, arr_stretched, labels, title_labels): + + #labels=[(caption,(x,y),font,scale,color,thickness,line_type)] + try: + + image_with_text = arr_stretched.copy() + + for label in labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + for label in title_labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + + # Encode the image to a memory buffer using imencode + success, encoded_image = cv2.imencode('.png', image_with_text) + AddToLog(f"success={success}") + if not success: + raise Exception("Could not encode image!") + + #AddToLog(f"DAILY_MAPS_BUCKET_NAME={DAILY_MAPS_BUCKET_NAME}") + + image_bytes = encoded_image.tobytes() + AddToLog(f"len(image_bytes)={len(image_bytes)}") + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(image_bytes), + len(image_bytes)) + return True + except Exception as e: + AddToLog(f"{traceback.format_exc()}") + logger.error(f"{traceback.format_exc()}") + return False + +def GetLocalTimeForDate(selected_date, time_zone_s, minutes_padding = 0): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + if minutes_padding > 0: + local_start = local_start - timedelta(minutes=minutes_padding) + local_next = local_next + timedelta(minutes=minutes_padding) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Format as strings + time_from_str = utc_start.strftime("%Y-%m-%d %H:%M:%S") + time_to_str = utc_next.strftime("%Y-%m-%d %H:%M:%S") + + return time_from_str + "+0000", time_to_str + "+0000" + +def GetLocalTimeForDateSimple(selected_date, time_zone_s, minutes_padding = 0): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + if minutes_padding > 0: + local_start = local_start - timedelta(minutes=minutes_padding) + local_next = local_next + timedelta(minutes=minutes_padding) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + return utc_start, utc_next + +def GetLocalTimeEpochsForDate(selected_date, time_zone_s): + """ + Get start and end of day epochs for a given date in a specific timezone. + + Args: + selected_date (str): Date in "YYYY-MM-DD" format + time_zone_s (str): Timezone string (e.g., "America/New_York") + + Returns: + tuple: (start_epoch, end_epoch) - Unix timestamps for start and end of day + """ + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Convert to epochs (Unix timestamps) + start_epoch = int(utc_start.timestamp()) + end_epoch = int(utc_next.timestamp()) + + return start_epoch, end_epoch + +def UTC2Local(utc_time, time_zone_s): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + #local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(selected_date) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Format as strings + time_from_str = utc_start.strftime("%Y-%m-%d %H:%M:%S") + time_to_str = utc_next.strftime("%Y-%m-%d %H:%M:%S") + + return time_from_str + "+0000", time_to_str + "+0000" + +def get_timezone_aware_datetime(time_str, timezone_str="America/Los_Angeles"): + """ + Convert a naive datetime string to a timezone-aware datetime object. + + Parameters: + time_str: String in format 'YYYY-MM-DD HH:MM:SS' + timezone_str: String representing the timezone (default: "America/Los_Angeles") + + Returns: + datetime: A timezone-aware datetime object + """ + # Parse the naive datetime + naive_dt = datetime.datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S') + + # Get the timezone + tz = pytz.timezone(timezone_str) + + # Localize the datetime (make it timezone-aware) + # localize() is the correct way to do this, as it handles DST transitions properly + aware_dt = tz.localize(naive_dt) + return aware_dt + +def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6 + } + # Add sensor columns dynamically + cols_len = len(columns) + for i in range(10): + columns[f'sensor_min_s{i}'] = i + cols_len #smell * 10 + 5 + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_smell_array_from_timescale(day_data, time_from_str, device_to_index, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Define column mappings (sensor type to position in record) + columns = { + 's0': 2, + 's1': 3, + 's2': 4, + 's3': 5, + 's4': 6, + 's5': 7, + 's6': 8, + 's7': 9, + 's8': 10, + 's9': 11 + } + ## Add sensor columns dynamically + #cols_len = len(columns) + #for i in range(10): + #columns[f'sensor_min_s{i}'] = i + cols_len #smell * 10 + 5 + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_radar_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) + columns = { + 's2': 2, + 's3': 3, + 's4': 4, + 's5': 5, + 's6': 6, + 's7': 7, + 's8': 8 + } + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def ScaleTemperature(temperature_in_f): + # Define our key temperature points and their corresponding color values + temp_points = [30, 50, 70, 90, 110] + color_values = [768, 640, 384, 128, 0] # Color values in the rainbow scale + + # Clamp the temperature to our range + clamped_temp = max(min(temperature_in_f, 110), 30) + + # Find which segment the temperature falls into + for i in range(len(temp_points) - 1): + if temp_points[i] <= clamped_temp <= temp_points[i + 1]: + # Linear interpolation between the two nearest points + t = (clamped_temp - temp_points[i]) / (temp_points[i + 1] - temp_points[i]) + color_value = int(color_values[i] + t * (color_values[i + 1] - color_values[i])) + return color_value + + # Fallback (should never reach here due to clamping) + return 0 + +def GetTemperatureColor(temperature_in_f): + color_value = ScaleTemperature(temperature_in_f) + return BestColor(color_value) + +def BestColor(in_val): + #this function uses numbers from 0 to 1279 to convert to rainbow from Blue to Red(1024) to Violet 1279 + r,g,b=0,0,0 + in_val = int(in_val) + if(in_val > 1279): + in_val = 1279 + if (in_val < 256): + r = 255 + g = in_val + elif (in_val < 512): + r = 511 - in_val + g = 255 + elif (in_val < 768): + g = 255 + b = in_val-512 + elif (in_val < 1024): + g = 1023 - in_val + b = 255 + else: + r = in_val - 1024 + b = 255 + + #if (r > 255): + # print(in_val) + # print(int(r),int(g),int(b)) + return(int(r),int(g),int(b)) + +def GrayColor(in_val): + #this function uses numbers from 0 to 1279 to convert to rainbow from Blue to Red(1024) to Violet 1279 + r,g,b=0,0,0 + in_val = int(in_val) + if(in_val < 0): + in_val = 0 + if(in_val > 255): + in_val = 255 + + r = in_val + g = r + b = r + return(int(r),int(g),int(b)) + +def fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str): + """ + Fill numpy array with data from TimeScaleDB query results. + + Parameters: + day_data: List of tuples from database query + time_from_str: Starting datetime string in format 'YYYY-MM-DD HH:MM:SS' + devices_list: List of device IDs + arr_source: Pre-initialized numpy array to fill + + Returns: + numpy.ndarray: Filled array + """ + # Parse the start time + #start_time = get_timezone_aware_datetime(time_from_str, timezone_str) + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + # Create mapping of device_ids to their index positions + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define columns and their positions in the result tuple + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Process each record + for record in day_data: + # Get minute and device_id from record + record_time = record[0] # minute column + device_id = record[1] # device_id column + + if record_time and device_id: + # Calculate minute delta + minute_delta = int((record_time - start_time).total_seconds() / 60) + + if 0 <= minute_delta < arr_source.shape[1]: + # Calculate base index for this device + base_idx = device_to_index[device_id] * len(columns) + + # Fill data for each sensor/measurement type + for col_name, col_offset in columns.items(): + value = record[col_offset] + if value is not None: # Skip NULL values + row_idx = base_idx + list(columns.keys()).index(col_name) + arr_source[row_idx, minute_delta] = value + + return arr_source + +def fast_fill_array_from_timescale_bad(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) - KEEP EXACT SAME ORDER as original + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Pre-compute column keys list for consistent indexing + column_keys = list(columns.keys()) + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + # Use pre-computed column_keys list for consistent indexing + row_idx = base_idx + column_keys.index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) - KEEP EXACT SAME ORDER as original + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Pre-compute column keys list for consistent indexing + column_keys = list(columns.keys()) + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + # Use pre-computed column_keys list for consistent indexing + row_idx = base_idx + column_keys.index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_array_from_timescale_single(day_data, time_from_str, devices_list, arr_source, sensor, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + if sensor != None: + columns = { + 'avg_temperature': 2, + 'avg_humidity': 2, + 'pressure_amplitude': 2, + 'max_light': 2, + 'radar': 2, + 'sensor_min_s0': 2, + 'sensor_min_s1': 2, + 'sensor_min_s2': 2, + 'sensor_min_s3': 2, + 'sensor_min_s4': 2, + 'sensor_min_s5': 2, + 'sensor_min_s6': 2, + 'sensor_min_s7': 2, + 'sensor_min_s8': 2, + 'sensor_min_s9': 2 + } + else: + + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + column_keys = list(columns.keys()) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] #* len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + # Use pre-computed column_keys list for consistent indexing + + + #row_idx = base_idx + 2#column_keys.index(col_name) + #values = records_array[:, column_keys.index(col_name)] + + ## Filter out None values + #valid_values = ~np.equal(values, None) + #if not np.any(valid_values): + #continue + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def CalcExtremes(arr_source, length, height): + """ + Calculate min and max values for each row within legal bounds. + Optimized version using numpy vectorized operations. + + Parameters: + arr_source: numpy array of shape (height, length+4) containing data and bounds + length: number of data points to process (typically 1440 for minutes in a day) + height: number of rows in the array + + Returns: + numpy array with min/max values stored in columns 1442 and 1443 + """ + # Extract the data portion and bounds + data = arr_source[:, :length] + ignore_below = arr_source[:, 1440:1441] # Keep 2D shape for broadcasting + ignore_above = arr_source[:, 1441:1442] # Keep 2D shape for broadcasting + + # Create masks for valid values + above_min_mask = data >= ignore_below + below_max_mask = data <= ignore_above + valid_mask = above_min_mask & below_max_mask + + # Create a masked array to handle invalid values + masked_data = np.ma.array(data, mask=~valid_mask) + + # Calculate min and max values for each row + row_mins = np.ma.min(masked_data, axis=1).filled(-0.001) + row_maxs = np.ma.max(masked_data, axis=1).filled(-0.001) + + # Store results + arr_source[:, 1442] = row_mins + arr_source[:, 1443] = row_maxs + + return arr_source + +def plot(arr, filename="histogram.png", title="Histogram Plot", figsize=(12, 6), + color='blue', style='histogram', bins=1000): + """ + Plot a 1D numpy array as a line or scatter plot + + Parameters: + arr : 1D numpy array + title : str, plot title + figsize : tuple, figure size in inches + color : str, line/point color + style : str, 'line' or 'scatter' + """ + title = filename + plt.figure(figsize=figsize) + x = np.arange(len(arr)) + + if style == 'line': + x = np.arange(len(arr)) + plt.plot(x, arr, color=color) + elif style == 'scatter': + x = np.arange(len(arr)) + plt.scatter(x, arr, color=color, alpha=0.6) + elif style == 'histogram': + plt.hist(arr.ravel(), bins=bins, range=(0, 100), color=color, alpha=0.8) + plt.yscale('log') # Using log scale for better visualization + plt.xlabel('Signal Value') + plt.ylabel('Frequency') + + plt.title(title) + plt.xlabel('Index') + plt.ylabel('Value') + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig(filename) + plt.close() + print(f"Plot saved to: {filename}") + #plt.show() + +def ShowArray(arr, threshold, filename="histogram.png", title="Histogram Plot", figsize=(12, 6), + color='blue', style='histogram', bins=1000): + """ + Plot a 1D numpy array as a line or scatter plot + + Parameters: + arr : 1D numpy array + title : str, plot title + figsize : tuple, figure size in inches + color : str, line/point color + style : str, 'line' or 'scatter' + """ + title = filename + plt.figure(figsize=figsize) + x = np.arange(len(arr)) + + if style == 'line': + x = np.arange(len(arr)) + plt.plot(x, arr, color=color) + plt.axhline(y=threshold, color='red', linestyle='--', + label=f'Threshold: {threshold:.3f}') + plt.xlabel('Index') + plt.ylabel('Value') + elif style == 'scatter': + x = np.arange(len(arr)) + plt.scatter(x, arr, color=color, alpha=0.6) + elif style == 'histogram': + plt.hist(arr.ravel(), bins=bins, range=(0, 100), color=color, alpha=0.8) + plt.yscale('log') # Using log scale for better visualization + plt.xlabel('Signal Value') + plt.ylabel('Frequency') + + plt.title(title) + plt.xlabel('Index') + plt.ylabel('Value') + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig(filename) + plt.close() + print(f"Plot saved to: {filename}") + #plt.show() + +def AddLimits_optimized(arr_source, devices_c, sensors_c, percentile): + """ + Vectorized version of AddLimits that processes all sensors at once. + + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + + # Create arrays of sensor indices for all rows + sensor_indices = np.arange(total_sensors) % sensors_c + + # Convert sensor_legal_values into arrays for vectorized access + sensor_types = np.array([s_table[i] for i in range(sensors_c)]) + min_vals = np.array([sensor_legal_values[t][0] for t in sensor_types]) + max_vals = np.array([sensor_legal_values[t][1] for t in sensor_types]) + windows = np.array([sensor_legal_values[t][2] for t in sensor_types]) + + # Get values for each row based on sensor type + row_windows = windows[sensor_indices] + row_mins = min_vals[sensor_indices] + row_maxs = max_vals[sensor_indices] + + # Process rows that need cleaning (window > 2) + clean_mask = row_windows > 2 + if np.any(clean_mask): + # Clean each row with its corresponding window size + for window in np.unique(row_windows[clean_mask]): + # Get indices of rows that need this window size + rows_to_clean = np.where(clean_mask & (row_windows == window))[0] + + # Clean each row individually (since clean_data_vectorized expects 1D input) + for row_idx in rows_to_clean: + arr_source[row_idx, :1440] = clean_data_vectorized( + arr_source[row_idx, :1440], + window, + percentile + ) + + # Set min/max values for all rows at once + arr_source[:, 1440] = row_mins + arr_source[:, 1441] = row_maxs + + return arr_source + +def AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile): + """ + Vectorized version of AddLimits that processes all sensors at once. + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + # Create arrays of sensor indices for all rows + sensor_indices = np.arange(total_sensors) % sensors_c + # Convert sensor_legal_values into arrays for vectorized access + sensor_types = np.array([smells_table[i] for i in range(sensors_c)]) + min_vals = np.array([smell_legal_values[t][0] for t in sensor_types]) + max_vals = np.array([smell_legal_values[t][1] for t in sensor_types]) + # Get values for each row based on sensor type + row_mins = min_vals[sensor_indices] + row_maxs = max_vals[sensor_indices] + + # Replace values smaller than smell_min and larger than smell_max with no_smell + # Create a mask for the data points (first 1440 columns) + data_mask_below = arr_source[:, :1440] < smell_min + data_mask_above = arr_source[:, :1440] > smell_max + data_mask_invalid = data_mask_below | data_mask_above + + # Replace invalid values with no_smell + arr_source[:, :1440][data_mask_invalid] = no_smell + + # Set min/max values for all rows at once + arr_source[:, 1440] = row_mins + arr_source[:, 1441] = row_maxs + return arr_source + +def AddLimits(arr_source, devices_c, sensors_c, percentile): + for y in range(devices_c*sensors_c): + sensor_index = y % sensors_c + min_ok, max_ok, window = sensor_legal_values[s_table[sensor_index]] + #if EnablePlot: + #if (y == 33): + #print("stop") + #plot(arr_source[y, :1440], "before_clean_sensor.png") + if window > 2: + arr_source[y, :1440] = clean_data_vectorized(arr_source[y, :1440], window, percentile) + + #if EnablePlot: + #if (y == 33): + #print("stop") + #plot(arr_source[y, :1440], "after_clean_sensor.png") + + arr_source[y][1440] = min_ok + arr_source[y][1441] = max_ok + return arr_source + +def clean_data_vectorized(data, window, percentile): + """ + Vectorized version of clean_data function using pure numpy + First removes zeros, then cleans outliers + + Parameters: + data: numpy array of sensor readings + window: int, size of rolling window + percentile: float, percentile threshold for deviation filtering + """ + # Create a copy to avoid modifying original data + working_data = data.copy() + + # Replace zeros with NaN + zero_mask = working_data == 0 + working_data[zero_mask] = np.nan + + # Create rolling window view of the data + def rolling_window(a, window): + shape = a.shape[:-1] + (a.shape[-1] - window + 1, window) + strides = a.strides + (a.strides[-1],) + return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides) + + # Pad array for edge handling + pad_width = window // 2 + padded = np.pad(working_data, pad_width, mode='edge') + + # Create rolling windows + windows = rolling_window(padded, window) + + # Calculate rolling median (ignoring NaN values) + medians = np.nanmedian(windows, axis=1) + + # Forward/backward fill any NaN in medians + # Forward fill + mask = np.isnan(medians) + idx = np.where(~mask, np.arange(mask.shape[0]), 0) + np.maximum.accumulate(idx, out=idx) + medians[mask] = medians[idx[mask]] + + # Backward fill any remaining NaNs + mask = np.isnan(medians) + idx = np.where(~mask, np.arange(mask.shape[0]), mask.shape[0] - 1) + idx = np.minimum.accumulate(idx[::-1])[::-1] + medians[mask] = medians[idx[mask]] + + # Calculate deviations (ignoring NaN values) + deviations = np.abs(working_data - medians) + + # Calculate threshold (ignoring NaN values) + threshold = np.nanpercentile(deviations, percentile) + + # Create mask and replace outliers with median values + # Points are good if they're not NaN and deviation is within threshold + good_points = (~np.isnan(working_data)) & (deviations <= threshold) + + # Replace all bad points (including zeros and outliers) with median values + result = np.where(good_points, working_data, medians) + + return result + + +def process_chunk(args): + """ + Process a chunk of rows + """ + chunk, sensors_c, sensor_legal_values, s_table, window, percentile = args + result = np.copy(chunk) + + # Process all time series in the chunk at once + result[:, :1440] = np.array([ + clean_data_vectorized(row[:1440], window, percentile) + for row in chunk + ]) + + # Set limits for all rows in chunk using vectorized operations + sensor_indices = np.arange(len(chunk)) % sensors_c + min_values = np.array([sensor_legal_values[s_table[i]][0] for i in sensor_indices]) + max_values = np.array([sensor_legal_values[s_table[i]][1] for i in sensor_indices]) + + result[:, 1440] = min_values + result[:, 1441] = max_values + + return result + + +def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw): + """ + Optimized version of FillImage function that fills the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + sensors_c: number of sensors per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + group_by: grouping strategy ("sensortype" or other) + bw: boolean flag for black and white output + + Returns: + arr_stretched: Filled array with RGB values + and vocs_scaled aray from 0 to 1280 + """ + stripes = devices_c * sensors_c + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Pre-calculate VOC rows mask + if group_by != "sensortype": + voc_rows = np.arange(stripes) >= 5 * devices_c + else: + voc_rows = (np.arange(stripes) % sensors_c) >= 5 + + # Pre-calculate destination row mapping for sensortype grouping + if group_by == "sensortype": + row_indices = np.arange(stripes) + sensor_indices = row_indices % sensors_c + device_indices = row_indices // sensors_c + dest_rows = sensor_indices * devices_c + device_indices + dest_rows = dest_rows[:, np.newaxis] * stretch_by + np.arange(stretch_by) + else: + row_indices = np.arange(stripes)[:, np.newaxis] * stretch_by + np.arange(stretch_by) + + # Optimize color calculation functions + def best_color_vectorized(vals): + """Vectorized version of BestColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 1279).astype(np.int32) + r = np.zeros_like(vals, dtype=np.int32) + g = np.zeros_like(vals, dtype=np.int32) + b = np.zeros_like(vals, dtype=np.int32) + + # Region 0-255 + mask1 = vals < 256 + r[mask1] = 255 + g[mask1] = vals[mask1] + + # Region 256-511 + mask2 = (vals >= 256) & (vals < 512) + r[mask2] = 511 - vals[mask2] + g[mask2] = 255 + + # Region 512-767 + mask3 = (vals >= 512) & (vals < 768) + g[mask3] = 255 + b[mask3] = vals[mask3] - 512 + + # Region 768-1023 + mask4 = (vals >= 768) & (vals < 1024) + g[mask4] = 1023 - vals[mask4] + b[mask4] = 255 + + # Region 1024-1279 + mask5 = vals >= 1024 + r[mask5] = vals[mask5] - 1024 + b[mask5] = 255 + + return r, g, b + + def gray_color_vectorized(vals): + """Vectorized version of GrayColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 255).astype(np.int32) + return vals, vals, vals + + color_func = gray_color_vectorized if bw else best_color_vectorized + + # Process all rows at once + valid_mask = scaled_day[:, :minutes] != -0.001 + big_min = scaled_day[:, 1442:1443] # Keep 2D shape for broadcasting + big_max = scaled_day[:, 1443:1444] + + # Calculate k factors where max > min + valid_range_mask = big_max > big_min + k = np.zeros_like(big_min) + k[valid_range_mask] = (1280 if not bw else 255) / (big_max[valid_range_mask] - big_min[valid_range_mask]) + + # Calculate normalized values for all rows at once + normalized_vals = np.zeros_like(scaled_day[:, :minutes]) + valid_range_indices = np.where(valid_range_mask)[0] + + normalized_vals[valid_range_indices] = ( + k[valid_range_indices] * + (scaled_day[valid_range_indices, :minutes] - big_min[valid_range_indices]) + ) + + # Invert VOC rows + normalized_vals[voc_rows] = (1279 if not bw else 255) - normalized_vals[voc_rows] + + # Apply valid mask + normalized_vals[~valid_mask] = 0 + + # Convert to RGB + r, g, b = color_func(normalized_vals) + + # Create RGB array + rgb_values = np.stack([r, g, b], axis=-1) + + # Handle special case where max == min + equal_range_mask = ~valid_range_mask + if np.any(equal_range_mask): + rgb_values[equal_range_mask.ravel()] = 128 + + # Fill the stretched array efficiently + if group_by == "sensortype": + arr_stretched[dest_rows] = rgb_values[:, None] + else: + arr_stretched[row_indices] = rgb_values[:, None] + + return arr_stretched, normalized_vals[voc_rows] + + +def FillSmellImage_optimized(scaled_day, arr_stretched, y_offset): + """ + Fill the stretched array with colored sensor data from scaled_day. + + Parameters: + scaled_day: 2D array of shape (70, 1444) containing sensor readings + arr_stretched: 3D array of shape (2685, 1640, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stretch_by = 20 + x_offset = 200 + + def best_color_vectorizedS(vals): + """Vectorized version of BestColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 1279).astype(np.int32) + r = np.zeros_like(vals, dtype=np.int32) + g = np.zeros_like(vals, dtype=np.int32) + b = np.zeros_like(vals, dtype=np.int32) + + # Region 0-255 + mask1 = vals < 256 + r[mask1] = 255 + g[mask1] = vals[mask1] + + # Region 256-511 + mask2 = (vals >= 256) & (vals < 512) + r[mask2] = 511 - vals[mask2] + g[mask2] = 255 + + # Region 512-767 + mask3 = (vals >= 512) & (vals < 768) + g[mask3] = 255 + b[mask3] = vals[mask3] - 512 + + # Region 768-1023 + mask4 = (vals >= 768) & (vals < 1024) + g[mask4] = 1023 - vals[mask4] + b[mask4] = 255 + + # Region 1024-1279 + mask5 = vals >= 1024 + r[mask5] = vals[mask5] - 1024 + b[mask5] = 255 + + return r, g, b + + # Process each row in scaled_day + for row_idx in range(scaled_day.shape[0]): + # Extract min and max for this row + row_min = scaled_day[row_idx, 1442] + row_max = scaled_day[row_idx, 1443] + + # Get data for this row (first 1440 elements) + row_data = scaled_day[row_idx, :1440] + + # Check if min and max are the same + if row_min == row_max: + # Create gray stripe + stripe = np.ones((stretch_by, 1440, 3), dtype=np.int32) * 128 + else: + # Normalize the data between 0 and 1279 + k = 1280 / (row_max - row_min) + normalized_vals = k * (row_data - row_min) + normalized_vals = np.clip(normalized_vals, 0, 1279) + + # Convert to RGB + r, g, b = best_color_vectorizedS(normalized_vals) + + # Create RGB stripe + stripe = np.zeros((stretch_by, 1440, 3), dtype=np.int32) + + # Fill stripe with the same color pattern for all stretch_by rows + for i in range(stretch_by): + stripe[i, :, 0] = r + stripe[i, :, 1] = g + stripe[i, :, 2] = b + + # Calculate the y position for this stripe + y_pos = y_offset + row_idx * stretch_by + + # Place the stripe into arr_stretched + print(stretch_by, stripe.shape, arr_stretched.shape, y_pos) + arr_stretched[y_pos:y_pos+stretch_by, x_offset:x_offset+1440, :] = stripe + + return arr_stretched + +def FillImage(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw): + """ + Fill the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + sensors_c: number of sensors per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stripes = devices_c * sensors_c + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Create a boolean mask for VOC sensors + if group_by != "sensortype": + voc_rows = np.array([i for i in range(stripes) if int(i/devices_c) >= 5]) + else: + voc_rows = np.array([i for i in range(stripes) if int(i % sensors_c) >= 5]) + # Vectorize the BestColor function + if not bw: + vectorized_best_color = np.vectorize(BestColor) + else: + vectorized_best_color = np.vectorize(GrayColor) + + # Process each row + for row in range(stripes): + + row_data = scaled_day[row, :minutes] # Get minute data + + #if row == 33: + # print("stop") + # plot(row_data, "row_data.png") + big_min = scaled_day[row, 1442] # min value + big_max = scaled_day[row, 1443] # max value + + # Create mask for valid values + valid_mask = row_data != -0.001 + + # Initialize RGB row with zeros + rgb_row = np.zeros((minutes, 3), dtype=np.uint8) + + if big_max > big_min: + # Scale factor + if not bw: + k = 1280/(big_max-big_min) + else: + k = 255/(big_max-big_min) + # Calculate normalized values + normalized_vals = k * (row_data - big_min) + + # Invert if it's a VOC row + if row in voc_rows: + if not bw: + normalized_vals = 1279 - normalized_vals + else: + normalized_vals = 255 - normalized_vals + + # Apply valid mask + normalized_vals = np.where(valid_mask, normalized_vals, 0) + #if row == 33: + # plot(normalized_vals, "normalized_vals.png") + + # Convert to RGB colors (vectorized) + r, g, b = vectorized_best_color(normalized_vals) + + # Combine into RGB array + rgb_row[valid_mask] = np.stack([r[valid_mask], + g[valid_mask], + b[valid_mask]], axis=1) + else: + # Set to gray where valid + rgb_row[valid_mask] = 128 + + + if group_by == "sensortype": + + # Fill the stretched rows + sensor_index = row % sensors_c + device_index = int(row/sensors_c) + dest_row = sensor_index * devices_c + device_index #0-0, 1- + start_idx = dest_row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + else: + # Fill the stretched rows + start_idx = row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + + return arr_stretched + +def FillRadarImage(scaled_day, devices_c, bands, arr_stretched, group_by, map_type): + """ + Fill the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + bands: number of bands per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stripes = devices_c * bands + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Create a boolean mask for VOC sensors + if group_by != "sensortype": + voc_rows = np.array([i for i in range(stripes) if int(i/devices_c) >= 5]) + else: + voc_rows = np.array([i for i in range(stripes) if int(i % bands) >= 5]) + # Vectorize the BestColor function + if map_type == 3: + vectorized_best_color = np.vectorize(BestColor) + else: + vectorized_best_color = np.vectorize(GrayColor) + + # Process each row + for row in range(stripes): + + row_data = scaled_day[row, :minutes] # Get minute data + + #if row == 33: + # print("stop") + # plot(row_data, "row_data.png") + big_min = 0 #scaled_day[row, 1442] # min value + big_max = 255 #scaled_day[row, 1443] # max value + + # Create mask for valid values + valid_mask = row_data != -0.001 + + # Initialize RGB row with zeros + rgb_row = np.zeros((minutes, 3), dtype=np.uint8) + + if big_max > big_min: + # Scale factor + if map_type == 3: + k = 1280/(big_max-big_min) + else: + k = 255/(big_max-big_min) + # Calculate normalized values + normalized_vals = k * (row_data - big_min) + + # Invert if it's a VOC row + if row in voc_rows: + if map_type == 3: + normalized_vals = 1279 - normalized_vals + else: + normalized_vals = 255 - normalized_vals + + # Apply valid mask + normalized_vals = np.where(valid_mask, normalized_vals, 0) + #if row == 33: + # plot(normalized_vals, "normalized_vals.png") + + # Convert to RGB colors (vectorized) + r, g, b = vectorized_best_color(normalized_vals) + + # Combine into RGB array + rgb_row[valid_mask] = np.stack([r[valid_mask], + g[valid_mask], + b[valid_mask]], axis=1) + else: + # Set to gray where valid + rgb_row[valid_mask] = 128 + + + if group_by == "sensortype": + + # Fill the stretched rows + band_index = row % bands + device_index = int(row/bands) + dest_row = band_index * devices_c + device_index #0-0, 1- + start_idx = dest_row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + else: + # Fill the stretched rows + start_idx = row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + + return arr_stretched + +def GetFullLocMapDetails(map_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + local_timezone = pytz.timezone('America/Los_Angeles') # Replace with your local timezone + dest_path = os.path.dirname(map_file) + + parts = map_file.split("/") + deployment = parts[-2] + parts1 = parts[-1].split("_") + date_string = parts1[1] + + deployments = GetDeploymentB(deployment, -1) #All + last_locations_file = "" + last_per_minute_file = "" + + today = datetime.today() + deployment_details = deployments[0] + deployment_pair = deployment_details[0] + proximity_lst = deployment_details[1] + + date_object = datetime.strptime(date_string, "%Y-%m-%d") + date_object_midnight = local_timezone.localize(date_object.replace(hour=0, minute=0, second=0, microsecond=0)) + selected_epoch = int(date_object_midnight.timestamp()) + + sel_date = datetime.fromtimestamp(selected_epoch) + devices_list_str = GetDevicesList(deployment_details, sel_date)#.split(',') + devices_list = ast.literal_eval(devices_list_str) + return devices_list, selected_epoch, dest_path + +def median_filter(data, window_size): + filtered_data = [] + print(len(data)) + window = deque(maxlen=window_size) + last_value = -1 + offset = 0 + added_old = 0 + for value in data: + if value != '': + added_old = 0 + last_value = value + window.append(value) + if len(window) == window_size: + # Sort the window and get the median value + sorted_window = sorted(window) + median = sorted_window[window_size // 2] + filtered_data.append(median) + else: + if last_value != -1: + if added_old < window_size: + added_old = added_old + 1 + window.append(last_value) + else: + window.append(-1) + + if len(window) == window_size: + # Sort the window and get the median value + sorted_window = sorted(window) + median = sorted_window[window_size // 2] + filtered_data.append(median) + + else: + offset +=1 + + if len(filtered_data) > 0: + offset += (window_size // 2) + #if starts empty, just leav it such, do not fake backwards from midnight + first_val = -1# filtered_data[0] + last_val = filtered_data[-1] + front_padding = [first_val] * offset + remaining = len(data) - len(filtered_data) - len(front_padding) + back_padding = [last_val] * remaining + out_data = front_padding + filtered_data + back_padding + else: + out_data = data + #add front and back padding + + return out_data + +def FilterGlitches(wave_in, filter_minutes): + + if(filter_minutes > 0): + notfiltered_wave = [i[0] for i in wave_in] + filtered_wave = median_filter(notfiltered_wave, filter_minutes) + for i, value in enumerate(filtered_wave): + wave_in[i][0] = value + + return wave_in + +def setup_timezone_converter(time_zone_st): + """ + Setup timezone converter to be reused + + Parameters: + time_zone_st (str): Timezone string (e.g. 'Europe/Berlin') + + Returns: + pytz.timezone: Timezone object for conversion + """ + return pytz.timezone(time_zone_st) + +def ReadDailyRadar(MAC, current_date): + + #This will return all 1 Minute radar data for each gate in the file + #Will return list (2 items) of lists: Maxes, Mins + + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + start_of_day = ToLocal(calendar.timegm(datetime(current_date.year, current_date.month,current_date.day, 0, 0).timetuple())) + end_of_day = start_of_day + 1440 * 60 + file = os.path.join(scriptDir, "DB/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+".db") + file = file.replace("\\","/") + file1 = os.path.join(scriptDir, "DB/processed_db/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+".db") + file1 = file1.replace("\\","/") + if (not path.exists(file) and not path.exists(file1)): + print(file + " and " + file1 + " are not found") + return [] + result = [] + min_OK = "0" + sqlr = "SELECT * FROM radars WHERE time >= "+str(start_of_day) +" and time < "+str(end_of_day) +" ORDER BY time ASC" + #sqlr = "SELECT Date, high, low from "+sensor.lower()+"s1Min"+" WHERE low >= "+min_OK+" and Date >= "+str(start_of_day) +" and Date < "+str(end_of_day) + print(sqlr) + if os.path.exists(file): + result = QuerrySql(file, sqlr) + elif os.path.exists(file1): + result = QuerrySql(file1, sqlr) + + # M0 ............M8 S2 ........S8 + #day_minutes_data = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]] * (24 * 60 + 2) + day_minutes_data = [[0] * 16 for _ in range(24 * 60)] + #for each gate lets find maximum value per minute + for mgate in range(9): + max_per_min = 0 + for minute_data in result: + seconde = minute_data[0] + date_time_minute = datetime.fromtimestamp(seconde) + minute_m = 60*date_time_minute.hour+date_time_minute.minute + if minute_data[mgate + 6] > day_minutes_data[minute_m][mgate]: + day_minutes_data[minute_m][mgate] = minute_data[mgate + 6] + + for sgate in range(7): + for minute_data in result: + seconde = minute_data[0] + date_time_minute = datetime.fromtimestamp(seconde) + minute_m = 60*date_time_minute.hour+date_time_minute.minute + if minute_data[sgate + 17] > day_minutes_data[minute_m][sgate+9]: + day_minutes_data[minute_m][sgate+9] = minute_data[sgate + 17] + + return day_minutes_data + + +def FromLocalMidnight(epoch_time, local_delta): + + # Convert epoch time to UTC datetime object + print(type(epoch_time)) + print(epoch_time) + local_datetime = datetime.datetime.utcfromtimestamp(epoch_time+local_delta).replace(tzinfo=pytz.UTC) + + # Calculate minute count from midnight + minutes_from_midnight = (local_datetime - local_datetime.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60 + return minutes_from_midnight + +def process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest): + """ + NumPy-based version of wave processing + + Parameters: + my_data: List of tuples containing (time_val, device_id, other radar_fields_of_interest) + time_zone_s: Target timezone string + device_id_2_threshold: Dictionary mapping device_ids to their thresholds + + Returns: + List of [device_id, max_val] pairs for each minute + """ + wave_m = None + tz = pytz.timezone(time_zone_s) + if not my_data: + return [["", -1] for _ in range(1440)] + + vectorized_BestColor = np.vectorize(BestColor) + stripes = len(device_id_2_threshold) + stretch_by = 5 + minutes = 1440 + arr_source = np.zeros((int(stripes), minutes), dtype=np.float32) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + index_map = {word: idx for idx, word in enumerate(radar_fields_of_interest)} + devices_map = {word: idx for idx, word in enumerate(device_id_2_threshold)} + times = [] + start_time = 0 + for data_set in my_data: + time_stamp = data_set[0] + if start_time == 0: + + # Convert timestamp to a datetime object in UTC + local_tz = pytz.timezone(time_zone_s) + local_time = time_stamp.astimezone(local_tz) + # Set the time to the start of the day in the local time zone + start_of_day_local = local_time.replace(hour=0, minute=0, second=0, microsecond=0) + # Convert the start of the day back to UTC + start_time = start_of_day_local.astimezone(pytz.utc) + + diff = time_stamp - start_time + minute = int(diff.total_seconds() / 60) + device_id = data_set[1] + field_name = device_id_2_threshold[device_id][0] + field_index = index_map[field_name] + threshold = device_id_2_threshold[device_id][1] + value = data_set[2+field_index] + if value > threshold: + arr_source[devices_map[device_id]][minute] = value + #np.savetxt('output.csv', arr_source, delimiter=',') + if False: + for yy in range(stripes): + rgb_row = vectorized_BestColor(1280*arr_source[yy]/100) + rgb_reshaped = np.array(rgb_row).reshape(3, minutes).T + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_reshaped + + SaveImageInBlob(image_file, arr_stretched, []) + + + max_values = np.max(arr_source, axis=0) + + # Get indices (0-based) + wave_m = np.argmax(arr_source, axis=0) + + # Add 1 to convert to 1-based indexing + wave_m = wave_m + 1 + + # Set to 0 where the column was all zeros + wave_m[max_values == 0] = 0 + + return wave_m + + + +def ReadDailyCollapsedFastRadar(MAC, time_from_str, time_to_str): + + #This will return all 1 Minute radar data for each gate in the file + #Will return list (2 items) of lists: Maxes, Mins based on s28 (stationary[2] to [8]) + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + result = [] + min_OK = "0" + sqlr = "SELECT radar_max FROM devices WHERE MAC = '"+MAC +"'" + print(sqlr) + DB_to_be_found_in_full = os.path.join(scriptDir, "main.db") + DB_to_be_found_in_full = DB_to_be_found_in_full.replace("\\","/") + result = QuerrySql(DB_to_be_found_in_full, sqlr) + sqlr = "SELECT date, low FROM radars1Min WHERE date >= "+str(start_of_day) +" and date < "+str(end_of_day) + " ORDER BY date" + if len(result)>0: + if result[0][0] == 1: + sqlr = "SELECT date, high FROM radars1Min WHERE date >= "+str(start_of_day) +" and date < "+str(end_of_day) + " ORDER BY date" + + print(sqlr) + if os.path.exists(file): + result = QuerrySql(file, sqlr) + elif os.path.exists(file1): + result = QuerrySql(file1, sqlr) + + return result + +def vectorized_best_color_numpy(values): + """Vectorized version of BestColor using pure NumPy""" + # Ensure values are within range + values = np.clip(values, 0, 1279) + + # Initialize output arrays + r = np.zeros_like(values, dtype=np.uint8) + g = np.zeros_like(values, dtype=np.uint8) + b = np.zeros_like(values, dtype=np.uint8) + + # Create masks for each range + mask_0_255 = values < 256 + mask_256_511 = (values >= 256) & (values < 512) + mask_512_767 = (values >= 512) & (values < 768) + mask_768_1023 = (values >= 768) & (values < 1024) + mask_1024_plus = values >= 1024 + + # Set values for each range using masks + r[mask_0_255] = 255 + g[mask_0_255] = values[mask_0_255] + + r[mask_256_511] = 511 - values[mask_256_511] + g[mask_256_511] = 255 + + g[mask_512_767] = 255 + b[mask_512_767] = values[mask_512_767] - 512 + + g[mask_768_1023] = 1023 - values[mask_768_1023] + b[mask_768_1023] = 255 + + r[mask_1024_plus] = values[mask_1024_plus] - 1024 + b[mask_1024_plus] = 255 + + return np.stack([r, g, b], axis=-1) + +def create_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val, max_val): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] - min_val / (max_val - min_val)) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Calculate color values + color_values = np.zeros_like(values[batch_slice]) + + color_values[:, :] = ((values[batch_slice, :] - min_val) / (max_val - min_val)) * 1279.0 + #color_values[:, :] = (values[batch_slice, :] / 100.0) * 1279.0 # other fields + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Convert to RGB colors + for i in range(end_idx - start_idx): + rgb_values = vectorized_best_color_numpy(color_values[i]) + wave_m[y_coords[i], x_coords[batch_slice][i]] = rgb_values + + return wave_m + +def create_light_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=4095): + """ + Create an optimized heatmap for light data (range 0-4095) + + Parameters: + my_data (list): Data from the database query + bw (bool): Whether to create a black and white (True) or color (False) heatmap + fields (list): List of field names + wave_m (numpy.ndarray): The image array to fill + device_to_index (dict): Mapping from device_id to index + base_minute (datetime): The base minute for time calculations + timezone_st (str): Timezone string + min_val (float): Minimum value for normalization (default: 0) + max_val (float): Maximum value for normalization (default: 4095) + + Returns: + numpy.ndarray: The filled image array + """ + if len(my_data) < 1: + return wave_m + + import numpy as np + import pytz + + # Get the local timezone + local_tz = pytz.timezone(timezone_st) + + # Number of fields (should be 1 for light data) + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float - light data is in column 2 + # Reshape to match expected format (n_samples, n_fields) + values = data_array[:, 2].astype(np.float32).reshape(-1, 1) + + # Process in batches to avoid memory issues + batch_size = 1000 + + if bw: + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Normalize light values (0-4095) to grayscale (0-255) + gray_values = ((values[batch_slice] - min_val) / (max_val - min_val) * 255.0) + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + + # Assign values to the image array + for i in range(end_idx - start_idx): + # Create RGB grayscale (same value for R, G, B) + gray_rgb = np.full(3, gray_values[i, 0], dtype=np.uint8) + wave_m[y_coords[i, 0], x_coords[batch_slice][i]] = gray_rgb + else: + # Color mode + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Normalize light values (0-4095) to color range (0-1279) + color_values = ((values[batch_slice] - min_val) / (max_val - min_val) * 1279.0) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + + # For each value, calculate its RGB color and assign to the image + for i in range(end_idx - start_idx): + # Convert normalized value to RGB using vectorized_best_color_numpy + rgb_value = vectorized_best_color_numpy(np.array([color_values[i, 0]]))[0] + wave_m[y_coords[i, 0], x_coords[batch_slice][i]] = rgb_value + + return wave_m + +def create_temperature_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=4095): + """ + Create an optimized heatmap for temperature data with alarm levels + + Parameters: + my_data (list): Data from the database query with columns for minute, device_id, temperature_avg, alarm_level + bw (bool): Whether to create a black and white (True) or color (False) heatmap + fields (list): List of field names - should be ['temperature', 'temperature_state'] + wave_m (numpy.ndarray): The image array to fill + device_to_index (dict): Mapping from device_id to index + base_minute (datetime): The base minute for time calculations + timezone_st (str): Timezone string + min_val (float): Minimum value for temperature normalization + max_val (float): Maximum value for temperature normalization + + Returns: + numpy.ndarray: The filled image array + """ + if len(my_data) < 1: + return wave_m + + import numpy as np + import pytz + + # Get the local timezone + local_tz = pytz.timezone(timezone_st) + + # Number of fields (should be 2 for temperature data: temperature and alarm state) + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Process in batches to avoid memory issues + batch_size = 1000 + + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + for i in range(end_idx - start_idx): + # Get data for this record + temperature = data_array[batch_slice][i, 2] + if temperature == None: + temperature = min_val + alarm_level = 0 + + # If we have an alarm_level column (index 3), use it + if data_array.shape[1] > 3: + alarm_level = data_array[batch_slice][i, 3] + + # Calculate base y-coordinate for this device + base_y = device_indices[batch_slice][i] * n_fields + + # Temperature row (even row - index 0, 2, 4...) + # Normalize temperature to the color range and create color + if not bw: + # For color mode + normalized_temp = np.clip((temperature - min_val) / (max_val - min_val) * 1279.0, 0, 1279) + temp_rgb = vectorized_best_color_numpy(np.array([normalized_temp]))[0] + else: + # For B&W mode + normalized_temp = np.clip((temperature - min_val) / (max_val - min_val) * 255.0, 0, 255) + gray_value = int(normalized_temp) + temp_rgb = np.array([gray_value, gray_value, gray_value], dtype=np.uint8) + + # Set the temperature color in the even row + wave_m[base_y, x_coords[batch_slice][i]] = temp_rgb + + # Alarm level row (odd row - index 1, 3, 5...) + # Set color based on alarm level (0=green, 1=yellow, 2=red) + if alarm_level == 0: + # Green for normal + alarm_rgb = np.array([0, 255, 0], dtype=np.uint8) + elif alarm_level == 1: + # Yellow for warning + alarm_rgb = np.array([0, 255, 255], dtype=np.uint8) + else: # alarm_level == 2 + # Red for critical + alarm_rgb = np.array([0, 0, 255], dtype=np.uint8) + + # Set the alarm color in the odd row + wave_m[base_y + 1, x_coords[batch_slice][i]] = alarm_rgb + + return wave_m + + +def create_humidity_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=100): + """ + Create a heatmap with the exact blue-cyan-green-yellow-red-violet spectrum + matching Image 2, with green at position 40 + """ + if len(my_data) < 1: + return wave_m + + # Number of fields + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Process in batches to avoid memory issues + batch_size = 1000 + + # Define the color mapping function based on the exact spectrum we want + def get_color(t): + """Get RGB color from humidity 0-100""" + # Define color stops - exact RGB values at each step + # Format: (position, (r, g, b)) + #color_stops = [ + #(0, (0, 0, 255)), # Blue + #(20, (0, 255, 255)), # Cyan + #(40, (0, 255, 0)), # Green (centered at 40) + #(60, (255, 255, 0)), # Yellow + #(80, (255, 0, 0)), # Red + #(100, (255, 0, 255)) # Violet + #] + + color_stops = [ + (0, (0, 0, 255)), # Blue + (16, (0, 255, 255)), # Cyan + (32, (0, 255, 0)), # Green (now centered at 32) + (60, (255, 255, 0)), # Yellow + (80, (255, 0, 0)), # Red + (100, (255, 0, 255)) # Violet + ] + + # Ensure t is within range + t = max(0, min(100, t)) + + # Find the two stops to interpolate between + for i in range(len(color_stops) - 1): + pos1, color1 = color_stops[i] + pos2, color2 = color_stops[i+1] + + if pos1 <= t <= pos2: + # Linear interpolation between the two color stops + ratio = (t - pos1) / (pos2 - pos1) + r = int(color1[0] + ratio * (color2[0] - color1[0])) + g = int(color1[1] + ratio * (color2[1] - color1[1])) + b = int(color1[2] + ratio * (color2[2] - color1[2])) + return r, g, b + + # Should never reach here + return 0, 0, 0 + + humidity = min_val + + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + for i in range(end_idx - start_idx): + # Get data for this record + if data_array[batch_slice][i, 2] != None: + humidity = float(data_array[batch_slice][i, 2]) + + # Map humidity from min_val-max_val to 0-100 for our color function + normalized_temp = 100.0 * (humidity - min_val) / (max_val - min_val) if max_val > min_val else 0 + normalized_temp = max(0, min(100, normalized_temp)) # Clamp to 0-100 + + alarm_level = 0 + # If we have an alarm_level column (index 3), use it + if data_array.shape[1] > 3: + alarm_level = data_array[batch_slice][i, 3] + + # Calculate base y-coordinate for this device + base_y = device_indices[batch_slice][i] * n_fields + + # Temperature row (even row) + if not bw: + # Get RGB color from our direct mapping function + r, g, b = get_color(normalized_temp) + + # OpenCV uses BGR ordering, not RGB + temp_rgb = np.array([b, g, r], dtype=np.uint8) + else: + # For B&W mode + gray_value = int(normalized_temp * 2.55) # 0-100 to 0-255 + gray_value = max(0, min(255, gray_value)) + temp_rgb = np.array([gray_value, gray_value, gray_value], dtype=np.uint8) + + # Set the humidity color in the even row + wave_m[base_y, x_coords[batch_slice][i]] = temp_rgb + + # Alarm level row (odd row) + if alarm_level == 0: + # Green for normal + alarm_rgb = np.array([0, 255, 0], dtype=np.uint8) #thisis B,G,R !!! + elif alarm_level == 1: + # Yellow for warning + alarm_rgb = np.array([0, 255, 255], dtype=np.uint8) + else: # alarm_level == 2 + # Red for critical + alarm_rgb = np.array([0, 0, 255], dtype=np.uint8) + + # Set the alarm color in the odd row + wave_m[base_y + 1, x_coords[batch_slice][i]] = alarm_rgb + + return wave_m + +def create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields, device_to_index, base_minute, timezone_st, smell_component_stretch_by, selected_date, y_offset): + """ + Create a heatmap with the exact blue-cyan-green-yellow-red-violet spectrum + matching Image 2, with green at position 40 + """ + if len(my_data) < 1: + return + + minutes = 1440 + devices_c = len(device_to_index) + sensors_c = len(fields) + stripes = devices_c * sensors_c #2 for upper maxes, lower mins + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) + st = time.time() + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, timezone_st) + st = time.time() + arr_source = fast_fill_smell_array_from_timescale(my_data, time_from_str, device_to_index, arr_source_template, timezone_st) + arr_source = AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + + arr_stretched = FillSmellImage_optimized(scaled_day, arr_stretched, y_offset) + + return + + +def create_optimized_heatmap_simple(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val, max_val): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] - min_val / (max_val - min_val)) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for i in range(0, len(data_array)): + rgb_value = ((values[i] - min_val) / (max_val - min_val)) * 1279.0 + wave_m[i, x_coords[i]] = rgb_value + + return wave_m + + +def create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] / 100.0) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Calculate color values + color_values = np.zeros_like(values[batch_slice]) + color_values[:, :] = (values[batch_slice, :] / 100.0) * 1279.0 # other fields + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Convert to RGB colors + for i in range(end_idx - start_idx): + rgb_values = vectorized_best_color_numpy(color_values[i]) + wave_m[y_coords[i], x_coords[batch_slice][i]] = rgb_values + + return wave_m + +def visualize_gmm_fit(stationary_signal, output_file='gmm_explanation.png'): + """ + Visualize how GMM separates the stationary signal into components + """ + # Prepare data + X = stationary_signal.reshape(-1, 1) + + # Fit GMM + gmm = GaussianMixture(n_components=2, random_state=42) + gmm.fit(X) + + # Get parameters + means = gmm.means_.flatten() + stds = np.sqrt(gmm.covariances_.flatten()) + weights = gmm.weights_ + + # Create histogram of actual data + plt.figure(figsize=(12, 6)) + + # Plot histogram of actual data + plt.hist(X, bins=50, density=True, alpha=0.6, color='gray', + label='Actual Signal Distribution') + + # Generate points for GMM curves + x = np.linspace(X.min(), X.max(), 200) + + # Plot individual components + for i in range(len(means)): + plt.plot(x, weights[i] * stats.norm.pdf(x, means[i], stds[i]), + label=f'Component {i+1}: mean={means[i]:.2f}, std={stds[i]:.2f}') + + # Plot combined GMM + gmm_curve = np.zeros_like(x) + for i in range(len(means)): + gmm_curve += weights[i] * stats.norm.pdf(x, means[i], stds[i]) + plt.plot(x, gmm_curve, 'r--', linewidth=2, label='Combined GMM') + + # Add vertical lines for threshold + baseline = min(means) + threshold = baseline + 3 * np.sqrt(gmm.covariances_.flatten()[np.argmin(means)]) + plt.axvline(x=baseline, color='g', linestyle='--', label='Baseline') + plt.axvline(x=threshold, color='r', linestyle='--', label='Threshold') + + plt.title('Gaussian Mixture Model Components of Stationary Signal') + plt.xlabel('Signal Value') + plt.ylabel('Density') + plt.legend() + plt.grid(True) + + # Save and close + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.close() + +def process_location_data(location_data): + """ + Convert raw location data into aligned time series. + """ + timestamps = np.array([t[0] for t in location_data]) + stationary = np.array([t[1] for t in location_data]) + motion = np.array([t[2] for t in location_data]) + return timestamps, stationary, motion + +def detect_presence_for_location(stationary_signal, motion_signal, + motion_threshold=5, gmm_components=2): + """ + Simplified presence detection for a single location. + Returns presence mask and parameters. + """ + # Fit GMM to stationary signal + gmm = GaussianMixture(n_components=gmm_components, random_state=42) + X = stationary_signal.reshape(-1, 1) + gmm.fit(X) + + visualize_gmm_fit(stationary_signal, output_file='gmm_explanation.png') + + # Get baseline and threshold + baseline = min(gmm.means_)[0] + components_sorted = sorted(zip(gmm.means_.flatten(), gmm.covariances_.flatten())) + baseline_std = np.sqrt(components_sorted[0][1]) + threshold = baseline + 3 * baseline_std + + # Detect presence + presence_mask = (motion_signal > motion_threshold) | (stationary_signal > threshold) + + # Smooth presence detection (15 seconds window = 1.5 samples at 10sec sampling) + smooth_window = 3 + presence_mask = np.convolve(presence_mask.astype(int), + np.ones(smooth_window)/smooth_window, + mode='same') > 0.5 + + return presence_mask, threshold + +def find_current_location(data_sets, start_time, end_time, motion_threshold=10): + """ + Analyze presence across multiple locations for each minute. + + Parameters: + ----------- + data_sets : dict + Dictionary of location_name: data_tuples pairs + start_time : datetime + Start time for analysis + end_time : datetime + End time for analysis + motion_threshold : float + Threshold for significant motion detection + + Returns: + -------- + dict + Minute by minute analysis of presence and movement + """ + # Process each location's data + location_data = {} + for location, data in data_sets.items(): + timestamps, stationary, motion = process_location_data(data) + presence, threshold = detect_presence_for_location(stationary, motion, motion_threshold) + location_data[location] = { + 'timestamps': timestamps, + 'presence': presence, + 'motion': motion, + 'stationary': stationary, + 'threshold': threshold + } + + # Create minute-by-minute analysis + current_time = start_time + results = [] + + while current_time < end_time: + minute_end = current_time + timedelta(minutes=1) + + # Analysis for current minute + minute_status = { + 'timestamp': current_time, + 'locations': [], + 'moving_locations': [], + 'presence_values': {}, + 'motion_values': {}, + 'status': 'nobody_present' + } + + # First pass: collect all presence and motion values + for location, data in location_data.items(): + # Find indices for current minute + mask = (data['timestamps'] >= current_time) & (data['timestamps'] < minute_end) + if not any(mask): + continue + + presence_in_minute = data['presence'][mask] + motion_in_minute = data['motion'][mask] + stationary_in_minute = data['stationary'][mask] + + if any(presence_in_minute): + minute_status['presence_values'][location] = np.max(stationary_in_minute) + minute_status['motion_values'][location] = np.max(motion_in_minute) + + # If no presence detected anywhere + if not minute_status['presence_values']: + minute_status['status'] = 'nobody_present' + results.append(minute_status) + current_time += timedelta(minutes=1) + continue + + + # Find location with strongest presence + primary_location = max(minute_status['presence_values'].items(), + key=lambda x: x[1])[0] + + # Count locations with significant motion + moving_locations = [loc for loc, motion in minute_status['motion_values'].items() + if motion > motion_threshold] + + plot(motion, filename=f"motion.png", title=f"Motion", style='line') + # Update status based on motion and presence + if len(moving_locations) > 1: + # Multiple locations with significant motion indicates multiple people + minute_status['status'] = 'multiple_people_moving' + minute_status['locations'] = moving_locations + minute_status['moving_locations'] = moving_locations + else: + # Single or no motion - assign to location with strongest presence + minute_status['locations'] = [primary_location] + if moving_locations: + minute_status['status'] = f'single_person_moving_in_{primary_location}' + minute_status['moving_locations'] = moving_locations + else: + minute_status['status'] = f'single_person_stationary_in_{primary_location}' + + results.append(minute_status) + current_time += timedelta(minutes=1) + + return results + +def get_size(obj, seen=None): + # Recursively find size of objects and their contents + if seen is None: + seen = set() + + obj_id = id(obj) + if obj_id in seen: + return 0 + + seen.add(obj_id) + size = sys.getsizeof(obj) + + if isinstance(obj, (list, tuple, set, dict)): + if isinstance(obj, (list, tuple, set)): + size += sum(get_size(i, seen) for i in obj) + else: # dict + size += sum(get_size(k, seen) + get_size(v, seen) for k, v in obj.items()) + + return size + +def CreatePresenceMap(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, + fast, filter_minutes, time_zone_s): + + #global Id2MACDict + data_sets = {} + ids_list = [] + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + time_from, time_to = GetLocalTimeForDateSimple(selected_date, time_zone_s) + + for details in devices_list: + + sql = get_device_radar_only_query(str(details[1]), time_from_str, time_to_str, [details[1]]) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + data_sets[details[2]] = cur.fetchall()#cur.fetchone() + + + + + # Get minute-by-minute analysis + location_analysis = find_current_location(data_sets, time_from, time_to) + + # Example of printing results + for minute in location_analysis: + print(f"Time: {minute['timestamp']}") + print(f"Status: {minute['status']}") + print(f"Present in: {', '.join(minute['locations'])}") + if minute['moving_locations']: + print(f"Movement in: {', '.join(minute['moving_locations'])}") + print("---") + + print(f"Dictionary size: {get_size(data_sets)} bytes") + devices_list_str = ','.join(str(device[1]) for device in devices_list) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + sql = get_device_radar_only_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #thresholds_dict = {} + + #stretch_to_min_max = True + #devices_c = len(devices_list) + + #data_sets = { + #'living_room': my_data1, + #'kitchen': my_data2, + #'bedroom1': my_data3, + #'bedroom2': my_data4, + #'hallway': my_data5, + #'bathroom': my_data6, + #'office': my_data7 + #} + + sensors_c = 1#len(sensors_table) + + + image_file = location_image_file + + minutes = 1440 + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] #Why 'm8_max' and 'm08_max' ?because m08 is m0 + m1 .. to 8! + + fields_n = len(fields) + + stripes = len(devices_list) * fields_n + + #device_counter = 0 + stretch_by = 5 + + #arr_source = np.zeros((stripes, minutes), dtype=float) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + ids_list = [] + + labels = [] + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_scale = 1 + label_font_color = (255, 255, 255) + label_font_thickness = 2 + label_font_line = cv2.LINE_AA + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, 10 + text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + + + # Get start and end times from your data + start_time = min(data_sets['living_room'][0][0], + data_sets['kitchen'][0][0], + # ... add other locations + ) + end_time = max(data_sets['living_room'][-1][0], + data_sets['kitchen'][-1][0], + # ... add other locations + ) + + # Get minute-by-minute analysis + location_analysis = find_current_location(data_sets, start_time, end_time) + + # Example of printing results + for minute in location_analysis: + print(f"Time: {minute['timestamp']}") + print(f"Status: {minute['status']}") + print(f"Present in: {', '.join(minute['locations'])}") + if minute['moving_locations']: + print(f"Movement in: {', '.join(minute['moving_locations'])}") + print("---") +#---------------------------------------------------------------------------------------------------- + + + + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #device_ids = sorted(set(record[1] for record in my_data)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + + base_minute = ConvertToBase(time_from_str, time_zone_s) + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + if False: + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + for record in my_data: + + #(minute,device_id,absent_min,stationary_max,moving_max,both_max,m0_max,m1_max,m2_max,m3_max,m4_max, + # m5_max,m6_max,m7_max,m8_max,m08_max,s2_max,s3_max,s4_max,s5_max,s6_max,s7_max,s8_max,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the max/min values + x = int((minute - base_minute).total_seconds()/60) + device_idx = device_to_index[device_id] + + if bw: + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 255.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = [gray_value, gray_value, gray_value] + else: #color + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * 22 + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 1279.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = BestColor(gray_value) + + print(time.time()-st) + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + + SaveImageInBlob(image_file, arr_stretched, labels) + #arr_source[2*gate, :] = wave_m + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_m[col] + #if sens_val != 0: + #r,g,b=BestColor(km*(sens_val-m_min)) + #if r > 255 or g > 255 or b > 255: + #print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + ##print(y, row, devices_c, sensor_index, location_index, stretch_index) + ##arr_stretched[y, :] = rgb_row + + #if gate > 1: + #ks = 0 + #if(s_max > s_min): + #if bw: + #ks = 255/(s_max - s_min) + #else: + #ks = 1280/(s_max - s_min) + ##wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + #wave_s = np.array([0.0] * 1440) + + #for minute_m in range(1440): + #wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + ##wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + ##DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + #arr_source[2*gate + 1, :] = wave_s + + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_s[col] + #if sens_val != 0: + #if bw: + #r = ks*(sens_val-s_min) + #g = r + #b = r + #else: + #r,g,b=BestColor(ks*(sens_val-s_min)) + ##print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + #y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + + print("stop") + +def ConvertToBase(time_from_str, time_zone_s): + print(time_from_str) + dt = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + return dt + +def GetTimeAndEvents(data): + """ + Calculates non-zero elements and consecutive non-zero groups using itertools. + This is often the most readable and efficient pure Python approach. + """ + # Fast way to count non-zeros since they are all 1.0 + #non_zeros = int(sum(data)) + non_zeros = sum(1 for x in data if x != 0) + # Count groups of non-zero elements + events = sum(1 for key, group in itertools.groupby(data) if key != 0.0) + return non_zeros, events + +def current_date_at_tz(timezone_str): + """ + Returns the current date in the specified timezone in yyyy-mm-dd format. + + Args: + timezone_str (str): Timezone string like "America/Los_Angeles" + + Returns: + str: Current date in yyyy-mm-dd format + """ + # Get the timezone object + tz = pytz.timezone(timezone_str) + + # Get current datetime in the specified timezone + current_dt = datetime.datetime.now(tz) + + # Format as yyyy-mm-dd + return current_dt.strftime('%Y-%m-%d') + + +def GetActivities(device_id, well_id, date_str, filter_size, refresh, timezone_str, radar_threshold_group_st): + #filtered_day has non 0 points that exceeded threshold of radar reads + device_id_str = str(device_id) + + try: + + time_from_str, time_to_str = GetLocalTimeForDate(date_str, timezone_str) + filename_day_presence = f"/{device_id_str}/{device_id_str}_{date_str}_{filter_size}_presence.bin" + filtered_day_str = None + if refresh == False and date_str != current_date_at_tz(timezone_str): + has_larger = False + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence, date_str) + if filtered_day_str != None and filtered_day_str != "": + has_larger = bool(re.search(r'\b(?:[2-9]|\d{2,})\.\d+\b', filtered_day_str)) + if has_larger: + filtered_day_str = None + if filtered_day_str == None: + + radar_fields_of_interest = [] + + try: + threshold_lst = json.loads(radar_threshold_group_st) + except: + threshold_lst = ["s3_max",12] + radar_fields_of_interest = [threshold_lst[0]] + ids_list = [int(device_id)] + devices_list_str = device_id_str + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + sql = get_deployment_radar_10sec_snapped_query_min_max(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = None + my_data = cur.fetchall() + + days_difference_long = 2 + + presence_map = {'longpresence': {}, 'raw': {}} + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference_long + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3",12] + + #device_id_2_location = {device_id: ""} + device_id_2_threshold = {device_id: radar_threshold_group} + device_field_indexes = {radar_threshold_group[0].split("_")[0]: 1} #len(radar_fields_of_interest) + id2well_id = {device_id: well_id} + + if len(my_data) > 1: + + start_time_ = my_data[0][0] + parsed_time_ = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + #) + + presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, "presence") + + presence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter_size, device_id_str, date_str, date_str, timezone_str) + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence) + filtered_day = json.loads(filtered_day_str) + else: + filtered_day = json.loads(filtered_day_str) + + non_zeros, events = GetTimeAndEvents(filtered_day) + + return(non_zeros / 360, events) #decas to hours + except Exception as e: + print(filename_day_presence) + print(filtered_day_str) + print(traceback.format_exc()) + return(0, 0) +def CreateFullLocationMap(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s): + #global Id2MACDict + + thresholds_dict = {} + + stretch_to_min_max = True + devices_c = len(devices_list) + + if devices_c == 0: + return + + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + if scale_global and chart_type != 3 and chart_type != 4: #"digital" and chart_type != "collapsed" + + max_gate={} + for gate in range(9): + max_gate[str(gate)+"_m"] = 0 + max_gate[str(gate)+"_s"] = 0 + + device_counter = 0 + for details in devices_list: + MAC, threshold, location_name, description = details + if threshold == None: + threshold = '["s3_max",12]' + + + #day_minutes_data = [(0,0)] * (24 * 60 + 2) + #day_minutes_data_l = [[0] * 10 for _ in range(24 * 60 + 2)] + + minute_radar_lists = ReadDailyRadar(MAC, current_date) + + for gate in range(9): + for minute_m in range(1440): + if (minute_radar_lists[minute_m][gate] > max_gate[str(gate)+"_m"]): + max_gate[str(gate)+"_m"] = minute_radar_lists[minute_m][gate] + + if gate > 1: + if (minute_radar_lists[minute_m][gate + 7] > max_gate[str(gate)+"_s"]): + max_gate[str(gate)+"_s"] = minute_radar_lists[minute_m][gate + 7] + + + if (chart_type == 2): #"analog" + + + #fields = ['absent_min', 'stationary_max', 'moving_max', 'both_max', + #'m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + #'m6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + #'s4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max'] + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] + + + fields_n = len(fields) + + stripes = len(devices_list) * fields_n + + device_counter = 0 + stretch_by = 5 + + arr_source = np.zeros((stripes, minutes), dtype=float) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + + labels = [] + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_scale = 1 + label_font_color = (255, 255, 255) + label_font_thickness = 2 + label_font_line = cv2.LINE_AA + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, 10 + text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #device_ids = sorted(set(record[1] for record in my_data)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + if False: + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + for record in my_data: + + #(minute,device_id,absent_min,stationary_max,moving_max,both_max,m0_max,m1_max,m2_max,m3_max,m4_max, + # m5_max,m6_max,m7_max,m8_max,m08_max,s2_max,s3_max,s4_max,s5_max,s6_max,s7_max,s8_max,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the max/min values + x = int((minute - base_minute).total_seconds()/60) + device_idx = device_to_index[device_id] + + if bw: + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 255.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = [gray_value, gray_value, gray_value] + else: #color + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * 22 + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 1279.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = BestColor(gray_value) + + print(time.time()-st) + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + + SaveImageInBlob(image_file, arr_stretched, labels) + #arr_source[2*gate, :] = wave_m + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_m[col] + #if sens_val != 0: + #r,g,b=BestColor(km*(sens_val-m_min)) + #if r > 255 or g > 255 or b > 255: + #print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + ##print(y, row, devices_c, sensor_index, location_index, stretch_index) + ##arr_stretched[y, :] = rgb_row + + #if gate > 1: + #ks = 0 + #if(s_max > s_min): + #if bw: + #ks = 255/(s_max - s_min) + #else: + #ks = 1280/(s_max - s_min) + ##wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + #wave_s = np.array([0.0] * 1440) + + #for minute_m in range(1440): + #wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + ##wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + ##DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + #arr_source[2*gate + 1, :] = wave_s + + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_s[col] + #if sens_val != 0: + #if bw: + #r = ks*(sens_val-s_min) + #g = r + #b = r + #else: + #r,g,b=BestColor(ks*(sens_val-s_min)) + ##print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + #y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + + print("stop") + elif (chart_type == 3): #"digital" + device_counter = 0 + for details in devices_list: + dev_id = details[0] + MAC, threshold, location_id, description = GetMacThrFromId(dev_id) + if threshold == None: + threshold = 30 + + sensor = "Radar" + location_name = location_names[location_id] + pickle_file = os.path.join(scriptDir, "scratch/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_radarM.pkl") + pickle_file = pickle_file.replace("\\","/") + #day_minutes_data = [(0,0)] * (24 * 60 + 2) + #day_minutes_data_l = [[0] * 10 for _ in range(24 * 60 + 2)] + + minute_radar_lists = ReadDailyRadar(MAC, current_date) + + y = 0 + sensor_index = 0 + + #location_index = 0 + + for gate in range(9): + threshold = 15 + if (gate > 1): + threshold = thresholds_dict[dev_id][gate-2] + + for minute_m in range(1440): + if (minute_radar_lists[minute_m][gate] > threshold): + minute_radar_lists[minute_m][gate] = 100 + else: + minute_radar_lists[minute_m][gate] = 0 + + if gate > 1: + if (minute_radar_lists[minute_m][gate + 7] > threshold): + minute_radar_lists[minute_m][gate + 7] = 100 + else: + minute_radar_lists[minute_m][gate + 7] = 0 + + m_max = 100 + m_min = 0 + s_max = 100 + s_min = 0 + + km = 0 + if(m_max > m_min): + km = 1280/(m_max - m_min) + #wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + wave_m = np.array([0.0] * 1440) + + for minute_m in range(1440): + wave_m[minute_m] = minute_radar_lists[minute_m][gate] + + if gate < 2: + DoDisplay(wave_m, location_name+" "+ description+" " + str(gate)) + #wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + arr_source[2*gate, :] = wave_m + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + for col in range(1440): + sens_val = wave_m[col] + if sens_val != 0: + r,g,b=BestColor(km*(sens_val-m_min)) + #print(r,g,b) + rgb_row[col] = r,g,b + + for stretch_index in range(stretch_by): + y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + #print(y, row, devices_c, sensor_index, location_index, stretch_index) + + #arr_stretched[y, :] = rgb_row + + if gate > 1: + ks = 0 + if(s_max > s_min): + if bw: + ks = 255/(s_max - s_min) + else: + ks = 1280/(s_max - s_min) + #wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + wave_s = np.array([0.0] * 1440) + + for minute_m in range(1440): + wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + #wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + arr_source[2*gate + 1, :] = wave_s + + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + for col in range(1440): + sens_val = wave_s[col] + if sens_val != 0: + if bw: + r = ks*(sens_val-s_min) + g = r + b = r + else: + r,g,b=BestColor(ks*(sens_val-s_min)) + #print(r,g,b) + rgb_row[col] = r,g,b + + for stretch_index in range(stretch_by): + y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + device_counter += 1 + print("stop") + + elif (chart_type == 4): #"collapsed" + + stretch_by = 50 + arr_source = np.zeros((1, minutes), dtype=float) + arr_stretched = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + device_counter = 0 + wave_m = [["", -1] for _ in range(1440)] + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + radar_fields_of_interest = [] + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3_max",12] + + + if isinstance(threshold_lst, int): + threshold_lst = ["s3_max",threshold_lst] + + radar_field = threshold_lst[0] + if radar_field not in radar_fields_of_interest: + radar_fields_of_interest.append(radar_field) + + threshold = threshold_lst[1] + dev_id = details[0] + ids_list.append(details[1]) + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + cnt += 1 + row_nr_2_device_id[cnt] = device_id + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + device_id_2_location[device_id] = location_name + + device_id_2_threshold[device_id] = radar_threshold_group + + target_tz = pytz.timezone(time_zone_s) + st = time.time() + + #each record in my_data has time, device_id and radar_fields_of_interest in it + result_np = None + try: + result_np = process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest) + print(time.time() - st) + except Exception as err: + print(str(err)) + + if False: + for record in my_data: + time_val, device_id, min_val, max_val = record + radar_threshold = device_id_2_threshold[device_id] + local_time = time_val.astimezone(target_tz) + minute_m = int((local_time - local_time.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60) + + if (wave_m[minute_m][0] == ""): + if max_val > radar_threshold: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + else: + if max_val > radar_threshold: + if max_val > wave_m[minute_m][1]: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + + + + print(time.time()-st) + if result_np is not None: + wave_m = result_np + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + #wave_m = FilterGlitches(wave_m, filter_minutes) + r = 0 + g = 0 + b = 0 + + if isinstance(wave_m[0], np.int64): + inital_device_id = row_nr_2_device_id[wave_m[0]] + else: + inital_device_id = 0 + + present_at = [[inital_device_id, 0, 1]] #device_id, minute, duration + + for minute_m in range(1440): + try: + + if isinstance(wave_m[minute_m], np.int64): + device_id = row_nr_2_device_id[wave_m[minute_m]] + else: + device_id = 0 + + if device_id != "" and device_id != -1: + r,g,b = Loc2Color[device_id_2_location[device_id]][0] + rgb_row[minute_m] = b,g,r + + if minute_m > 0: + if present_at[-1][0] != device_id: + present_at.append([device_id, minute_m, 1]) + else: + present_at[-1][2] += 1 + + except Exception as err: + print(str(err)) + + for stretch_index in range(stretch_by): + y = stretch_index + arr_stretched[y, :] = rgb_row + + #print("stop") + #print(r,g,b) + SaveObjectInBlob(image_file+".bin", present_at) + SaveImageInBlob(image_file, arr_stretched) + +def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s): + #global Id2MACDict + + thresholds_dict = {} + + stretch_to_min_max = True + devices_c = len(devices_list) + + if devices_c == 0: + return + + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + + if (chart_type == 8): #"all graphs" + + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] + + + show_radar = True + show_light = True + show_temperature = True + show_humidity = True + show_smell = True + + labels_width = 200 + title_labels_height = 40 + title_label_width = 100 + + #common + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_line = cv2.LINE_AA + + #different + title_label_font_scale = 1 + title_label_font_color = (0, 0, 0)#(128, 255, 255) + title_label_font_thickness = 2 + + label_font_scale = 0.5 + label_font_color = (0, 0, 0)#(0, 255, 255) + label_font_thickness = 1 + + fields_n = len(fields) + + radar_stripes = len(devices_list) * fields_n + radar_stretch_by = 5 + + light_stripes = len(devices_list) + light_stretch_by = 20 + + smell_sensors_stripes = 10 * len(devices_list) + other_sensors_stripes = len(devices_list) + + temp_stripe_width = 15 + alarm_stripe_width = 5 + temperature_stretch_by = temp_stripe_width + alarm_stripe_width # Total height per device + + humidity_stripe_width = 15 + humidity_stretch_by = humidity_stripe_width + alarm_stripe_width + smell_component_stretch_by = 20 + + text_dimensions = get_text_dimensions("TEST", label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + + all_maps_height = 0 + + # radar, light, temperature, humidity, smell*10 + + if show_radar: + all_maps_height = title_labels_height + radar_stripes*radar_stretch_by + + if show_light: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*light_stretch_by + + if show_temperature: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*temperature_stretch_by + + if show_humidity: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*humidity_stretch_by + + if show_smell: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*smell_component_stretch_by * 10 + + + if all_maps_height == 0: + return + + vertical_offset = 0 + arr_stretched = np.full((all_maps_height, minutes+labels_width, 3), [255, 174, 70], dtype=np.uint8) + + #Lets add divider lines + x = 190 + if show_radar: + stretch_by = radar_stretch_by + cnt = 0 + for details in devices_list: + y = vertical_offset + title_labels_height + (cnt)*fields_n*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + + section_height = title_labels_height + radar_stripes*radar_stretch_by + vertical_offset = vertical_offset + section_height + if show_light: + stretch_by = light_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + + section_height = title_labels_height + other_sensors_stripes*stretch_by + vertical_offset = vertical_offset + section_height + + if show_temperature: + stretch_by = temperature_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + section_height = title_labels_height + other_sensors_stripes*stretch_by + vertical_offset = vertical_offset + section_height + if show_humidity: + stretch_by = humidity_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + section_height = title_labels_height + other_sensors_stripes*humidity_stretch_by + vertical_offset = vertical_offset + section_height + if show_smell: + stretch_by = smell_component_stretch_by + + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*10*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + #section_height = title_labels_height + other_sensors_stripes**stretch_by * 10 + #vertical_offset = vertical_offset + section_height + + #all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*stretch_by * 10 + + + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + labels = [] + title_labels = [] + vertical_offset = 0 + ######################################## RADAR ################################################################## + if show_radar: + title_label_text = "RADAR" + fields_s = fields + stripes = radar_stripes + stretch_by = radar_stretch_by + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), vertical_offset + 10 + title_text_height), label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data != None: + + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields_s, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(radar_stretch_by): + y = yy * radar_stretch_by + stretch_index + arr_stretched[title_labels_height+y, 200:] = rgb_row + + print(time.time()-st) + vertical_offset = vertical_offset + title_labels_height + stripes*radar_stretch_by + ######################################## LIGHT ################################################################## + if show_light: + title_label_text = "LIGHT" + fields_s = ['light'] + min_val = 0 + max_val = 4095 + stretch_by = light_stretch_by + stripes = len(devices_list) * len(fields_s) # Calculate number of rows needed + # Calculate the correct vertical offset for light section + + # Draw the light section title at the correct position + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for light section + cnt = 0 + light_ids_list = [] # Create a separate list for light section + for details in devices_list: + dev_id = details[0] + light_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the light section + labels.append((descriptor, (10, vertical_offset + title_labels_height + text_height + (cnt)*len(fields_s)*stretch_by), + label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get light data using the existing query function + sql = get_deployment_light_only_query(devices_list_str, time_from_str, time_to_str, light_ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(light_ids_list)} + + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process light data + st = time.time() + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + # Use the light-specific function + wave_m = create_light_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Light heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap vertically + st = time.time() + section_start = vertical_offset + title_labels_height + + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + target_y = section_start + y + + # Make sure we're within bounds of the array + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = rgb_row + else: + print(f"Warning: Row {target_y} is out of bounds (max: {arr_stretched.shape[0]-1})") + + vertical_offset = vertical_offset + title_labels_height + stripes*stretch_by + print(f"Light stretching time: {time.time()-st:.4f} seconds") + + ######################################## TEMPERATURE ################################################################## + if show_temperature: + title_label_text = "TEMPERATURE" + fields_s = ['temperature', 'temperature_state'] + + # Define different stripe widths for temperature and alarm + + temp_offset = -10#GetTempOffset(device_id) + min_val = 20 + max_val = 30 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height + text_height + cnt * temperature_stretch_by + #y_pos = vertical_offset + title_labels_height + text_height + (cnt)*len(fields_s)*stretch_by) + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get temperature data + sql = get_deployment_temperature_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, temp_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process temperature data + st = time.time() + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + if False: + # Simulate data for testing + for i in range(min(len(my_data), 500)): + if i >= 100: # Only modify indices 100-500 + t = (i - 100) / 4.0 # Temperature value + + # Set correct alarm levels based on temperature + if CelsiusToFahrenheit(t) <= 50 or CelsiusToFahrenheit(t) >= 90: + alarm_level = 2 # Critical - should be red + elif CelsiusToFahrenheit(t) <= 60 or CelsiusToFahrenheit(t) >= 80: + alarm_level = 1 # Warning - should be yellow + else: + alarm_level = 0 # Normal - should be green + + # Replace the tuple with new values + my_data[i] = (my_data[i][0], my_data[i][1], t, alarm_level) + + # Create the heatmap data + wave_m = create_temperature_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Temperature heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap with different heights for temperature and alarm + st = time.time() + section_start = vertical_offset + title_labels_height + + # Loop through each device + for device_idx in range(len(temp_ids_list)): + # Get the data rows for this device + temp_row = wave_m[device_idx * 2] # Temperature row (even index) + alarm_row = wave_m[device_idx * 2 + 1] # Alarm row (odd index) + + # Calculate the starting y-position for this device + device_y_start = section_start + device_idx * temperature_stretch_by + + # Draw the temperature stripe (15 pixels) + for stretch_index in range(temp_stripe_width): + target_y = device_y_start + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = temp_row + + # Draw the alarm stripe (5 pixels) + for stretch_index in range(alarm_stripe_width): + target_y = device_y_start + temp_stripe_width + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = alarm_row + + print(f"Temperature stretching time: {time.time()-st:.4f} seconds") + + ######################################## HUMIDITY ################################################################## + ''' + Ideal indoor humidity: 30-50% + Too dry: Below 30% - Can cause dry skin, irritated eyes, and respiratory issues + Too humid: Above 60% - Feels warmer than actual temperature, promotes mold growth + ''' + if show_humidity: + title_label_text = "HUMIDITY" + fields_s = ['humidity', 'humidity_state'] + + # Define different stripe widths for humidity and alarm + + humidity_offset = 0 + min_val = 40 + max_val = 90#60 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + if show_temperature: + vertical_offset += title_labels_height + other_sensors_stripes * temperature_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height + text_height + cnt * humidity_stretch_by + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get humidity data + sql = get_deployment_humidity_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, humidity_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process temperature data + st = time.time() + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + if False: + # Simulate data for testing + for i in range(min(len(my_data), 500)): + if i >= 100: # Only modify indices 100-500 + h = (i - 100) / 4.0 # Temperature value + + # Set correct alarm levels based on temperature + if h <= 20 or h >= 60: + alarm_level = 2 # Critical - should be red + elif h <= 30 or h >= 50: + alarm_level = 1 # Warning - should be yellow + else: + alarm_level = 0 # Normal - should be green + + # Replace the tuple with new values + my_data[i] = (my_data[i][0], my_data[i][1], h, alarm_level) + + # Create the heatmap data + wave_m = create_humidity_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Humidity heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap with different heights for humidity and alarm + st = time.time() + section_start = vertical_offset + title_labels_height + + # Loop through each device + for device_idx in range(len(temp_ids_list)): + # Get the data rows for this device + humidity_row = wave_m[device_idx * 2] # Humidity row (even index) + alarm_row = wave_m[device_idx * 2 + 1] # Alarm row (odd index) + + # Calculate the starting y-position for this device + device_y_start = section_start + device_idx * humidity_stretch_by + + # Draw the humidity stripe (15 pixels) + for stretch_index in range(humidity_stripe_width): + target_y = device_y_start + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = humidity_row + + # Draw the alarm stripe (5 pixels) + for stretch_index in range(alarm_stripe_width): + target_y = device_y_start + temp_stripe_width + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = alarm_row + + print(f"Temperature stretching time: {time.time()-st:.4f} seconds") + + ######################################## SMELL ################################################################## + if show_smell: + title_label_text = "SMELL" + fields_s = ['S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8', 'S9'] + + # Define different stripe widths for humidity and alarm + + smell_offset = 0 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + if show_temperature: + vertical_offset += title_labels_height + other_sensors_stripes * temperature_stretch_by + if show_humidity: + vertical_offset += title_labels_height + other_sensors_stripes * humidity_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height +80+ text_height + cnt * smell_component_stretch_by * 10 + #y_pos = vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by) + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get smell data + sql = get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, smell_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Create the heatmap data + create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields_s, device_to_index, base_minute, time_zone_s, smell_component_stretch_by, selected_date, vertical_offset + 18 + title_text_height) + + + + SaveImageInBlobLabelsOut(image_file, arr_stretched, labels, title_labels) + + print("stop") + + + + + +def CreateDailyLocationMap(location_image_file, devices_list, selected_date, filter_minutes, time_zone_s, stretch_by): + + devices_c = len(devices_list) + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + arr_source = np.zeros((1, minutes), dtype=float) + arr_stretched = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + arr_stretched_sorted = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + device_counter = 0 + wave_m = [["", -1] for _ in range(1440)] + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + radar_fields_of_interest = [] + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + if len(threshold_lst) > 2: + threshold_lst = ["s3_max",12] + radar_field = threshold_lst[0] + except: + threshold_lst = ["s3_max",12] + + radar_field = threshold_lst[0] + if radar_field not in radar_fields_of_interest: + radar_fields_of_interest.append(radar_field) + + threshold = threshold_lst[1] + dev_id = details[0] + ids_list.append(details[1]) + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + cnt += 1 + row_nr_2_device_id[cnt] = device_id + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + device_id_2_location[device_id] = location_name + + device_id_2_threshold[device_id] = radar_threshold_group + + target_tz = pytz.timezone(time_zone_s) + st = time.time() + + #each record in my_data has time, device_id and radar_fields_of_interest in it + + try: + result_np = process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest) + print(time.time() - st) + except Exception as err: + print(str(err)) + + if False: + for record in my_data: + time_val, device_id, min_val, max_val = record + radar_threshold = device_id_2_threshold[device_id] + local_time = time_val.astimezone(target_tz) + minute_m = int((local_time - local_time.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60) + + if (wave_m[minute_m][0] == ""): + if max_val > radar_threshold: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + else: + if max_val > radar_threshold: + if max_val > wave_m[minute_m][1]: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + + + + print(time.time()-st) + wave_m = result_np + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + rgbsorted_row = np.zeros(( 1440, 3), dtype=np.uint8) + #wave_m = FilterGlitches(wave_m, filter_minutes) + r = 0 + g = 0 + b = 0 + + presence_minutes = {} + + #we want to generate present_at array + if isinstance(wave_m[0], np.int64): + inital_device_id = row_nr_2_device_id[wave_m[0]] + else: + inital_device_id = 0 + present_at = [[inital_device_id, 0, 1]] #device_id, minute, duration + for minute_m in range(1440): + try: + if isinstance(wave_m[minute_m], np.int64): + device_id = row_nr_2_device_id[wave_m[minute_m]] + else: + device_id = 0 + + if device_id != "" and device_id != -1: + r,g,b = Loc2Color[device_id_2_location[device_id]][0] + rgb_row[minute_m] = b,g,r + + if Loc2Color[device_id_2_location[device_id]][1] in presence_minutes: + presence_minutes[Loc2Color[device_id_2_location[device_id]][1]] = [presence_minutes[Loc2Color[device_id_2_location[device_id]][1]][0] + 1, Loc2Color[device_id_2_location[device_id]][0]] + else: + presence_minutes[Loc2Color[device_id_2_location[device_id]][1]] = [1, Loc2Color[device_id_2_location[device_id]][0]] + + if minute_m > 0: + if present_at[-1][0] != device_id: + present_at.append([device_id, minute_m, 1]) + else: + present_at[-1][2] += 1 + + except Exception as err: + print(str(err)) + + start_minute = 0 + for color_key in sorted(presence_minutes): + print(color_key, presence_minutes[color_key]) + rgbsorted_row[start_minute:start_minute+presence_minutes[color_key][0]] = presence_minutes[color_key][1][::-1] + start_minute += presence_minutes[color_key][0] + + #we need to save present_at list to blob + SaveObjectInBlob(image_file+".bin", present_at) + #present_at_back_s = ReadObjectMinIO("daily-maps", image_file+".bin") + #present_at_back = json.loads(present_at_back_s) + #print(present_at_back) + for stretch_index in range(stretch_by): + y = stretch_index + arr_stretched[y, :] = rgb_row + arr_stretched_sorted[y, :] = rgbsorted_row + #print("stop") + #print(r,g,b) + SaveImageInBlob(image_file, arr_stretched) + SaveImageInBlob(image_file[:-4]+"S.png", arr_stretched_sorted) + + +def GenerateFullLocationMap(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreateFullLocationMap(map_file, devices_list, ddate, 1, recreate_or_not, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + +def GenerateFullLocationMapLabelsOut(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreateFullLocationMapLabelsOut(map_file, devices_list, ddate, 1, recreate_or_not, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + +def CreateMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by): + global Id2MACDict, s_table_temp + + st = time.time() + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + try: + + #stretch_to_min_max = True + + #current_date_p = selected_date.replace("-", "_") + #current_date_s = selected_date + + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + for details in devices_list[0]: + dev_id = details[0] + ids_list.append(details[1]) + if dev_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return False, [] + + if larger_than200 > 0: + sensors_c = len(s_table_temp) + else: #old sensors not supported + return False, [] + + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + image_file = map_file + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + temp_offset = -10 + if sensors_c > 1: + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + else: + sql = get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, s_table_temp[0]) + print(sql) + #print(sql) + #st = time.time() + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + day_data = cur.fetchall()#cur.fetchone() + #print(result) + if day_data == None: + return False, [] + + stretch_by = 10 + minutes = 1440 + stripes = devices_c * sensors_c #2 for upper maxes, lower mins + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) + arr_stretched_template = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) # 3 for RGB channels + arr_source = fast_fill_array_from_timescale(day_data, time_from_str, devices_list[1], arr_source_template, time_zone_s) + arr_source = AddLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) + SaveImageInBlob(image_file, arr_stretched) + return True, vocs_scaled + + except Exception as e: + AddToLog(traceback.format_exc()) + return False, [] + +def get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+ {temp_offset} as avg_temperature, + sr.avg_humidity, + sr.pressure_amplitude, + sr.max_light, + rr.radar, + sr.min_s0 as sensor_min_s0, + sr.min_s1 as sensor_min_s1, + sr.min_s2 as sensor_min_s2, + sr.min_s3 as sensor_min_s3, + sr.min_s4 as sensor_min_s4, + sr.min_s5 as sensor_min_s5, + sr.min_s6 as sensor_min_s6, + sr.min_s7 as sensor_min_s7, + sr.min_s8 as sensor_min_s8, + sr.min_s9 as sensor_min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS pressure_amplitude, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, sensor_in): + """ + Generate a TimeScaleDB query for a single sensor reading based on device IDs. + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + temp_offset (float): Temperature offset to apply + sensor (str): Single sensor to query from s_table + Returns: + str: Generated SQL query + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + if "_" in sensor_in: + sensor = sensor_in.split("_")[1] + else: + sensor = sensor_in + + # Handle different sensor types + if sensor == "radar": + # Query only radar readings + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS {sensor_in} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "temperature": + # Query temperature with offset + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) + {temp_offset} AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "humidity": + # Query humidity + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(humidity) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "pressure": + # Query pressure + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(pressure) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "light": + # Query light + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(light) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor.startswith("voc"): + # Query VOC sensors (voc0-voc9) - these correspond to s0-s9 in the original query + voc_num = sensor[3:] # Extract number from "voc0", "voc1", etc. + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(CASE WHEN s{voc_num} > 0 THEN s{voc_num} END) AS {sensor} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + else: + raise ValueError(f"Unknown sensor type: {sensor}. Must be one of: temperature, humidity, pressure, light, radar, voc0-voc9") + + return sql + +def get_deployment_single_query_rz(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, sensor): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + + Returns: + str: Generated SQL query + """ + + #table_sens = {"temperature": (f"sr.avg_temperature+ {temp_offset} as avg_temperature", "avg_temperature"), + #"humidity": ("sr.avg_humidity", "avg_humidity"), + #"pressure": ("sr.pressure_amplitude", "pressure_amplitude"), + #"light":("sr.max_light", "max_light"), + #"radar":("rr.radar") + #"voc0": + #"voc1": + #"voc2": + #"voc3": + #"voc4": + #"voc5": + #"voc6": + #"voc7": + #"voc8": + #"voc9": , + #} + # derived + + #if sensor == + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+ {temp_offset} as avg_temperature, + sr.avg_humidity, + sr.pressure_amplitude, + sr.max_light, + rr.radar, + sr.min_s0 as sensor_min_s0, + sr.min_s1 as sensor_min_s1, + sr.min_s2 as sensor_min_s2, + sr.min_s3 as sensor_min_s3, + sr.min_s4 as sensor_min_s4, + sr.min_s5 as sensor_min_s5, + sr.min_s6 as sensor_min_s6, + sr.min_s7 as sensor_min_s7, + sr.min_s8 as sensor_min_s8, + sr.min_s9 as sensor_min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS pressure_amplitude, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + + +def get_deployment_rd_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+{temp_offset} as avg_temperature, + sr.avg_humidity, + sr.avg_pressure, + sr.max_light, + sr.min_s0 as smell_s0, + sr.min_s1 as smell_s1, + sr.min_s2 as smell_s2, + sr.min_s3 as smell_s3, + sr.min_s4 as smell_s4, + sr.min_s5 as smell_s5, + sr.min_s6 as smell_s6, + sr.min_s7 as smell_s7, + sr.min_s8 as smell_s8, + sr.min_s9 as smell_s9, + rr.absent as radar_absent, + rr.moving as radar_moving, + rr.stationary as radar_stationary, + rr.both as radar_both, + rr.m0 as radar_m0, + rr.m1 as radar_m1, + rr.m2 as radar_m2, + rr.m3 as radar_m3, + rr.m4 as radar_m4, + rr.m5 as radar_m5, + rr.m6 as radar_m6, + rr.m7 as radar_m7, + rr.m8 as radar_m8, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS avg_pressure, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(absent) AS absent, + MAX(moving) AS moving, + MAX(stationary) AS stationary, + MAX(\"both\") AS both, + MAX(m0) AS m0, + MAX(m1) AS m1, + MAX(m2) AS m2, + MAX(m3) AS m3, + MAX(m4) AS m4, + MAX(m5) AS m5, + MAX(m6) AS m6, + MAX(m7) AS m7, + MAX(m8) AS m8, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_radar_only_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_device_radar_s28_only_query(time_from_str, time_to_str, device_id): + sql = f""" + SELECT + time, + (s2+s3+s4+s5+s6+s7+s8)/7 AS s28, + (m2+m3+m4+m5+m6+m7+m8)/7 AS m28 + FROM + radar_readings + WHERE + device_id = {device_id} + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + time ASC + """ + return sql + +def get_device_radar_only_query(devices_list_str, time_from_str, time_to_str, device_id): + sql = f""" + SELECT + time, + (s2+s3+s4+s5+s6+s7+s8)/7 AS s28, + (m2+m3+m4+m5+m6+m7+m8)/7 AS m28 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + time ASC + """ + return sql + + +def get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list) list of different unique fields required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + radar_fields_to_get = "" + q_parts = "" + for field in radar_fields_of_interest: + if field == "s28_min": + q_part = "MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min" + elif field == "s28_max": + q_part = "MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max" + elif field == "m08_max": + q_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max" + elif field == "s2_max": + q_part = "MAX(s2) AS s2_max" + elif field == "s3_max": + q_part = "MAX(s3) AS s3_max" + elif field == "s4_max": + q_part = "MAX(s4) AS s4_max" + elif field == "s5_max": + q_part = "MAX(s5) AS s5_max" + elif field == "s6_max": + q_part = "MAX(s6) AS s6_max" + elif field == "s7_max": + q_part = "MAX(s7) AS s7_max" + elif field == "s8_max": + q_part = "MAX(s8) AS s8_max" + elif field == "m0_max": + q_part = "MAX(m0) AS m0_max" + elif field == "m1_max": + q_part = "MAX(m1) AS m1_max" + elif field == "m2_max": + q_part = "MAX(m2) AS m2_max" + elif field == "m3_max": + q_part = "MAX(m3) AS m3_max" + elif field == "m4_max": + q_part = "MAX(m4) AS m4_max" + elif field == "m5_max": + q_part = "MAX(m5) AS m5_max" + elif field == "m6_max": + q_part = "MAX(m6) AS m6_max" + elif field == "m7_max": + q_part = "MAX(m7) AS m7_max" + elif field == "m8_max": + q_part = "MAX(m8) AS m8_max" + else: + q_part = field + + if q_parts == "": + q_parts = q_part + else: + q_parts = q_parts + ", " + q_part + if radar_fields_to_get == "": + radar_fields_to_get = field + else: + radar_fields_to_get = radar_fields_to_get + ", " + field + sql = f""" + SELECT + minute, + device_id, + {radar_fields_to_get} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {q_parts} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_radar_only_colapsed_query_wid(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + radar_fields_to_get = "" + q_parts = "" + for field in radar_fields_of_interest: + if field == "s28_min": + q_part = "MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min" + elif field == "s28_max": + q_part = "MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max" + elif field == "m08_max": + q_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max" + elif field == "s2_max": + q_part = "MAX(s2) AS s2_max" + elif field == "s3_max": + q_part = "MAX(s3) AS s3_max" + elif field == "s4_max": + q_part = "MAX(s4) AS s4_max" + elif field == "s5_max": + q_part = "MAX(s5) AS s5_max" + elif field == "s6_max": + q_part = "MAX(s6) AS s6_max" + elif field == "s7_max": + q_part = "MAX(s7) AS s7_max" + elif field == "s8_max": + q_part = "MAX(s8) AS s8_max" + elif field == "m0_max": + q_part = "MAX(m0) AS m0_max" + elif field == "m1_max": + q_part = "MAX(m1) AS m1_max" + elif field == "m2_max": + q_part = "MAX(m2) AS m2_max" + elif field == "m3_max": + q_part = "MAX(m3) AS m3_max" + elif field == "m4_max": + q_part = "MAX(m4) AS m4_max" + elif field == "m5_max": + q_part = "MAX(m5) AS m5_max" + elif field == "m6_max": + q_part = "MAX(m6) AS m6_max" + elif field == "m7_max": + q_part = "MAX(m7) AS m7_max" + elif field == "m8_max": + q_part = "MAX(m8) AS m8_max" + else: + q_part = field + + if q_parts == "": + q_parts = q_part + else: + q_parts = q_parts + ", " + q_part + if radar_fields_to_get == "": + radar_fields_to_get = field + else: + radar_fields_to_get = radar_fields_to_get + ", " + field + sql = f""" + SELECT + minute, + device_id, + {radar_fields_to_get} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {q_parts} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + This is looking for presence, NOT absence... othervise all MAXes need to be converted to MINs + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + m0_max, + m1_max, + m2_max, + m3_max, + m4_max, + m5_max, + m6_max, + m7_max, + m8_max, + m08_max, + s2_max, + s3_max, + s4_max, + s5_max, + s6_max, + s7_max, + s8_max, + s28_max, + s28_min + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(m0) AS m0_max, + MAX(m1) AS m1_max, + MAX(m2) AS m2_max, + MAX(m3) AS m3_max, + MAX(m4) AS m4_max, + MAX(m5) AS m5_max, + MAX(m6) AS m6_max, + MAX(m7) AS m7_max, + MAX(m8) AS m8_max, + MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max, + MAX(s2) AS s2_max, + MAX(s3) AS s3_max, + MAX(s4) AS s4_max, + MAX(s5) AS s5_max, + MAX(s6) AS s6_max, + MAX(s7) AS s7_max, + MAX(s8) AS s8_max, + MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max, + MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_light_only_query(devices_list_str, time_from_str, time_to_str, ids_list): + #light detailed + """ + Generate a TimeScaleDB query for light readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + light_max + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(light) AS light_max + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_temperature_only_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + """ + Generate a TimeScaleDB query for temperature readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with temperature and alarm level + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + temperature_avg, + CASE + WHEN (temperature_avg * 9/5 + 32) <= 50 OR (temperature_avg * 9/5 + 32) >= 90 THEN 2 + WHEN (temperature_avg * 9/5 + 32) <= 60 OR (temperature_avg * 9/5 + 32) >= 80 THEN 1 + ELSE 0 + END AS alarm_level + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature)+{temp_offset} AS temperature_avg + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_humidity_only_query(devices_list_str, time_from_str, time_to_str, ids_list, humidity_offset): + """ + Generate a TimeScaleDB query for humidity readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with humidity and alarm level + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + col_expr = f"2.3592 * AVG(humidity) + 23.5546" #= 2.3592 * J2 + 33.5546 + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + humidity_avg, + CASE + WHEN humidity_avg <= 20 OR humidity_avg >= 60 THEN 2 + WHEN humidity_avg <= 30 OR humidity_avg >= 50 THEN 1 + ELSE 0 + END AS alarm_level + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {col_expr} AS humidity_avg + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str, ids_list, humidity_offset): + """ + Generate a TimeScaleDB query for smell readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with smell components + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + min_s0, + min_s1, + min_s2, + min_s3, + min_s4, + min_s5, + min_s6, + min_s7, + min_s8, + min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + + +def get_deployment_radar_only_detailed_all_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + This is looking for presence, NOT absence... othervise all MAXes need to be converted to MINs + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + absent_min, + stationary_max, + moving_max, + both_max, + m0_max, + m1_max, + m2_max, + m3_max, + m4_max, + m5_max, + m6_max, + m7_max, + m8_max, + m08_max, + s2_max, + s3_max, + s4_max, + s5_max, + s6_max, + s7_max, + s8_max, + s28_max + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(absent) AS absent_min, + MAX(stationary) AS stationary_max, + MAX(moving) AS moving_max, + MAX("both") AS both_max, + MAX(m0) AS m0_max, + MAX(m1) AS m1_max, + MAX(m2) AS m2_max, + MAX(m3) AS m3_max, + MAX(m4) AS m4_max, + MAX(m5) AS m5_max, + MAX(m6) AS m6_max, + MAX(m7) AS m7_max, + MAX(m8) AS m8_max, + MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max, + MAX(s2) AS s2_max, + MAX(s3) AS s3_max, + MAX(s4) AS s4_max, + MAX(s5) AS s5_max, + MAX(s6) AS s6_max, + MAX(s7) AS s7_max, + MAX(s8) AS s8_max, + MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql +def get_deployment_deca_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+{temp_offset} as avg_temperature, + sr.avg_humidity, + sr.avg_pressure, + sr.max_light, + sr.min_s0 as smell_s0, + sr.min_s1 as smell_s1, + sr.min_s2 as smell_s2, + sr.min_s3 as smell_s3, + sr.min_s4 as smell_s4, + sr.min_s5 as smell_s5, + sr.min_s6 as smell_s6, + sr.min_s7 as smell_s7, + sr.min_s8 as smell_s8, + sr.min_s9 as smell_s9, + rr.absent as radar_absent, + rr.moving as radar_moving, + rr.stationary as radar_stationary, + rr.both as radar_both, + rr.m0 as radar_m0, + rr.m1 as radar_m1, + rr.m2 as radar_m2, + rr.m3 as radar_m3, + rr.m4 as radar_m4, + rr.m5 as radar_m5, + rr.m6 as radar_m6, + rr.m7 as radar_m7, + rr.m8 as radar_m8, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('10 seconds', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS avg_pressure, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('10 seconds', time) AS minute, + device_id, + MAX(absent) AS absent, + MAX(moving) AS moving, + MAX(stationary) AS stationary, + MAX(\"both\") AS both, + MAX(m0) AS m0, + MAX(m1) AS m1, + MAX(m2) AS m2, + MAX(m3) AS m3, + MAX(m4) AS m4, + MAX(m5) AS m5, + MAX(m6) AS m6, + MAX(m7) AS m7, + MAX(m8) AS m8, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + """ + Generate a TimeScaleDB query for radar readings based on device IDs with time snapped to 10-second intervals. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list): List of field names required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + # Handle fields processing + select_fields = [] + for field in radar_fields_of_interest: + if field == "s28": + select_fields.append("(s2+s3+s4+s5+s6+s7+s8)/7 AS s28") + else: + select_fields.append(field) + + fields_str = ", ".join(select_fields) + + sql = f""" + SELECT + time_bucket('10 seconds', time) AS ten_seconds, + device_id, + {fields_str} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + CASE device_id + {case_order} + END, + ten_seconds + """ + return sql + +def get_deployment_radar_10sec_snapped_query_min_max(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + """ + Generate a TimeScaleDB query for radar readings based on device IDs with time snapped to 10-second intervals. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list): List of field names required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + # Handle fields processing + select_fields = [] + for field in radar_fields_of_interest: + + radar_fields = field.split("_") + field_t = radar_fields[0] + if field_t == "s28": + if radar_fields[1] == "max": + select_fields.append("MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28") + else: + select_fields.append("MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28") + else: + if radar_fields[1] == "max": + select_fields.append(f"MAX({field_t}) as {field}") + else: + select_fields.append(f"MIN({field_t}) as {field}") + + fields_str = ", ".join(select_fields) + + sql = f""" + SELECT + time_bucket('10 seconds', time) AS ten_seconds, + device_id, + {fields_str} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + ten_seconds, + device_id + ORDER BY + CASE device_id + {case_order} + END, + ten_seconds + """ + return sql + +def export_query_to_minio_chunked(connection_params, query, minio_client, bucket_name, blob_name=None, chunksize=10000): + """ + Export query results to MinIO as CSV in chunks to handle large datasets + + Parameters: + connection_params (dict): Database connection parameters + query (str): SQL query to execute + minio_client: Initialized MinIO client + bucket_name (str): Name of the MinIO bucket + blob_name (str): Name for the blob in MinIO. If None, generates timestamped name + chunksize (int): Number of rows to process at a time + + Returns: + str: Name of the created blob + """ + try: + # Create direct connection using psycopg2 + conn = psycopg2.connect(**connection_params) + + # Generate blob name if not provided + if blob_name is None: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + blob_name = f'query_results_{timestamp}.csv' + + # Create a buffer to store CSV data + csv_buffer = io.StringIO() + + # Stream the query results in chunks + first_chunk = True + for chunk_df in pd.read_sql_query(query, conn, chunksize=chunksize): + # Write header only for the first chunk + chunk_df.to_csv( + csv_buffer, + index=False, + header=first_chunk, + mode='a' + ) + first_chunk = False + + # Get the CSV data as bytes + csv_buffer.seek(0) + csv_bytes = csv_buffer.getvalue().encode('utf-8') + + # Upload to MinIO + minio_client.put_object( + bucket_name, + blob_name, + io.BytesIO(csv_bytes), + len(csv_bytes) + ) + + print(f"Data exported successfully to MinIO: {bucket_name}/{blob_name}") + return blob_name + + except Exception as e: + print(f"Error exporting data: {str(e)}") + print(f"Traceback: {traceback.format_exc()}") + raise + + finally: + if 'conn' in locals(): + conn.close() + if 'csv_buffer' in locals(): + csv_buffer.close() + +def export_query_to_csv_pandas(connection_params, query, output_path=None): + """ + Export query results to CSV using pandas with psycopg2 connection + + Parameters: + connection_params (dict): Database connection parameters + query (str): SQL query to execute + output_path (str): Path for output CSV file. If None, generates timestamped filename + + Returns: + str: Path to the created CSV file + """ + try: + # Create direct connection using psycopg2 + conn = psycopg2.connect(**connection_params) + + # Generate output path if not provided + if output_path is None: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + output_path = f'query_results_{timestamp}.csv' + + # Read query directly into DataFrame using the psycopg2 connection + df = pd.read_sql_query(query, conn) + + # Export to CSV with all headers + df.to_csv(output_path, index=False) + print(f"Data exported successfully to {output_path}") + return output_path + + except Exception as e: + print(f"Error exporting data: {str(e)}") + raise + + finally: + if 'conn' in locals(): + conn.close() + + +def CreateDailyCSV(csv_file, devices_list, selected_date, vocs_scaled, time_zone_s, radar_part, consolidated_by, temp_offset): + global Id2MACDict + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + try: + + #stretch_to_min_max = True + + #current_date_p = selected_date.replace("-", "_") + #current_date_s = selected_date + + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + for details in devices_list[0]: + dev_id = details[0] + ids_list.append(details[1]) + if dev_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return "" + + if larger_than200 > 0: + sensors_c = len(s_table) + else: #old sensors not supported + return "" + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + if consolidated_by == "by_minute_rc": + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + elif consolidated_by == "by_deca_rd": + sql = get_deployment_deca_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset) + elif consolidated_by == "by_minute_rd": + sql = get_deployment_rd_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset) + + print(sql) + + connection_params = { + 'host': DB_HOST, + 'database': DB_NAME, + 'user': DB_USER, + 'password': DB_PASSWORD, + 'port': DB_PORT + } + + # Using pandas approach (recommended) + output_file = export_query_to_minio_chunked( + connection_params, + sql, + miniIO_blob_client, + "data-downloads", + csv_file, + chunksize=10000 + ) + return output_file + except Exception as e: + logging.error(str(traceback.format_exc())) + return "" + +def GetBlob(file_name, bucket_name="daily-maps"): + """ + Retrieve image from blob storage + + Args: + file_name (str): Name of the file to retrieve from blob storage + + Returns: + tuple: (image_bytes, content_type) + Returns None, None if image not found or error occurs + """ + logger.debug(f"GetBlob({file_name})") + try: + # Get the object from blob storage + data = miniIO_blob_client.get_object( + bucket_name, + file_name + ) + + # Read the data into bytes + data_bytes = data.read() + #logger.debug(f"len(data_bytes)={len(data_bytes)}") + + if bucket_name == "daily-maps": + return data_bytes, 'image/png' + else: + return data_bytes, 'application/zip' + + except Exception as e: + logger.error(f"Error: {traceback.format_exc()}") + return None, None + + +def MapFileToDate(map_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + parts = map_file.split("/") + parts = parts[-1].split("_") + + if "-" in parts[0]: + date_string = parts[0] + elif "-" in parts[1]: + date_string = parts[1] + + date_object = datetime.datetime.strptime(date_string, "%Y-%m-%d") + date_only = date_object.date() + return date_only + +def CSVFileToDate(csv_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + parts = csv_file.split("/") + parts = parts[-1].split("_") + + if "-" in parts[0]: + date_string = parts[0] + elif "-" in parts[1]: + date_string = parts[1] + + date_object = datetime.datetime.strptime(date_string, "%Y-%m-%d") + date_only = date_object.date() + return date_only + +def GetMACsListSimple(list_of_lists): + + result = [] + if len(list_of_lists) > 0: + result = [sublist[3] for sublist in list_of_lists] + + return(result) + +def datetime_handler(obj): + """Handle datetime serialization for JSON""" + if isinstance(obj, datetime.datetime): + if obj.tzinfo: + return obj.isoformat() + return obj.strftime('%Y-%m-%d %H:%M:%S.%f') + raise TypeError(f"Object of type {type(obj)} is not JSON serializable") + +def ReadCandles(file, sensor, period, time_from, time_to): + result = [] + if sensor == "voc0": + sqlr = "SELECT * from vocs_0"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc1": + sqlr = "SELECT * from vocs_1"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc2": + sqlr = "SELECT * from vocs_2"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc3": + sqlr = "SELECT * from vocs_3"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc4": + sqlr = "SELECT * from vocs_4"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc5": + sqlr = "SELECT * from vocs_5"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc6": + sqlr = "SELECT * from vocs_6"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc7": + sqlr = "SELECT * from vocs_7"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc8": + sqlr = "SELECT * from vocs_8"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc9": + sqlr = "SELECT * from vocs_9"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + else: + sqlr = "SELECT * from "+sensor+"s"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + logger.debug(f"sqlr = {sqlr}") + + with get_db_connection() as conn: + with conn.cursor() as cur: + devices_string = ReadCleanStringDB(cur, sqlr) + result = QuerrySql(file, sqlr) + return result + +def ReadSensor(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + result = [] + if sensor == "radar": + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor[0] == "s": + sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + if sensor == "temperature": + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype=17 ORDER BY time ASC" + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadSensorDeltas(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + 'for now pressure and light only' + sensor_2_mtype = {"light": 4, "pressure": 1, "temperature": 2, "humidity": 3} + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + result = [] + if sensor == "radar": + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor[0] == "s": + sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' and mtype=4 ORDER BY time ASC" + else: + if sensor == "temperature": + sqlr = f"SELECT time, {sensor} - 16 from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor == "pressure": + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype=1 ORDER BY time ASC" + else: + mtype = sensor_2_mtype[sensor] + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype={mtype} ORDER BY time ASC" + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadSensor3(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + import datetime + from datetime import timezone + + # Convert epoch to datetime and format as ISO 8601 strings with timezone + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + # If bucket_size is provided (i.e. not "no"), then use time bucketing. + use_bucket = bucket_size != "no" + if use_bucket: + # Map the shorthand bucket sizes to PostgreSQL interval strings. + mapping = { + "10s": "10 seconds", + "1m": "1 minute", + "5m": "5 minutes", + "10m": "10 minutes", + "15m": "15 minutes", + "30m": "30 minutes", + "1h": "1 hour" + } + bucket_interval = mapping.get(bucket_size, bucket_size) + + avgmax = "AVG" + # Build the SQL query based on sensor type. + if sensor == "radar": + # For radar sensors, a special aggregation is needed. + avgmax = "MAX" + if radar_part == "s28": + radar_expr = "(s2+s3+s4+s5+s6+s7+s8)/7" + else: + radar_expr = radar_part + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({radar_expr}) AS radar + FROM radar_readings + WHERE device_id = {device_id} + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {radar_expr} AS radar + FROM radar_readings + WHERE device_id = {device_id} + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor[0] == "s": + # For sensors whose name starts with "s" (for example, smell sensors) + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({sensor}) AS smell + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {sensor} AS smell + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "co2": + alias = sensor + sensor = "s4" + sqlr = f""" + WITH s4_values AS ( + SELECT s4 + FROM public.sensor_readings + WHERE device_id = 559 + AND s4 IS NOT NULL + ), + s4_percentile AS ( + SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY s4 DESC) AS s4_25_percentile + FROM s4_values + ) + SELECT s4_25_percentile + FROM s4_percentile; + """ + co2_max = 22536000#102400000 + co2_min = 2400000#16825674 #387585 + + real_co2_max = 2000 + real_co2_min = 430 + + + #logger.debug(f"sqlr = {sqlr}") + #with get_db_connection() as conn: + #with conn.cursor() as cur: + #cur.execute(sqlr) + #result = cur.fetchall() + #co2_max = result[0][0] + #co2_min = result[0][1] + #=E17+E20*(102400000-A24)/B18 + #col_expr = f"{real_co2_min}+({real_co2_max}-{real_co2_min})*(102400000-{sensor})/({co2_min}-{co2_max})" + col_expr = f"GREATEST({real_co2_min},{real_co2_min}+({real_co2_max}-{real_co2_min})*({co2_max}-percentile_cont(0.5) WITHIN GROUP (ORDER BY {sensor}))/({co2_max}-{co2_min}))" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + ({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "voc": + sensor = "s9" + alias = sensor + col_expr = f"{sensor} - 0" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "humidity": + alias = sensor + #col_expr = f"2.3592 * {sensor} + 32.5546" #= 2.3592 * J2 + 33.5546 + col_expr = f"1* {sensor} + 5.0" #= 2.3592 * J2 + 33.5546 + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + else: + # For other sensors (including temperature, which requires a subtraction) + alias = sensor + col_expr = sensor + if sensor == "temperature": + col_expr = f"{sensor}" + alias = "temperature" + elif sensor == "light": + avgmax = "MAX" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + AND mtype = 17 + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadRadarDetail(device_id, sensor, time_from_epoch, time_to_epoch, alt_key_state): + + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + #sensor_index = int(sensor_index) + + + result = [] + + #time_period_sec can be "10" (RAW) or "60" + if alt_key_state == "1": #"RAW = 10 sec" + radar_part = sensor + if sensor == "m08_max": + radar_part = "(m0+m1+m2+m3+m4+m5+m6+m7+m8)/9" + elif sensor == "s28_max" or sensor == "s28_min": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + if sensor == "m08_max": + radar_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/7) AS m08_max" + elif sensor == "s28_max": + radar_part = f"MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS {sensor}" + elif sensor == "s28_min": + radar_part = f"MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS {sensor}" + else: + radar_part = f"MAX({sensor}) AS {sensor}" + + sqlr = f""" + SELECT + minute, + {sensor} as {sensor} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + {radar_part} + FROM + radar_readings + WHERE + device_id = {device_id} + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute + ) rr + + ORDER BY + minute + """ + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def check_and_parse(data_str): + # Remove whitespace to handle cases with spaces + cleaned = data_str.strip() + # Check if second character is '[' + is_list_of_lists = cleaned[1] == '[' + if cleaned[0] == '[': + # Parse the string regardless of type + parsed = json.loads(cleaned) + else: + #parsed = cleaned.split(",") + parsed = SmartSplit(cleaned) + return is_list_of_lists, parsed + +def clean_data_with_rolling_spline(line_part_t, window=5, threshold=2.0): + """ + Filter outliers using rolling median and replace with spline interpolation + Returns data in the same format as input: [(timestamp, value), ...] + """ + # Unzip the input tuples + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) # explicitly convert to float + + # Calculate rolling median and MAD using a safer approach + rolling_median = [] + rolling_mad = [] + + for i in range(len(y)): + start_idx = max(0, i - window//2) + end_idx = min(len(y), i + window//2 + 1) + window_values = y[start_idx:end_idx] + + # Skip if window is empty or contains invalid values + if len(window_values) == 0 or np.any(np.isnan(window_values)): + rolling_median.append(y[i]) + rolling_mad.append(0) + continue + + med = np.median(window_values) + mad = np.median(np.abs(window_values - med)) + + rolling_median.append(med) + rolling_mad.append(mad) + + rolling_median = np.array(rolling_median) + rolling_mad = np.array(rolling_mad) + + # Identify outliers (protect against division by zero) + outlier_mask = np.abs(y - rolling_median) > threshold * (rolling_mad + 1e-10) + good_data_mask = ~outlier_mask + + if np.sum(good_data_mask) < 4: + return line_part_t # return original data if we can't interpolate + + try: + # Create and apply spline + spline = interpolate.InterpolatedUnivariateSpline( + x[good_data_mask], + y[good_data_mask], + k=3 + ) + + y_cleaned = y.copy() + y_cleaned[outlier_mask] = spline(x[outlier_mask]) + except Exception as e: + print(f"Spline interpolation failed: {e}") + return line_part_t + + # Return in the same format as input + return list(zip(x, y_cleaned)) + +def DatesSpan(date_from: str, date_to: str) -> list: + """ + Generate a list of dates between date_from and date_to (inclusive). + Handles cases where date_from is later than date_to. + + Args: + date_from (str): Start date in 'YYYY-MM-DD' format + date_to (str): End date in 'YYYY-MM-DD' format + + Returns: + list: List of dates in 'YYYY-MM-DD' format + """ + # Convert string dates to datetime objects + start_date = datetime.datetime.strptime(date_from, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + start_date, end_date = end_date, start_date + + # Generate list of dates + dates_list = [] + current_date = start_date + + while current_date <= end_date: + dates_list.append(current_date.strftime('%Y-%m-%d')) + current_date += timedelta(days=1) + + # Reverse the list if original date_from was later than date_to + #if datetime.datetime.strptime(date_from, '%Y-%m-%d') > datetime.datetime.strptime(date_to, '%Y-%m-%d'): + # dates_list.reverse() + + return dates_list + +def zip_blobs(blob_paths, zip_blob_name, bucket_name, minio_client=None): + """ + Zip multiple blobs from MinIO storage into a single zip file without saving locally. + + Args: + blob_paths (list): List of blob paths to zip + zip_blob_name (str): Name/path for the output zip file in MinIO + bucket_name (str): MinIO bucket name + minio_client (Minio, optional): Existing MinIO client instance + + Returns: + bool: True if successful, False otherwise + """ + try: + # Create zip file in memory + zip_buffer = BytesIO() + + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + # Process each blob + for blob_path in blob_paths: + # Get file name from path for zip entry + file_name = blob_path.split('/')[-1] + + # Get blob data into memory + data = minio_client.get_object(bucket_name, blob_path.lstrip('/')) + + # Add file to zip + zip_file.writestr(file_name, data.read()) + + # Close the object to free memory + data.close() + + # Seek to start of zip file + zip_buffer.seek(0) + + # Upload zip file to MinIO + minio_client.put_object( + bucket_name, + zip_blob_name.lstrip('/'), + zip_buffer, + length=zip_buffer.getbuffer().nbytes + ) + + return True + + except Exception as e: + print(f"Error creating zip file: {str(e)}") + return False + finally: + # Clean up + zip_buffer.close() + +def clean_data_with_spline(x, y, threshold=2.0): + """ + Filter outliers and replace with spline interpolation + + Parameters: + x : array-like, timestamps or x-coordinates + y : array-like, values to be filtered + threshold : float, number of median absolute deviations for outlier detection + + Returns: + array-like : cleaned data with outliers replaced by spline interpolation + """ + # Convert inputs to numpy arrays + x = np.array(x) + y = np.array(y) + + # Calculate median and median absolute deviation + median = np.median(y) + mad = stats.median_abs_deviation(y) + + # Identify outliers + outlier_mask = np.abs(y - median) > threshold * mad + good_data_mask = ~outlier_mask + + # If we have too few good points for interpolation, adjust threshold + min_points_needed = 4 # minimum points needed for cubic spline + if np.sum(good_data_mask) < min_points_needed: + return y # return original data if we can't interpolate + + # Create spline with non-outlier data + spline = interpolate.InterpolatedUnivariateSpline( + x[good_data_mask], + y[good_data_mask], + k=3 # cubic spline + ) + + # Replace outliers with interpolated values + y_cleaned = y.copy() + y_cleaned[outlier_mask] = spline(x[outlier_mask]) + + return y_cleaned + +def clean_data(line_part_t, window=5, threshold=2.0): + """ + Remove obvious outliers based on window comparison + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) + + cleaned_data = [] + + for i in range(len(y)): + # Get window around current point + start_idx = max(0, i - window//2) + end_idx = min(len(y), i + window//2 + 1) + window_values = y[start_idx:end_idx] + + # Calculate median and MAD for the window + window_median = np.median(window_values) + deviation = abs(y[i] - window_median) + + # Keep point if it's not too far from window median + if deviation <= threshold * window_median: + cleaned_data.append((x[i], y[i])) + #else: + #print(window_values) + return cleaned_data + +def clean_data_fast(line_part_t, window=5, threshold=2.0): + """ + Remove obvious outliers based on window comparison - vectorized version + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) + + # Calculate rolling median using numpy + half_window = window // 2 + medians = np.array([ + np.median(y[max(0, i-half_window):min(len(y), i+half_window+1)]) + for i in range(len(y)) + ]) + + # Calculate deviations for all points at once + deviations = np.abs(y - medians) + + # Create mask for good points + good_points = deviations <= threshold * medians + + # Return filtered data using boolean indexing + return list(zip(x[good_points], y[good_points])) + +def clean_data_pd(line_part_t, window=5, percentile=99): + """ + Remove obvious outliers based on window comparison - pandas version + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + #line_part_t = line_part_t[2000:2100] + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + + # Create pandas Series and calculate rolling median + series = pd.Series(y) + medians = series.rolling(window=window, center=True, min_periods=1).median() + + # Calculate deviations + deviations = np.abs(series - medians) + + largest_deviations = deviations.nlargest(10) + #print(largest_deviations) + + # Create mask for good points + deviation_threshold = np.percentile(deviations, percentile) + good_points = deviations <= deviation_threshold + + # Convert back to numpy arrays for filtering + x = np.array(x) + y = np.array(y) + + # Return filtered data + return list(zip(x[good_points], y[good_points])) + +def CombineStripes(result_filename, stripes_files): + try: + # Open the first image to get the width and initialize the height + first_image = Image.open(stripes_files[0]) + width, height = first_image.size + + # Calculate the total height of the combined image + total_height = height * len(stripes_files) + + # Create a new blank image with the same width and the calculated height + result_image = Image.new('RGB', (width, total_height)) + + # Paste each image onto the result image vertically + y_offset = 0 + for file_name in stripes_files: + image = Image.open(file_name) + result_image.paste(image, (0, y_offset)) + y_offset += height + + # Save the result image + result_image.save(result_filename) + + # Return success flag + return True + + except Exception as e: + print("Error:", e) + return False + +def FindFirstLocalMinimum(counts, bins): + """ + Find the first local minimum in a histogram after the main peak and calculate its offset. + + Parameters: + hist: tuple of (counts, bin_edges) from np.histogram() + The histogram data to analyze + + Returns: + tuple: (TR, THR_OFFSET) + TR: float, the bin value (position) of the first local minimum after the main peak + THR_OFFSET: int, number of bins between the global maximum and the local minimum + """ + + # Find the main peak (global maximum) + main_peak_idx = np.argmax(counts) + + # Look for the first local minimum after the main peak + for i in range(main_peak_idx + 1, len(counts) - 1): + # Check if current point is less than or equal to both neighbors + if counts[i] <= counts[i-1] and counts[i] <= counts[i+1]: + # Calculate the bin center value for TR + TR = (bins[i] + bins[i+1]) / 2 + # Calculate offset from main peak in number of bins + THR_OFFSET = (bins[i] + bins[i+1]) / 2 - (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2 + return (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2, TR, THR_OFFSET + + # If no local minimum is found, return None for both values + return None, None + +def process_raw_data(data_tuples): + """ + Convert list of (timestamp, stationary, motion) tuples to separate arrays + + Parameters: + ----------- + data_tuples : list of tuples + Each tuple contains (datetime, stationary_value, motion_value) + + Returns: + -------- + timestamps : array of datetime + stationary : array of float + motion : array of float + """ + timestamps = np.array([t[0] for t in data_tuples]) + stationary = np.array([t[1] for t in data_tuples]) + motion = np.array([t[2] for t in data_tuples]) + + return timestamps, stationary, motion + +def rolling_std_fast(arr, window_size): + """ + Fast calculation of rolling standard deviation using NumPy's stride tricks. + + Parameters: + ----------- + arr : numpy array + Input array + window_size : int + Size of rolling window + + Returns: + -------- + numpy array + Rolling standard deviation + """ + # Compute rolling sum of squares + r = np.array(arr, dtype=float) + r2 = np.array(arr, dtype=float) ** 2 + + # Calculate cumulative sums + cum = np.cumsum(np.insert(r, 0, 0)) + cum2 = np.cumsum(np.insert(r2, 0, 0)) + + # Get rolling windows + x = (cum[window_size:] - cum[:-window_size]) + x2 = (cum2[window_size:] - cum2[:-window_size]) + + # Calculate standard deviation + n = window_size + return np.sqrt((x2/n) - (x/n) ** 2) + +def detect_presence(timestamps, stationary_signal, motion_signal, window_size=100, + motion_threshold=5, gmm_components=2): + """ + Detect presence using both stationary and motion signals with adaptive thresholding. + + Parameters: + ----------- + timestamps : array-like + Array of datetime objects + stationary_signal : array-like + Time series of stationary signal (0-100) + motion_signal : array-like + Time series of motion signal (0-100) + window_size : int + Size of rolling window for statistics (used only for temporal smoothing) + motion_threshold : float + Threshold for significant motion + gmm_components : int + Number of components for Gaussian Mixture Model + + Returns: + -------- + presence_mask : numpy array + Boolean array indicating presence + baseline : float + Computed baseline for stationary signal + threshold : float + Computed threshold for stationary signal + """ + + # Convert inputs to numpy arrays + stationary_signal = np.array(stationary_signal) + motion_signal = np.array(motion_signal) + + # 1. Fit Gaussian Mixture Model to stationary signal + gmm = GaussianMixture(n_components=gmm_components, random_state=42) + X = stationary_signal.reshape(-1, 1) + gmm.fit(X) + + # Get the component with lowest mean as baseline + baseline = min(gmm.means_)[0] + + # 2. Calculate adaptive threshold using GMM components + components_sorted = sorted(zip(gmm.means_.flatten(), gmm.covariances_.flatten())) + baseline_std = np.sqrt(components_sorted[0][1]) + threshold = baseline + 3 * baseline_std # 3 sigma rule + + # 3. Combine motion and stationary detection + presence_mask = np.zeros(len(stationary_signal), dtype=bool) + + # Vectorized operations instead of loop + presence_mask = (motion_signal > motion_threshold) | (stationary_signal > threshold) + + # 4. Apply temporal smoothing to reduce false transitions + smooth_window = min(window_size // 4, 10) # Smaller window for smoothing + presence_mask = np.convolve(presence_mask.astype(int), + np.ones(smooth_window)/smooth_window, + mode='same') > 0.5 + + return presence_mask, baseline, threshold + +def visualize_detection(timestamps, stationary_signal, motion_signal, presence_mask, + baseline, threshold, output_file='presence_detection.png'): + """ + Visualize the detection results and save to file. + + Parameters: + ----------- + timestamps : array-like + Array of datetime objects + stationary_signal : array-like + Time series of stationary signal + motion_signal : array-like + Time series of motion signal + presence_mask : array-like + Boolean array indicating presence + baseline : float + Computed baseline for stationary signal + threshold : float + Computed threshold for stationary signal + output_file : str + Path to save the output PNG file + """ + plt.figure(figsize=(15, 10)) + + # Configure time formatting + date_formatter = mdates.DateFormatter('%H:%M:%S') + + # Plot signals + plt.subplot(3, 1, 1) + plt.plot(timestamps, stationary_signal, label='Stationary Signal') + plt.axhline(y=baseline, color='g', linestyle='--', label='Baseline') + plt.axhline(y=threshold, color='r', linestyle='--', label='Threshold') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.legend() + plt.title('Stationary Signal with Baseline and Threshold') + plt.grid(True) + + plt.subplot(3, 1, 2) + plt.plot(timestamps, motion_signal, label='Motion Signal') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.legend() + plt.title('Motion Signal') + plt.grid(True) + + plt.subplot(3, 1, 3) + plt.plot(timestamps, presence_mask, label='Presence Detection') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.ylim(-0.1, 1.1) + plt.legend() + plt.title('Presence Detection Result') + plt.grid(True) + + plt.tight_layout() + + # Save to file and close figure to free memory + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.close() + +def FindZeroIntersection(counts, bins, save_plot, device_id): + """ + Find the zero intersection point by fitting a parabola to the descending slope + between 50% and 10% of the maximum peak height. Also returns the peak position. + + Parameters: + counts: array-like + The histogram counts + bins: array-like + The histogram bin edges + save_plot: str or None, optional + If provided, saves the visualization to the specified file path + + Returns: + tuple: (zero_intersections, peak_position) + zero_intersections: list of floats, x-coordinates where parabola intersects y=0 + peak_position: float, x-coordinate of the histogram maximum peak + """ + # Find the main peak + main_peak_idx = np.argmax(counts) + peak_height = counts[main_peak_idx] + peak_position = (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2 + + # Calculate 75% and 25% of peak height + height_50 = 0.50 * peak_height + height_10 = 0.10 * peak_height + + # Find indices where counts cross these thresholds after the peak + idx_50 = main_peak_idx + idx_10 = main_peak_idx + + for i in range(main_peak_idx, len(counts)): + if counts[i] <= height_50 and idx_50 == main_peak_idx: + idx_50 = i + if counts[i] <= height_10: + idx_10 = i + break + + # If we couldn't find valid points, return None + if idx_50 == main_peak_idx or idx_10 == main_peak_idx: + return None, peak_position + + # Get x and y coordinates for fitting + # Use bin centers for x coordinates + x_points = np.array([(bins[i] + bins[i+1])/2 for i in range(idx_50, idx_10+1)]) + y_points = counts[idx_50:idx_10+1] + + # Define quadratic function for fitting + def quadratic(x, a, b, c): + return a*x**2 + b*x + c + + try: + popt, pcov = curve_fit(quadratic, x_points, y_points) + a, b, c = popt + + # Find zeros using quadratic formula + if a != 0: + discriminant = b**2 - 4*a*c + if discriminant >= 0: + x1 = (-b + np.sqrt(discriminant)) / (2*a) + x2 = (-b - np.sqrt(discriminant)) / (2*a) + zero_intersections = sorted([x1, x2]) + # Filter zeros to only include those after the peak + zero_intersections = [x for x in zero_intersections if x > peak_position] + else: + zero_intersections = [] + else: + # If a ≈ 0, fallback to linear solution + if b != 0: + zero_intersections = [-c/b] + else: + zero_intersections = [] + + if save_plot: + plt.figure(figsize=(10, 6)) + + # Plot histogram + bin_centers = [(bins[i] + bins[i+1])/2 for i in range(len(counts))] + plt.bar(bin_centers, counts, width=bins[1]-bins[0], alpha=0.6, + color='skyblue', label='Histogram') + + # Plot peak height lines + plt.axhline(y=height_50, color='g', linestyle='--', alpha=0.5, + label='50% Peak Height') + plt.axhline(y=height_10, color='r', linestyle='--', alpha=0.5, + label='10% Peak Height') + + # Plot fitted parabola + x_fit = np.linspace(min(x_points), max(x_points), 100) + y_fit = quadratic(x_fit, a, b, c) + plt.plot(x_fit, y_fit, 'r-', label='Fitted Parabola') + + # Plot points used for fitting + plt.plot(x_points, y_points, 'ro', alpha=0.5, label='Fitting Points') + + # Plot zero intersections + for x_zero in zero_intersections: + plt.plot(x_zero, 0, 'ko', label='Zero Intersection') + + # Plot peak position + plt.axvline(x=peak_position, color='purple', linestyle='--', alpha=0.5, + label='Peak Position') + + # Add labels and legend + plt.xlabel('Bin Values') + plt.ylabel('Counts') + plt.title(f'Histogram Analysis with Parabolic Fit {device_id}') + plt.legend() + + # Show zero line + plt.axhline(y=0, color='k', linestyle='-', alpha=0.2) + + # Add text with intersection and peak values + text = f'Peak Position: {peak_position:.2f}\n' + if zero_intersections: + text += f'Zero Intersection(s): {", ".join([f"{x:.2f}" for x in zero_intersections])}' + plt.text(0.02, 0.98, text, transform=plt.gca().transAxes, + verticalalignment='top', + bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)) + + # Save plot to file + plt.savefig(save_plot, dpi=300, bbox_inches='tight') + plt.close() + + return zero_intersections, peak_position + except RuntimeError: + print("Warning: Failed to fit parabola") + return None, peak_position + + +def GeneratePresenceHistory(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + #maps_dates, proximity = GetDeploymentDatesBoth(deployment_id) + minutes = 1440 + stripes_files = [] + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + maps_dates.reverse() + days = len(maps_dates) + stretch_by = int(1000 / days) + if stretch_by > 50: + stretch_by = 50 + + #lets use 1000 pixels + #arr_stretched = np.zeros((int(days*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + result_image = Image.new('RGB', (minutes, int(days*stretch_by))) + + + # Paste each image onto the result image vertically + y_offset = 0 + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + + file_exists, time_modified_utc = check_file_exists(filename_day) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + file_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if file_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) + + + image_bytes, content_type = GetBlob(filename_day) + if image_bytes != None: + image_stream = io.BytesIO(image_bytes) + image = Image.open(image_stream) + + #image = Image.open(file_name) + result_image.paste(image, (0, y_offset)) + image.close() + image_stream.close() + + y_offset += stretch_by + + # Save directly to MinIO instead of local file + success = save_to_minio(result_image, filename, DAILY_MAPS_BUCKET_NAME) + # Clean up + result_image.close() + return success + +def AddText(room_image_cv2, x, y, room_name, font_size): + pil_im = Image.fromarray(room_image_cv2) + draw = ImageDraw.Draw(pil_im) + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + #print(f"Attempting to load font from: {font_path}") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error(f"Poppins font not found in {font_path}. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + draw.text((x, y), room_name, font=font, fill=(150, 150, 150)) # Black color in RGB + room_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + return room_image_cv2 + +def AddTextList(room_image_cv2, strings_list, font_size): + pil_im = Image.fromarray(room_image_cv2) + draw = ImageDraw.Draw(pil_im) + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error("Poppins font not found. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + + for x, y, room_name in strings_list: + draw.text((x, y), room_name, font=font, fill=(150, 150, 150)) # Black color in RGB + + room_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + return room_image_cv2 + + +def AddRoomData(room_image, room_name, data): + + # Example usage: + radius = 10 + color_t = data["color"] # BGR format for red + color = (color_t[2], color_t[1], color_t[0]) + x_offset = 12 + + room_image = AddText(room_image, 13, 20, room_name, 50) + print(data) + for present in data["presence"]: + device_id, minute, duration = present + #duration = 10 + top_left = (x_offset + minute, 140) #bottom_right = (300, 200) + bottom_right = (x_offset + minute + duration, 260) + draw_rounded_rectangle(room_image, top_left, bottom_right, radius, color) + return room_image + +def AddFooterData(image): + '12, 370, 736, 1092, 1452' + step_size = 1440 / 4 + string_width = 60 + offset = 12 + yoffset = 30 + step = 0 + font_size = 40 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "3 AM", font_size) + step = 1 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "9 AM", font_size) + step = 2 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "3 PM", font_size) + step = 3 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "9 PM", font_size) + return image + +def draw_rounded_rectangle(image, top_left, bottom_right, radius, color): + """ + Draw a filled rectangle with rounded corners, using simple rectangle for small dimensions + :param image: Image to draw on + :param top_left: Top-left corner coordinates (x, y) + :param bottom_right: Bottom-right corner coordinates (x, y) + :param radius: Desired corner radius (will be adjusted if needed) + :param color: Rectangle color in BGR format + """ + x1, y1 = top_left + x2, y2 = bottom_right + + # Calculate width + width = x2 - x1 + + + # Adjust radius if width or height is too small + # Maximum radius should be half of the smaller dimension + max_radius = abs(width) // 2 + radius = min(radius, max_radius) + + # If width is too small, fallback to regular rectangle + if width <= 4 or radius <= 1: + cv2.rectangle(image, top_left, bottom_right, color, -1) + return + + # Adjust radius if needed + radius = min(radius, width // 2) + + # Create points for the main rectangle + pts = np.array([ + [x1 + radius, y1], + [x2 - radius, y1], + [x2, y1 + radius], + [x2, y2 - radius], + [x2 - radius, y2], + [x1 + radius, y2], + [x1, y2 - radius], + [x1, y1 + radius] + ], np.int32) + + # Fill the main shape + cv2.fillPoly(image, [pts], color) + + # Fill the corners + cv2.ellipse(image, (x1 + radius, y1 + radius), (radius, radius), 180, 0, 90, color, -1) + cv2.ellipse(image, (x2 - radius, y1 + radius), (radius, radius), 270, 0, 90, color, -1) + cv2.ellipse(image, (x1 + radius, y2 - radius), (radius, radius), 90, 0, 90, color, -1) + cv2.ellipse(image, (x2 - radius, y2 - radius), (radius, radius), 0, 0, 90, color, -1) + +def filter_device(locations_list, device_id): + result = [] + for entry in locations_list: + if entry[0] == device_id: + result.append(entry) + + return result + +def GenerateLocationsMap(date_st, devices_list, devices_map, locations_list, time_zone_s): + + devices_list_t = [("date",date_st)] + + + for mac in devices_list: + well_id, device_id, room = devices_map[mac] + #room = devices[well_id][0] + if room in Loc2Color: + color = Loc2Color[room][0] + else: + color = Loc2Color[room.split()[0]][0] + presence_data = filter_device(locations_list, device_id) + room_details = (room, {"color": color, "presence": presence_data}) + devices_list_t.append(room_details) + + well_id = 0 + device_id = 0 + room = "Outside/?" + color = (0, 0, 0) + + #lets's not draw future unknown! + presence_data = filter_device(locations_list, device_id) + current_utc = datetime.datetime.now(pytz.UTC) + current_date_local = current_utc.astimezone(pytz.timezone(time_zone_s)) + current_minute_of_day = current_date_local.hour * 60 + current_date_local.minute + + if date_st == current_date_local.strftime('%Y-%m-%d'): + filtered_presence_data = [] + for entry in presence_data: + if entry[1] < current_minute_of_day : + if entry[1] + entry[2] < current_minute_of_day: + filtered_presence_data.append(entry) + else: + entry[2] = (current_minute_of_day - entry[2]) + if entry[2] > 0: + filtered_presence_data.append(entry) + #print(presence_data) + else: + filtered_presence_data = presence_data + + room_details = (room, {"color": color, "presence": filtered_presence_data}) + devices_list_t.append(room_details) + + return devices_list_t + +def CreateDailyLocationChart(filename_chart_image_day, locations): + result = False + header_image_file = "header.png" + room_image_file = "room.png" + footer_image_file = "footer.png" + + #ToDo: change it so it reads files from MinIo + header_image_file = os.path.join(filesDir, header_image_file) + header_image_file = header_image_file.replace("\\","/") + header_image = cv2.imread(header_image_file) + #header_height, header_width = header_image.shape[:2] + + room_image_file = os.path.join(filesDir, room_image_file) + room_image_file = room_image_file.replace("\\","/") + room_image = cv2.imread(room_image_file) + #room_height, room_width = room_image.shape[:2] + + footer_image_file = os.path.join(filesDir, footer_image_file) + footer_image_file = footer_image_file.replace("\\","/") + footer_image = cv2.imread(footer_image_file) + + all_images = [header_image] + for item_c in locations: + item = item_c[0] + if item == "date": + date = item_c[1] + else: + room_image = cv2.imread(room_image_file) + data = item_c[1] + room_image = AddRoomData(room_image, item, data) + all_images.append(room_image) + + footer_image = AddFooterData(footer_image) + all_images.append(footer_image) + final_image = np.vstack(all_images) + #this needs to write straight to MinIo ! + SaveImageInBlob(filename_chart_image_day, final_image) + result = True + #cv2.imwrite(filename_chart_image_day, final_image) + #print(rooms_count) + return result + + +def GetOptimumFontSize(target_width, text="00", min_size=1, max_size=100, tolerance=1): + """ + Find optimal font size to fit text within target width using binary search. + + Args: + target_width (int): Desired width in pixels + text (str): Text to measure (default "00") + min_size (int): Minimum font size to try + max_size (int): Maximum font size to try + tolerance (int): Acceptable difference from target width + + Returns: + int: Optimal font size + """ + while min_size <= max_size: + current_size = (min_size + max_size) // 2 + width, _ = GetStringSize(text, current_size) + + if abs(width - target_width) <= tolerance: + return current_size + elif width > target_width: + max_size = current_size - 1 + else: + min_size = current_size + 1 + + # Return the largest size that fits within target width + width, _ = GetStringSize(text, min_size) + return min_size if width <= target_width else min_size - 1 + +def GetStringSize(some_string, font_size): + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error("Poppins font not found. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + + bbox = font.getbbox(some_string) + return bbox[2] - bbox[0], bbox[3] - bbox[1] + +def GeneratePresenceHistoryChart(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + #maps_dates, proximity = GetDeploymentDatesBoth(deployment_id) + minutes = 1440 + stripes_files = [] + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + #maps_dates.reverse() + days = len(maps_dates) + #stretch_by = int(1000 / days) + #if stretch_by > 50: + #stretch_by = 50 + stretch_by = 30 + + #background_image_file = os.path.join(filesDir, "multi_day_template.png") + background_image_file = os.path.join(filesDir, "multi_day_template2.png") + background_image_file = background_image_file.replace("\\","/") + background_image = cv2.imread(background_image_file) + + rgb_image = background_image #cv2.cvtColor(background_image, cv2.COLOR_BGR2RGB) + result_image = Image.fromarray(rgb_image) # Convert to PIL Image + #result_image = Image.new('RGB', (minutes, int(days*stretch_by))) + + # Paste each image onto the result image vertically + y_offset = 0 + locations_list = [] + font_size = 50 + string_width, string_height = GetStringSize("00", font_size) + + + success = False + if len(maps_dates) == 1: + + filename_chart_image_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations_chart.png" + force_recreate = recreate_in + #it is faster to resize existing daily location chart (length is always 1440), than having to re-create it each time... + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + filename_chart_data_day = filename_day+".bin" + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_chart_image_day) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = ddate + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + if not force_recreate: + file_exists1, time_modified_utc1 = check_file_exists(filename_chart_data_day) + if file_exists1: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = ddate + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list_a, device_ids = GetProximityList(deployment_id, timee) + CreateDailyLocationMap(filename_day, devices_list_a, ddate, filter_minutes, time_zone_s, stretch_by) + locations_list_s = ReadObjectMinIO("daily-maps", filename_chart_data_day) + locations_list = json.loads(locations_list_s) + + devices_map = {} + devices_list = [] + for device_entry in devices_list_a: + #if T: + if device_entry[3] == None or device_entry[3].strip() == "": + devices_map[device_entry[4]] = [device_entry[0], device_entry[1], device_entry[2]] + else: + devices_map[device_entry[4]] = [device_entry[0], device_entry[1], device_entry[2] + " " + device_entry[3]] + devices_list.append(device_entry[4]) + + locations = GenerateLocationsMap(ddate, devices_list, devices_map, locations_list, time_zone_s) + success = CreateDailyLocationChart(filename, locations) + else: + + day_counter = 0 + day_step_width = int(1780 / days) + x_offset = 563 + y_offset = 1615 + h_labels_bottom = 1720 + day_width = int(0.9 * day_step_width) + day_height = 1440 + font_size = GetOptimumFontSize(day_width, "00", 10, 50, 0) + string_width, string_height = GetStringSize("00", font_size) + #logger.debug(f"font_size={font_size} string_width={string_width}") + + y_offset = y_offset - day_height + filename_chart_image_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations_chart.png" + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_day) + file_existsS, time_modifiedS_utc = check_file_exists(filename_day[:-4]+"S.png") + if file_exists and file_existsS: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) + + #here we need to rotate and resize to: + + image_bytes, content_type = GetBlob(filename_day) + image_bytes_s, content_type_s = GetBlob(filename_day[:-4]+"S.png") + if image_bytes != None: + image_stream = io.BytesIO(image_bytes) + image = Image.open(image_stream) + numpy_image = np.array(image) + rotated_image = cv2.rotate(numpy_image, cv2.ROTATE_90_COUNTERCLOCKWISE) + scaled_image = cv2.resize(rotated_image, (day_width, day_height), interpolation=cv2.INTER_AREA) + + # Convert from BGR to RGB + rgb_image = cv2.cvtColor(scaled_image, cv2.COLOR_BGR2RGB) + # Convert to PIL Image + pil_image = Image.fromarray(rgb_image) + + #image = Image.open(file_name) + x_origin = x_offset + day_step_width * day_counter + int(0.05 * day_step_width) + result_image.paste(pil_image, (x_origin, y_offset)) + + image_stream = io.BytesIO(image_bytes_s) + image = Image.open(image_stream) + numpy_image = np.array(image) + rotated_image = cv2.rotate(numpy_image, cv2.ROTATE_90_COUNTERCLOCKWISE) + scaled_image = cv2.resize(rotated_image, (day_width, day_height), interpolation=cv2.INTER_AREA) + + # Convert from BGR to RGB + rgb_image = cv2.cvtColor(scaled_image, cv2.COLOR_BGR2RGB) + # Convert to PIL Image + pil_image = Image.fromarray(rgb_image) + + #image = Image.open(file_name) + x_origin = x_offset + day_step_width * day_counter + int(0.05 * day_step_width) + result_image.paste(pil_image, (x_origin, 1807+y_offset)) + + + image.close() + image_stream.close() + + day_counter += 1 + + pil_im = result_image + #result_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + result_image_cv2 = np.array(pil_im)#cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + strings_list = [] + day_counter = 0 + for ddate in maps_dates: + if string_width <= day_width: + date_str = ddate[8:10] + x_origin = x_offset + int(day_step_width * (day_counter + 0.5)) - int(string_width / 2) + strings_list.append((x_origin, h_labels_bottom, date_str)) + day_counter += 1 + result_image_cv2 = AddTextList(result_image_cv2, strings_list, font_size) + + + #Y 124 to 1636 + labels_bottom = 1636 - 1.5 * string_height + x_offset = 340 + step = -4 * 60 #4 hours + font_size = 50 + strings_list = [] + count = 0 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 AM")) + count = 1 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "4 AM")) + count = 2 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "8 AM")) + count = 3 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 PM")) + count = 4 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "4 PM")) + count = 5 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "8 PM")) + count = 6 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 AM")) + + result_image_cv2 = AddTextList(result_image_cv2, strings_list, font_size) + + numpy_image = np.array(result_image_cv2) + + success = SaveImageInBlob(filename, numpy_image) + #SaveImageInBlob(filename, result_image) + # Save directly to MinIO instead of local file + #if success: + # success = save_to_minio(result_image, filename, DAILY_MAPS_BUCKET_NAME) + # Clean up + if success: + return filename + else: + return "" + +def GeneratePresenceHistoryFiles(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + stretch_by = 30 + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + + day_counter = 0 + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_day+".bin") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, 10) + + day_counter += 1 + + return filename + +def CalcStdevs(row, stdev_range, stdevs): + half_range = stdev_range // 2 + data_len = len(row) + + # Calculate standard deviations with proper window alignment + for i in range(data_len): + # Calculate window boundaries + start = max(0, i - half_range) + end = min(data_len, i + half_range + 1) + + # Get data within window + window_data = row[start:end] + + # Calculate standard deviation if we have data + if len(window_data) > 0: + stdevs[i] = np.std(window_data) + + # Find amplitude (max - min of standard deviations) + amplitude = np.max(stdevs) - np.min(stdevs) + + # Scale to range 0-1279 + if amplitude > 0: # Avoid division by zero + stdevs = ((stdevs - np.min(stdevs)) / amplitude * 1279).astype(np.float32) + + return stdevs, amplitude + +def CalcLife(row, stdev_range, stdevs): + half_range = stdev_range // 2 + data_len = len(row) + + # Calculate standard deviations with proper window alignment + for i in range(data_len): + # Calculate window boundaries + start = max(0, i - half_range) + end = min(data_len, i + half_range + 1) + + # Get data within window + window_data = row[start:end] + + # Calculate standard deviation if we have data + if len(window_data) > 0: + stdevs[i] = np.std(window_data) + + # Find amplitude (max - min of standard deviations) + amplitude = np.max(stdevs) - np.min(stdevs) + + # Scale to range 0-1279 + if amplitude > 0: # Avoid division by zero + stdevs = ((stdevs - np.min(stdevs)) / amplitude * 1279).astype(np.float32) + + return stdevs, amplitude + +def FindCalibrationDate(device_ids, ddate): + PCD = 50 #% (Peak Contained Data %) + PHB = 50 #% (Peak Height from Base %) + MPW = 10 #? (Max Peak Width) + MPSD =10 #? (Minimum Presence signal Standard Deviation) + #Find first day with, for all devices: + #- enough radar data points collected + #-Single histogram peak containing more than PCD% of data and peak width (at PHB% height) is < MPW + #Stdev of Data larger > MPSD + return ddate + +def FindThreshold(data, percent_list): + """ + Find the threshold value above which lies the specified percentage of points. + + Args: + data: numpy array of values + percent: percentage of points that should be above the threshold (0-100) + + Returns: + threshold value + """ + percent_from, percent_to = percent_list + # Sort data in descending order + sorted_data = np.sort(data)[::-1] + + # Calculate the index corresponding to the desired percentage + index_from = int((percent_from / 100) * len(data)) + index_to = int((percent_to / 100) * len(data)) + + # Return the threshold value + return sorted_data[index_from], sorted_data[index_to] + +def ShowThresholdGraph(data, filename, threshold_low, threshold_high, title, AveragePercentSpendsThere, location): + """ + Create and save a threshold analysis graph with maximum curvature point. + """ + dpi=600 + # Get min and max values + min_val = np.min(data) + max_val = np.max(data) + + # Create 1000 threshold levels from max to min + thresholds = np.linspace(max_val, min_val, 1000) + threshold_percentages = np.linspace(0, 100, 1000) + + # Calculate percentage of points above each threshold + points_above = [] + total_points = len(data) + + for thresh in thresholds: + above_count = np.sum(data > thresh) + percentage = (above_count / total_points) * 100 + points_above.append(percentage) + + points_above = np.array(points_above) + + # Calculate derivatives and smooth them + first_derivative = np.gradient(points_above) + second_derivative = np.gradient(first_derivative) + + #first_derivative = savgol_filter(np.gradient(points_above), window_length=51, polyorder=3) + #second_derivative = savgol_filter(np.gradient(first_derivative), window_length=51, polyorder=3) + + + # Find the point of maximum absolute second derivative + # Exclude edges (first and last 5% of points) to avoid edge effects + edge_margin = len(second_derivative) // 20 # 5% of points + valid_range = slice(edge_margin, -edge_margin) + max_curve_idx = edge_margin + np.argmax(np.abs(second_derivative[valid_range])) + + max_curve_x = threshold_percentages[max_curve_idx] + max_curve_y = points_above[max_curve_idx] + max_curve_second_deriv = second_derivative[max_curve_idx] + + # Calculate the actual threshold value for this point + threshold2 = max_val - (max_curve_x/100) * (max_val - min_val) + + # Create subplot figure + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[3, 2]) + + # Plot 1: Original curve with thresholds + ax1.plot(threshold_percentages, points_above, 'b-', linewidth=2, label='Distribution') + ax1.grid(True, linestyle='--', alpha=0.7) + + # Add original threshold line if provided + if threshold_low is not None: + threshold_percent = ((max_val - threshold_low) / (max_val - min_val)) * 100 + percent_above = (np.sum(data > threshold_low) / total_points) * 100 + + ax1.axvline(x=threshold_percent, color='r', linestyle='--', label=f'Minimum % spent in {location}: {AveragePercentSpendsThere[0]:.3f}') + ax1.axhline(y=percent_above, color='r', linestyle='--') + + #ax1.annotate(f'Threshold 1: {threshold_low:.3f}\nPoints above: {percent_above:.1f}%', + #xy=(threshold_percent, percent_above), + #xytext=(10, 10), textcoords='offset points', + #bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5), + #arrowprops=dict(arrowstyle='->')) + + if threshold_high is not None: + threshold_percent = ((max_val - threshold_high) / (max_val - min_val)) * 100 + percent_above = (np.sum(data > threshold_high) / total_points) * 100 + + ax1.axvline(x=threshold_percent, color='b', linestyle='--', label=f'Maximum % spent in {location}: {AveragePercentSpendsThere[1]:.3f}') + ax1.axhline(y=percent_above, color='b', linestyle='--') + + #ax1.annotate(f'Threshold 1: {threshold_high:.3f}\nPoints above: {percent_above:.1f}%', + #xy=(threshold_percent, percent_above), + #xytext=(10, 10), textcoords='offset points', + #bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5), + #arrowprops=dict(arrowstyle='->')) + + # Add maximum curvature point threshold + ax1.axvline(x=max_curve_x, color='g', linestyle='--', label=f'Threshold 2: {threshold2:.3f}') + ax1.axhline(y=max_curve_y, color='g', linestyle='--') + ax1.plot(max_curve_x, max_curve_y, 'go', markersize=10) + + ax1.annotate(f'Threshold 2: {threshold2:.3f}\nPoints above: {max_curve_y:.1f}%', + xy=(max_curve_x, max_curve_y), + xytext=(10, -20), textcoords='offset points', + bbox=dict(boxstyle='round,pad=0.5', fc='lightgreen', alpha=0.5), + arrowprops=dict(arrowstyle='->')) + + ax1.set_xlabel('Threshold Level (%)\n0% = Maximum, 100% = Minimum') + ax1.set_ylabel('Points Above Threshold (%)') + ax1.set_title(title) + ax1.set_xlim(0, 100) + ax1.set_ylim(0, 100) + ax1.legend() + + # Plot 2: Rate of change + ax2.plot(threshold_percentages, first_derivative, 'g-', label='First derivative', alpha=0.7) + ax2.plot(threshold_percentages, second_derivative, 'r-', label='Second derivative', alpha=0.7) + ax2.grid(True, linestyle='--', alpha=0.7) + + # Mark maximum curvature point on derivative plot + ax2.axvline(x=max_curve_x, color='g', linestyle='--') + # Plot point exactly on the second derivative curve + ax2.plot(max_curve_x, max_curve_second_deriv, 'go', markersize=10, + label=f'Max curvature at {max_curve_x:.1f}%') + + ax2.set_xlabel('Threshold Level (%)') + ax2.set_ylabel('Rate of Change') + ax2.set_title('Rate of Change Analysis') + ax2.legend() + + plt.tight_layout() + plt.savefig(filename, dpi=dpi, bbox_inches='tight') + plt.close() + + return threshold2, max_curve_x, max_curve_y + +def add_boundary_points(line_part_t, time_zone): + """ + Add boundary points (00:00:00 and 23:59:59) to a time series list. + + Args: + line_part_t: List of tuples (timestamp, value) + time_zone: String representing the timezone (e.g., "America/Los_Angeles") + + Returns: + List of tuples with added boundary points + """ + if not line_part_t: + return line_part_t + + tz = pytz.timezone(time_zone) + + # Get the date from the first point + first_dt = datetime.datetime.fromtimestamp(line_part_t[0][0], tz) + date = first_dt.date() + + last_dt = datetime.datetime.fromtimestamp(line_part_t[-1][0], tz) + last_date = last_dt.date() + + + # Create datetime objects for start and end of the day + start_dt = tz.localize(datetime.datetime.combine(date, datetime.datetime.min.time())) + end_dt = tz.localize(datetime.datetime.combine(last_date, datetime.datetime.max.time())) + + # Convert to timestamps + start_ts = start_dt.timestamp() + end_ts = end_dt.timestamp() + + result = list(line_part_t) + + # Handle start point (00:00:00) + first_point_dt = datetime.datetime.fromtimestamp(line_part_t[0][0], tz) + time_diff = first_point_dt - start_dt + + start_value = line_part_t[0][1] + + # Add start point at the beginning + #result.insert(0, (start_ts, start_value)) + + # Handle end point (23:59:59) + last_point_dt = datetime.datetime.fromtimestamp(line_part_t[-1][0], tz) + end_value = line_part_t[-1][1] + # Add end point + result.append((end_ts, end_value)) + + return result + +def calculate_life_and_average(my_data1, stdev_range=5): + # Convert data to numpy array for faster operations + data_array = np.array(my_data1) + + # Calculate half range + stdev_range_h = stdev_range // 2 + + # Pre-calculate indices for the sliding window + indices = np.arange(len(data_array) - 2 * stdev_range_h)[:, None] + np.arange(2 * stdev_range_h + 1) + + # Get sliding windows of data + windows = data_array[indices] + + # Calculate average (using column 3) + average = np.mean(windows[:, :, 3], axis=1) + + # Calculate life (using columns 2, 3, and 4) + deltas = windows[:, :, 3] - windows[:, :, 2] + windows[:, :, 4] + life = np.mean(deltas, axis=1) + + return life.tolist(), average.tolist() + +def TryJulia(prompt): + + if len(prompt) > 0: + + if prompt[0] == "#": + return prompt.upper() + + if prompt not in utterances: + return "" + else: + intent = utterances[prompt] + action = intents[intent] + + return action[0] + else: + return "" + +def AskGPT(in_prompt, language_from, language_to): + + if len(in_prompt) > 4: + + prompt = in_prompt.lower() + + if language_to.lower() not in language_from.lower(): + prompt = in_prompt + " Answer in " + language_to + + + print(prompt) + + #lets see if question is looking for OSM query + pattern = "what is only the node line for query for * on openstreetmap api? do not answer with url to nominatim, but with query!" + + + if match_with_wildcard(prompt, pattern): + differing_part = extract_differing_part(prompt, pattern) + + if differing_part != "": + + print(differing_part) + + if differing_part in searches_dict: + response = searches_dict[differing_part] + print(response) + return response, language_to + else: + #check if one of synonims: + if differing_part in searches_dict["synonims"]: + differing_part = searches_dict["synonims"][differing_part] + if differing_part != "": + if differing_part in searches_dict[differing_part]: + response = searches_dict[differing_part] + print(response) + return response, language_to + + hash_string = hashlib.sha256(str(prompt).encode('utf-8')).hexdigest() + #filename=os.path.join(cache_path, "chgpt_query_" + hash_string+".pkl") + + julia_present = False + if prompt.startswith("julia"): + prompt = prompt[len("julia") + 1:] + julia_present = True + + completion = "" + if julia_present == False: + completion = TryJulia(prompt) + #if completion == "": + # if os.path.exists(filename): + # #completion = pickle.load(open( filename, "rb" )) + # completion = (completion.choices[0].message.content.strip(), language_to)[0] + else: + completion = TryJulia(prompt) + + + if completion == "": + + st = time.time() + #import wandb + + #run = wandb.init(project='GPT-4 in Python') + #prediction_table = wandb.Table(columns=["prompt", "prompt tokens", "completion", "completion tokens", "model", "total tokens"]) + + print(time.time() - st) + + openai.api_key = OPENAI_API_KEY + client = OpenAI( + # This is the default and can be omitted + api_key = OPENAI_API_KEY + ) + + completion = client.chat.completions.create( + messages=[ + { + "role": "user", + "content": prompt, + } + ], + model="gpt-3.5-turbo", + ) + + + #with open(filename, 'wb') as handle: + #pickle.dump(completion, handle, protocol=pickle.HIGHEST_PROTOCOL) + + response = (completion.choices[0].message.content.strip(), language_to) + else: + response = (completion, language_to) + + else: + response = ("question is too short", language_to) + + + print(response) + return response + +def AskGPTPure(in_prompt): + + if len(in_prompt) > 4: + + prompt = in_prompt.lower() + + print(prompt) + + st = time.time() + + print(time.time() - st) + + openai.api_key = OPENAI_API_KEY + client = OpenAI( + # This is the default and can be omitted + api_key = OPENAI_API_KEY + ) + + completion = client.chat.completions.create( + messages=[ + { + "role": "user", + "content": prompt, + } + ], + model="gpt-3.5-turbo", + ) + + response = completion.choices[0].message.content.strip() + + + else: + response = "question is too short" + + + print(response) + return response + +def get_last_n_days(n=14, timezone_str='America/Los_Angeles'): + # Get current UTC time + utc_now = datetime.datetime.now(pytz.UTC) + + # Convert to the specified timezone + local_now = utc_now.astimezone(pytz.timezone(timezone_str)) + + # Get the current date in the specified timezone + current_date = local_now.date() + + # Determine the last whole day + if local_now.hour > 0 or local_now.minute > 0 or local_now.second > 0: + # Yesterday in the specified timezone + last_whole_day = current_date - timedelta(days=1) + else: + # If it's exactly midnight, the last whole day is two days ago + last_whole_day = current_date - timedelta(days=2) + + # Generate list of n days, ending with the last whole day + date_list = [] + for i in range(n-1, -1, -1): + day = last_whole_day - timedelta(days=i) + date_list.append(day.strftime('%Y-%m-%d')) + + return date_list + + +def numpy_to_json(arr, devices_list): + """ + Convert numpy array to JSON-serializable format + + Args: + arr (numpy.ndarray): 2D numpy array to serialize + + Returns: + str: JSON string containing array data and metadata + """ + if not isinstance(arr, np.ndarray): + raise TypeError("Input must be a numpy array") + + array_dict = { + 'dtype': str(arr.dtype), + 'shape': arr.shape, + 'devices_list': devices_list, + 'data': arr.tolist() # Convert to nested Python lists + } + + return json.dumps(array_dict) + +def format_time_difference(minutes): + # Calculate days, hours, minutes + days = int(minutes // (24 * 60)) + remaining_minutes = minutes % (24 * 60) + hours = int(remaining_minutes // 60) + mins = int(remaining_minutes % 60) + + parts = [] + + # Add days if any + if days > 0: + parts.append(f"{days} day{'s' if days != 1 else ''}") + + # Add hours if any + if hours > 0: + parts.append(f"{hours} hour{'s' if hours != 1 else ''}") + + # Add minutes if any + if mins > 0 or (days == 0 and hours == 0): + parts.append(f"{mins} minute{'s' if mins != 1 else ''}") + + # Combine the parts into a sentence + if len(parts) == 1: + return parts[0] + elif len(parts) == 2: + return f"{parts[0]} and {parts[1]}" + else: + return f"{parts[0]}, {parts[1]}, and {parts[2]}" + +def RunCommand(commmand, args_dictionary, deployment_id): + + to_return = "" + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + local_tz = pytz.timezone(time_zone_s) + + filter_minutes = 5 + dates = get_last_n_days(28, time_zone_s) + ddate = dates[0] #2025-02-02 req.params.get("date") + to_date = dates[-1] + date_s = datetime.datetime.now(pytz.UTC).astimezone(local_tz).date().strftime("%Y-%m-%d") + + if commmand == "#STATUS#": + force_recreate_orig = False #True + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{to_date}_{filter_minutes}_history_image.png" + filename = GeneratePresenceHistoryFiles(filename, force_recreate_orig, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + stretch_by = 30 + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + + day_counter = 0 + minutes_spent_there_list = [] + minutes_locations_list = [] + filename_4w = f"/{deployment_id}/{deployment_id}_{maps_dates[0]}_{maps_dates[-1]}_{filter_minutes}_{stretch_by}_4w_locations.png.bin" + for ddate in maps_dates: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + Id2Location = {} + for device in devices_list: + Id2Location[device[1]] = device[2] + Id2Location[0] = "Outside/?" + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png.bin" + locations_list_s = ReadObjectMinIO("daily-maps", filename_day) + locations_list = ast.literal_eval(locations_list_s) + minutes_locations_list.append((ddate, locations_list)) + #print(locations_list_s) + minutes_spent_there = {} + + + for loc in Id2Location: + minutes_spent_there[Id2Location[loc]] = 0 + minutes_spent_there[Id2Location[0]] = 0 + + for loc in locations_list: + #print(loc[0]) + #if loc[0] == 559: + # print("Stop") + if loc[0] in Id2Location: + print(Id2Location[loc[0]]) + minutes_spent_there[Id2Location[loc[0]]] += loc[2] + + for loc in minutes_spent_there: + minutes_spent_there[loc] = int(1000 * minutes_spent_there[loc] / 1440) / 10 + minutes_spent_there_list.append((ddate, minutes_spent_there)) + data_part = str(minutes_spent_there_list) + minutes_locations_list_str = str(minutes_locations_list) + obj_to_save = {"Location_indexes": str(Id2Location), "Locations": minutes_locations_list_str} + print(obj_to_save) + #SaveObjectInBlob(filename_4w, obj_to_save) + #print(data_part) + + #prompt = "Attached is 4 weeks of data representing % of time where person living alone is spending each day" + #prompt = prompt + " Assess his last week compared to previous 3 weeks. Comment only on significant changes." + #prompt = prompt + " Ignore days where data is all (or mostly) 0!" + #prompt = prompt + " Consider that office and living room are equivalent for this individual. Entertainment is consumed on computer (office) and in living room TV." + #prompt = prompt + " But he is also napping in living room. Comment on his sleeping pattern as well" + #prompt = prompt + " Can you summarize all in 1 sentence?" + #prompt = prompt + " " + data_part + #result = AskGPTPure(prompt) + + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + #current_time = datetime.datetime.now() + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + before_last_location = details["before_last_location"] + last_present_duration = details["last_present_duration"] + detected_time = datetime.datetime.fromisoformat(details["last_detected_time"]) + local_time = local_tz.localize(detected_time) + + result = f"There are no significant changes in his routines. He slept {details['sleep_hours']} hours last night and woke 1 time for bathroom. He is in the {last_location} for last {last_present_duration} minutes, and I can smell coffee in the kitchen" + to_return = result + #to_return = "Your father appears to be fine. He was walking around the house 10 minutes ago and is currently in the living room. And I can smell coffee" + + elif commmand == "#STATUS_F#": + to_return = "Your mother is doing well. She slept 8hr and 23min last night. She used the restroom twice last night. She is now in the kitchen. I can smell coffee." + elif commmand == "#HELP#": + to_return = "There is number of things you can ask me about. For example: 'how is my dad doing?' Or 'How is his environment' or any other question you like" + elif commmand == "#SLEEP#": + to_return = "Your dad slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#SLEEP_F#": + to_return = "Your mom slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#ENVIRONMENT#": + to_return = "The temperature in the house is 23 degrees Celsius, CO2 level is 662 ppm, and I can smell coffee brewing. Your dad slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#WEEK#": + to_return = "Showing his weekly activity" + elif commmand == "#WEEK_F#": + to_return = "Showing her weekly activity" + elif commmand == "#ACTIVITY#": + to_return = "Your dad has been less active this week than usual. He spent more time sitting in the living room and he got up later than usual by 38min. He also did not go outside as frequently and had less visitors. He only showered once this week." + elif commmand == "#ACTIVITY_F#": + to_return = "Your mom has been less active this week than usual. She spent more time sitting in the living room and she got up later than usual by 38min. She also did not go outside as frequently and had less visitors. She only showered once this week." + elif commmand == "#ACTIVITY_COMPARE#": + to_return = "Overall your dad is less active this year compared to last year. He slept longer in the mornings and had less visitors. Also his shower activity is reduced from typically 2 times a week to once a week." + elif commmand == "#ACTIVITY_COMPARE_F#": + to_return = "Overall your mom is less active this year compared to last year. She slept longer in the mornings and had less visitors. Also her shower activity is reduced from typically 2 times a week to once a week." + elif commmand == "#LOCATION#": + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + #current_time = datetime.datetime.now() + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + before_last_location = details["before_last_location"] + last_present_duration = details["last_present_duration"] + detected_time = datetime.datetime.fromisoformat(details["last_detected_time"]) + local_time = local_tz.localize(detected_time) + # Convert to UTC + detected_utc_time = local_time.astimezone(pytz.UTC) + + time_diff = current_time - detected_utc_time + minutes = time_diff.total_seconds() / 60 + #patch... needs investigating todo + if minutes > 1400: + minutes = 0 + time_sentence = format_time_difference(minutes) + if minutes < 2: + to_return = f"He is now in the {last_location} for {last_present_duration} minutes. Before that he was in {before_last_location}" + else: + to_return = f"He was last detected in the {last_location} {time_sentence} ago" + elif commmand == "#SHOWER#": + to_return = "In the last 7 days, your Dad took a shower on Friday, Sunday and Tuesday" + elif commmand == "#SHOWER_F#": + to_return = "The last time your mom took a shower was Yesterda at 9:33AM" + elif commmand == "#BATHROOM#": + to_return = "Last night your Dad used the restroom only once at 6.10am" + elif commmand == "#KITCHEN#": + to_return = "Your Dad only cooked Dinner on Wednesday and he turned off the stove afterwards" + elif commmand == "#MOLD#": + to_return = "I cannot smell any mold. Also, the humidity is very low. In any of the rooms never exceeded 27% RH in the last 7 days." + elif commmand == "#VISITORS#": + to_return = "Yes, on Tuesday, I could detect motion in both office and kitchen at the same time and CO2 levels in the living room exceeded 900ppm." + elif commmand == "#TEMPERATURE#": + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + temperature = int(details["temperature"]) + if "America" in time_zone_s: + temperature_sentence = f"{int(CelsiusToFahrenheit(temperature))} degrees Farenhight" + else: + temperature_sentence = f"{temperature} degrees Celsius." + to_return = f"The temperature in the {last_location} is {temperature_sentence}." + elif commmand == "#TEMPERATURE_B#": + to_return = "The temperature in the main bathroom is 80 degrees Farenhight." + elif commmand == "#OXYGEN#": + to_return = "His last oxygen level was at 95%." + elif commmand == "#OXYGEN_F#": + to_return = "Her last oxygen level was at 95%." + elif commmand == "#HEART_RATE#": + to_return = "His last heart rate was 74 bpm." + elif commmand == "#BLOOD_PRESSURE#": + to_return = "His latest blood pressure was measured 5 hours ago and it was 137 over 83." + elif commmand == "#BLOOD_PRESSURE_F#": + to_return = "Her latest blood pressure was measured 5 hours ago and it was 137 over 83." + elif commmand == "#EKG#": + to_return = "His latest HeartBeam EKG was done on Monday and it was within his baseline!" + elif commmand == "#EKG_F#": + to_return = "Her latest HeartBeam EKG was done on Monday and it was within her baseline!" + return to_return + +def ScaleToCommon(data, sensor): + + if sensor == "temperature": + new_min = 0 + new_max = 100 + elif sensor == "humidity": + new_min = 100 + new_max = 200 + elif sensor == "light": + new_min = 200 + new_max = 300 + elif sensor == "radar": + new_min = 300 + new_max = 400 + elif sensor == "s0": + new_min = 400 + new_max = 500 + elif sensor == "s1": + new_min = 500 + new_max = 600 + elif sensor == "s2": + new_min = 600 + new_max = 700 + elif sensor == "s3": + new_min = 700 + new_max = 800 + elif sensor == "s4": + new_min = 800 + new_max = 900 + elif sensor == "s5": + new_min = 900 + new_max = 1000 + elif sensor == "s6": + new_min = 1000 + new_max = 1100 + elif sensor == "s7": + new_min = 1100 + new_max = 1200 + elif sensor == "s8": + new_min = 1200 + new_max = 1300 + else: #s9 + new_min = 1300 + new_max = 1400 + + # Split timestamps and values into separate arrays + timestamps = np.array([x[0] for x in data]) + values = np.array([x[1] for x in data]) + + # Get current min and max + if len(values) > 0: + current_min = np.min(values) + current_max = np.max(values) + else: + current_min = 0; + current_max = 0; + + # Scale the values using the min-max formula + if current_max - current_min > 0: + scaled_values = (values - current_min) * (new_max - new_min) / (current_max - current_min) + new_min + else: + mid_val = (new_max + new_min) / 2 + scaled_values = np.full_like(values, mid_val) + + # Zip back together with original timestamps + return list(zip(timestamps, scaled_values)) + +def CreateLocationsStripe(locations_file, time_zone_s): + + parts = locations_file.split("/") + parts1 = parts[2].split("_") + ddate = parts1[1] + deployment_id = parts1[0] + filter_minutes = parts1[2] + bw = False + chart_type = 4 + force_recreate = True + motion = False + scale_global = False + fast = True + GenerateFullLocationMap(locations_file, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes) + + +def CelsiusToFahrenheit(C): + F = (C * 9/5) + 32 + return F + +def CelsiusToFahrenheitList(compressed_readings: List[Tuple[datetime.datetime, np.float64]]) -> List[Tuple[datetime.datetime, np.float64]]: + + # Create a new list with converted temperatures + converted_readings = [ + [reading[0], CelsiusToFahrenheit(reading[1])] + for reading in compressed_readings + ] + + return converted_readings + +def GetPriviledgesOnly(user_name): + with get_db_connection() as conn: + if isinstance(user_name, (int)) or user_name.isdigit(): + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_id = " + user_name + else: + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetPriviledgesAndUserId(user_name): + with get_db_connection() as conn: + sql = "SELECT access_to_deployments, user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0] + else: + return "[0,0]" + +def AddToLog(message): + """Add message to log""" + logger.info(message) + +def FillFields(blob_data, record, form_type): + """ + Fill in the input fields in the HTML blob_data with values from the caretaker dictionary. + + :param blob_data: str - The initial HTML string containing empty or placeholder input fields. + :param caretaker: dict - The dictionary containing values to populate the fields. + :return: str - The HTML string with the input fields filled with the appropriate values. + """ + # Ensure blob_data is a string + #blob_data = str(blob_data) + + # Populate the fields + for field in record: + logger.debug(f"field= {field}") + if field == "user_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_user_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "deployment_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_deployment_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "device_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_device_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "user_name": + if record[field] != None: + escaped_string = html.escape(record[field]) + pattern = rf'(]+id="new_user_name"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="new_user_name"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + elif field == "location": + if record[field] != None: + blob_data = SelectOption(blob_data, 'location', record[field]) + + elif field == "gender": + if record[field] != None: + blob_data = SelectOption(blob_data, 'gender', record[field]) + + elif field == "race": + if record[field] != None: + blob_data = SelectOption(blob_data, 'race', record[field]) + + elif field == "time_zone_s": + if record[field] != None: + blob_data = SelectOption(blob_data, 'time_zone_s', record[field]) + + elif field == "time_edit" or field == "user_edit": + pass + else: + if record[field] != None: + escaped_string = html.escape(str(record[field])) + pattern = rf'(]+id="{field}"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="{field}"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + return blob_data + +def convert_timestamps_lc(data, time_zone_s): + target_tz = pytz.timezone(time_zone_s) + return [[datetime.datetime.fromtimestamp(epoch, pytz.UTC).astimezone(target_tz), value] + for epoch, value in data] + + +subbedToL = [("/wellget",1),("/wellget_cmp",1),("/well_hub",1)] +def on_connectL(client_, userdata, flags, rc): + print(MQTTSERVERL + " L. Connected with result code "+str(rc)) + + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client_.subscribe(subbedToL) + print("SubscribedL to: "+str(subbedToL)) + +def on_messageL(client_, userdata, msg): #message from GUI + print(msg.topic+" "+str(msg.payload)) + #msga = msg.payload.decode("ascii") + #print(msg.timestamp) + #in_queue.append((str(time.time()), msg.topic, msg.payload)) + +def MQSendL(topic, content, qos=1): + print(topic, content[0:100]) + #return MQSend(topic, content) + #currentTime = int(time.time()) + try: + if "_cmp" in topic: + enc_msg = zlib.compress(content.encode('utf-8')) + else: + enc_msg = content + clientL.publish(topic, enc_msg, qos=qos, retain=False) + except Exception as err: + print ("Err2B:", err) + try: + clientL.disconnect() + #client.username_pw_set('telegraf', 'well18') + clientL.connect(MQTTSERVERL, MQTT_PortL, 60) + except Exception as e: + print ("Err3b:", e) + +def StoreFloorPlan(deployment_id, layout): + + conn = get_db_connection() + cur = conn.cursor() + print(layout) + data = json.loads(layout) + + # Extract the overlapping list + overlapping_list = str(data["overlapping"]) + + try: + sql = f""" + UPDATE public.deployment_details SET floor_plan = '{CleanObject(layout)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + sql1 = f""" + INSERT INTO public.deployment_details (deployment_id, "overlapps") + VALUES ({deployment_id}, '{CleanObject(overlapping_list)}') + ON CONFLICT (deployment_id) + DO UPDATE SET "overlapps" = '{CleanObject(overlapping_list)}'; + """ + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def GetFloorPlan(deployment_id): + + conn = get_db_connection() + + try: + sql = f""" + SELECT floor_plan FROM public.deployment_details WHERE deployment_id = {deployment_id}; + """ + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "" + + logger.debug(f"sql= {sql}") + conn.close() + return 1 + except Exception as err: + return 0 + +# CORS Middleware +class CORSMiddleware: + def process_request(self, req, resp): + resp.set_header('Access-Control-Allow-Origin', '*') + resp.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + resp.set_header('Access-Control-Allow-Headers', '*') + + def process_response(self, req, resp, resource, req_succeeded): + if req.method == 'OPTIONS': # Handle preflight requests + resp.status = falcon.HTTP_200 + +# Add this class to your code +class RequestParser: + def __init__(self): + # Detect if we're running in debug/development mode + self.debug_mode = __name__ == "__main__" or os.environ.get('DEBUG', 'false').lower() in ('true', '1', 'yes') + logger.debug(f"RequestParser initialized in {'DEBUG' if self.debug_mode else 'PRODUCTION'} mode") + + def process_request(self, req, resp): + """Pre-process the request to ensure media is parsed early""" + logger.debug(f"RequestParser processing: {req.method} {req.path}") + + # Initialize an empty form_data dict + req.context.form_data = {} + + # Only process POST requests with the right content type + if req.method != 'POST' or not req.content_type or 'form-urlencoded' not in req.content_type: + logger.debug("RequestParser: Skipping (not a form POST)") + return + + try: + # Different handling based on environment + if self.debug_mode: + self._process_debug(req) + else: + self._process_production(req) + + except Exception as e: + logger.error(f"RequestParser error: {str(e)}") + logger.error(traceback.format_exc()) + + def _process_debug(self, req): + """Process request in debug mode - optimized for local development""" + logger.debug("RequestParser: Using DEBUG mode processing") + + # In debug mode, we can use Content-Length and know it's reliable + content_length = req.get_header('content-length') + + if content_length: + # Content-Length is present + content_length = int(content_length) + logger.debug(f"RequestParser: Reading {content_length} bytes using Content-Length") + + raw_body = req.stream.read(content_length) + if raw_body: + body_text = raw_body.decode('utf-8') + logger.debug(f"RequestParser: Successfully read {len(body_text)} chars") + + # Parse the form data + import urllib.parse + form_data = dict(urllib.parse.parse_qsl(body_text)) + + # Store in context + req.context.form_data = form_data + logger.debug(f"RequestParser: Parsed form data: {form_data}") + + # Reset the stream with the original content + import io + req.stream = io.BytesIO(raw_body) + else: + logger.debug("RequestParser: No body data read") + else: + logger.debug("RequestParser (debug): No Content-Length header") + + def _process_production(self, req): + """Process request in production mode - optimized for OpenFaaS/faasd deployment""" + logger.debug("RequestParser: Using PRODUCTION mode processing") + + # Simple direct read approach for production (OpenFaaS/faasd) + # We'll limit the read to 1MB for safety + MAX_SIZE = 1024 * 1024 # 1MB + + # Just read directly from the stream without checking + raw_body = req.stream.read(MAX_SIZE) + if raw_body: + body_text = raw_body.decode('utf-8') + logger.debug(f"RequestParser: Successfully read {len(body_text)} chars") + + # Parse the form data + import urllib.parse + form_data = dict(urllib.parse.parse_qsl(body_text)) + + # Store in context + req.context.form_data = form_data + logger.debug(f"RequestParser: Parsed form data: {form_data}") + + # Reset the stream with the original content + import io + req.stream = io.BytesIO(raw_body) + else: + logger.debug("RequestParser: No body data read") + + +def FindDeviceByRole(deployment_id, location_list): + + #For purposes of activity report, Bedroom and Bathroom are determined in order of priority: + #Bedroom: "Bedroom Master", "Bedroom", "Bedroom Guest" (106, 56, 107) + #Bathroom: ""Bathroom Main","Bathroom","Bathroom Guest" (104, 103, 105) + + #location_names_inverted = {"All":-1 ,"?": 0,"Office": 5,"Hallway": 6,"Garage": 7,"Outside": 8,"Conference Room": 9,"Room": 10,"Kitchen": 34, + # "Bedroom": 56,"Living Room": 78,"Bathroom": 102,"Dining Room": 103,"Bathroom Main": ,104,"Bathroom Guest": 105, + # "Bedroom Master": 106, "Bedroom Guest": 107, "Conference Room": 108, "Basement": 109, "Attic": 110, "Other": 200} + + + ttime = datetime.datetime.utcnow().timestamp() + + devices_list, device_ids = GetProximityList(deployment_id, ttime) + + if location_list != []: + for location in location_list: + for device in devices_list: + well_id = device[0] + device_id = device[1] + location_t = device[2] + if location_t == location: + return (device_id, location, well_id) + + else: + conn = get_db_connection() + with conn.cursor() as cur: + + #we need to find beneficiaries from list of deployments + #sql = f'SELECT device_id FROM public.devices where device_id in {device_ids} and other="other"' + sql = "SELECT device_id, location, well_id FROM public.devices WHERE device_id = ANY(%s) AND other = %s" + #print(sql) + cur.execute(sql, (device_ids, "other")) + result = cur.fetchall()#cur.fetchone() + if len(result) > 0: + return result[0] + else: + + devices_list, device_ids = GetProximityList(deployment_id, ttime) + for device in devices_list: + well_id = device[0] + device_id = device[1] + location_t = device[2] + if "Bathroom" in location_t or "Bedroom" in location_t or "Kitchen" in location_t: + pass + else: + return (device_id, location_t, well_id) + + return (0, 0, 0) + + +def ensure_date_order(from_date, to_date): + """ + Ensures that from_date is earlier than to_date. + If not, swaps the dates. + + Args: + from_date: Date string in format 'YYYY-MM-DD' + to_date: Date string in format 'YYYY-MM-DD' + + Returns: + Tuple of (from_date, to_date) in correct order + """ + # Compare the date strings + # This works because the 'YYYY-MM-DD' format allows for string comparison + if from_date > to_date: + # Swap the dates + return to_date, from_date + else: + # Dates are already in correct order + return from_date, to_date + +def signum(x): + return (x > 0) - (x < 0) + + +def get_week_days_and_dates(days_back, timezone_str="America/Los_Angeles"): + """ + Generate weekdays and dates from 7 days ago until today for a given timezone. + + Args: + timezone_str (str): Timezone string like "America/Los_Angeles" + + Returns: + list: List of tuples containing (weekday_name, date_string) + """ + # Get the timezone object + tz = pytz.timezone(timezone_str) + + # Get current date in the specified timezone + today = datetime.datetime.now(tz).date() + + # Generate dates from days_back days ago to today + result = [] + for i in range(days_back-1, -1, -1): # days_back days ago to today (inclusive) + date = today - timedelta(days=i) + weekday_name = date.strftime("%A") # Full weekday name + date_string = date.strftime("%Y-%m-%d") # ISO format date + day_of_month = date.day + result.append((date_string, weekday_name, day_of_month)) + + return result + + +def filter_short_groups_numpy_orig(presence_list, filter_size, device_id, dates_str): + """ + Optimized version using NumPy to remove groups of consecutive zeros + or consecutive non-zeros (based on sign) shorter than filter_size. + Mimics the iterative, shortest-first logic of filter_short_groupss. + + Args: + presence_list: List of numbers (can include floats, ints, 0s). + filter_size: Minimum size of consecutive groups (by sign) to keep. + + Returns: + Filtered list with short groups removed. Output contains 0s and 1s. + (Note: Differs slightly from filter_short_groupss if negative numbers + were present, as this version converts them to 0, not 2). + """ + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"NumPy: Early exit/no processing time: {time.time() - st:.6f}s") + # Return a copy to avoid modifying the original list + return presence_list[:] if isinstance(presence_list, list) else list(presence_list) + + # Work with a NumPy array for efficiency, ensure float type for consistency + result = np.array(presence_list, dtype=float) + n = len(result) + + # Use a set to store hashable representations (tuples) of previous states for cycle detection + previous_states = set() + + while True: + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("NumPy: Cycle detected, breaking.") + break + previous_states.add(current_state_tuple) + + # 1. Calculate the sign of each element (-1, 0, 1) + signs = np.sign(result) + + # 2. Find indices where the sign changes + # np.diff calculates the difference between adjacent elements. + # A non-zero difference means the sign changed. + # np.where returns the indices *before* the change. Add 1 to get the start of the new run. + change_indices = np.where(np.diff(signs) != 0)[0] + 1 + + # 3. Define the boundaries of all consecutive runs (start and end indices) + # Include the start (0) and end (n) of the array. + boundaries = np.concatenate(([0], change_indices, [n])) + + # 4. Identify short runs + short_runs_to_process = [] + for i in range(len(boundaries) - 1): + start = boundaries[i] + end = boundaries[i+1] # Slicing is exclusive of the end index + length = end - start + + if length > 0: # Ensure the run is not empty + # Determine the characteristic sign of the run (use the first element) + run_sign = signs[start] + + if length < filter_size: + # --- Verification Step (Crucial) --- + # Check if the segment *still* consists of elements with the same sign. + # This handles cases where a previous modification might have altered + # part of what *was* a longer run. + current_segment_signs = np.sign(result[start:end]) + if np.all(current_segment_signs == run_sign): + # If the run is short and its sign consistency is verified, + # add it to the list of candidates for modification. + short_runs_to_process.append({ + 'start': start, + 'end': end, + 'sign': run_sign, + 'length': length + }) + # --- End Verification --- + + # 5. Check if any short runs were found + if not short_runs_to_process: + # No modifiable short runs found in this pass, the list is stable. + break + + # 6. Sort the short runs: shortest first, then by start index for determinism + # This ensures we process the same run as the original iterative function would. + short_runs_to_process.sort(key=lambda r: (r['length'], r['start'])) + + # 7. Process ONLY the *first* (shortest) identified run in this pass + run_to_process = short_runs_to_process[0] + start = run_to_process['start'] + end = run_to_process['end'] + run_sign = run_to_process['sign'] + + # Determine the replacement value based on the sign of the run being removed + # Short runs of 0 become 1 + # Short runs of non-zero (positive or negative) become 0 + replacement_value = 1.0 if run_sign == 0 else 0.0 + + # 8. Apply the replacement to the segment using NumPy slicing + result[start:end] = replacement_value + # Loop continues because a change was made + + print(f"filter_short_groups_numpy time: {time.time() - st:.6f}s") + if (time.time() - st) > 40: + print(presence_list) + # Convert back to a standard Python list for the return value + return result.tolist() + + + +def filter_short_groups_numpy(presence_list, filter_size, device_id, dates_str): + """ + Optimized version using NumPy to remove groups of consecutive zeros + or consecutive non-zeros (based on sign) shorter than filter_size. + Mimics the iterative, shortest-first logic. + + Optimization: + - Vectorized extraction of segment properties. + - Removed redundant sign verification within the segment analysis loop. + """ + # Start timer (optional, for benchmarking) + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"NumPy Optimized: Early exit/no processing time: {time.time() - st:.6f}s") + return presence_list[:] if isinstance(presence_list, list) else list(presence_list) + + result = np.array(presence_list, dtype=float) + n = len(result) + + previous_states = set() + + while True: + # Cycle detection + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("NumPy Optimized: Cycle detected, breaking.") + break + previous_states.add(current_state_tuple) + + # 1. Calculate the sign of each element (-1, 0, 1) + signs = np.sign(result) + + # 2. Find indices where the sign changes + change_indices = np.where(np.diff(signs) != 0)[0] + 1 + + # 3. Define the boundaries of all consecutive runs + boundaries = np.concatenate(([0], change_indices, [n])) + + # If there's only one segment (e.g., all zeros, all ones, or array is too short to have changes), + # or if the array was empty (n=0 leading to boundaries=[0,0]), no further processing is needed. + if len(boundaries) <= 2: # e.g., boundaries is [0, n] or [0,0] + break + + # 4. Vectorized extraction of run properties + run_starts = boundaries[:-1] + run_ends = boundaries[1:] + run_lengths = run_ends - run_starts + # The sign of the first element of a run (from the 'signs' array computed at the + # start of this 'while' iteration) is representative of the entire run's sign, + # by definition of how 'boundaries' were created. + run_signs = signs[run_starts] + + # 5. Identify short runs and collect their properties + short_runs_to_process = [] + for i in range(len(run_starts)): # Iterate over all identified runs + # Ensure run_length is positive (should be, due to boundary logic, but good check) + if run_lengths[i] > 0 and run_lengths[i] < filter_size: + short_runs_to_process.append({ + 'start': run_starts[i], + 'end': run_ends[i], + 'sign': run_signs[i], + 'length': run_lengths[i] + }) + + # 6. Check if any modifiable short runs were found + if not short_runs_to_process: + # No short runs found in this pass, the list is stable. + break + + # 7. Sort the short runs: shortest first, then by start index for determinism + short_runs_to_process.sort(key=lambda r: (r['length'], r['start'])) + + # 8. Process ONLY the *first* (shortest) identified run in this pass + run_to_process = short_runs_to_process[0] + start = run_to_process['start'] + end = run_to_process['end'] + run_sign = run_to_process['sign'] + + # Determine the replacement value + replacement_value = 1.0 if run_sign == 0 else 0.0 + + # 9. Apply the replacement + result[start:end] = replacement_value + # A change was made, so the 'while True' loop continues (unless a cycle is detected next) + + # End timer and print (optional) + # Your original print statements for timing: + print(f"filter_short_groups_numpy time: {time.time() - st:.6f}s") + # if (time.time() - st) > 40: + # print(presence_list) # This would print the original input on long runs + + return result.tolist() + +def filter_short_groups(presence_list, filter_size): + """ + Corrected version to perform the same task as filter_short_groupss, + including handling of non-zero/non-one values based on signum. + Iteratively removes the shortest group < filter_size by flipping its + signum representation (0->1, pos->0, neg->2). + + Args: + presence_list: List of numbers (0s, 1s, or any other number). + filter_size: Minimum size of groups (based on signum) to keep. + Returns: + Filtered list with short groups removed, potentially containing 0, 1, 2. + """ + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"filter_short_groups: Early exit/no processing time: {time.time() - st:.6f}s") + return presence_list.copy() + + result = presence_list.copy() + n = len(result) + + # Using a set for faster cycle detection lookups + previous_states = set() + + while True: + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("Cycle detected in filter_short_groups, breaking.") + break + previous_states.add(current_state_tuple) + + # --- Start of logic mimicking filter_short_groupss --- + changes_made_outer = False + + # 1. Find all segments based on signum + segments = [] + i = 0 + while i < n: + start = i + # Use signum to define the characteristic value of the run + current_signum = signum(result[i]) + + # Find the end of the group based on *consistent signum* + while i < n and signum(result[i]) == current_signum: + i += 1 + + group_length = i - start + # Store the signum value associated with the run + segments.append((start, i - 1, current_signum, group_length)) + + # 2. Sort segments by length (ascending) to process shortest first + segments.sort(key=lambda x: x[3]) + + # 3. Process the segments (find the first short one to modify) + for start, end, run_signum, length in segments: + if length < filter_size: + # Verify the segment hasn't been fundamentally altered (signum-wise) + # This check mirrors filter_short_groupss's intent, using signum consistently. + is_still_original_signum_segment = True + for k_idx in range(start, end + 1): + if signum(result[k_idx]) != run_signum: + is_still_original_signum_segment = False + break + + if is_still_original_signum_segment: + # Calculate replacement value based on signum (0->1, pos->0, neg->2) + replacement_value = 1 - run_signum + + # Apply replacement + segment_modified = False + for j in range(start, end + 1): + # Use direct comparison as replacement values are integers (0, 1, 2) + if result[j] != replacement_value: + result[j] = replacement_value + segment_modified = True + + if segment_modified: + changes_made_outer = True + # Break after making *one* change and restart the whole process + # (finding segments, sorting, finding shortest modifiable) + break # Break from the 'for segment in segments' loop + + # --- End of logic mimicking filter_short_groupss --- + + if not changes_made_outer: + # If we went through all segments and made no changes, we're done. + break + + print(f"filter_short_groups time: {time.time() - st:.6f}s") + return result + + +def filter_short_groupss(presence_list, filter_size): + """ + Iteratively remove groups of consecutive 0s or 1s that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of 0s and 1s + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + st = time.time() + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = signum(result[i]) + + # Find the end of the group + while i < n and signum(result[i]) == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + # Sort segments by length (ascending) to process shortest first + segments.sort(key=lambda x: x[3]) + + # Process the segments + for start, end, value, length in segments: + # If segment is too short, replace with opposite value + if length < filter_size: + # Verify the segment hasn't been modified by previous replacements + if all(result[j] == value for j in range(start, end+1)): + replacement = 1 - value # Toggle between 0 and 1 + for j in range(start, end+1): + result[j] = replacement + changes_made = True + #print(start, end) + break # Break after making a change and restart + + print("s", time.time()-st) + return result + +def filter_short_segments(segments, filter_size): + """ + Iteratively remove segments that are shorter than filter_size, + replacing them with data from the previous segment. + + Args: + segments: List of tuples (start_time, end_time, num_persons, duration) + filter_size: Minimum duration to keep a segment + + Returns: + Filtered list of segments covering the entire time range + """ + if not segments or filter_size <= 0: + return segments.copy() + + result = segments.copy() + changes_made = True + + while changes_made: + changes_made = False + + i = 1 # Start from the second segment + while i < len(result): + _, _, _, duration = result[i] + + if duration < filter_size: + # Get the previous segment's person count + if i > 0: + _, _, prev_persons, _ = result[i-1] + start, end, _, dur = result[i] + + # Replace with previous person count + result[i] = (start, end, prev_persons, dur) + changes_made = True + + # Check if we can merge with previous segment + if i > 0: + prev_start, prev_end, prev_persons, prev_dur = result[i-1] + curr_start, curr_end, curr_persons, curr_dur = result[i] + + if prev_persons == curr_persons and prev_end + 1 == curr_start: + # Merge segments + merged = (prev_start, curr_end, prev_persons, prev_dur + curr_dur) + result[i-1] = merged + result.pop(i) + i -= 1 # Adjust index after removing an element + changes_made = True + + i += 1 + + # Sort segments by start time to ensure proper order + result.sort(key=lambda x: x[0]) + + return result + +def filter_out_short_high_segments(segments, filter_size): + """ + Iteratively remove segments that are shorter than filter_size, + replacing them with data from the previous segment. + + Args: + segments: List of tuples (start_time, end_time, num_persons, duration) + filter_size: Minimum duration to keep a segment + + Returns: + Filtered list of segments covering the entire time range + """ + if not segments: + return segments.copy() + + result = segments.copy() + changes_made = True + + while changes_made: + changes_made = False + + i = 1 # Start from the second segment + while i < len(result): + _, _, _, duration = result[i] + + if duration < filter_size: + # Get the previous segment's person count + if i > 0: + _, _, prev_persons, _ = result[i-1] + start, end, _, dur = result[i] + + # Replace with previous person count + result[i] = (start, end, prev_persons, dur) + changes_made = True + + # Check if we can merge with previous segment + if i > 0: + prev_start, prev_end, prev_persons, prev_dur = result[i-1] + curr_start, curr_end, curr_persons, curr_dur = result[i] + + if prev_persons == curr_persons and prev_end + 1 == curr_start: + # Merge segments + merged = (prev_start, curr_end, prev_persons, prev_dur + curr_dur) + result[i-1] = merged + result.pop(i) + i -= 1 # Adjust index after removing an element + changes_made = True + + i += 1 + + # Sort segments by start time to ensure proper order + result.sort(key=lambda x: x[0]) + + return result + +def filter_out_short_same_groups_iterative(presence_list, filter_size): + """ + Iteratively remove groups of consecutive sames that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of values + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list: + return presence_list.copy() + + result = presence_list.copy() + + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + + result = filter_out_short_high_segments(segments, filter_size) + + return result + +def filter_out_short_highs_iterative(presence_list, filter_size): + """ + Iteratively remove groups of consecutive sames that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of values + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list: + return presence_list.copy() + + result = presence_list.copy() + + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + + result = filter_out_short_high_segments(segments, filter_size) + + return result + +def filter_short_groups_iterative_analog(presence_list, filter_size): + """ + Iteratively remove groups of consecutive similar values that are shorter than filter_size. + For non-zero values, replaces with 0. For zero values, needs context to determine replacement. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # Identify all segments of consecutive similar values + segments = [] + i = 0 + n = len(result) + + while i < n: + start = i + is_zero = (result[i] == 0) + + # Find the end of the group with same characteristic (zero or non-zero) + while i < n and ((result[i] == 0) == is_zero): + i += 1 + + group_length = i - start + segments.append((start, i-1, is_zero, group_length)) + + # Process segments from shortest to longest + segments.sort(key=lambda x: x[3]) + + for start, end, is_zero, length in segments: + if length < filter_size: + # For short non-zero groups, replace with zeros + if not is_zero: + for j in range(start, end+1): + result[j] = 0 + changes_made = True + break + else: + # For short zero groups, replace with average of surrounding non-zero values + # First, find surrounding values + left_value = 0 + right_value = 0 + + # Look for non-zero value on the left + for j in range(start-1, -1, -1): + if result[j] != 0: + left_value = result[j] + break + + # Look for non-zero value on the right + for j in range(end+1, n): + if result[j] != 0: + right_value = result[j] + break + + # Calculate replacement value + if left_value > 0 and right_value > 0: + replacement = (left_value + right_value) / 2 + elif left_value > 0: + replacement = left_value + elif right_value > 0: + replacement = right_value + else: + replacement = 0 # No surrounding non-zero values + + # Apply replacement + for j in range(start, end+1): + result[j] = replacement + + if replacement != 0: # Only mark as changed if we actually changed something + changes_made = True + break + return result + + +def filter_short_high_groups_iterative_analog(presence_list, filter_size): + st = time.time() + """ + More efficient implementation that still handles cascading effects. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + i = 0 + n = len(result) + + # Use a single pass to find all non-zero segments + segments = [] + while i < n: + # Skip zeros + if result[i] == 0: + i += 1 + continue + + # Found non-zero, find the end of this segment + start = i + while i < n and result[i] != 0: + i += 1 + + # Add segment to our list + segments.append((start, i)) + + # Process all short segments in one iteration + for start, end in segments: + length = end - start + if length < filter_size: + # Set all elements in this segment to zero + for j in range(start, end): + result[j] = 0 + changes_made = True + # Don't break - process all short segments in this pass + + # If we've made changes, we need to check again for newly formed short segments + print(f"filter_short_high_groups_iterative_analog time: {time.time() - st:.6f}s") + return result + +def filter_short_high_groups_iterative_analog_orig(presence_list, filter_size): + """ + Iteratively remove groups of consecutive similar values that are shorter than filter_size. + For non-zero values, replaces with 0. For zero values, needs context to determine replacement. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + st = time.time() + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # Identify all segments of consecutive similar values + segments = [] + i = 0 + n = len(result) + + while i < n: + start = i + is_zero = (result[i] == 0) + + # Find the end of the group with same characteristic (zero or non-zero) + while i < n and ((result[i] == 0) == is_zero): + i += 1 + + group_length = i - start + segments.append((start, i-1, is_zero, group_length)) + + # Process segments from shortest to longest + segments.sort(key=lambda x: x[3]) + + for start, end, is_zero, length in segments: + if length < filter_size: + # For short non-zero groups, replace with zeros + if not is_zero: + for j in range(start, end+1): + result[j] = 0 + changes_made = True + break + + print(f"filter_short_high_groups_iterative_analog time: {time.time() - st:.6f}s") + #if (time.time() - st) > 40: + # print(presence_list) + return result + + + +def filter_short_groupsWhat(presence_list, filter_size): + """ + Remove groups of consecutive 0s or 1s that are shorter than filter_size. + For short groups of 0s, replace with 1s. + For short groups of 1s, replace with 0s. + + Args: + presence_list: List of 0s and 1s + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + n = len(result) + + # Find groups and process them + i = 0 + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + + # If group is too short, replace with opposite value + if group_length < filter_size: + replacement = 1 - current_value # Toggle between 0 and 1 + for j in range(start, i): + result[j] = replacement + + return result + + +def GetOverlapps(deployment_id): + + with get_db_connection() as db_conn: + with db_conn.cursor() as cur: + sql = f"SELECT overlapps FROM public.deployment_details WHERE deployment_id = '{deployment_id}'" + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + return result[0] + + +def GetAmpitude(point_val, segment_lenght): + if point_val == 0: + return -segment_lenght + else: + return segment_lenght + +def CreateZGraph(well_id, presence_list): + """ + return size and position of consecutive groups of 0s and 1s + + Args: + presence_list: List of 0s and 1s + + Returns: + list of times and lengths + """ + if not presence_list: + return presence_list.copy() + + #if well_id == 290: + # print("Stop") + dekas_in_day = 6 * 1440 + result = [] + print(well_id) + #result will look like this: [(0,34),(34,-56),(92,6),...] where (A,B) + #A: is minute of section, B: height of section +=presence -=absence + #lets find point 0 first moving backward in time + segment_lenght = 0 + point_zero_val = signum(presence_list[dekas_in_day]) + for i in range(dekas_in_day-1, 0, -1): + if point_zero_val != signum(presence_list[i]): + segment_lenght = dekas_in_day -1 - i + break + x = 0 + y = GetAmpitude(point_zero_val, segment_lenght) + result.append((x, y)) + #x = x + segment_lenght + last_y = y + last_val = point_zero_val + last_source_minute = dekas_in_day + 1 + for i in range(last_source_minute, len(presence_list)): + if last_val != signum(presence_list[i]): + segment_lenght = i - dekas_in_day - x + x = x + segment_lenght + y = last_y + GetAmpitude(last_val, segment_lenght) + result.append((x, y)) + result.append((x, 0)) + last_y = 0 + last_val = signum(presence_list[i]) + + #last point i is NOT 1 + last above... it is last above so 2879! + segment_lenght = i - dekas_in_day - x + x = i - dekas_in_day #last point + y = GetAmpitude(last_val, segment_lenght) + result.append((x, y)) + + return result +def CreateZGraphAI(presence_list): + """ + Creates a step graph representation where: + - Presence periods: step up to +count, then drop to 0 + - Absence periods: step down to -count, then rise to 0 + - X-coordinate advances by the duration of each segment + + Pattern: [0,0] → [duration1, +/-count1] → [duration1, 0] → [duration1+duration2, +/-count2] → [duration1+duration2, 0] → ... + + Args: + presence_list: List of values (0 = absence, non-zero = presence) + + Returns: + List of [minute, height] coordinates for step graph + """ + if not presence_list: + return [] + + result = [[0, 0]] # Start at origin + + i = 0 + current_x = 0 + + while i < len(presence_list): + if presence_list[i] != 0: # Start of presence period + # Count consecutive non-zero values + count = 0 + while i < len(presence_list) and presence_list[i] != 0: + count += 1 + i += 1 + + # Move x forward by count, step up to +count + current_x += count + result.append([current_x, count]) + + # Add vertical line down to 0 + result.append([current_x, 0]) + + else: # Start of absence period + # Count consecutive zero values + count = 0 + while i < len(presence_list) and presence_list[i] == 0: + count += 1 + i += 1 + + # Move x forward by count, step down to -count + current_x += count + result.append([current_x, -count]) + + # Add vertical line up to 0 + result.append([current_x, 0]) + + return result + + +# Add this function to your code +def get_form_data(req): + """Helper function to get form data from either context or req.media""" + # First check if we pre-parsed the form data + if hasattr(req.context, 'form_data') and req.context.form_data: + logger.debug("Using pre-parsed form data from context") + return req.context.form_data + + # Otherwise try to get from req.media (for json) + try: + if req.content_type and ( + falcon.MEDIA_JSON in req.content_type or + falcon.MEDIA_URLENCODED in req.content_type + ): + logger.debug("Attempting to get form data from req.media") + return req.media or {} + except Exception as e: + logger.error(f"Error getting req.media: {str(e)}") + + logger.debug("No form data available, returning empty dict") + return {} + +def DetectMultiple(temporary_map_day_plus, overlaps_str_lst): + """ + Written by Robert Zmrzli + Detects time intervals of multiple vs single/no presence and outputs + the signed duration of each interval at its end time. + + Args: + temporary_map_day_plus: Map for each device radar reads that were detected to be above threshold + overlaps_lst: List of pairs of devices that have overlapping area + Returns: + A list of tuples representing the multiple presence timeline segments. + Each segment is represented by two tuples: + 1. (end_minute, signed_duration): signed_duration is the length of the + interval ending at end_minute (+ multiple, - single/none). + 2. (end_minute, 0): A marker for visualization. + """ + for location_id, data_list in temporary_map_day_plus.items(): + minutes_in_data = len(data_list) + break + events = [] + min_time = 0 + max_time = 0 + + + #['267:273', '273:291'] + seen_list = [0] * minutes_in_data + seen_where_list = [[] for _ in range(minutes_in_data)] + + for location_id, data_list in temporary_map_day_plus.items(): + for i in range(minutes_in_data): + if data_list[i] > 0: # Presence interval + seen_where_list[i].append(location_id) + + seen_where_list_uf = seen_where_list.copy() + overlap_pairs = set() + for overlap_str in overlaps_str_lst: + nums = [int(x) for x in overlap_str.split(':')] + # Add both orderings of the pair for easier checking + overlap_pairs.add((nums[0], nums[1])) + overlap_pairs.add((nums[1], nums[0])) + + # Process each sub-list in seen_where_list + for i in range(len(seen_where_list)): + locations = seen_where_list[i] + + # Skip empty lists and lists with only 0 or 1 item + if len(locations) <= 1: + continue + + has_non_overlapping_pair = False + + for j in range(len(locations)): + for k in range(j+1, len(locations)): + loc1, loc2 = locations[j], locations[k] + + # If this pair is not in our overlap_pairs, then they don't overlap + if (loc1, loc2) not in overlap_pairs: + has_non_overlapping_pair = True + break + + if has_non_overlapping_pair: + break + + # If all pairs overlap (no non-overlapping pairs found), clear the list + if not has_non_overlapping_pair: + seen_where_list[i] = [] + + + variations = [] + variation_index = {} + + for i in range(minutes_in_data): + if len(seen_where_list[i]) > 1: # Presence interval + if seen_where_list[i] not in variations: + variations.append(seen_where_list[i]) + variation_index[str(seen_where_list[i])] = len(variations) - 1 + + seen_list[i] = variation_index[str(seen_where_list[i])] + + + return seen_list, seen_where_list_uf + +def minutes_to_time(minutes): + """ + Convert minutes in a day (0-1439) to HH:MM format + + Args: + minutes (int): Minutes since midnight (0-1439) + + Returns: + str: Time in HH:MM format + """ + # Ensure the input is within valid range + #if not 0 <= minutes <= 1439: + # raise ValueError("Minutes must be between 0 and 1439") + + # Calculate hours and remaining minutes + minutes = minutes % 1440 + hours = minutes // 60 + mins = minutes % 60 + + # Format as HH:MM with leading zeros + return f"{hours:02d}:{mins:02d}" + +def decas_to_time(decas): + """ + Convert decas in a day (0-8639) to HH:MM format + + Args: + decas (int): decas since midnight (0-1439) + + Returns: + str: Time in HH:MM format + """ + # Ensure the input is within valid range + #if not 0 <= minutes <= 1439: + # raise ValueError("Minutes must be between 0 and 1439") + + # Calculate hours and remaining minutes + decas = decas % 8640 + hours = decas // (6 * 60) + mins = (decas // 6) % 60 + secs = 10 * (decas % 10) + + # Format as HH:MM with leading zeros + return f"{hours:02d}:{mins:02d}:{secs:02d}" + +def ClearOverlaps(temporary_map_day_plus, overlaps_str_lst): + """ + Detects reads that came from same person read by multiple devices that overlap, and removes weaker reads + Args: + temporary_map_day_plus: Map for each device radar reads that were detected to be above threshold + overlaps_lst: List of pairs of devices that have overlapping area + Returns: + An original temporary_map_day_plus with some reads removed + """ + ## Get the number of minutes + #for location_id, data_list in temporary_map_day_plus.items(): + #decas_in_data = len(data_list) + #break + + if temporary_map_day_plus: + decas_in_data = len(next(iter(temporary_map_day_plus.values()))) + else: + decas_in_data = 0 + + # Create seen_where_list with device-signal pairs + seen_where_list = [[] for _ in range(decas_in_data)] + for location_id, data_list in temporary_map_day_plus.items(): + for i in range(decas_in_data): + if data_list[i] > 0: # Presence interval + #if i == (8721): + # print("stop") + seen_where_list[i].append((location_id, data_list[i])) + + # Parse overlap pairs + overlap_pairs = set() + for overlap_str in overlaps_str_lst: + nums = [int(x) for x in overlap_str.split(':')] + overlap_pairs.add((nums[0], nums[1])) + overlap_pairs.add((nums[1], nums[0])) + + # Process each time slot + for i in range(len(seen_where_list)): + locations = seen_where_list[i] + + if len(locations) <= 1: + continue + + #if i == (5713 + 8640): + # print("stop") + #if i == (8721): + # print("stop") + # Create a new list to store the filtered results + filtered_list = [] + + # Make a copy of locations to process + to_process = locations.copy() + + # Process each device and decide whether to keep it + while to_process: + current = to_process.pop(0) + device_id, signal_strength = current + + should_keep = True + devices_to_remove = [] + + # Compare with all other devices (including those already in filtered_list) + for other in locations: + other_device_id, other_signal_strength = other + + # Skip if comparing with itself + if device_id == other_device_id: + continue + + # Check if these devices overlap + if (device_id, other_device_id) in overlap_pairs: + # They overlap, keep only the stronger signal + if signal_strength < other_signal_strength: + # Other device is stronger, don't keep current + should_keep = False + break + elif signal_strength == other_signal_strength and device_id > other_device_id: + # For equal signals, use device_id as tiebreaker + should_keep = False + break + + # If we should keep this device, add it to filtered list + if should_keep: + filtered_list.append(current) + + # Update the original list with filtered results + + #if i == (8721): + # print("stop") + seen_where_list[i] = filtered_list + + # Create a new temporary_map_day_plus with the filtered data + result = {} + for location_id, data_list in temporary_map_day_plus.items(): + result[location_id] = [0] * decas_in_data + + # Fill in the filtered data + for i in range(decas_in_data): + #if len(seen_where_list[i]) > 1: + #if i == (8721): + # print("stop") + #print(i, decas_to_time(i), seen_where_list[i]) + + for device_id, signal_strength in seen_where_list[i]: + result[device_id][i] = signal_strength + + return result + +# Path handling middleware +class StripPathMiddleware: + def process_request(self, req, resp): + # Strip the '/function/well-api' prefix if present + path = req.path + logger.info(f"Original request path: {path}") + + # Define patterns to match different URL formats + patterns = [ + r'^/function/well-api', # Standard OpenFaaS path + r'^/api/well_api', # API path + ] + + for pattern in patterns: + if re.match(pattern, path): + # Strip the matched prefix + path = re.sub(pattern, '', path) + # Ensure path starts with a slash + if not path.startswith('/'): + path = '/' + path + # Update the request path + req.path = path + logger.info(f"Modified request path: {path}") + break + + +def optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type): + last_device_id = None + + # Pre-compute seconds per minute + seconds_per_deka = 10 + + # Check if we need to process all data or just specific types + #process_all = data_type in ("all", "z-graph", "multiple") + process_all = True + + for radar_read in myz_data: + local_time = radar_read[0] + device_id = radar_read[1] + + # Calculate deca once + deca = int((local_time - start_time).total_seconds() / seconds_per_deka) + + # Use cached lookups when possible + if device_id != last_device_id: + last_device_id = device_id + + # Check if we've cached this device info + if device_id not in device_lookup_cache: + well_id = id2well_id[device_id] + radar_threshold_group_st = device_id_2_threshold[device_id]#well_id] + threshold_sig, threshold = radar_threshold_group_st + threshold_sig = threshold_sig.split("_")[0] + + # Cache the values + device_lookup_cache[device_id] = { + 'well_id': well_id, + 'threshold_sig': threshold_sig, + 'threshold': threshold + } + else: + # Use cached values + cached = device_lookup_cache[device_id] + well_id = cached['well_id'] + threshold_sig = cached['threshold_sig'] + threshold = cached['threshold'] + + days_decas = len(temporary_map_day_plus[well_id]) + else: + # Use already loaded values from last iteration + cached = device_lookup_cache[device_id] + well_id = cached['well_id'] + threshold_sig = cached['threshold_sig'] + threshold = cached['threshold'] + days_decas = len(temporary_map_day_plus[well_id]) + + # Get radar value using cached index + radar_val = radar_read[2 + device_field_indexes[threshold_sig]] + + # Process data if needed + if process_all and radar_val > threshold and deca < days_decas: + temporary_map_day_plus[well_id][deca] = radar_val + #if well_id == 269: + # print(local_time) + + return temporary_map_day_plus + +def optimized_radar_processing(my_data, start_time, id2well_id, device_id_2_threshold, + device_field_indexes, presence_map, data_type): + last_device_id = 0 + + # Cache for threshold_sig calculation which is expensive due to dictionary lookups and string splitting + threshold_sig_cache = {} + field_index_cache = {} + + for radar_read in my_data: + local_time = radar_read[0] + device_id = radar_read[1] + + # Calculate deca once + deca = int((local_time - start_time).total_seconds() / 10) + + # Device changed - update values that depend on device + if device_id != last_device_id: + last_device_id = device_id + well_id = id2well_id[device_id] + + # Calculate days_decas exactly like original + #if data_type == "raw" or data_type == "all": + days_decas = len(presence_map['raw'][well_id]) + #else: + # days_decas = len(presence_map['presence'][well_id]) + + # Calculate threshold_sig with caching + if device_id not in threshold_sig_cache: + radar_threshold_group_st = device_id_2_threshold[device_id] + threshold_sig, threshold = radar_threshold_group_st + threshold_sig = threshold_sig.split("_")[0] + threshold_sig_cache[device_id] = (threshold_sig, threshold) + else: + threshold_sig, threshold = threshold_sig_cache[device_id] + + # Calculate field index with caching + if threshold_sig not in field_index_cache: + field_index = 2 + device_field_indexes[threshold_sig] + field_index_cache[threshold_sig] = field_index + else: + field_index = field_index_cache[threshold_sig] + else: + # Use values from previous iteration for same device + #well_id = id2well_id[device_id] + + # Calculate days_decas exactly like original + #if data_type == "raw" or data_type == "all": + #days_decas = len(presence_map['raw'][well_id]) + #else: + # days_decas = len(presence_map['presence'][well_id]) + + # Use cached values + threshold_sig, threshold = threshold_sig_cache[device_id] + field_index = field_index_cache[threshold_sig] + + # Get radar value using cached field index + if field_index >= len(radar_read): + radar_val = radar_read[-1] + else: + radar_val = radar_read[field_index] + + + if well_id == 475: + print(".") + # Process presence data + if radar_val > threshold: + if deca < days_decas: + presence_map['longpresence'][well_id][deca] = 1 + + # Process raw data if needed + if data_type == "raw" or data_type == "all": + if deca < days_decas: + presence_map['raw'][well_id][deca] = radar_val + + return presence_map + +def CompressList(presence_devices_map): + + for key in presence_devices_map: + presence_map_list = presence_devices_map[key] + presence_map_list_compressed = Compress(presence_map_list) + presence_devices_map[key] = presence_map_list_compressed + + return presence_devices_map + +def Compress(presence_map_list): + + presence_map_list_compressed = [] + l = len(presence_map_list) + if l > 1: + last_data_point = presence_map_list[0] + presence_map_list_compressed.append([0, last_data_point]) + for i in range(1, l): + data_point = presence_map_list[i] + if data_point != last_data_point: + presence_map_list_compressed.append([i - 1, last_data_point]) + presence_map_list_compressed.append([i, data_point]) + last_data_point = data_point + presence_map_list_compressed.append([i, data_point]) + return presence_map_list_compressed + +def Decompress(pers_in_deka): + + last = pers_in_deka[-1] + last_index = 1 + last[1] + result = [0] * last_index + for points in pers_in_deka: + start_deca = points[0] + end_deca = points[1] + value_deca = points[2] + for i in range(start_deca, 1+end_deca): + result[i] = value_deca + return result + +def store_to_file(my_list, filename): + + try: + with open(filename, 'w') as f: + json.dump(my_list, f, indent=4) # indent for pretty printing + print(f"List saved to {filename} using JSON") + except IOError: + print(f"Error: Could not write to file {filename}") + except TypeError as e: + print(f"Error: Could not serialize list to JSON. {e}") # e.g. if list contains unsupported types like sets + +def find_custom_header(headers, name): + """Helper to find a custom header value (case-insensitive name).""" + if not headers: return None + for header in headers: + if header.get('name', '').lower() == name.lower(): return header.get('value') + return None + +def encode_state(parts): + """Joins parts with a pipe and base64 encodes the result.""" + plain_state = "|".join(map(str, parts)) + base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii') + # Assuming 'logger' is your app's logger instance + logger.debug(f"Encoded state: '{plain_state}' -> '{base64_state}'") + return base64_state + +def decode_state(b64_state): + """Decodes a base64 state and splits it by pipe.""" + if not b64_state: return [] + try: + decoded_plain = base64.b64decode(b64_state).decode('utf-8') + parts = decoded_plain.split('|') + logger.debug(f"Decoded state: '{b64_state}' -> '{decoded_plain}' -> {parts}") + return parts + except Exception as e: + logger.error(f"Failed to decode client_state '{b64_state}': {e}") + return [] + +def create_client_state(base_event, call_control_id, prefix): + """Create a base64 encoded client state string as required by Telnyx API""" + # Create the plain text client state string + plain_state = f"{prefix}_{base_event}_{call_control_id[:8]}" if call_control_id else f"{prefix}_{base_event}_unknownccid" + + # Encode to base64 as required by Telnyx API + base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii') + + logger.debug(f"Client state created: '{plain_state}' -> base64: '{base64_state}'") + return base64_state + +def send_telnyx_command(action_path, params, api_key): + """ + Sends a command to the Telnyx Call Control API actions endpoint. + This function should REPLACE your existing send_telnyx_command. + """ + if not api_key: + logger.error(f"CMDFAIL ('{action_path}'): API_KEY not available.") + return None + + ccid = params.get("call_control_id") + if not ccid: + logger.error(f"CMDFAIL ('{action_path}'): call_control_id missing in params.") + return None + + # Correct endpoint construction for V2 actions + endpoint = f"{TELNYX_API_BASE_URL}/calls/{ccid}/{action_path}" + + # Body should not contain call_control_id for actions API + body = {k: v for k, v in params.items() if k != 'call_control_id'} + + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + "Accept": "application/json" + } + + logger.info(f"SENDCMD ('{action_path}')") + logger.debug(f" Endpoint: POST {endpoint}") + logger.debug(f" JSON Payload: {json.dumps(body, indent=2)}") + + try: + response = requests.post(endpoint, json=body, headers=headers, timeout=10) + response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) + logger.info(f"CMDOK ('{action_path}'): Telnyx accepted. Status: {response.status_code}") + return response.json() + except requests.exceptions.HTTPError as e: + logger.error(f"CMDFAIL ('{action_path}'): Telnyx rejected. Status: {e.response.status_code}") + try: + logger.error(f" Telnyx Err Detail: {json.dumps(e.response.json(), indent=2)}") + except json.JSONDecodeError: + logger.error(f" Raw Err Body: {e.response.text[:500]}") + except requests.exceptions.RequestException as e: + logger.exception(f"CMDFAIL ('{action_path}'): Network error") + + return None + +def ParseAddress(address_string): + try: + payload = {"address": address_string} + + response = requests.post( + f"{base_url}/parse_address", + data=json.dumps(payload), + headers={"Content-Type": "application/json"} + ) + return response.json() + except Exception as e: + print(f"Error: {e}") + return {} + +def JoinAddress(address_map): + try: + payload = address_map + + response = requests.post( + f"{base_url}/join_address", + data=json.dumps(payload), + headers={"Content-Type": "application/json"} + ) + return response.json() + except Exception as e: + print(f"Error: {e}") + return {} + + +def StoreToDB(data): + + try: + + event_type = data.get('event_type') + sql = "" + + payload_json = json.dumps(data["payload"]) + call_session_id = data["payload"]['call_session_id'] + if event_type == "call.initiated": + timee = data.get('occurred_at') + sql = f""" + INSERT INTO public.alarms_voice ( + index, + "time", + call_session_id, + initiated + ) + VALUES ( + (SELECT COALESCE(MAX(index), 0) + 1 FROM public.alarms_voice), -- Auto-increment index + '{timee}'::timestamptz, -- occurred_at value + '{call_session_id}', -- call_session_id value + '{payload_json}' + ); """ + + elif event_type == "call.answered": + sql = f""" + UPDATE public.alarms_voice + SET answered = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.playback.started": + sql = f""" + UPDATE public.alarms_voice + SET playback_started = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.playback.ended": + sql = f""" + UPDATE public.alarms_voice + SET playback_ended = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.hangup": + sql = f""" + UPDATE public.alarms_voice + SET hangup = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + + if sql != "": + with get_db_connection() as conn: + with conn.cursor() as cur: + print(sql) + cur.execute(sql) + except Exception as e: + print ("Error in StoreToDB:", e) + +def handle_telnyx_webhook(webhook_data, remote_addr, request_id): + """ + Process Telnyx webhook events with DTMF controls for repeating and hanging up. + This version works with both well-alerts.py and tstMP3Call.sh. + """ + logger.info(f"Processing Telnyx webhook from {remote_addr}, Request-ID: {request_id}") + + try: + data = webhook_data.get('data', {}) + event_type = data.get('event_type') + record_type = data.get('record_type') + payload = data.get('payload', {}) + + logger.info(f"Event: {event_type}, Record Type: {record_type}") + + if not event_type or not record_type: + logger.error("Missing event_type or record_type in webhook data") + return False + + if record_type == 'message': + logger.info("Processing SMS event...") + # Existing SMS handling code can go here. + return True + + if record_type != 'event': + logger.warning(f"Unknown record type: {record_type}") + return False + + # --- Voice Event Handling --- + call_control_id = payload.get('call_control_id') + b64_client_state = payload.get("client_state") + state_parts = decode_state(b64_client_state) + state_name = state_parts[0] if state_parts else None + + logger.info(f"Processing voice event: {event_type}, State: {state_name}") + StoreToDB(data) # Store all events as before + + # --- State Machine Logic --- + + if event_type == 'call.initiated': + logger.info(f"Call initiated: From: {payload.get('from')}, To: {payload.get('to')}") + + elif event_type == 'call.answered': + logger.info(f"Call answered: From: {payload.get('from')}, To: {payload.get('to')}") + + # Determine media to play + custom_headers = payload.get('custom_headers', []) + audio_url = find_custom_header(custom_headers, 'X-Audio-Url') + tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') + + media_type = "audio" if audio_url else "tts" if tts_payload else "none" + media_value = audio_url or tts_payload + + if media_value: + logger.info(f"Playing main message via {media_type}.") + # Create the initial state and encode it + next_state = encode_state(['MAIN_MEDIA_PLAYED', media_type, media_value]) + + if media_type == "audio": + play_params = { + "call_control_id": call_control_id, + "client_state": next_state, + "audio_url": media_value + } + send_telnyx_command("actions/playback_start", play_params, TELNYX_API_KEY) + else: # tts + speak_params = { + "payload": media_value, + "voice": DEFAULT_TTS_VOICE, + "language": DEFAULT_TTS_LANGUAGE, + "call_control_id": call_control_id, + "client_state": next_state + } + send_telnyx_command("actions/speak", speak_params, TELNYX_API_KEY) + else: + logger.warning("No audio URL or TTS payload found. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": call_control_id}, TELNYX_API_KEY) + + elif event_type in ['call.speak.ended', 'call.playback.ended']: + logger.info(f"Media ended with status: {payload.get('status')}") + + if state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: + logger.info("Main message finished. Playing options menu.") + _, media_type, media_value = state_parts # Unpack state + + # Create new state for waiting for DTMF input + next_state = encode_state(['WAITING_DTMF', media_type, media_value]) + options_prompt = "press 0 to repeat the message, or press the pound key to hang up." + + gather_params = { + "payload": options_prompt, + "voice": DEFAULT_TTS_VOICE, + "language": DEFAULT_TTS_LANGUAGE, + "valid_digits": "0#", + "max_digits": 1, + "timeout_millis": 10000, # 10 seconds + "call_control_id": call_control_id, + "client_state": next_state + } + send_telnyx_command("actions/gather_using_speak", gather_params, TELNYX_API_KEY) + else: + logger.warning(f"Media ended with unhandled state '{state_name}'. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": call_control_id}, TELNYX_API_KEY) + + elif event_type == 'call.dtmf.received': + digit = payload.get('digit') + logger.info(f"DTMF Received: Digit='{digit}'") + if digit == '#': + logger.info("'#' received. Terminating call immediately.") + send_telnyx_command("actions/hangup", {"call_control_id": call_control_id}, TELNYX_API_KEY) + + elif event_type == 'call.gather.ended': + logger.info(f"Gather ended. Digits: '{payload.get('digits')}', Status: '{payload.get('status')}'") + + if state_name == 'WAITING_DTMF': + digits = payload.get('digits') + _, media_type, media_value = state_parts # Unpack state + + if digits == "0": + logger.info("'0' pressed. Replaying main message.") + next_state = encode_state(['REPLAYING_MEDIA', media_type, media_value]) + if media_type == "audio": + send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": call_control_id, "client_state": next_state}, TELNYX_API_KEY) + else: # tts + send_telnyx_command("actions/speak", {"payload": media_value, "voice": DEFAULT_TTS_VOICE, "language": DEFAULT_TTS_LANGUAGE, "call_control_id": call_control_id, "client_state": next_state}, TELNYX_API_KEY) + else: + logger.info("Gather ended without a repeat command (timeout or hangup). Ending call.") + send_telnyx_command("actions/hangup", {"call_control_id": call_control_id}, TELNYX_API_KEY) + + elif event_type == 'call.hangup': + logger.info(f"Call hung up: Cause='{payload.get('cause')}'") + + else: + logger.info(f"Unhandled voice event: {event_type}") + + return True # Acknowledge the webhook + + except Exception as e: + logger.exception(f"Critical error in handle_telnyx_webhook4: {e}") + return False + +# Assume these are defined globally or accessible (e.g., from app_args or .env) +# logger = logging.getLogger(...) +# ENABLE_AUDIO_PLAYBACK = True / False +# CLIENT_STATE_PREFIX = "app_state" +# DEFAULT_TTS_VOICE = "female" +# DEFAULT_TTS_LANGUAGE = "en-US" +# TELNYX_API_KEY = "YOUR_API_KEY" +# DTMF_GATHER_TIMEOUT_SECONDS = 15 # Wait 15 seconds for DTMF input + +# Placeholder for your DB function +# def StoreToDB(data): +# app_logger.debug(f"Placeholder: Storing to DB: {json.dumps(data)[:100]}") # Use app_logger + +# (Your existing find_custom_header, create_client_state, send_telnyx_command should be here) +# Make sure send_telnyx_command uses app_logger + +def handle_telnyx_webhook2(webhook_data, remote_addr, request_id): # Renamed logger to app_logger + """Process Telnyx webhook events with IVR logic.""" + logger.info(f"Processing Telnyx webhook from {remote_addr}, Request-ID: {request_id}") + + try: + data = webhook_data.get('data', {}) + event_type = data.get('event_type') + record_type = data.get('record_type') + payload = data.get('payload', {}) + + logger.info(f"Event: {event_type}, Record Type: {record_type}") + + if not event_type or not record_type: + logger.error("Missing event_type or record_type in webhook data") + return False # Indicate failure to process + + call_control_id = payload.get('call_control_id') + call_session_id = payload.get('call_session_id') + # Attempt to decode client_state if present + b64_client_state_rcvd = data.get("payload",{}).get("client_state") + plain_client_state_rcvd = "" + if b64_client_state_rcvd: + try: + plain_client_state_rcvd = base64.b64decode(b64_client_state_rcvd).decode('utf-8') + logger.info(f" Decoded Client State Received: '{plain_client_state_rcvd}'") + except Exception as e: + logger.warning(f" Could not decode client_state: {b64_client_state_rcvd}, Error: {e}") + plain_client_state_rcvd = "undecodable_state" + + + # Store all events to DB if needed + StoreToDB(webhook_data) # Pass the full webhook_data + + # Voice Event Handling + if record_type == 'event': + logger.info(f"Processing voice event: {event_type}, CCID: {call_control_id}") + + # --- Initial Call Setup --- + if event_type == 'call.initiated': + logger.info(f" Call initiated: From: {payload.get('from')}, To: {payload.get('to')}") + # No action needed here, wait for call.answered + + elif event_type == 'call.answered': + logger.info(f" Call answered: From: {payload.get('from')}, To: {payload.get('to')}") + custom_headers = payload.get('custom_headers', []) + logger.debug(f" Custom headers: {json.dumps(custom_headers)}") + + audio_url = find_custom_header(custom_headers, 'X-Audio-Url') + tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') + logger.info(f" X-Audio-Url: {audio_url}, X-TTS-Payload: {tts_payload}") + + # This state means the main message is about to be played. + # After it ends, we'll play the options prompt. + next_client_state = create_client_state("main_media_played", call_control_id, app_args.client_state_prefix) # Use app_args + + action_taken = False + if app_args.enable_audio_playback and audio_url: # Use app_args + logger.info(f" -> Playing main audio: {audio_url}") + play_params = {"call_control_id": call_control_id, "client_state": next_client_state, "audio_url": audio_url} + send_telnyx_command("actions/playback_start", play_params, app_args.api_key) # Use app_args + action_taken = True + elif tts_payload: + logger.info(f" -> Speaking main TTS: {tts_payload}") + speak_params = {"payload": tts_payload, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": call_control_id, "client_state": next_client_state} # Use app_args + send_telnyx_command("actions/speak", speak_params, app_args.api_key) # Use app_args + action_taken = True + + if not action_taken: + logger.warning(" -> No audio URL or TTS payload for main message. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("no_main_media_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + # --- Handling End of Main Media Playback --- + elif event_type in ['call.speak.ended', 'call.playback.ended']: + status = payload.get('status') + ended_event_type_root = event_type.split('.')[1] # speak or playback + + logger.info(f" Call {ended_event_type_root} ended: Status={status}, Current Decoded State='{plain_client_state_rcvd}'") + + # Check if the main media just finished playing + if plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_main_media_played"): + logger.info(" -> Main media finished. Playing DTMF options prompt.") + options_prompt_tts = "press 0 to repeat the message or press pound to hang up." + # This state means the options prompt is playing, and we're waiting for DTMF. + # gather_using_speak will trigger call.gather.ended + next_client_state = create_client_state("waiting_dtmf", call_control_id, app_args.client_state_prefix) + + gather_params = { + "call_control_id": call_control_id, + "client_state": next_client_state, + "payload": options_prompt_tts, + "voice": app_args.default_tts_voice, + "language": app_args.default_tts_language, + "valid_digits": "0#", # Only accept 0 or # + "max_digits": 1, # Expect only one digit + "timeout_millis": app_args.dtmf_timeout_seconds * 1000, # N seconds timeout + "terminating_digits": "#" # # will also terminate gather immediately + } + send_telnyx_command("actions/gather_using_speak", gather_params, app_args.api_key) + + elif plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_replaying_main_media"): + logger.info(" -> Replayed main media finished. Playing DTMF options prompt again.") + # Same logic as above for playing options prompt + options_prompt_tts = "press 0 to repeat the message or press pound to hang up." + next_client_state = create_client_state("waiting_dtmf", call_control_id, app_args.client_state_prefix) + gather_params = { + "call_control_id": call_control_id, "client_state": next_client_state, + "payload": options_prompt_tts, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, + "valid_digits": "0#", "max_digits": 1, "timeout_millis": app_args.dtmf_timeout_seconds * 1000, "terminating_digits": "#" + } + send_telnyx_command("actions/gather_using_speak", gather_params, app_args.api_key) + + else: + logger.warning(f" -> {ended_event_type_root} ended, but client_state ('{plain_client_state_rcvd}') doesn't match expected flow for options. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state(f"{ended_event_type_root}_unexpected_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + # --- Handling DTMF Input Result --- + elif event_type == 'call.gather.ended': + digits_received = payload.get('digits') + gather_status = payload.get('status') # e.g., 'completed_by_terminating_digit', 'timeout', 'call_hangup' + logger.info(f" Call Gather Ended: Digits='{digits_received}', Status='{gather_status}', Current Decoded State='{plain_client_state_rcvd}'") + + if plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_waiting_dtmf"): + if digits_received == "0": + logger.info(" -> DTMF '0' received. Replaying main message.") + # Replay the original message. We need to fetch it again from custom headers. + # This assumes the call.gather.ended payload still contains the original custom_headers. + # If not, we might need to store the original TTS/Audio URL in the client_state. + # For simplicity, let's assume custom_headers are still available or we re-evaluate. + # A more robust way would be to store the main message type/content in the client_state + # when transitioning from main_media_played. + + # Let's try to get custom_headers from the current payload. + # Telnyx usually includes original call details in subsequent events. + custom_headers = payload.get('custom_headers', []) # This might not be reliable for original headers + + # A BETTER APPROACH: Store original media info in client_state or retrieve from DB + # For this example, we'll try to re-evaluate based on what might be in custom_headers + # of the *call.gather.ended* event, which is NOT guaranteed to be the original ones. + # This part needs careful testing or a strategy to pass original media info. + + # Simplified: Assume we need to re-fetch original custom headers if they are not in this payload. + # For now, let's just log and assume we'd need a mechanism to get original X-TTS-Payload/X-Audio-Url + logger.warning(" -> Replay logic needs access to original X-TTS-Payload/X-Audio-Url. This example will try to use current custom_headers if any, or a default.") + + original_audio_url = find_custom_header(custom_headers, 'X-Audio-Url') # May not be original + original_tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') # May not be original + + next_client_state = create_client_state("replaying_main_media", call_control_id, app_args.client_state_prefix) + action_taken = False + if app_args.enable_audio_playback and original_audio_url: + logger.info(f" -> Replaying audio: {original_audio_url}") + play_params = {"call_control_id": call_control_id, "client_state": next_client_state, "audio_url": original_audio_url} + send_telnyx_command("actions/playback_start", play_params, app_args.api_key) + action_taken = True + elif original_tts_payload: + logger.info(f" -> Replaying TTS: {original_tts_payload}") + speak_params = {"payload": original_tts_payload, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": call_control_id, "client_state": next_client_state} + send_telnyx_command("actions/speak", speak_params, app_args.api_key) + action_taken = True + + if not action_taken: + logger.error(" -> Could not find original media to replay. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("replay_fail_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + elif digits_received == "#" or (gather_status == 'completed_by_terminating_digit' and payload.get('terminating_digit') == '#'): + logger.info(" -> DTMF '#' received or terminating digit. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_pound_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + elif gather_status == 'timeout': + logger.info(" -> DTMF gather timed out. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_timeout_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + else: + logger.warning(f" -> Gather ended with unhandled digits '{digits_received}' or status '{gather_status}'. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_unhandled_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + else: + logger.warning(f" -> Gather ended, but client_state ('{plain_client_state_rcvd}') doesn't match waiting_dtmf. Ignoring.") + + + elif event_type == 'call.hangup': + app_logger.info(f" Call Hangup Event: Cause='{payload.get('cause')}', SIPCause='{payload.get('sip_hangup_cause')}', Source='{payload.get('hangup_source')}'") + # Call is already over, no command to send. + + # Log other voice events not explicitly handled above for visibility + elif event_type not in ['call.initiated', 'call.answered', 'call.speak.ended', 'call.playback.ended', 'call.gather.ended', 'call.hangup', 'call.speak.started', 'call.playback.started']: + logger.info(f" Other Voice Event: Type='{event_type}'. Payload: {json.dumps(payload, indent=2)}") + + + # --- SMS Event Handling (Placeholder from your snippet) --- + elif record_type == 'message': + logger.info(f"Processing SMS event: {event_type}") + # Your existing SMS handling code would go here... + # For now, just acknowledge + logger.info(" -> SMS ACK (204)") + return Response(status=204) # Ensure SMS events are also ACKed + + else: + logger.warning(f"Unknown record type: {record_type}") + # Acknowledge to prevent retries from Telnyx + logger.info(" -> Unknown Record Type ACK (204)") + return Response(status=204) + + # If we reached here for a voice event and didn't send a command through send_telnyx_command, + # it means we are just acknowledging the event. + logger.info(" -> Voice Event Processed (no immediate command sent or command sent async). ACK (204) to Telnyx.") + return Response(status=204) # ALWAYS ACK THE WEBHOOK + + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook: {e}") + # Still try to ACK Telnyx if possible, but log the error. + # Depending on the error, Telnyx might retry if it doesn't get a 2xx. + return "Internal Server Error", 500 + +def handle_telnyx_webhook3(webhook_data, remote_addr, request_id): + """ + Processes Telnyx webhook events with full IVR logic for repeating messages. + This function should be added to your well-api.py. + """ + logger.info(f"Processing webhook in handle_telnyx_webhook3 from {remote_addr}, Request-ID: {request_id}") + + # --- ADAPT THIS SECTION to your app's config management --- + # This example assumes config values are accessible as global constants or from a dict. + # Replace these with your actual config access method (e.g., self.config['...']) + config = { + 'api_key': TELNYX_API_KEY, + 'dtmf_timeout_seconds': 10, + 'initial_silence_ms': 500, + 'replay_silence_ms': 100, + 'default_tts_voice': 'female', + 'default_tts_language': 'en-US', + 'client_state_prefix': 'well_api_state', + 'inbound_greeting': 'Thank you for calling. We will be with you shortly.' + } + # --- END ADAPTATION SECTION --- + + try: + StoreToDB(webhook_data) # Call your DB storage function first + + data, payload = webhook_data.get('data', {}), webhook_data.get('data', {}).get('payload', {}) + event_type, record_type, ccid = data.get('event_type'), data.get('record_type'), payload.get('call_control_id') + logger.info(f"EVENT '{event_type}' ({record_type})" + (f", CCID: {ccid}" if ccid else "")) + + if record_type != 'event': + logger.info(f" -> Non-voice event ('{record_type}') received. Ignoring in this handler.") + return True + + b64_client_state = payload.get("client_state") + decoded_parts = decode_state(b64_client_state) + state_name = decoded_parts[0] if decoded_parts else None + if state_name: logger.info(f" State Name Received: '{state_name}'") + + current_api_key = config['api_key'] + + # --- State Machine Logic --- + if event_type == 'call.answered': + if payload.get('direction') == 'incoming': + logger.info(" -> Inbound call detected. Playing generic greeting and hanging up.") + next_state = encode_state(['INBOUND_GREETING_HUP']) + speak_params = {"payload": config['inbound_greeting'], "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", speak_params, current_api_key) + else: # Outgoing call + audio_url = find_custom_header(payload.get('custom_headers'), 'X-Audio-Url') + tts_payload = find_custom_header(payload.get('custom_headers'), 'X-TTS-Payload') + media_type = "audio" if audio_url else "tts" if tts_payload else "none" + media_value = audio_url or tts_payload + if media_value: + logger.info(f" -> Outbound call. Playing {config['initial_silence_ms']}ms silence buffer.") + next_state = encode_state(['INIT_PLAY_MAIN', media_type, media_value]) + send_telnyx_command("actions/play_silence", {"milliseconds": str(config['initial_silence_ms']), "call_control_id": ccid, "client_state": next_state}, current_api_key) + else: + logger.warning(" -> Outbound call, but no audio/tts payload. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.playback.ended': + if state_name == 'INIT_PLAY_MAIN': # Silence ended + logger.info(" -> Silence buffer ended. Playing main message.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['MAIN_MEDIA_PLAYED', media_type, media_value]) + if media_type == "audio": + send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key) + elif media_type == "tts": + params = {"payload": media_value, "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", params, current_api_key) + elif state_name == 'REPLAY_SILENCE': # Replay silence ended + logger.info(" -> Replay silence ended. Replaying main message.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['REPLAYING_MEDIA', media_type, media_value]) + if media_type == "audio": + send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key) + elif media_type == "tts": + params = {"payload": media_value, "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", params, current_api_key) + elif state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: # Actual audio file ended + logger.info(f" -> Main audio playback finished. Playing options menu.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['WAITING_DTMF', media_type, media_value]) + options_prompt = "press 0 to repeat the message or press pound to hang up." + gather_params = { + "payload": options_prompt, "voice": config['default_tts_voice'], "language": config['default_tts_language'], + "valid_digits": "0#", "max_digits": 1, "timeout_millis": config['dtmf_timeout_seconds'] * 1000, "terminating_digit": "#", + "call_control_id": ccid, "client_state": next_state + } + send_telnyx_command("actions/gather_using_speak", gather_params, current_api_key) + else: + logger.warning(f" -> Playback ended with unhandled state '{state_name}'. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.speak.ended': + if state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: + logger.info(f" -> Main message TTS finished. Playing options menu.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['WAITING_DTMF', media_type, media_value]) + options_prompt = "press 0 to repeat the message or press pound to hang up." + gather_params = { + "payload": options_prompt, "voice": config['default_tts_voice'], "language": config['default_tts_language'], + "valid_digits": "0#", "max_digits": 1, "timeout_millis": config['dtmf_timeout_seconds'] * 1000, "terminating_digit": "#", + "call_control_id": ccid, "client_state": next_state + } + send_telnyx_command("actions/gather_using_speak", gather_params, current_api_key) + elif state_name == 'INBOUND_GREETING_HUP': + logger.info(" -> Inbound greeting finished. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + else: + logger.warning(f" -> Speak ended with unhandled state '{state_name}'. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.dtmf.received': + digit = payload.get('digit') + logger.info(f" DTMF Received: Digit='{digit}'") + if digit == '#': + logger.info(" -> '#' received. Terminating call immediately.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.gather.ended': + logger.info(f" -> Gather ended. Digits received: '{payload.get('digits')}', Status: '{payload.get('status')}'") + if state_name == 'WAITING_DTMF': + digits = payload.get('digits') + _, media_type, media_value = decoded_parts + if digits == "0": + logger.info(f" -> '0' pressed. Playing {config['replay_silence_ms']}ms silence before replay.") + next_state = encode_state(['REPLAY_SILENCE', media_type, media_value]) + send_telnyx_command("actions/play_silence", {"milliseconds": str(config['replay_silence_ms']), "call_control_id": ccid, "client_state": next_state}, current_api_key) + else: + logger.info(" -> Gather ended with non-repeat condition. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + else: + logger.warning(f" -> Gather ended with unhandled state '{state_name}'.") + + elif event_type == 'call.hangup': + logger.info(f" Call Hangup Event: Cause='{payload.get('cause')}'") + else: + logger.info(f" -> Unhandled Voice Event: '{event_type}' with state '{state_name}'.") + + return True # Return app-specific success + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook3: {e}") + return False + + +def FilterDevicesByDeviceId(devices_list, device_id_str): + """ + Filter devices list to include only the specified device_id. + + Parameters: + devices_list: tuple of (device_details_list, device_ids_list) + device_id_str: string representation of device_id to filter by + + Returns: + tuple: filtered (device_details_list, device_ids_list) + """ + try: + target_device_id = int(device_id_str) + except ValueError: + return ([], []) + + device_details_list, device_ids_list = devices_list + + filtered_details = [] + filtered_ids = [] + + for i, device_details in enumerate(device_details_list): + device_id = device_details[1] # device_id is second element (index 1) + if device_id == target_device_id: + filtered_details.append(device_details) + filtered_ids.append(device_ids_list[i]) + + return (filtered_details, filtered_ids) + + +def FilterSensorsBySensorType(sensor_type): + """ + Filter s_table to include only the specified sensor type. + + Parameters: + sensor_type: string name of sensor type (e.g., 'temperature', 'radar', 'voc0', etc.) + + Returns: + list: filtered s_table containing only the mapped sensor name + """ + # Map user-friendly sensor names to their s_table equivalents + sensor_mapping = { + 'temperature': 'avg_temperature', + 'humidity': 'avg_humidity', + 'pressure': 'pressure_amplitude', + 'light': 'max_light', + 'radar': 'radar', + 'voc0': 'sensor_min_s0', + 'voc1': 'sensor_min_s1', + 'voc2': 'sensor_min_s2', + 'voc3': 'sensor_min_s3', + 'voc4': 'sensor_min_s4', + 'voc5': 'sensor_min_s5', + 'voc6': 'sensor_min_s6', + 'voc7': 'sensor_min_s7', + 'voc8': 'sensor_min_s8', + 'voc9': 'sensor_min_s9' + } + + # Get the actual sensor name used in s_table + mapped_sensor = sensor_mapping.get(sensor_type, sensor_type) + + # Return the mapped sensor name if it's valid, otherwise empty list + if mapped_sensor in sensor_mapping.values() or mapped_sensor == sensor_type: + return [mapped_sensor] + + return [] + + +def set_character(some_string, bit_nr, new_char): + """ + Replace a character in a string at position bit_nr from the right. + + Parameters: + some_string (str): The input string + bit_nr (int): Position from right (0 = rightmost, 1 = second from right, etc.) + new_char (str): The replacement character + + Returns: + str: The modified string + """ + if bit_nr < 0 or bit_nr >= len(some_string): + return some_string # Invalid position + + # Convert string to list for easier manipulation + chars = list(some_string) + + # Replace character at position bit_nr from right + chars[len(chars) - 1 - bit_nr] = new_char + + # Convert back to string + return ''.join(chars) + +def GetBit(alarm_armed_settings, bit_nr): + if bit_nr <0 or bit_nr >= len(alarm_armed_settings): + return False + return alarm_armed_settings[-(bit_nr+1)] == "1" + +def CreateSensorsMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by, filtered_s_table): + """ + Create a sensor map with filtered devices and sensors. + Based on CreateMapFast but with filtering support. + + Parameters: + map_file: output file path + devices_list: filtered devices list + selected_date: date string + bw: black and white flag + time_zone_s: timezone string + radar_part: radar part specification + group_by: grouping strategy + filtered_s_table: filtered sensor table + + Returns: + tuple: (success_boolean, vocs_scaled_array) + """ + global Id2MACDict + + st = time.time() + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + + try: + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + + for details in devices_list[0]: + well_id = details[0] + ids_list.append(details[1]) + if well_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return False, [] + + if larger_than200 > 0: + sensors_c = len(filtered_s_table) + else: # old sensors not supported + return False, [] + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + image_file = map_file + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + temp_offset = -10 + + # Use filtered sensor table for queries + if sensors_c > 1: + sql = get_deployment_query_filtered( + devices_list_str, + time_from_str, + time_to_str, + ids_list, + radar_part, + temp_offset, + filtered_s_table + ) + else: + sql = get_deployment_single_query( + devices_list_str, + time_from_str, + time_to_str, + ids_list, + radar_part, + temp_offset, + filtered_s_table[0] + ) + + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + day_data = cur.fetchall() + if day_data == None: + return False, [] + + stretch_by = 10 + minutes = 1440 + stripes = devices_c * sensors_c + arr_source_template = np.full((stripes, minutes + 4), -0.001, dtype=float) + arr_stretched_template = np.zeros((int(stripes * stretch_by), minutes, 3), dtype=np.uint8) + + # Use filtered sensor table + arr_source = fast_fill_array_from_timescale_filtered( + day_data, + time_from_str, + devices_list[1], + arr_source_template, + filtered_s_table, + time_zone_s + ) + + arr_source = AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_table, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) + SaveImageInBlob(image_file, arr_stretched) + return True, vocs_scaled + + except Exception as e: + AddToLog(traceback.format_exc()) + return False, [] + + +def get_deployment_query_filtered(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, filtered_s_table): + """ + Generate a filtered TimeScaleDB query for specific sensors only. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name + temp_offset (float): Temperature offset + filtered_s_table (list): List of sensor names to include + + Returns: + str: Generated SQL query + """ + # Generate the CASE statement for ordering + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # Build sensor-specific SELECT clauses + sensor_selects = [] + sensor_aggregates = [] + radar_needed = False + + for sensor in filtered_s_table: + if sensor == "temperature": + sensor_selects.append(f"sr.avg_temperature+ {temp_offset} as avg_temperature") + sensor_aggregates.append("AVG(temperature) AS avg_temperature") + elif sensor == "humidity": + sensor_selects.append("sr.avg_humidity") + sensor_aggregates.append("AVG(humidity) AS avg_humidity") + elif sensor == "pressure": + sensor_selects.append("sr.pressure_amplitude") + sensor_aggregates.append("AVG(pressure) AS pressure_amplitude") + elif sensor == "light": + sensor_selects.append("sr.max_light") + sensor_aggregates.append("MAX(light) AS max_light") + elif sensor == "radar": + sensor_selects.append("rr.radar") + radar_needed = True + elif sensor.startswith("voc"): + # Extract sensor number (e.g., sensor_min_s0 -> s0) + sensor_num = sensor.replace("voc", "") + sensor_selects.append(f"sr.{sensor}") + sensor_aggregates.append(f"MIN(s{sensor_num}) AS {sensor}") + + # Build the query + if radar_needed and sensor_aggregates: + # Need both sensor readings and radar readings + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + {', '.join(sensor_selects)} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {', '.join(sensor_aggregates)} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + elif radar_needed: + # Only radar needed + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + else: + # Only sensor readings needed + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {', '.join(sensor_aggregates)} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + + return sql + + + +def fast_fill_array_from_timescale_filtered(day_data, time_from_str, devices_list, arr_source, filtered_s_table, timezone_str="Europe/Berlin"): + """ + Optimized array filling for filtered sensors. + + Parameters: + day_data: query results + time_from_str: start time string + devices_list: list of device IDs + arr_source: array to fill + filtered_s_table: list of sensor names to process + timezone_str: timezone string + + Returns: + numpy array: filled array + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Pre-process data into a more efficient structure + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Build column mapping based on filtered sensors + columns = {} + col_idx = 2 # Start after time and device_id + + for sensor in filtered_s_table: + columns[sensor] = col_idx + col_idx += 1 + + # Process each device's data + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(filtered_s_table) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1] - 4) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each filtered sensor + for sensor_idx, sensor_name in enumerate(filtered_s_table): + if sensor_name in columns: + row_idx = base_idx + sensor_idx + values = records_array[:, columns[sensor_name]] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + + +def AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_table, percentile): + """ + Vectorized version of AddLimits for filtered sensors. + + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + filtered_s_table: list of sensor names + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + + # Create arrays of sensor legal values for filtered sensors + min_vals = [] + max_vals = [] + windows = [] + + for sensor_name in filtered_s_table: + if sensor_name in sensor_legal_values: + min_vals.append(sensor_legal_values[sensor_name][0]) + max_vals.append(sensor_legal_values[sensor_name][1]) + windows.append(sensor_legal_values[sensor_name][2]) + else: + # Default values if sensor not found + min_vals.append(0) + max_vals.append(1000) + windows.append(1) + + # Repeat for each device + min_vals = np.tile(min_vals, devices_c) + max_vals = np.tile(max_vals, devices_c) + windows = np.tile(windows, devices_c) + + # Process rows that need cleaning (window > 2) + clean_mask = windows > 2 + if np.any(clean_mask): + for window in np.unique(windows[clean_mask]): + rows_to_clean = np.where(clean_mask & (windows == window))[0] + + for row_idx in rows_to_clean: + arr_source[row_idx, :1440] = clean_data_vectorized( + arr_source[row_idx, :1440], + window, + percentile + ) + + # Set min/max values for all rows + arr_source[:, 1440] = min_vals + arr_source[:, 1441] = max_vals + + return arr_source + +def GetNextWellId(min_well_id): + + conn = get_db_connection() + + sql = """ + SELECT COALESCE(MAX(well_id), 0) AS max_well_id + FROM public.devices + """ + + try: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + if result == None: + return min_well_id + else: + max_wel_id = result[0] + if max_wel_id != None and min_well_id != None: + if max_wel_id + 1 > min_well_id: + return max_wel_id + 1 + else: + return min_well_id + else: + if max_wel_id != None: + return max_wel_id + 1 + + except Exception as e: + return min_well_id + + +def GetAlarmAllDetails(deployment_id): + + + device_alarms_json_map = {} + devices = GetVisibleDevices(deployment_id) + deployment_alarms_json, dummy = GetAlarmSimple(deployment_id, 0) + + for device in devices: + device_id = device[0] + dummy, device_alarm_json = GetAlarmSimple(0, device_id) + device_alarms_json_map[device_id] = device_alarm_json + return deployment_alarms_json, device_alarms_json_map + +def GetCalibMaps(device_ids_list): + temp_calib = {} + humid_calib = {} + with get_db_connection() as conn: + with conn.cursor() as cur: + #list all devices that user has access to + sql = f"SELECT device_id, temperature_calib, humidity_calib FROM public.devices WHERE device_id in ({str(device_ids_list)[1:-1]})" + print(sql) + cur.execute(sql) + calib_records = cur.fetchall()#cur.fetchone() + for record in calib_records: + temp_calib[record[0]] = record[1] + humid_calib[record[0]] = record[2] + + return temp_calib, humid_calib +#==================================== ADD FUNCTIONS BEFORE ============================================ + +# Main API class +class WellApi: + def on_get_healthz(self, req, resp): + """Health check endpoint""" + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_TEXT + resp.text = "OK" + + def on_get(self, req, resp, path=""): + """Handle GET requests""" + global s_table_temp + + logger.debug(f"GET request to path: {path}") + logger.debug(f"Sent variables: {req.params}") + logger.debug(f"All headers: {dict(req.headers)}") + if path == "" or path == "/": + # Serve the main portal page + blob_data = read_file("well_portal.html") + if blob_data: + resp.content_type = "text/html" + resp.text = blob_data + else: + # Fall back to JSON response if file not found + resp.media = {"message": "Hello from OpenFaaS Serverless Web Server!", "method": "GET"} + return + elif path == "favicon.ico": + favicon_path = "favicon.ico" + if os.path.isfile(favicon_path): + resp.content_type = 'image/x-icon' + resp.data = read_file(favicon_path, type_="BIN") + resp.status = HTTP_200 + else: + resp.status = HTTP_404 + return + elif path == "health": + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"status": "healthy"}) + return + + # Authentication and authorization + token = req.params.get('token') + user_name = req.params.get('user_name') + ps = req.params.get('ps') + + if ps != "" and ps != None: + #was token sent in ps field? This allows for token and ps be populated by token or ps + user_info = verify_token(ps) + if user_info["username"] == user_name: + token = ps + else: + #is this valid password? + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + + + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + get_function_name = req.params.get('name') + logger.debug(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] - {__name__}.GET_API->{get_function_name}") + privileges = GetPriviledgesOnly(user_name) + + if (token and user_name) or (token and user_name): + user_info = verify_token(token) + if user_info is None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + get_function_name = req.params.get('name') + logger.debug(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] - {__name__}.GET_API->{get_function_name}") + + if get_function_name == "deployment_add": + user_id = req.params.get('user_id') + blob_data = read_file("edit_deployment.html") + caretaker = {'deployment_id': 0, 'beneficiary_id': user_id, 'caretaker_id': user_id, 'owner_id': user_id, 'installer_id': user_id, 'user_id': 0, 'role_ids': '2', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "devices_list": + st = time.time() + user_name = req.params.get('user_name') + privileges = GetPriviledgesOnly(user_name) + first_s = req.params.get('first') + last_s = req.params.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + blob_data = read_file("my_devices.html") + + devices = GetVisibleDevices(privileges) + users = GetUsersFromDeployments(privileges) + blob_data = UpdateDevicesTable(blob_data, devices, users) + blob_data = UpdateDeploymentsSelector(blob_data, users) + resp.content_type = "text/html" + resp.text = blob_data + #print(blob_data) + return + + elif get_function_name == "deployment_edit": + deployment_id = req.params.get('deployment_id') + blob_data = read_file("edit_deployment.html") + + deployment = DeploymentDetails(deployment_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, deployment, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "caretaker_add": + + blob_data = read_file("edit_caretaker.html") + caretaker = {'user_id': 0, 'role_ids': '2', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "caretaker_edit": + user_id = req.params.get('user_id') + blob_data = read_file("edit_caretaker.html") + + caretaker = UserDetails(user_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "device_add": + + blob_data = read_file("edit_device.html") + device = {'device_id': 0, 'device_mac': '', 'well_id': '', 'description': '', 'location': '', 'close_to': '', 'radar_threshold': '["s3_max",12]', 'temperature_calib': '0.0,1.0,0.0', 'humidity_calib': '0.0,1.0,0.0'} + blob_data = FillFields(blob_data, device, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "device_edit": + mac = req.params.get('mac') + + blob_data = read_file("edit_device.html") + + device_det = DeviceDetails(mac) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, device_det, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + + elif get_function_name == "beneficiary_edit": + user_id = req.params.get('user_id') + blob_data = read_file("edit_beneficiary.html") + + beneficiary = UserDetails(user_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, beneficiary, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "beneficiary_add": + + blob_data = read_file("edit_beneficiary.html") + beneficiary = {'user_id': 0, 'role_ids': '1', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, beneficiary, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "get_image_file": + #image represents day in local time + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + group_by = req.params.get("group_by") + timee = StringToEpoch(ddate, time_zone_s) + force_recreate = req.params.get("re_create") == "true" + radar_part = req.params.get("radar_part") + map_type = int(req.params.get("map_type")) + + bw = req.params.get("bw") == "true" + unique_identifier = req.params.get("unique_identifier") + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{group_by}_{radar_part}_{map_type}_{bw}_dayly_image.png" + + #print(check_file_exists(filename)) + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + #ddate is in Local Time + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + #time that describes new devices in deployment_history is in UTC therefore timee is in UTC + + st = time.time() + vocs_scaled = {} + #file_date is in Local time, so we are comparing that and current Local (to install) Date + if force_recreate: + st = time.time() + vocs_scaled = {} + devices_list = GetProximityList(deployment_id, timee) + s_table_temp = s_table + stored, vocs_scaled = CreateMapFast(filename, devices_list, ddate, bw, time_zone_s, radar_part, group_by) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + if stored != True: + AddToLog("Map not created") + #logger.warning("Map not created") + resp.media = package_response("Map not created", HTTP_401) + return + else: + AddToLog("Map created") + #lets send over MQTT vocs_scaled + json_data = numpy_to_json(vocs_scaled, devices_list) + MQSendL("/"+unique_identifier, json_data) + #print(time.time() - st) + + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + sys.stdout.flush() + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_sensors_map": + # Get filtering parameters + device_id_str = req.params.get('device_id') + sensor = req.params.get('sensor') + + # Get standard image parameters + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_", "-") + group_by = ""#req.params.get("group_by") + timee = StringToEpoch(ddate, time_zone_s) + force_recreate = "true" + radar_part = req.params.get("radar_part") + bw = req.params.get("bw") == "true" + unique_identifier = req.params.get("unique_identifier") + + # Create filename with filtering parameters + filter_suffix = "" + if device_id_str: + filter_suffix += f"_dev{device_id_str}" + if sensor: + filter_suffix += f"_sens{sensor}" + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{radar_part}_{bw}{filter_suffix}_sensors_map.png" + + # Check if file exists and needs recreation + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + # Convert date to UTC epoch for device queries + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 5 # add so date boundary is avoided + + st = time.time() + vocs_scaled = {} + + if force_recreate: + st = time.time() + vocs_scaled = {} + + # Get initial device list + devices_list = GetProximityList(deployment_id, timee) + + # Apply device filtering if specified + if device_id_str: + filtered_devices = FilterDevicesByDeviceId(devices_list, device_id_str) + else: + filtered_devices = devices_list + + # Apply sensor filtering if specified + if sensor: + filtered_s_table = [sensor]#FilterSensorsBySensorType(sensor) + else: + filtered_s_table = s_table + + # Validate we have devices and sensors to process + if not filtered_devices[0] or not filtered_s_table: + AddToLog("No devices or sensors match the specified filters") + resp.media = package_response("No devices or sensors match the specified filters", HTTP_400) + return + + # Create the filtered map + stored, vocs_scaled = CreateSensorsMapFast( + filename, + filtered_devices, + ddate, + bw, + time_zone_s, + radar_part, + group_by, + filtered_s_table + ) + + if stored != True: + AddToLog("Sensors map not created") + resp.media = package_response("Sensors map not created", HTTP_401) + return + else: + AddToLog("Sensors map created") + # Send over MQTT vocs_scaled + json_data = numpy_to_json(vocs_scaled, filtered_devices) + MQSendL("/" + unique_identifier, json_data) + + # Read and send image from blob + image_bytes, content_type = GetBlob(filename) + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + sys.stdout.flush() + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_full_location_map": + + raw = req.params.get("raw") == "true" + + if raw: + #function=request_deployment_map_new + #token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvYnN0ZXIiLCJleHAiOjE3MzgxNzYzNTZ9.5wzC2dVQhKlMygHPZfombTINbltNq8vxdilLIugNTtA& + #user_name=robster& + #date=2025-01-27& + #deployment_id=21& + #map_type=2 + chart_type = 8 + else: + chart_type = int(req.params.get("map_type")) + + #image represents day in local time + logger.debug("get_full_location_map") + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + + to_date = ddate + + try: + to_date = req.params.get("to_date") + to_date = to_date.replace("_","-") + except: + pass + + if to_date != ddate: + chart_type = int(req.params.get("map_type")) + + force_recreate = req.params.get("re_create") == "true" + force_recreate_orig = force_recreate + scale_global = req.params.get("scale_global") == "true" + fast = req.params.get("fast") == "true" + bw = req.params.get("bw") == "true" + motion = req.params.get("motion") == "true" + timee = StringToEpoch(ddate, time_zone_s) + + filter_minutes = int(req.params.get("filter")) + + if "flavor" in req.params: #this is to be used only when creating + flavor = int(req.params.get("flavor")) + else: + flavor = 0 + + if bw: + bw_s = "BW" + else: + bw_s = "CLR" + + if fast: + fast_s = "FAST" + else: + fast_s = "SLOW" + + if motion: + motion_s = "M" + else: + motion_s = "S" + + if scale_global: + scl_s = "scl" + else: + scl_s = "nscl" + + + if chart_type == 5 or chart_type == 7: + #now_date = req.params.get("now_date") + #now_date = now_date.replace("_","-") + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{to_date}_{filter_minutes}_history_image.png" + else: + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{bw_s}_{motion_s}_{scl_s}_{chart_type}_Flocation_image.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + #file_exists1, time_modified_utc1 = check_file_exists(filename+".bin") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - time_modified_local + #if time_passed.seconds > 300: #recreate if older than 5 minutes + # force_recreate = True + + else: + force_recreate = True + + if force_recreate: + ddate = ddate.replace("_","-") + #filter_minutes = 5 + #filename = os.path.join(scriptDir+"/daily_maps/"+deployment, proximity_string+"_"+deployment+"_"+ddate+"_dayly_image.png") + + filename = filename.replace('\\','/') + if chart_type == 4: #"collapsed": + GenerateFullLocationMap(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes) + elif chart_type == 5: #"history": + GeneratePresenceHistory(filename, force_recreate, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + elif chart_type == 7: #"history full chart": + filename = GeneratePresenceHistoryChart(filename, force_recreate_orig, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + elif chart_type == 8: #"set for mobile" + GenerateFullLocationMapLabelsOut(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast,time_zone_s, filter_minutes) + else: + GenerateFullLocationMap(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast,time_zone_s, filter_minutes) + + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_presence_map": + #image represents day in local time + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + force_recreate = req.params.get("re_create") == "true" + scale_global = req.params.get("scale_global") == "true" + fast = req.params.get("fast") == "true" + bw = req.params.get("bw") == "true" + motion = req.params.get("motion") == "true" + timee = StringToEpoch(ddate, time_zone_s) + chart_type = int(req.params.get("map_type")) + filter_minutes = int(req.params.get("filter")) + + if bw: + bw_s = "BW" + else: + bw_s = "CLR" + + if fast: + fast_s = "FAST" + else: + fast_s = "SLOW" + + if motion: + motion_s = "M" + else: + motion_s = "S" + + if scale_global: + scl_s = "scl" + else: + scl_s = "nscl" + + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{bw_s}_{motion_s}_{scl_s}_{chart_type}_Flocation_image.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + ddate = ddate.replace("_","-") + days = 7 + + filename = filename.replace('\\','/') + if chart_type == 6: #"AI Locations": + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreatePresenceMap(filename, devices_list, ddate, 1, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + + return + elif get_function_name == "download": + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + date_from = req.params.get("date_from") + date_to = req.params.get("date_to") + date_from = date_from.replace("_","-") + date_to = date_to.replace("_","-") + consolidated_by = req.params.get("consolidated_by") + force_recreate = req.params.get("re_create") == "true" + radar_part = req.params.get("radar_part") + zip_filename = f"/{deployment_id}/{deployment_id}_{date_from}_{date_to}_{consolidated_by}_data.zip" + + #print(check_file_exists(filename)) + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(zip_filename, bucket_name="data-downloads") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = CSVFileToDate(zip_filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + #ddate is in Local Time + dates = DatesSpan(date_from, date_to) + to_zip = [] + for ddate in dates: + force_recreate_csv = force_recreate + csv_dayly_filename = f"/{deployment_id}/{deployment_id}_{ddate}_{consolidated_by}_data.csv" + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + if not force_recreate_csv: + #time that describes new devices in deployment_history is in UTC therefore timee is in UTC + file_exists, time_modified_utc = check_file_exists(csv_dayly_filename, bucket_name="data-downloads") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = CSVFileToDate(csv_dayly_filename) + if time_modified_date <= file_date: + force_recreate_csv = True + else: + force_recreate_csv = True + st = time.time() + vocs_scaled = {} + #file_date is in Local time, so we are comparing that and current Local (to install) Date + if force_recreate_csv: + st = time.time() + vocs_scaled = {} + + devices_list = GetProximityList(deployment_id, timee) + temp_offset = -10 + file_stored = CreateDailyCSV(csv_dayly_filename, devices_list, ddate, vocs_scaled, time_zone_s, radar_part, consolidated_by, temp_offset) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + to_zip.append(file_stored) + else: + to_zip.append(csv_dayly_filename) + + if to_zip: + success = zip_blobs( + blob_paths=to_zip, + zip_blob_name=zip_filename, + bucket_name="data-downloads", + minio_client=miniIO_blob_client + ) + + if success: + print("Files successfully zipped") + else: + print("Error occurred while zipping files") + + #pack CSV files from BLOB into ZIP + #lets read and send image from blob + zip_bytes, content_type = GetBlob(zip_filename, bucket_name="data-downloads") + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if zip_bytes is None: + raise falcon.HTTPNotFound( + title='File not found', + description=f'File {zip_filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = zip_bytes + resp.status = falcon.HTTP_200 + return + resp.media = package_response("Use POST method for this endpoint", HTTP_400) + + # Default response for unmatched paths + #resp.media = package_response(f"Path: /{path}", HTTP_200) + + def on_post(self, req, resp, path=""): + #ToDo make sure that any read/write data functions are authorized for this user_name + global threshold_cache, device_lookup_cache + + """Handle POST requests""" + logger.debug(f"on_post called with path: {path}") + logger.debug(f"Request method: {req.method}") + logger.debug(f"Request path: {req.path}") + logger.debug(f"Request query string: {req.query_string}") + logger.debug(f"Request headers: {req.headers}") + logger.debug(f"Request content type: {req.content_type}") + + + # First, check if this is a Telnyx webhook request + is_telnyx_webhook = ( + req.content_type and 'application/json' in req.content_type and + req.headers.get('USER-AGENT') == 'telnyx-webhooks' + ) + + if is_telnyx_webhook: + logger.info("Processing Telnyx webhook request") + try: + # Read the raw request body + raw_body = req.stream.read().decode('utf-8') + logger.debug(f"Raw webhook request body: {raw_body}") + + if not raw_body: + logger.error("Empty request body received from Telnyx") + resp.status = falcon.HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Empty request body"}) + return + + # Parse JSON + webhook_data = json.loads(raw_body) + logger.debug(f"Parsed webhook data: {json.dumps(webhook_data)}") + + # Get remote address and request ID + remote_addr = req.headers.get('X-REAL-IP') or req.headers.get('X-FORWARDED-FOR') or 'unknown' + request_id = req.headers.get("X-Request-Id") or req.headers.get("Telnyx-Request-Id") or req.headers.get("X-CALL-ID") or "N/A" + + # Process the webhook + handle_telnyx_webhook(webhook_data, remote_addr, request_id) + + # Set response status - always acknowledge webhooks with 204 No Content + resp.status = falcon.HTTP_204 + return + + except json.JSONDecodeError as e: + logger.error(f"Failed to decode JSON from webhook request body: {e}") + resp.status = falcon.HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Invalid JSON payload"}) + return + except Exception as e: + logger.exception(f"Error processing webhook: {e}") + resp.status = falcon.HTTP_500 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Internal Server Error"}) + return + + # If we get here, it's not a Telnyx webhook, so process as normal + try: + # For non-webhook requests, get form data + form_data = get_form_data(req) + logger.debug(f"Form data: {form_data}") + + except Exception as e: + logger.exception(f"Error in on_post: {e}") + resp.status = falcon.HTTP_500 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Internal Server Error"}) + + # Get form data using our helper function - but don't read stream again + #form_data = get_form_data(req) + logger.debug(f"Form data: {form_data}") + + + try: + + # Get basic parameters + function = form_data.get('function') + user_name = form_data.get('user_name') + logger.debug(f"Function: {function}, User: {user_name}") + + if function != "credentials" and function != "new_user_form" and function != "set_deployment": + token = form_data.get('token') + ps = form_data.get('ps') + + if ps != "" and ps != None: + #was token sent in ps field? This allows for token and ps be populated by token or ps + user_info = verify_token(ps) + if user_info != None: + if user_info["username"] == user_name: + token = ps + else: + #is this valid password? + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + + #with get_db_connection() as db_conn: + privileges = GetPriviledgesOnly(user_name) + + # Handle credentials function - most common case + if function == "credentials": + + clientId = form_data.get('clientId') + nonce = form_data.get('nonce') + ps = form_data.get('ps') + + if not user_name: + resp.media = package_response("Required field 'user_name' is missing", HTTP_400) + return + + if not clientId: + resp.media = package_response("Required field 'clientId' is missing", HTTP_400) + return + + if not nonce: + resp.media = package_response("Required field 'nonce' is missing", HTTP_400) + return + + if not ps: + resp.media = package_response("Required field 'ps' is missing", HTTP_400) + return + + + + if False: + pass + else: + #lets check for real + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + access_token = 0 + privileges = 0 + else: + access_token = generate_token(user_name) + + if privileges == "-1": + max_role = -1 + else: + max_role = GetMaxRole(user_name) + if "2" in max_role: + max_role = 2 + else: + max_role = 1 + + token_payload = {'access_token': access_token, 'privileges': privileges, 'user_id': user_id, 'max_role': max_role} + resp.media = package_response(token_payload) + resp.status = falcon.HTTP_200 + return + + # Handle token-protected functions + elif function == "messages_age": + + macs = form_data.get('macs') + + with get_db_connection() as conn: + + #print (sqlr) + with conn.cursor() as cur: + + devices = MACsStrToDevIds(cur, macs) + + devices_string = ",".join(f"{device_id}" for mac, device_id in devices) + + + sqlr = f""" + SELECT + device_id, + GREATEST( + radar_last_time, + sensor_last_time + ) AS latest_time + FROM + (SELECT unnest(ARRAY[{devices_string}]) AS device_id) d + LEFT JOIN LATERAL ( + SELECT time AS radar_last_time + FROM radar_readings + WHERE device_id = d.device_id + ORDER BY time DESC + LIMIT 1 + ) r ON true + LEFT JOIN LATERAL ( + SELECT time AS sensor_last_time + FROM sensor_readings + WHERE device_id = d.device_id + ORDER BY time DESC + LIMIT 1 + ) s ON true;""" + logger.debug(f"sqlr= {sqlr}") + cur.execute(sqlr) + times_list = cur.fetchall() + result = {} + for i in range(len(times_list)): + if times_list[i][1] is not None: + result[devices[i][0]] = times_list[i][1].timestamp() + else: + result[devices[i][0]] = 0 + + dataa = {} + dataa['Command'] = "REPORT" + dataa['body'] = result + dataa['time'] = time.time() + #json_data = json.dumps(dataa) + payload = {'ok': True, 'response': dataa} + resp.media = package_response(payload) + logger.warning(f"Responded: {str(payload)}") + resp.status = falcon.HTTP_200 + return + + elif function == "voice_ask": + + question = form_data.get('question') + deployment_id = form_data.get('deployment_id') + + if ('language_from' in form_data): + language_from = form_data.get('language_from').strip() + else: + language_from = "English" + + if ('language_to' in form_data): + language_to = form_data.get('language_to').strip() + else: + language_to = "English" + + + result, language = AskGPT(question, language_from, language_to) + + if result[0] == "#": + result = RunCommand(result, {}, deployment_id) + + dataa = {} + dataa['Command'] = "REPORT" + dataa['body'] = result + dataa['name'] = "" + dataa['reflected'] = "" + dataa['language'] = language + dataa['time'] = time.time() + #json_data = json.dumps(dataa) + payload = {'ok': True, 'response': dataa} + resp.media = package_response(payload) + logger.warning(f"Responded: {str(payload)}") + resp.status = falcon.HTTP_200 + return + + + elif function == "calibrate_thresholds": + #this will use current date to calibrate radar presence thresholds. + #make sure that data is well defined (has clear absence/presence signature) for all rooms for chosen day + #Format of radar_threshold field = [gates_to_use_Presence_list, p_threshold] + #We need to automate this functionality!!! + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = form_data.get("date") + ddate = ddate.replace("_","-") + selected_date = ddate + + + + stdev_range = int(form_data.get("stdev_range")) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 + devices_list, device_ids = GetProximityList(deployment_id, timee) + + selected_date = FindCalibrationDate(device_ids, ddate) + + devices_c = len(devices_list[0]) + + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s, stdev_range) + fields = ["radar_s_min", "radar_s_max", "radar_m_max", "radar_stdev"] + cnt = 0 + ids_list = [] + for details in devices_list: + ids_list.append(details[1]) + devices_list_str = ",".join(map(str, ids_list)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + + minutes = 1440 + + with get_db_connection() as conn: + with conn.cursor() as cur: + for device_index in range(devices_c): + well_id = devices_list[device_index][0] + device_id = devices_list[device_index][1] + location = devices_list[device_index][2] + + sql = get_device_radar_s28_only_query(time_from_str, time_to_str, device_id) + print(sql) + + #sql1 = get_deployment_radar_only_colapsed_query(str(device_id), time_from_str, time_to_str, [device_id]) + #print(sql1) + st = time.time() + cur.execute(sql) + my_data = cur.fetchall() + + timestamps, stationary, motion = process_raw_data(my_data) + print(type(stationary)) + # Find threshold above which 20% of points lie + AveragePercentSpendsThere = AveragePercentPerLocation[Consolidataed_locations[location]] + threshold_high, threshold_low = FindThreshold(stationary, AveragePercentSpendsThere) + file_save = f"threshold_graph_{location}.png" + title = f"{well_id}_{location}" + + threshold2, x_percent, y_percent = ShowThresholdGraph(stationary, file_save, threshold_low, threshold_high, title, AveragePercentSpendsThere, location) + + print(f"Maximum curvature point found at:") + print(f"Threshold value: {threshold2:.3f}") + print(f"X: {x_percent:.1f}% of range") + print(f"Y: {y_percent:.1f}% of points above") + + ShowArray(stationary, threshold2, filename=f"stationary_{devices_list[device_index][0]}.png", title=f"stationary_{devices_list[device_index][0]}_{devices_list[device_index][2]}", style='line') + + + ##threshold + ##presence_mask, baseline, threshold = detect_presence(timestamps, stationary, motion) + + ### Save visualization to file + ##visualize_detection(timestamps, stationary, motion, presence_mask, + ## baseline, threshold) + + #cur.execute(sql1) + #my_data1 = cur.fetchall()#cur.fetchone() + #print(time.time() - st) + #if my_data == None or my_data1 == None: + #logger.warning(f"No data found for device_id {device_id}") + #else: + #print(type(my_data)) + ##minute, + ##device_id, + ##s_min as radar_s_min, + ##s_max as radar_s_max, + ##m_max as radar_m_max + + #values = [tup[1] for tup in my_data] #10 sec (RAW) data + + #hist, bins = np.histogram(values, bins=1000, range=(0, 100)) + #TR, BR = FindZeroIntersection(hist, bins, f'raw_{device_id}_histogram.png', device_id) + #if True:#device_id == 560: + #plot(values, filename=f"radar_{device_id}_s28.png", title=f"Radar s28 {device_id}", style='line') + #plot(hist, filename=f"radar_{device_id}_s28_hist.png", title=f"Radar s28 {device_id} histogram", style='line') + + ##life = [tup[3] - tup[2] + tup[4] for tup in my_data1] + #life, average = calculate_life_and_average(my_data1, stdev_range) #5 min data + #lhist, lbins = np.histogram(life, bins=1000) + #TLIFE, BLIFE = FindZeroIntersection(lhist, lbins, f'life_{device_id}_histogram.png', device_id) + + #StoreThresholds2DB(device_id, TR, BR, TLIFE, BLIFE) + ##for now not needed... + ##ahist, abins = np.histogram(average, bins=1000) + ##dummy1, dummy = FindZeroIntersection(ahist, abins) + #if True:#device_id == 560: + #plot(average, filename=f"average_{device_id}.png", title=f"Average {device_id}", style='line') + #plot(life, filename=f"life_{device_id}.png", title=f"Life {device_id}", style='line') + #plot(lhist, filename=f"life_{device_id}_hist.png", title=f"life {device_id} histogram", style='line') + ##plot(ahist, filename=f"average_{device_id}_hist.png", title=f"average {device_id} histogram", style='line') + + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + my_data = [] + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + fields_n = len(fields) + stripes = devices_c * fields_n #radar_min and radar_max + print(my_data) + base_minute = ConvertToBase(time_from_str, time_zone_s) + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + #remember: base_minute is offset (smaller) by numbr of minutes in stdev_range + st = time.time() + wave_m = np.zeros((stripes, 1440+2*stdev_range, 1), dtype=np.float32) + + for record in my_data: + #(minute,device_id,s28_min,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the min/max values + x = int((minute - base_minute).total_seconds()/60) + + device_idx = device_to_index[device_id] + #value[0] are mins, value[1] are maxes + #when trying to illustrate presence, use s28_max, when absence (night leaving bed) use s28s_min + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + wave_m[y, x] = value + + print(time.time()-st) + + #we need to reliably determine presence and LIFE (motion) in every 5 minutes of data... + #presence is determined by average value being significntly different from last known base + #last known base is determined by average value during extended periods ( >= H hours) of low stdev (<) while it is determined that: + #person is moving elsewhere, and only 1 person is determined to be in monitored area. + + #lets calculate stdevs + for device_index in range(devices_c): + y = device_index * fields_n + row = wave_m[y] + stdevs = np.zeros((1440+2*stdev_range, 1), dtype=np.float32) + stdevs, amplitude = CalcStdevs(row, stdev_range, stdevs) + wave_m[y+3] = stdevs + plot(stdevs, filename=f"radar{device_index}_stdevs.png", title=f"Radar Stedevs {device_index}", style='line') + + minutes = 1440 + + + device_index = 0 + y = 0 + for device in devices_list: + wave = wave_m[y][stdev_range: stdev_range + minutes] + plot(wave, + filename="radar_wave_min.png", + title="Radar Signal Min", + style='line') + # Create histogram with 1000 bins + hist, bins = np.histogram(wave, bins=1000, range=(0, 100)) + + #bin_centers = (bins[:-1] + bins[1:]) / 2 + hist_line = hist # These are your y values + + # Plot with proper axis labels + plot(hist_line, + filename="radar_histogram_min.png", + title="Radar Signal Histogram Min (1000 bins)", + style='line') + + wave = wave_m[y+1] + plot(wave, + filename="radar_wave_max.png", + title="Radar Signal", + style='line') + # Create histogram with 1000 bins + hist, bins = np.histogram(wave, bins=1000, range=(0, 100)) + + #bin_centers = (bins[:-1] + bins[1:]) / 2 + hist_line = hist # These are your y values + + # Plot with proper axis labels + plot(hist_line, + filename="radar_histogram_max.png", + title="Radar Signal Histogram Max(1000 bins)", + style='line') + + print(wave) + device_to_index += 1 + + #lets see this map + stretch_by = 5 + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + st = time.time() + for yy in range(stripes): + rgb_row = [] + row = wave_m[yy] + for x in range(minutes): + value = 1280 * row[x] / 100 + rgb_row.append(BestColor(value)) + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + filename = f"{deployment_id}/{deployment_id}_{ddate}_min_max_radar.png" + SaveImageInBlob(filename, arr_stretched, []) + + + return + elif function == "get_time_deltas": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + sensor = form_data.get('sensor') + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + radar_part = "" + sensor_data = {} + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + device_id = form_data.get('device_id') + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + + all_slices = {} + + cleaned_values = {} + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + st = time.time() + cleaned_values = [ + (line_part[i][0], (line_part[i][0] - line_part[i-1][0]).total_seconds() * 1000) + for i in range(1, len(line_part)) + ] + print(time.time()-st) + + if True: + # Create CSV content as a string + csv_content = "Record_Index,Timestamp,Value,Time_Diff_Seconds,Time_Diff_Milliseconds\n" + + for i in range(len(line_part)): + timestamp, value = line_part[i] + + if i == 0: + # First record has no previous record to compare + time_diff_seconds = 0 + time_diff_ms = 0 + else: + # Calculate time difference from previous record + prev_timestamp = line_part[i-1][0] + time_diff = timestamp - prev_timestamp + time_diff_seconds = time_diff.total_seconds() + time_diff_ms = time_diff_seconds * 1000 + + # Format the row + row = f"{i},{timestamp.isoformat()},{value},{round(time_diff_seconds, 6)},{round(time_diff_ms, 3)}\n" + csv_content += row + + # Write to file + with open(f'time_differences_{sensor}_{device_id}.csv', 'w', encoding='utf-8') as f: + f.write(csv_content) + + print(f"CSV file 'time_differences_{sensor}_{device_id}.csv' created successfully!") + + line_part_t = [(x[0].timestamp(), x[1]) for x in cleaned_values] + + sensor_data[sensor] = line_part_t + dataa = {} + all_slices = {} + all_slices[device_id] = sensor_data + dataa['Function'] = "time_deltas" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['device_id'] = device_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "get_sensor_deltas": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + sensor = form_data.get('sensor') + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + radar_part = "" + sensor_data = {} + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + device_id = form_data.get('device_id') + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + + all_slices = {} + + cleaned_values = {} + line_part = ReadSensorDeltas(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + st = time.time() + cleaned_values =line_part + #[ + #(line_part[i][0], (line_part[i][1] - line_part[i-1][1]) / (line_part[i][0] - line_part[i-1][0]).total_seconds()) + #for i in range(1, len(line_part)) + #if (line_part[i][0] - line_part[i-1][0]).total_seconds() > 0 + #and abs((line_part[i][1] - line_part[i-1][1]) / (line_part[i][0] - line_part[i-1][0]).total_seconds()) <= 100 + #] + #print(time.time()-st) + + + + line_part_t = [(x[0].timestamp(), x[1]) for x in cleaned_values] + + sensor_data[sensor] = line_part_t + dataa = {} + all_slices = {} + all_slices[device_id] = sensor_data + dataa['Function'] = "time_deltas" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['device_id'] = device_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "request_single_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + devices_list = form_data.get('devices_list') + radar_details = {} + #devices_list = '[267,560,"?",null,"64B70888F6F0"]' + #devices_list = '[[267,560,"?",null,"64B70888F6F0"],[268,561,"?",null,"64B70888F6F1"]]' + sensor_list_loc = [form_data.get('sensor_list')] + is_nested, device_details = check_and_parse(devices_list) + if not is_nested: + device_ids_list = [device_details[1]] + well_ids_list = [device_details[0]] + else: + device_ids_list = list(map(lambda x: x[1], device_details)) + well_ids_list =list(map(lambda x: x[0], device_details)) + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + #we need to + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + + days = (epoch_to_utc - epoch_from_utc) / (60 * 1440) + + well_id = well_ids_list[0] + all_slices = {} + radar_part = "" + if len(device_details) > 4: + device_id2_mac = {device_details[1]: device_details[4]} + #device_id2_mac = {device_details[1]: device_details[3]} + #epoch_to = '1730592010' #smal sample to test + #radar_part = form_data.get('radar_part') we need to find what radar part is configured in device settings + if len(device_details) > 5: + radar_part_all = device_details[5] + + if type(radar_part_all) == str: + radar_part_all = ["s3_max",int(radar_part_all)] + elif type(radar_part_all) == int: + radar_part_all = ["s3_max",radar_part_all] + elif type(radar_part_all) == list: + pass + + + + else: + radar_part_all = ["s3_max",12] + + + if len(radar_part_all) > 1: + radar_part = radar_part_all[0] + #we need only column name and not min or max here + if "_" in radar_part: + radar_parts = radar_part.split("_") + radar_part = radar_parts[0] + radar_details[device_details[1]] = radar_part_all + + #devices = GetVisibleDevices(deployment_id) + temp_calib, humid_calib = GetCalibMaps(device_ids_list) + + for device_id in device_ids_list: + + sensor_data = {} + for sensor in sensor_list_loc: + st = time.time() + if days < 3: + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + elif days < 14: + bucket_size = "1m" + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + else: + bucket_size = "10m" + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + #Lets apply calibration: + if sensor == "temperature": + temperature_calib = temperature_offset #float(temp_calib[device_id].split(",")[2]) + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + if sensor == "humidity": + line_part = [(timestamp, value + humidity_offset) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + + if False: + # Create CSV content as a string + csv_content = "Record_Index,Timestamp,Value,Time_Diff_Seconds,Time_Diff_Milliseconds\n" + + for i in range(len(line_part)): + timestamp, value = line_part[i] + + if i == 0: + # First record has no previous record to compare + time_diff_seconds = 0 + time_diff_ms = 0 + else: + # Calculate time difference from previous record + prev_timestamp = line_part[i-1][0] + time_diff = timestamp - prev_timestamp + time_diff_seconds = time_diff.total_seconds() + time_diff_ms = time_diff_seconds * 1000 + + # Format the row + row = f"{i},{timestamp.isoformat()},{value},{round(time_diff_seconds, 6)},{round(time_diff_ms, 3)}\n" + csv_content += row + + # Write to file + with open('time_differences.csv', 'w', encoding='utf-8') as f: + f.write(csv_content) + + print("CSV file 'time_differences.csv' created successfully!") + + #print("@1", time.time() - st) + #first = 3300 + #last = 3400 + #line_part = line_part[first:last] + line_part_t = [] + #st = time.time() + #line_part_t = [tuple(x[:2]) for x in line_part] + #print(time.time() - st) + #st = time.time() + #line_part_t = list({(dt.timestamp(), value) for dt, value in line_part}) + #print(time.time() - st) + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + #print("@2", time.time() - st) + + #Lets add point in minute 0 and minute 1439 + + #st = time.time() + #cleaned_values = clean_data_fast(line_part_t, window=5, threshold=2.0) + #print("@3", time.time() - st) + + sensor_data[sensor] = cleaned_values + + + if len(device_details) > 4: + all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + else: + all_slices[device_id] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + dataa = {} + dataa['Function'] = "single_slicedata" + dataa['devices_list'] = devices_list + dataa['all_slices'] = all_slices + dataa['radar_details'] = radar_details + dataa['time_zone_st'] = time_zone_s + dataa['well_id'] = well_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + elif function == "get_sensor_bucketed_data_by_room_sensor": + # Inputs: + # user_name and token + # deployment_id - from which report gets deployment set (all rooms and devices) to get timezone + # date - one day in a format YYYY-MM-DD + # sensor - temperature/radar/etc.. see full list + # (tells what sensor data to be retrieved) + # "voc" for all smell use s4 (lower reading is higher smell, max=0 find min for 100%) + # "radar" returns s28 + # radar_part - optional and applies only to radar (tells which segment of radar to be retrieved) + # bucket_size - ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + # location - room name (has to be unique) + # data_type - ML + # Output: son structure with the following info + # chart_data with rooms : [list] + deployment_id = form_data.get('deployment_id') + selected_date = form_data.get('date') + sensor = form_data.get('sensor') # one sensor + radar_part = form_data.get('radar_part') + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + bucket_size = "no" if (result := form_data.get('bucket_size')) in (None, "") else (result.strip() if result.strip() in buckets else "no") + #bucket_size = res2 if (res := form_data.get('bucket_size')) is not None and (res2 := str(res).strip()) and res2 in {'no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'} else 'no' + location = form_data.get('location') + data_type = form_data.get('data_type') + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) + + # obtain devices_list for deployment_id + selected_date = selected_date.replace("_","-") + devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + sensor_data = {} + units = "°C" + if "America" in time_zone_s: + units = "°F" + # see https://www.w3schools.com/cssref/css_colors.php + sensor_props = {"temperature": ["red", units], + "humidity": ["blue", "%"], + "voc": ["orange", "PPM"], + "co2": ["orange", "PPM"], + "pressure": ["magenta", "Bar"], + "radar": ["cyan", "%"], + "light": ["yellow", "Lux"]} + + current_time_la = datetime.datetime.now(pytz.timezone(time_zone_s)) + formatted_time = current_time_la.strftime('%Y-%m-%dT%H:%M:%S') #"2025-02-06T20:09:00" + + result_dictionary = { + "last_report_at": formatted_time, + "color": sensor_props[sensor][0] if sensor in s_table else "grey", + "units": sensor_props[sensor][1] if sensor in s_table else "?" + } + #sensor_mapping = {"co2": "s4", "voc": "s9"} + #sensor = sensor_mapping.get(sensor, sensor) + temp_calib, humid_calib = GetCalibMaps(device_ids) + #print(device_ids) + #print(temp_calib) + #print(humid_calib) + #print("++++++++++++++++++") + chart_data = [] + # example data in each element of devices_list is (266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + for well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to in devices_list: + loc_and_desc = location_name + if description != None and description != "": + loc_and_desc = loc_and_desc + " " + description + + if loc_and_desc == location: + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + if sensor == "temperature": + if "," in temp_calib[device_id]: + temperature_calib = float(temp_calib[device_id].split(",")[2]) + else: + temperature_calib = -10 + + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + + if sensor == "humidity": + line_part = [(timestamp, value + humidity_offset) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + line_part_t = [] + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + compressed_readings = convert_timestamps_lc(cleaned_values, time_zone_s) + if sensor == "temperature": + if units == "°F":#"America" in time_zone_s: + compressed_readings = CelsiusToFahrenheitList(compressed_readings) + + sensor_data[sensor] = compressed_readings + chart_data.append({'name': location_name, 'data': compressed_readings}) + result_dictionary['chart_data'] = chart_data + payload = result_dictionary + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "get_sensor_data_by_deployment_id": + # Inputs: + # user_name and token + # deployment_id - from which report gets deployment set (all rooms and devices) + # date - one day in a format YYYY-MM-DD + # sensor - temperature/radar/etc.. see full list (tells what sensor data to be retrieved) + # radar_part - optional and applies only to radar (tells which segment of radar to be retrieved) + # bucket_size - ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + # data_type - ML + # Output: son structure with the following info + # chart_data with rooms : [list] + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + sensor = form_data.get('sensor') # one sensor + radar_part = form_data.get('radar_part') + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + bucket_size = "no" if (result := form_data.get('bucket_size')) in (None, "") else (result.strip() if result.strip() in buckets else "no") + #bucket_size = res2 if (res := form_data.get('bucket_size')) is not None and (res2 := str(res).strip()) and res2 in {'no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'} else 'no' + data_type = form_data.get('data_type') + + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + # obtain devices_list for deployment_id + selected_date = selected_date.replace("_","-") + #timee = LocalDateToUTCEpoch(selected_date, time_zone_s)+5 + devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + sensor_data = {} + # see https://www.w3schools.com/cssref/css_colors.php + sensor_props = {"temperature": ["red", "°C"], + "humidity": ["blue", "%"], + "voc": ["orange", "PPM"], + "co2": ["orange", "PPM"], + "pressure": ["magenta", "Bar"], + "radar": ["cyan", "%"], + "light": ["yellow", "Lux"]} + result_dictionary = { + "last_report_at": "2025-02-06T20:09:00", + "color": sensor_props[sensor][0] if sensor in s_table else "grey", + "units": sensor_props[sensor][1] if sensor in s_table else "?" + } + #sensor_mapping = {"co2": "s4", "voc": "s9"} + #sensor = sensor_mapping.get(sensor, sensor) + + chart_data = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + if sensor == "temperature": + temperature_calib = float(temp_calib[device_id].split(",")[2]) + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + line_part_t = [] + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + compressed_readings = convert_timestamps_lc(cleaned_values, time_zone_s) + + #compressed_readings = [(time.strftime("%H:%M", time.gmtime(lst[0][0])), float(sum(t for _, t in lst)/len(lst))) + #for _, lst in ((k, list(g)) + #for k, g in itertools.groupby(cleaned_values, key=lambda x: time.gmtime(x[0]).tm_hour))] + sensor_data[sensor] = compressed_readings + chart_data.append({'name': location_name, + 'data': compressed_readings}) + result_dictionary['chart_data'] = chart_data + #all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + #is_neste, device_details = check_and_parse(devices_list) + #if not is_nested: + #device_ids_list = [device_details[1]] + #well_ids_list = [device_details[0]] + #else: + #device_ids_list = list(map(lambda x: x[1], device_details)) + #well_ids_list =list(map(lambda x: x[0], device_details)) + #well_id = well_ids_list[0] + #all_slices = {} + #device_id2_mac = {device_details[1]: device_details[4]} + #for device_id in device_ids_list: + #device_id2_mac + #sensor_data = {} + #for sensor in sensor_list_loc: + #st = time.time() + #line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + #window = sensor_legal_values[sensor][2] + #line_part_t = [] + #line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + #st = time.time() + #cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + #cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + #sensor_data[sensor] = cleaned_values + #all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + #dataa = {} + #dataa['Function'] = "single_slicedata" + #dataa['devices_list'] = devices_list + #dataa['all_slices'] = all_slices + #dataa['time_zone_st'] = time_zone_s + #dataa['well_id'] = well_id + #resp.media = package_response(dataa) + #resp.status = falcon.HTTP_200 + result_dictionary2 = { + "alert_text": "No alert", + "alert_color": "bg-green-100 text-green-700", + "last_report_at": "ISO TIMESTAMP", + "chart_data": [ + { + "rooms": [ + { "name": "Bathroom", + "data": [ + {"title": "12AM","value": 20}, + {"title": "01AM","value": 20}, + {"title": "02AM","value": 26}, + {"title": "03AM","value": 16}, + {"title": "04AM","value": 27}, + {"title": "05AM","value": 23}, + {"title": "06AM","value": 26}, + {"title": "07AM","value": 17}, + {"title": "08AM","value": 18}, + {"title": "09AM","value": 21}, + {"title": "10AM","value": 28}, + {"title": "11AM","value": 24}, + {"title": "12PM","value": 18}, + {"title": "01PM","value": 27}, + {"title": "02PM","value": 27}, + {"title": "03PM","value": 19}, + {"title": "04PM","value": 0}, + {"title": "05PM","value": 0}, + {"title": "06PM","value": 0}, + {"title": "07PM","value": 0}, + {"title": "08PM","value": 0}, + {"title": "09PM","value": 0}, + {"title": "10PM","value": 0}, + {"title": "11PM","value": 0} + ] + }, + { "name": "Kitchen", + "data": [ + {"title": "00AM","value": 19}, + {"title": "01AM","value": 10}, + {"title": "02AM","value": 8}, + {"title": "03AM","value": 14}, + {"title": "04AM","value": 20}, + {"title": "05AM","value": 8}, + {"title": "06AM","value": 7}, + {"title": "07AM","value": 17}, + {"title": "08AM","value": 3}, + {"title": "09AM","value": 19}, + {"title": "10AM","value": 4}, + {"title": "11AM","value": 6}, + {"title": "12PM","value": 4}, + {"title": "01PM","value": 14}, + {"title": "02PM","value": 17}, + {"title": "03PM","value": 20}, + {"title": "04PM","value": 19}, + {"title": "05PM","value": 15}, + {"title": "06PM","value": 5}, + {"title": "07PM","value": 19}, + {"title": "08PM","value": 3}, + {"title": "09PM","value": 30}, + {"title": "10PM","value": 1}, + {"title": "11PM","value": 12 } + ] + }, + { "name": "Living Room", + "data": [ + {"title": "00AM","value": 25}, + {"title": "01AM","value": 24}, + {"title": "02AM","value": 19}, + {"title": "03AM","value": 20}, + {"title": "04AM","value": 22}, + {"title": "05AM","value": 20}, + {"title": "06AM","value": 11}, + {"title": "07AM","value": 5}, + {"title": "08AM","value": 16}, + {"title": "09AM","value": 22}, + {"title": "10AM","value": 23}, + {"title": "11AM","value": 14}, + {"title": "12PM","value": 0}, + {"title": "01PM","value": 7}, + {"title": "02PM","value": 25}, + {"title": "03PM","value": 29}, + {"title": "04PM","value": 23}, + {"title": "05PM","value": 27}, + {"title": "06PM","value": 27}, + {"title": "07PM","value": 20}, + {"title": "08PM","value": 2}, + {"title": "09PM","value": 24}, + {"title": "10PM","value": 21}, + {"title": "11PM","value": 14 } + ] + } + ] + } + ] + } + payload = result_dictionary + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + #AddToLog(payload) + #return + elif function == "request_device_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + epoch_from_utc = form_data.get('epoch_from') + epoch_to_utc = form_data.get('epoch_to') + device_id = form_data.get('device_id') + well_id = form_data.get('well_id') + MAC = form_data.get('MAC') + sensor_list_loc = form_data.get('sensors_list') + sensor_list = sensor_list_loc.split(",") + device_ids_list = [device_id] + well_ids_list = [well_id] + maps_dates, positions_list, timezone_s = GetDeploymentDatesBoth(deployment_id) + + data_type = "RL" + #epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + + #epoch_to = '1730592010' #smal sample to test + radar_part = form_data.get('radar_part') + well_id = well_ids_list[0] + all_slices = {} + #device_id2_mac = {device_details[1]: device_details[4]} + for device_id in device_ids_list: + #device_id2_mac + sensor_data = {} + for sensor in sensor_list: + st = time.time() + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + window = sensor_legal_values[sensor][2] + #print("@1", time.time() - st) + #first = 3300 + #last = 3400 + #line_part = line_part[first:last] + line_part_t = [] + #st = time.time() + #line_part_t = [tuple(x[:2]) for x in line_part] + #print(time.time() - st) + #st = time.time() + #line_part_t = list({(dt.timestamp(), value) for dt, value in line_part}) + #print(time.time() - st) + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + #cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + #print("@2", time.time() - st) + + #Lets add point in minute 0 and minute 1439 + + #st = time.time() + #cleaned_values = clean_data_fast(line_part_t, window=5, threshold=2.0) + #print("@3", time.time() - st) + cleaned_values = ScaleToCommon(cleaned_values_t, sensor) + sensor_data[sensor] = cleaned_values + all_slices[device_id] = sensor_data + + dataa = {} + dataa['Function'] = "device_slicedata" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['proximity'] = positions_list + dataa['well_id'] = well_id + dataa['MAC'] = MAC + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + #return + elif function == "request_single_radar_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + devices_list = form_data.get('devices_list') + ctrl_key_state = form_data.get('ctrl_key_state') + alt_key_state = form_data.get('alt_key_state') + #devices_list = '[267,560,"?",null,"64B70888F6F0"]' + #devices_list = '[[267,560,"?",null,"64B70888F6F0"],[268,561,"?",null,"64B70888F6F1"]]' + sensor_index_list = [form_data.get('sensor_index_list')] + is_nested, device_details = check_and_parse(devices_list) + if not is_nested: + device_ids_list = [device_details[1]] + well_ids_list = [device_details[0]] + else: + device_ids_list = list(map(lambda x: x[1], device_details)) + well_ids_list =list(map(lambda x: x[0], device_details)) + + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + + #epoch_to = '1730592010' #smal sample to test + radar_part = form_data.get('radar_part') + well_id = well_ids_list[0] + all_slices = {} + device_id2_mac = {device_details[1]: device_details[4]} + for device_id in device_ids_list: + device_id2_mac + sensor_data = {} + for sensor_index in sensor_index_list: + st = time.time() + sensor = ["m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m08_max", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s28_max", "s28_min"][int(sensor_index)] + + line_part = ReadRadarDetail(device_id, sensor, epoch_from_utc, epoch_to_utc, alt_key_state) + window = sensor_legal_values["radar"][2] + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + if len(sensor) < 4: + sensor_data[sensor+"_max"] = cleaned_values + else: + sensor_data[sensor] = cleaned_values + all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + dataa = {} + dataa['Function'] = "single_slicedata" + dataa['devices_list'] = devices_list + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['well_id'] = well_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + elif function == "get_deployment": + blob_data = read_file("deployment.html") + deployment_id = form_data.get('deployment_id') + #lets update "Deployments" select + users = GetUsersFromDeployments(privileges) + blob_data = UpdateDeploymentsSelector(blob_data, users, False, deployment_id) + + resp.content_type = "text/html" + resp.text = blob_data + return + elif function == "get_deployment_j": + deployment_id = form_data.get('deployment_id') + time_zone_st = GetTimeZoneOfDeployment(deployment_id) + date = form_data.get('date') + if date == None: + + # Get today's date + local_timezone = pytz.timezone(time_zone_st) # Replace with your local timezone + date = datetime.datetime.now(local_timezone).strftime('%Y-%m-%d') + + #epoch_from_utc = int(datetime.datetime.strptime(date, "%Y-%m-%d").timestamp()) + #devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + + dataa = {} + dataa['Function'] = "deployment_details" + if privileges == "-1": + deployment = DeploymentDetails(deployment_id) + dataa['deployment_details'] = deployment + else: + privileges = privileges.split(",") + if deployment_id in privileges: + deployment = DeploymentDetails(deployment_id) + dataa['deployment_details'] = deployment + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + elif function == "set_floor_layout": + deployment_id = form_data.get('deployment_id') + layout = form_data.get('layout') + + if privileges == "-1" or deployment_id in privileges: + ok = StoreFloorPlan(deployment_id, layout) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + else: + payload = {'ok': 0, 'error': "not allowed"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + return + elif function == "get_floor_layout": + deployment_id = form_data.get('deployment_id') + + dataa = {} + dataa['Function'] = "deployment_details" + if privileges == "-1": + layout = GetFloorPlan(deployment_id) + dataa['layout'] = layout + else: + privileges = privileges.split(",") + if deployment_id in privileges: + layout = GetFloorPlan(deployment_id) + dataa['layout'] = layout + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + elif function == "get_beneficiary": + user_id = form_data.get('user_id') + all_beneficiaries = ListBeneficiaries(privileges, user_id) + beneficiaries_list = [] + for beneficiary_temp in all_beneficiaries: + beneficiaries_list.append(str(beneficiary_temp[0])) + + dataa = {} + dataa['Function'] = "beneficiary_details" + if user_id in beneficiaries_list: + beneficiary = UserDetails(user_id) + #lets remove fields not relevant for beneficiary + try: + del beneficiary['time_edit'] + except: + pass + + try: + del beneficiary['user_edit'] + except: + pass + + try: + del beneficiary['access_to_deployments'] + except: + pass + dataa['beneficiary_details'] = beneficiary + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + elif function == "get_caretaker": + + user_name = form_data.get('user_name') + + + all_caretakers = ListCaretakers(privileges, user_name) + if len(all_caretakers) > 1: + user_id = form_data.get('user_id') + else: + user_id = str(all_caretakers[0][0]) + + caretakers_list = [] + for caretakers_temp in all_caretakers: + caretakers_list.append(str(caretakers_temp[0])) + + dataa = {} + dataa['Function'] = "caretaker_details" + if user_id in caretakers_list: + caretaker = UserDetails(user_id) + #lets remove fields not relevant for beneficiary + try: + del caretaker['time_edit'] + except: + pass + + try: + del caretaker['user_edit'] + except: + pass + + dataa['caretaker_details'] = caretaker + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "get_device": + device_id = form_data.get('device_id') + device_mac = form_data.get('mac') + min_well_id = form_data.get('min_well_id') + if min_well_id != None: + min_well_id = int(min_well_id) + + if device_mac != None: + device_det = GetDeviceDetailsSingleFromMac(device_mac) + print(device_det) + dataa = {} + dataa['Function'] = "device_details" + dataa['device_details'] = device_det + if device_det == {}: + dataa['next_well_id'] = GetNextWellId(min_well_id) + else: + devices = GetVisibleDevices(privileges) + dataa = {} + dataa['Function'] = "device_details" + dataa['device_details'] = {} + if privileges == "-1": + #device_det = GetDeviceDetails(device_id) + device_det = GetDeviceDetailsSingle(device_id) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + dataa['device_details'] = device_det + else: + devices_list = [] + for device_id_temp in devices: + devices_list.append(str(device_id_temp[0])) + + if device_id in devices_list: + device_det = GetDeviceDetailsSingle(device_id) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + + + dataa['device_details'] = device_det + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + + + elif function == "set_deployment": + + user_name = form_data.get('user_name') + token = form_data.get('token') + password = form_data.get('password') + + + if user_name == "" or user_name == None: #new user + + user_name + resp.media = package_response("Log-Out", HTTP_401) + return + + if password != "" and password != None: + privileges, user_id = ValidUser(user_name, password) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + + + if token != "" and token != None: + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + + deployment = form_data.get('deployment') + beneficiary_name = form_data.get('beneficiary_name') + beneficiary_email = form_data.get('beneficiary_email') + beneficiary_address = form_data.get('beneficiary_address') + caretaker_username = form_data.get('caretaker_username') + caretaker_email = form_data.get('caretaker_email') + num_people = int(form_data.get('num_people')) + pets = int(form_data.get('pets')) + year_born = int(form_data.get('year_born')) + gender = form_data.get('gender') + race = form_data.get('race') + gps_lat = form_data.get('gps_lat') + gps_lng = form_data.get('gps_lng') + devices = form_data.get('devices') + devices_list = json.loads(devices) + wifis = form_data.get('wifis') + wifis_list = json.loads(wifis) + + print(deployment) + print(beneficiary_name) + print(beneficiary_email) + print(beneficiary_address) + print(caretaker_username) + print(caretaker_email) + print(num_people) + print(pets) + print(year_born) + print(gender) + print(race) + print(gps_lat) + print(gps_lng) + print(devices_list) + print(wifis_list) + + #lets check if devices listed are not part of existing deployment + success, result = DevicesNotUsed(devices, user_name) + + if success: + if result["deployed"]: + error_string = f"These devices are already deployed: {result['deployed']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if result["not_found"]: + error_string = f"These devices are not available: {result['not_found']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if not result["deployed"] and not result["not_found"]: + print("All devices are available for deployment") + ok, error_string = StoreDisclaimer2DB(form_data) + + if ok == 1: + #Lets check if this user already has account or not + if not AccountExists(user_name): + #lets create new account for this caretaker + if password == None or password == "": + password = CreatePassword(8) + + #lets suplement form_data with parts needed for existing StoreCaretaker2DB function + editing_user_id = "0" #specify if editing existing user, otherwise "0" + user_id = "0" #user that is adding this record. New user so "0" + form_data['role_ids'] = "1,2" + form_data['access_to_deployments'] = "45" + #form_data['email'] = "" #this one matches + form_data['new_user_name'] = form_data['user_name'] + form_data['first_name'] = form_data['firstName'] + form_data['last_name'] = form_data['lastName'] + form_data['address_street'] = "" + form_data['address_city'] = "" + form_data['address_zip'] = "" + form_data['address_state'] = "" + form_data['address_country'] = "" + form_data['phone_number'] = form_data['phone'] + form_data['picture'] = "/" + form_data['key'] = password + + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + + if ok == 1: + + #we need to call cd ~/mqtt-auth-service/acl_manager.py + + SendWelcomeEmail(form_data['email'], form_data['first_name'], form_data['last_name'], devices, form_data['phone_number'], form_data['new_user_name'], form_data['key']) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + privileges, user_id = ValidUser(user_name, password) + if user_id == "0": #bad password + error_string = f"Password does not match user {user_name}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + else: + error_string = f"Error: {result}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + + elif function == "purge_phantom_records": + last_valid_id = int(form_data.get('last_valid_device_id')) + + try: + conn = get_db_connection() + with conn.cursor() as cur: + # 1. Check for non-null radar_threshold records + cur.execute(""" + SELECT COUNT(*) + FROM public.devices + WHERE device_id > %s AND radar_threshold IS NOT NULL + """, (last_valid_id,)) + + non_null_count = cur.fetchone()[0] + + if non_null_count > 0: + resp.media = { + "status": "aborted", + "reason": f"Found {non_null_count} records with device_id > {last_valid_id} that have radar_threshold NOT NULL" + } + return + + # 2. Delete phantom records + cur.execute(""" + DELETE FROM public.devices + WHERE device_id > %s AND radar_threshold IS NULL + """, (last_valid_id,)) + + deleted_count = cur.rowcount + + if deleted_count > 0: + # 3. Reset sequence to the ACTUAL maximum device_id in the table + cur.execute("SELECT COALESCE(MAX(device_id), 0) FROM public.devices") + actual_max_id = cur.fetchone()[0] + + # Reset sequence to actual max + cur.execute("SELECT setval('devices_device_id_seq', %s, true)", (actual_max_id,)) + + # Get next ID to verify + cur.execute("SELECT nextval('devices_device_id_seq')") + next_id = cur.fetchone()[0] + + conn.commit() + + resp.media = { + "status": "success", + "deleted_count": deleted_count, + "actual_max_device_id": actual_max_id, + "sequence_reset_to": actual_max_id, + "next_device_id": next_id + } + else: + resp.media = { + "status": "no_action", + "message": "No phantom records found to delete" + } + + except Exception as e: + conn.rollback() + resp.media = {"error": str(e)} + resp.status = falcon.HTTP_500 + + elif function == "request_deployment_map_new": + st = time.time() + print(f"$0 ----{time.time() - st}") + deployment_id = form_data.get('deployment_id') + map_type = form_data.get('map_type') + print(f"$1 ----{time.time() - st}") + maps_dates, positions_list, timezone_s = GetDeploymentDatesBoth(deployment_id) + print(f"$2 ----{time.time() - st}") + datee = form_data.get('date') + if maps_dates != []: + + if datee == "2022-4-2": #that one is default in HTML so disregard + datee = maps_dates[0] + + locations_desc_map = {} + for details in positions_list: + well_id = details[0] + location = details[2] + if details[3] != None and details[3] != "": + location = location +" "+ details[3] + + if details[6] != None and details[6] != "": + location = location +" "+ details[6] + + MAC = details[4] + locations_desc_map[well_id] = location + print(f"$3 ----{time.time() - st}") + + dataa = {} + dataa['Function'] = "deployments_maps_report" + dataa['proximity'] = positions_list + maps_dates.sort(reverse = True) + dataa['maps_dates'] = maps_dates + dataa['device_count'] = len(positions_list) + dataa['time_zone'] = timezone_s + dataa['map_type'] = map_type + + #MACs_list = GetMACsListSimple(positions_list) + #MACs_map = {} + + #for details in positions_list: + # id = details[0] + # MAC = details[3] + # MACs_map[id] = MAC + #for i in range(len(MACs_list)): + # MACs_map[devices_list[i]] = MACs_list[i][0] + + id = positions_list[0][0] + #dataa['MACs_map'] = MACs_map + dataa['locations_desc_map'] = locations_desc_map + #proximity_list = proximity.split(",") + print(f"$4 ----{time.time() - st}") + + if id < 200: + checkmarks_string = 'T>\n' + checkmarks_string = checkmarks_string + 'H>\n' + checkmarks_string = checkmarks_string + 'P>\n' + checkmarks_string = checkmarks_string + 'C>\n' + checkmarks_string = checkmarks_string + 'V>\n' + checkmarks_string = checkmarks_string + 'L>\n' + checkmarks_string = checkmarks_string + 'R>
    ' + else: #>200 = ["Temperature", "Humidity", "Pressure", "Light", "Radar", "VOC"] + + checkmarks_string = 'T>\n' + checkmarks_string = checkmarks_string + 'H>\n' + checkmarks_string = checkmarks_string + 'P>\n' + checkmarks_string = checkmarks_string + 'L>\n' + checkmarks_string = checkmarks_string + 'R>\n' + + checkmarks_string = checkmarks_string + 'S0>\n' + checkmarks_string = checkmarks_string + 'S1>\n' + checkmarks_string = checkmarks_string + 'S2>\n' + checkmarks_string = checkmarks_string + 'S3>\n' + checkmarks_string = checkmarks_string + 'S4>\n' + checkmarks_string = checkmarks_string + 'S5>\n' + checkmarks_string = checkmarks_string + 'S6>\n' + checkmarks_string = checkmarks_string + 'S7>\n' + checkmarks_string = checkmarks_string + 'S8>\n' + checkmarks_string = checkmarks_string + 'S9>
    ' + + checked_or_not = " checked" + + for index in range(len(positions_list)): + details = positions_list[index] + device_id = details[0] + location = details[2] + if details[3] != None and details[3] != "": + location = location + " " + details[3] + + if details[6] != None and details[6] != "": + location = location + " " + details[6] + + checkmarks_string = checkmarks_string + str(device_id) + '>\n' + checked_or_not = '' + + print(f"$5 ----{time.time() - st}") + + dataa['checkmarks'] = checkmarks_string + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "request_proximity": + deployment = form_data.get('deployment_id') + timee = form_data.get('time') + #timee = StringToEpoch(datee) + #print(deployment, timee) + well_ids, device_ids = GetProximityList(deployment, timee) + #print(proximity) + dataa = {} + dataa['Function'] = "proximity_report" + if len(well_ids) > 0: + dataa['proximity'] = well_ids + else: + dataa['proximity'] = [] + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + elif function == "request_devices": + deployment_id = form_data.get('deployment_id') + group_id = form_data.get('group_id') + location = form_data.get('location') + if location == "0": + location = "All" + is_fresh = form_data.get('is_fresh') + matching_devices = GetMatchingDevices(privileges, group_id, deployment_id, location) + dataa = {} + dataa['Function'] = "devices_report" + if len(matching_devices) > 0: + dataa['devices'] = matching_devices + else: + dataa['devices'] = [] + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "get_deployment_details": + deployment_id = form_data.get('deployment_id') + group_id = form_data.get('group_id') + location = form_data.get('location') + if location == "0": + location = "All" + is_fresh = form_data.get('is_fresh') + matching_devices = GetMatchingDevicesComplete(privileges, group_id, deployment_id, location) + deployment = DeploymentDetails(deployment_id) + dataa = {} + dataa['Function'] = "devices_report" + if len(matching_devices) > 0: + dataa['devices'] = matching_devices + else: + dataa['devices'] = [] + + if len(deployment) > 0: + dataa['details'] = deployment + else: + dataa['details'] = {} + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "device_form": + import uuid + request_id = str(uuid.uuid4())[:8] + logger.debug(f"[{request_id}] device_form ENTRY") + + device_id = None + if 'editing_device_id' in form_data: + device_id = int(form_data.get('editing_device_id')) + logger.debug(f"[{request_id}] Found editing_device_id: {device_id}") + else: + logger.debug(f"[{request_id}] No editing_device_id found, device_id = {device_id}") + + ok = 0 + logger.debug(f"[{request_id}] privileges = {privileges}") + + if privileges == "-1": + logger.debug(f"[{request_id}] CALLING StoreDevice2DB with device_id: {device_id}") + ok = StoreDevice2DB(form_data, device_id) + logger.debug(f"[{request_id}] StoreDevice2DB returned: {ok}") + payload = {'ok': 1} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + logger.debug(f"[{request_id}] Non-admin path...") + if device_id != None: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id: + logger.debug(f"[{request_id}] CALLING StoreDevice2DB in loop with device_id: {device_id}") + ok = StoreDevice2DB(form_data, device_id) + logger.debug(f"[{request_id}] StoreDevice2DB in loop returned: {ok}") + break + else: + logger.debug(f"[{request_id}] device_id is None, returning error") + payload = {'ok': 0} + resp.media = package_response(payload) + resp.status = falcon.HTTP_500 + return + + logger.debug(f"[{request_id}] Final ok value: {ok}") + elif function == "device_set_group": + group_id = int(form_data.get('group_id')) + + MAC = form_data.get('mac') + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = StoreGroupToDevice(device_id_or_mac, group_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreGroupToDevice(device_id_or_mac, group_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_set_well_id": + well_id = int(form_data.get('well_id')) + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + + ok = "" + if privileges == "-1": + ok = StoreWellIdToDevice(device_id_or_mac, well_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreWellIdToDevice(device_id_or_mac, well_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_get_live": + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = GetDeviceLive(device_id_or_mac, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = GetDeviceLive(device_id_or_mac, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_set_network_id": + network_id = int(form_data.get('network_id')) + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = StoreNetworkIdToDevice(device_id_or_mac, network_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreNetworkIdToDevice(device_id_or_mac, network_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_reboot": + if 'mac' in form_data: + MAC = form_data.get('mac').upper() + device_id_or_mac = MAC + else: + device_id = int(form_data.get('device_id')) + device_id_or_mac = device_id + + ok = "" + if privileges == "-1": + ok = DeviceReboot(device_id_or_mac, user_name) + else: + devices = GetVisibleDevices(privileges) + #for this to work, device_id needs to be specified, not MAC! + for device in devices: + if device[0] == device_id_or_mac: + ok = DeviceReboot(device_id_or_mac, user_name) + break + + print(f"OK = {ok}") + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_delete": + + #check if admin! + + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "alarm_on_off": + deployment_id = form_data.get('deployment_id') + alarm_on = int(form_data.get('alarm_on')) + + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + # Lets prepare data to do same as store_alarms function + #read alarm_deployment_settings and all alarm_device_settings from db, and armm all bits that are enabled + deployment_alarms_json, device_alarms_json_map = GetAlarmAllDetails(deployment_id) + deployment_alarms = json.loads(deployment_alarms_json) + enabled = deployment_alarms["enabled"] + if alarm_on == 0: + if GetBit(enabled, 2): + enabled = set_character(enabled, 2, "0") + deployment_alarms["enabled"] = enabled + deployment_alarms_json = json.dumps(deployment_alarms) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + ok = StoreAlarms2DBSimple(deployment_id, 0, deployment_alarms_json, "") + else: + if not GetBit(enabled, 2): + enabled = set_character(enabled, 2, "1") + deployment_alarms["enabled"] = enabled + deployment_alarms_json = json.dumps(deployment_alarms) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + ok = StoreAlarms2DBSimple(deployment_id, 0, deployment_alarms_json, "") + + + if False: #no need to do it since every new_alarms call reads alarm_deployment_settings_ always + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + record_json = json.dumps(record) + redis_conn.lpush('new_alarms', record_json) + + if alarm_on != 0: + for device_id in device_alarms_json_map: + device_alarms_json = device_alarms_json_map[device_id] + device_alarms = json.loads(device_alarms_json) + enabled_alarms = device_alarms["enabled_alarms"] + armed_states = device_alarms["armed_states"] + + if GetBit(enabled_alarms, 8): + armed_states = set_character(armed_states, 8, "1") + if GetBit(enabled_alarms, 9): + armed_states = set_character(armed_states, 9, "1") + if GetBit(enabled_alarms, 10): + armed_states = set_character(armed_states, 10, "1") + + device_alarms["armed_states"] = armed_states + device_alarms_json = json.dumps(device_alarms) + redis_conn.set(f'alarm_device_settings_{device_id}', device_alarms_json) + ok = StoreAlarms2DBSimple(0, device_id, "", device_alarms_json) + + #of course it is needed, how will well-alerts know that new data is stored to db? + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('new_alarms', record_json) + + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_alarm_state": + deployment_id = form_data.get('deployment_id') + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + # Lets prepare data to do same as store_alarms function + #read alarm_deployment_settings and all alarm_device_settings from db, and armm all bits that are enabled + deployment_alarms_json, device_alarms_json_map = GetAlarmAllDetails(deployment_id) + deployment_alarms = json.loads(deployment_alarms_json) + enabled = deployment_alarms["enabled"] + if not GetBit(enabled, 2): + alarm_state = 2 #off + else: + #if any device was trigerred, show 0, otherwise 1 + alarm_state = 1 + for device_id in device_alarms_json_map: + device_alarms_json = device_alarms_json_map[device_id] + device_alarms = json.loads(device_alarms_json) + enabled_alarms = device_alarms["enabled_alarms"] + armed_states = device_alarms["armed_states"] + + if GetBit(enabled_alarms, 8): + if not GetBit(armed_states, 8): #if 0 + alarm_state = 0 + break + if GetBit(enabled_alarms, 9): + if not GetBit(armed_states, 9): + alarm_state = 0 + break + if GetBit(enabled_alarms, 10): + if not GetBit(armed_states, 10): + alarm_state = 0 + break + + + payload = {'ok': 1, 'alarm_state':alarm_state} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "submit_mobile_message": + message = form_data.get('message') + mqtt_id = form_data.get("mqtt_id") + privileges, user_id = GetPriviledgesAndUserId(user_name) + if "function" in message: + current_utc_time = datetime.datetime.now(timezone.utc) + message_map = json.loads(message) + func = message_map["function"] + + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + ok = 1 + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = int(current_utc_time.timestamp() *1000) + + + sql = f""" + INSERT INTO public.mobile_clients_messages + (time, mqtt_id, message, function) + VALUES + ({current_epoch_time}, '{CleanObject(mqtt_id)}','{CleanObject(message)}','{CleanObject(func)}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + + except Exception as e: + logger.error(f"Error inserting to mobile_clients_messages: {str(e)}") + ok = 0 + + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = int(current_utc_time.timestamp() *1000) + + + sql1 = f""" + INSERT INTO public.mobile_clients + (mqtt_id, user_name, user_id, last_message, last_message_time) + VALUES + ('{CleanObject(mqtt_id)}', '{CleanObject(user_name)}', {user_id}, '{CleanObject(message)}', {current_epoch_time}) + ON CONFLICT (mqtt_id) + DO UPDATE SET + user_name = EXCLUDED.user_name, + user_id = EXCLUDED.user_id, + last_message = EXCLUDED.last_message, + last_message_time = EXCLUDED.last_message_time; + """ + logger.debug(f"sql= {sql1}") + # Execute update query + cur.execute(sql1) + + # Commit the changes to the database + conn.commit() + + except Exception as e: + logger.error(f"Error inserting to mobile_clients: {str(e)}") + ok = 0 + + cur.close() + conn.close() + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "get_raw_data": + #container = GetReference("/MAC") + #MAC = req_dict["MAC"][0] + #sensor = req_dict["sensor"][0] + #if "part" in req_dict: + #part = req_dict["part"][0] + #else: + #part = "" + #from_time = req_dict["from_time"][0] + #to_time = req_dict["to_time"][0] + #timezone_str = req_dict["tzone"][0] + #AddToLog("get_raw_data:" + str(MAC) +","+ str(sensor) + "," + str(from_time) + "," + str(to_time) + "," + part+ "," + timezone_str) + ##raw_data = GetRawSensorData(container, MAC, sensor, from_time, to_time, timezone_str) + raw_data = []#GetRawSensorDataFromBlobStorage(MAC, sensor, part, from_time, to_time, timezone_str) + data_payload = {'raw_data': raw_data} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_presence_data": + + deployment_id = form_data.get('deployment_id') + device_id_in_s = form_data.get('device_id') + device_id_in = None + refresh = True#form_data.get('refresh') == "1" + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + filter = int(form_data.get('filter')) + ddate = form_data.get('date') + ddate = ddate.replace("_","-") + to_date = form_data.get('to_date') + + if to_date == None: + to_date = ddate + else: + to_date = to_date.replace("_","-") + + ddate, to_date = ensure_date_order(ddate, to_date) + + + date_obj = datetime.datetime.strptime(ddate, "%Y-%m-%d") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + # Convert back to string + prev_date = previous_day.strftime("%Y-%m-%d") + + data_type = form_data.get('data_type') #all, raw, presence, z-graph + if data_type == None or data_type == "": + data_type = "presence" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + if device_id_in_s != None: #lets remove other devices, since asking for one + device_id_in = int(device_id_in_s) + device_ids = [id for id in device_ids if id == device_id_in] + devices_list = [device for device in devices_list if device[1] == device_id_in] + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + + + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + + + + # Calculate the difference in days + + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from_z.utctimetuple()) + + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + + + #epoch_time = calendar.timegm(time_from.utctimetuple()) + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + + if device_id_in == None or details[1] == device_id_in: + if "," in details[5]: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + #threshold_lst = ["s3_max",12] + else: + threshold_lst = ["s3",int(details[5])] + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #print(sql) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + cur.execute(zsql) + myz_data = cur.fetchall() + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + presence_map['longpresence'] = {} + presence_map['raw'] = {} + + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour - 7, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + #) + start_time = parsed_time.astimezone(pytz.UTC) + + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3",12] + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + + + print(f"start_time: {start_time}") + print(f"epoch_time being sent: {epoch_time}") + print(f"epoch_time as date: {datetime.datetime.fromtimestamp(epoch_time, tz=pytz.UTC)}") + + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + presence_map = optimized_radar_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + #save_list_to_csv_method1(presence_map['longpresence'][475], "longpresence_initial_data.csv") + + + + overlaps_str = GetOverlapps(deployment_id) + overlaps_lst = [] + if overlaps_str != None: + if ":" in overlaps_str: + overlaps_lst = json.loads(overlaps_str) + temporary_map_day_plus = ClearOverlaps(temporary_map_day_plus, overlaps_lst) + + for device_id in ids_list: + device_id_str = str(device_id) + + if filter > 1: + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s, refresh) + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + + #if data_type != "presence": + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + presence_map["z_graph"][id2well_id[device_id]] = z_graph + + + if data_type == "all" or data_type == "multiple": + #lets create "multiple" series + seen_at_lst, seen_where_list_uf = DetectMultiple(temporary_map_day_plus, overlaps_lst) + #here seen_at is straight decas + #seen_at = [1 if x >= 2 else 0 for x in seen_at] + pers_in_deka = [] + dekas_in_day = 6 * 1440 + for i in range(dekas_in_day, len(seen_where_list_uf)): + n_pers = seen_where_list_uf[i] + pers_in_deka.append(100*len(n_pers)) + + seen_at = filter_out_short_highs_iterative(seen_at_lst, filter) #this converts decas into compressed format! + seen_at_lst = Decompress(seen_at) + pers_in_deka = filter_out_short_same_groups_iterative(pers_in_deka, filter) + persons_decompressed = Decompress(pers_in_deka) + persons = Compress(persons_decompressed) + + multiple_list = CreateZGraphAI(seen_at_lst) + presence_map["multiple"] = multiple_list + presence_map["persons"] = persons + + presence_map["presence"] = CompressList(presence_map["longpresence"]) + + if data_type == "z-graph": + if "raw" in presence_map: + del presence_map["raw"] + + #if "presence" in presence_map: + # del presence_map["presence"] + + if "longpresence" in presence_map: + del presence_map["longpresence"] + + if data_type == "multiple": + if "raw" in presence_map: + del presence_map["raw"] + #if "presence" in presence_map: + # del presence_map["presence"] + + if "z_graph" in presence_map: + del presence_map["z_graph"] + + #if "presence" in presence_map: + if "longpresence" in presence_map: + del presence_map["longpresence"] + + data_payload = presence_map + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_zgraph_data": + + deployment_id = form_data.get('deployment_id') + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + device_id = int(form_data.get('device_id')) + + devices = GetVisibleDevices(privileges) + + if not any(item[0] == device_id for item in devices): + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + filter = int(form_data.get('filter')) + ddate = form_data.get('date') + ddate = ddate.replace("_","-") + to_date = form_data.get('to_date') + + if to_date == None: + to_date = ddate + else: + to_date = to_date.replace("_","-") + + ddate, to_date = ensure_date_order(ddate, to_date) + data_type = "z-graph" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from_z.utctimetuple()) + + #time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #epoch_time = calendar.timegm(time_from.utctimetuple()) + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + # Calculate the difference in days + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + #threshold_lst = ["s3_max",12] + + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zsql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + cur.execute(zsql) + myz_data = cur.fetchall() + + #if my_data != None: + + #device_id_2_threshold = {} + #device_id_2_location = {0: "Outside"} + #row_nr_2_device_id = {} + #cnt = 0 + #row_nr_2_device_id[0] = 0 + + ##presence_map['longpresence'] and temporary_map_day_plus are similar, except one is used for Z-graph, and another for multiple persons detection + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'] = {} + #presence_map['longpresence'] = {} + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'] = {} + + #for details in devices_list: + ##(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + #well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "raw" or data_type == "all": + #zeros_list = [0] * 6 * 1440 * days_difference + #presence_map['raw'][well_id] = zeros_list + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #zeros_list = [0] * 6 * 1440 * days_difference + #presence_map['presence'][well_id] = zeros_list + + + ##presence_map[][well_id] = zeros_list + #cnt += 1 + #row_nr_2_device_id[cnt] = well_id + + #if radar_threshold_group_st == None: + #radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + #if len(radar_threshold_group_st) > 8: + #radar_threshold_group = json.loads(radar_threshold_group_st) + #else: + #radar_threshold_group = ["s3",12] + + #device_id_2_location[well_id] = location_name + #device_id_2_threshold[well_id] = radar_threshold_group + + #start_time_ = my_data[0][0] + #parsed_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour - 7, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + #) + + #presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + ##last_device_id = 0 + ##for radar_read in my_data: #(datetime.datetime(2025, 4, 28, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200))), 559, 6.512857142857143, 6.91, 9.28) + ##local_time = radar_read[0] + ##deca = int((local_time - start_time).total_seconds() / 10) + ##device_id = radar_read[1] + ##if device_id != last_device_id: + ##last_device_id = device_id + ##if data_type == "raw" or data_type == "all": + ##days_decas = len(presence_map['raw'][id2well_id[device_id]]) + ##else: + ##days_decas = len(presence_map['presence'][id2well_id[device_id]]) + ##well_id = id2well_id[device_id] + ##radar_threshold_group_st = device_id_2_threshold[device_id] + ##threshold_sig, threshold = radar_threshold_group_st + ##threshold_sig = threshold_sig.split("_")[0] + + ##radar_val = radar_read[2+device_field_indexes[threshold_sig]] + ##if data_type == "presence" or data_type == "z-graph" or data_type == "all" or data_type == "multiple": + ##if radar_val > threshold: + ##if deca < days_decas: + ##presence_map['presence'][id2well_id[device_id]][deca] = 1 + + ##if data_type == "raw" or data_type == "all": + ##if deca < days_decas: + ##presence_map['raw'][id2well_id[device_id]][deca] = radar_val + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zeros_list = [0] * 6 * 1440 * (days_difference_long) #+1 is for previous day + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * (days_difference_long) + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * (days_difference_long) #just place holder + + + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + start_time = datetime.datetime( + parsed_time.year, + parsed_time.month, + parsed_time.day, + parsed_time.hour - 7, # Adjust for UTC-7 + parsed_time.minute, + parsed_time.second, + tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + ) + + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + + if data_type == "all" or data_type == "z-graph" or data_type == "presence" or data_type == "multiple": + overlaps_str = GetOverlapps(deployment_id) + overlaps_lst = [] + if overlaps_str != None: + if ":" in overlaps_str: + overlaps_lst = json.loads(overlaps_str) + temporary_map_day_plus = ClearOverlaps(temporary_map_day_plus, overlaps_lst) + + #if data_type == "all" or data_type == "z-graph" or data_type == "presence" or data_type == "multiple": + for device_id in ids_list: + device_id_str = str(device_id) + #if data_type == "presence" or data_type == "all" or data_type == "z-graph": + if filter > 1: + #presence_list = filter_short_groups_numpy(presence_map["presence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + #presence_list = filter_short_groups_c_wc(presence_map["presence"][id2well_id[device_id]], filter, device_id_str, ddate, to_date, time_zone_s) + #presence_listt = filter_short_groupss(presence_map["presence"][id2well_id[device_id]], filter) + #if presence_list != presence_listt: + # print("stop") + #if data_type != "presence": + #longpresence_list = filter_short_groups_numpy(presence_map["longpresence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s) + #longpresence_listt = filter_short_groupss(presence_map["longpresence"][id2well_id[device_id]], filter) + #if longpresence_list != longpresence_listt: + # print("stop") + # store_to_file(presence_map["longpresence"][id2well_id[device_id]], "test_list") + #presence_map["presence"][id2well_id[device_id]] = presence_list + #if data_type != "presence": + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + + #if data_type != "presence": + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + if filter > 1: #straight decas + presence_list1 = filter_short_high_groups_iterative_analog(temporary_map_day_plus[id2well_id[device_id]], filter) + else: + presence_list1 = temporary_map_day_plus[id2well_id[device_id]] + + temporary_map_day_plus[id2well_id[device_id]] = presence_list1 + + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + for device_id in ids_list: + #print(device_id_2_threshold[id2well_id[device_id]]) + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + presence_map["z_graph"][id2well_id[device_id]] = z_graph + + + if data_type == "all" or data_type == "multiple": + #lets create "multiple" series + seen_at_lst, seen_where_list_uf = DetectMultiple(temporary_map_day_plus, overlaps_lst) + #here seen_at is straight decas + #seen_at = [1 if x >= 2 else 0 for x in seen_at] + pers_in_deka = [] + dekas_in_day = 6 * 1440 + for i in range(dekas_in_day, len(seen_where_list_uf)): + n_pers = seen_where_list_uf[i] + pers_in_deka.append(100*len(n_pers)) + + seen_at = filter_out_short_highs_iterative(seen_at_lst, filter) #this converts decas into compressed format! + seen_at_lst = Decompress(seen_at) + pers_in_deka = filter_out_short_same_groups_iterative(pers_in_deka, filter) + persons_decompressed = Decompress(pers_in_deka) + persons = Compress(persons_decompressed) + + multiple_list = CreateZGraphAI(seen_at_lst) + presence_map["multiple"] = multiple_list + presence_map["persons"] = persons + + presence_map["presence"] = presence_map["longpresence"] + if data_type == "z-graph": + if "raw" in presence_map: + del presence_map["raw"] + if "presence" in presence_map: + del presence_map["presence"] + if "longpresence" in presence_map: + del presence_map["longpresence"] + + if data_type == "multiple": + if "raw" in presence_map: + del presence_map["raw"] + if "presence" in presence_map: + del presence_map["presence"] + if "longpresence" in presence_map: + del presence_map["longpresence"] + if "z_graph" in presence_map: + del presence_map["z_graph"] + + if "presence" in presence_map: + presence_map["presence"] = CompressList(presence_map["presence"]) + + data_payload = presence_map + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_candle_data": + container = GetReference("/MAC") + MAC = req_dict["MAC"][0] + sensor = req_dict["sensor"][0] + from_time = req_dict["from_time"][0] + to_time = req_dict["to_time"][0] + part = req_dict["part"][0] + tzone = req_dict["tzone"][0] + AddToLog(str(req_dict)) + candle_data = GetCandleSensorData(container, MAC, sensor, from_time, to_time, part, tzone) + data_payload = {'candle_data': candle_data} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "deployment_form": + editing_deployment_id = form_data.get('editing_deployment_id') + + ok = StoreDeployment2DB(form_data, editing_deployment_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "deployment_delete": + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "deployments_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + user_id = form_data.get('user_id') + first = 0 + last = 1000000 + + try: + if first_s != None: + first = int(first_s) + except ValueError: + pass + + try: + if last_s != None: + last = int(last_s) + except ValueError: + pass + + #user_id = form_data.get('user_id') + if user_id == "" or user_id == None: + #user_id = GetUserId(user_name) + privileges, user_id = GetPriviledgesAndUserId(user_name) + else: + privileges = GetPriviledgesOnly(user_name) + + all_deployments = ListDeployments(privileges, user_id) + + cnt = 0 + + for deployment in all_deployments: + cnt += 1 + if cnt >= first: + if deployment['beneficiary_id'] in user_id_2_user.keys(): + caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": user_id_2_user[deployment['beneficiary_id']][3], "first_name": user_id_2_user[deployment['beneficiary_id']][5], "last_name": user_id_2_user[deployment['beneficiary_id']][6]} + else: + caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": deployment['beneficiary_id'][3], "first_name": deployment['beneficiary_id'][5], "last_name": deployment['beneficiary_id'][6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "device_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + #user_id = form_data.get('user_id') + + devices = GetVisibleDevices(privileges) + + payload = {'result_list': devices} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_devices_locations": + well_ids = form_data.get('well_ids') + details_list = WellId2Details(well_ids) + #print(details_list) + + to_report = [] + + for details in details_list: + if details[3] == -1: + report_record = str(details[0]) + " ?" + else: + report_record = str(details[0]) + " " + location_names[details[3]] + if details[4] != "" and details[4] != "initial": + report_record = report_record + " " +details[4] + if details[3] == -1: + to_report.append((details[0], "?", details[4], report_record)) + else: + to_report.append((details[0], location_names[details[3]], details[4], report_record)) + + #print(to_report) + payload = {'deployments': to_report} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "find_deployments": + #For all devices, find deployments that they are part of + #For all those deployments, return: + #deployment_id First_name Last_name (of beneficiary) + #list of (well_id, location_descriptions) all devices in each deployment + + + well_ids = form_data.get('well_ids') + #well_ids_lst = well_ids.split(",") + details_list = WellId2Details(well_ids) + to_report = [] + + privileges_lst = [] + if "," in privileges: + privileges_lst = privileges.split(",") + + if len(details_list) > 0: + macs_list_clean = [] + devices_details = {} + + for entry in details_list: + macs_list_clean.append(entry[2]) + macs_formatted = "', '".join(macs_list_clean) + + with get_db_connection() as conn: + with conn.cursor() as cur: + sql = f"SELECT deployment_id, beneficiary_id, devices FROM public.deployment_details WHERE devices::text ~* ANY(ARRAY['{macs_formatted}']);" + print(sql) + cur.execute(sql) + result = cur.fetchall() + + if result != None and result != []: + users_list_clean = [] + for entry in result: #for every deployment + macs_list_clean = [] + deployment_id = str(entry[0]) + if privileges == '-1': + deployment = DeploymentDetails(deployment_id) + address_str = ExtractAddress(deployment) + deployment = PurgeDeployment(deployment) + deployment["address"] = address_str + + users_list_clean.append(str(entry[1])) + users_formatted = ", ".join(users_list_clean) + + mac_devices_in_deployment = ast.literal_eval(entry[2]) + for mac in mac_devices_in_deployment: + macs_list_clean.append(mac) + + device_ids, device_list = MACsToWellIds(cur, macs_list_clean) + + devices_details[deployment_id] = deployment + + elif deployment_id in privileges_lst: + deployment = DeploymentDetails(deployment_id) + address_str = ExtractAddress(deployment) + deployment = PurgeDeployment(deployment) + deployment["address"] = address_str + users_list_clean.append(str(entry[1])) + users_formatted = ", ".join(users_list_clean) + + mac_devices_in_deployment = ast.literal_eval(entry[2]) + for mac in mac_devices_in_deployment: + macs_list_clean.append(mac) + + device_ids, device_list = MACsToWellIds(cur, macs_list_clean) + devices_details[deployment_id] = deployment + + device_list_to_report = [] + + for device in device_list: + #we need well_id, location and decription only + device_list_to_report.append((device[0], device[2], device[3])) + sql = f"SELECT first_name, last_name, email FROM public.person_details WHERE user_id IN ({users_formatted});" + print(sql) + cur.execute(sql) + result1 = cur.fetchall() + counter = 0 + + for entry in result: + deployment_id = str(entry[0]) + deployment = devices_details[deployment_id] + first_name, last_name, email = result1[counter] + deployment["beneficiary_first_name"] = first_name + deployment["beneficiary_last_name"] = last_name + deployment["beneficiary_email"] = last_name + devices_details[deployment_id] = deployment + to_report.append((entry, device_list_to_report, devices_details[deployment_id])) + + + print(to_report) + payload = {'deployments': to_report} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "device_list_by_deployment": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + deployment_id = form_data.get('deployment_id') + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + + if privileges == "-1": + devices = GetVisibleDevices(deployment_id) + else: + privileges = privileges.split(",") + if deployment_id in privileges: + devices = GetVisibleDevices(deployment_id) + + payload = {'result_list': devices} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "device_list_4_gui": + result_list = [] + deploymentData = [] + deviceData = [] + macs_list = [] + user_id = GetUserId(user_name) + all_deployments = ListDeployments(privileges, user_id) + #{'deployment_id': 21, 'beneficiary_id': 25, 'caretaker_id': 1, 'owner_id': 1, 'installer_id': 1, 'address_street': '661 Encore Way', 'address_city': 'San Jose', 'address_zip': '95134', 'address_state': 'CA', 'address_country': 'USA', 'devices': '["64B70888FAB0","64B70888F860","64B70888F6F0","64B708896BDC","64B708897428","64B70888FA84","64B70889062C"]', 'wifis': '', 'persons': 1, 'gender': 1, 'race': 1, 'born': 1940, 'pets': 0, 'time_zone': 'America/Los_Angeles'} + MAC2Deployment = {} + + for deployment in all_deployments: + beneficiary_id = deployment['beneficiary_id'] + user = GetNameFromUserId(beneficiary_id) + name = f"{user[1]} {user[2]}" + deploymentData.append({'deployment_id': str(deployment['deployment_id']), 'name': name}) + devices = deployment['devices'] + if devices != None and devices != None != "": + + devices_list = ToList(devices) + for device in devices_list: + macs_list.append(device) + MAC2Deployment[device] = deployment['deployment_id'] + #deviceData.append({'well_id': device[0], 'mac': device[1]}) + + deployment_id_list = [] + deviceData = [] + + #row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + + with get_db_connection() as conn: + with conn.cursor() as cur: + device_ids, device_list = MACsToWellIds(cur, macs_list) + + + for device in device_list: + if MAC2Deployment[device[4]] != "": + deviceData.append({'well_id': device[0], 'mac': device[4], 'room_name': device[2], 'deployment_id': MAC2Deployment[device[4]]}) + + #deploymentData = [{'deployment_id': '21', 'name': 'Robert Zmrzli House'}, {'deployment_id': '36', 'name': 'Fred Zmrzli Apartment'}] + #deviceData = [{ 'well_id': '300', 'mac': '64B70888F6F0', 'room_name': 'Living Room', 'deployment_id': '21' }, { 'well_id': '301', 'mac': '64B70888F6F1', 'room_name': 'Bathroom Main', 'deployment_id': '36' }] + + payload = { + 'status': "success", 'deploymentData': deploymentData, 'deviceData': deviceData + } + + logger.debug(f"device_list_4_gui------ {payload} ------------------------------------------") + + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "caretaker_form": + editing_user_id = form_data.get('editing_user_id') + email = form_data.get('email') + user_id = form_data.get('user_id') + if "@" not in email: + resp.media = package_response("Missing or illegal 'email' parameter", HTTP_400) + return + + print(privileges) + if privileges == "-1": + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif "-1" in privileges: + payload = {'ok': 0, 'error': "Not allowed!"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + + elif function == "caretaker_delete": + if privileges == "-1": + ok = DeleteRecordFromDB(form_data) + else: + ok = 0 + AddToLog(ok) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "caretakers_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + if privileges == "-1": + all_caretakers = ListCaretakers(privileges, user_name) + + cnt = 0 + + for caretaker in all_caretakers: + cnt += 1 + if cnt >= first: + caretaker_min_object = {"user_id": caretaker[0], "email": caretaker[3], "first_name": caretaker[5], "last_name": caretaker[6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + elif "-1" in privileges: + all_caretakers = ListCaretakers(privileges, user_name) + + cnt = 0 + + for caretaker in all_caretakers: + cnt += 1 + if cnt >= first: + caretaker_min_object = {"user_id": caretaker[0], "email": caretaker[3], "first_name": caretaker[5], "last_name": caretaker[6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "new_user_form": + devices = form_data.get('devices') + user_name = form_data.get('user_name') + password = form_data.get('password') + #lets check if devices listed are not part of existing deployment + success, result = DevicesNotUsed(devices, user_name) + + if success: + if result["deployed"]: + error_string = f"These devices are already deployed: {result['deployed']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if result["not_found"]: + error_string = f"These devices are not available: {result['not_found']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if not result["deployed"] and not result["not_found"]: + print("All devices are available for deployment") + ok, error_string = StoreDisclaimer2DB(form_data) + + if ok == 1: + #Lets check if this user already has account or not + if not AccountExists(user_name): + #lets create new account for this caretaker + if password == None or password == "": + password = CreatePassword(8) + + #lets suplement form_data with parts needed for existing StoreCaretaker2DB function + editing_user_id = "0" #specify if editing existing user, otherwise "0" + user_id = "0" #user that is adding this record. New user so "0" + form_data['role_ids'] = "1,2" + form_data['access_to_deployments'] = "45" + #form_data['email'] = "" #this one matches + form_data['new_user_name'] = form_data['user_name'] + form_data['first_name'] = form_data['firstName'] + form_data['last_name'] = form_data['lastName'] + form_data['address_street'] = "" + form_data['address_city'] = "" + form_data['address_zip'] = "" + form_data['address_state'] = "" + form_data['address_country'] = "" + form_data['phone_number'] = form_data['phone'] + form_data['picture'] = "/" + form_data['key'] = password + + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + + if ok == 1: + + #we need to call cd ~/mqtt-auth-service/acl_manager.py + + SendWelcomeEmail(form_data['email'], form_data['first_name'], form_data['last_name'], devices, form_data['phone_number'], form_data['new_user_name'], form_data['key']) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + privileges, user_id = ValidUser(user_name, password) + if user_id == "0": #bad password + error_string = f"Password does not match user {user_name}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + else: + error_string = f"Error: {result}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiary_form": + editing_user_id = form_data.get('editing_user_id') + email = form_data.get('email') + user_id = GetUserId(user_name) + if "@" in email: + ok, error_string = StoreBeneficiary2DB(form_data, editing_user_id, user_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiary_delete": + + + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiaries_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + user_id = form_data.get('user_id') + all_beneficiaries = ListBeneficiaries(privileges, user_id) + + cnt = 0 + + for beneficiary in all_beneficiaries: + cnt += 1 + if cnt >= first: + beneficiary_min_object = {"user_id": beneficiary[0], "email": beneficiary[3], "first_name": beneficiary[5], "last_name": beneficiary[6]} + result_list.append(beneficiary_min_object) + if cnt > last: + break + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "activities_report_details": + deployment_id = form_data.get('deployment_id') + + timezone_str = GetTimeZoneOfDeployment(deployment_id) + filterr = form_data.get('filter') + if filterr == None: + filterr = 6 + else: + filterr = int(filterr) + + refresh = form_data.get('refresh') == "1" + ddate = current_date_at_tz(timezone_str) + timee = LocalDateToUTCEpoch(ddate, timezone_str)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Here we need to add per day: (all based on Z-graph data!) + #Bathroom visits number + #Bathroom time spent + #Sleep weakes number (As breaks in Z-graph indicates in 10PM to 9AM period) + #Sleep length (For now add all times seen in bedroom) + #Kitchen visits number + #Kitchen time spent + #Most frequented room visits number + #Most frequented room time spent + + #Lets find device_id of bathroom sensor + + + bathroom_device_id, location_ba, bathroom_well_id = FindDeviceByRole(deployment_id, ["Bathroom Main", "Bathroom", "Bathroom Guest"]) + bedroom_device_id, location_be, bedroom_well_id = FindDeviceByRole(deployment_id, ["Bedroom Master", "Bedroom", "Bedroom Guest"]) + kitchen_device_id, location_ke, kitchen_well_id = FindDeviceByRole(deployment_id, ["Kitchen"]) + most_present_device_id, location_ot, most_present_well_id = FindDeviceByRole(deployment_id, []) #this will find most_present (as defined in other filed of device record) + + if isinstance(location_ot, int): + other_location = location_names[location_ot] + else: + other_location = location_ot + + #weekly + week_dates = get_week_days_and_dates(7, timezone_str) + month_dates = get_week_days_and_dates(30, timezone_str) + six_months_dates = get_week_days_and_dates(180, timezone_str) + + other_color = Loc2Color[other_location][0] + rgb_string = f"rgb({other_color[0]}, {other_color[1]}, {other_color[2]})" + + rooms_reports = [("Bathroom", "blue", bathroom_device_id, bathroom_well_id), ("Bedroom", "green", bedroom_device_id, bedroom_well_id), ("Kitchen", "red", kitchen_device_id, kitchen_well_id), (other_location, rgb_string, most_present_device_id, most_present_well_id)] + + six_months_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in six_months_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + if hours > 18: + print("Too long 6m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + six_months_report.append(room) + + weekly_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in week_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + data_record = { "title": day_activity[1], "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + weekly_report.append(room) + + monthly_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in month_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + #if datee == "2025-05-20" and device_id == 572: + # print(hours) + if hours > 18: + print("Too long m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + monthly_report.append(room) + + + + + result_dictionary = { + "alert_text": "No alert", + "alert_color": "bg-green-100 text-green-700", + "chart_data": [ + { + "name": "Weekly", + "rooms": [ + { + "name": "Bathroom", + "color": "blue", + "data": [ + { "title": "Monday", "events": 186, "hours": 80.56 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Bedroom", + "color": "green", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Kitchen", + "color": "red", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Other", + "color": "yellow", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + } + ] + }, + { + "name": "Monthly", + "rooms": [ + { + "name": "Bathroom", + "color": "purple", + "data": [ + { "title": "01", "events": 67, "hours": 45 }, + { "title": "02", "events": 97, "hours": 67 }, + { "title": "03", "events": 87, "hours": 23 }, + { "title": "04", "events": 42, "hours": 12 }, + { "title": "05", "events": 64, "hours": 48 }, + { "title": "06", "events": 53, "hours": 34 }, + { "title": "07", "events": 75, "hours": 23 }, + { "title": "08", "events": 45, "hours": 56 }, + { "title": "09", "events": 85, "hours": 47 }, + { "title": "10", "events": 34, "hours": 29 }, + { "title": "11", "events": 49, "hours": 30 }, + { "title": "12", "events": 62, "hours": 33 }, + { "title": "13", "events": 75, "hours": 44 }, + { "title": "14", "events": 88, "hours": 57 }, + { "title": "15", "events": 94, "hours": 65 }, + { "title": "16", "events": 45, "hours": 21 }, + { "title": "17", "events": 76, "hours": 54 }, + { "title": "18", "events": 85, "hours": 62 }, + { "title": "19", "events": 43, "hours": 28 }, + { "title": "20", "events": 59, "hours": 34 }, + { "title": "21", "events": 78, "hours": 56 }, + { "title": "22", "events": 64, "hours": 39 }, + { "title": "23", "events": 93, "hours": 72 }, + { "title": "24", "events": 52, "hours": 28 }, + { "title": "25", "events": 71, "hours": 48 }, + { "title": "26", "events": 85, "hours": 63 } + ] + }, + { + "name": "Bedroom", + "color": "#3b82f6", + "data": [ + { "title": "01", "events": 61, "hours": 42 }, + { "title": "02", "events": 72, "hours": 36 }, + { "title": "03", "events": 94, "hours": 49 }, + { "title": "04", "events": 67, "hours": 59 }, + { "title": "05", "events": 54, "hours": 20 }, + { "title": "06", "events": 77, "hours": 64 }, + { "title": "07", "events": 81, "hours": 70 }, + { "title": "08", "events": 53, "hours": 25 }, + { "title": "09", "events": 79, "hours": 42 }, + { "title": "10", "events": 84, "hours": 65 }, + { "title": "11", "events": 62, "hours": 54 }, + { "title": "12", "events": 45, "hours": 23 }, + { "title": "13", "events": 88, "hours": 71 }, + { "title": "14", "events": 74, "hours": 44 }, + { "title": "15", "events": 91, "hours": 59 }, + { "title": "16", "events": 46, "hours": 31 }, + { "title": "17", "events": 73, "hours": 40 }, + { "title": "18", "events": 85, "hours": 63 }, + { "title": "19", "events": 78, "hours": 66 }, + { "title": "20", "events": 66, "hours": 42 }, + { "title": "21", "events": 95, "hours": 78 }, + { "title": "22", "events": 57, "hours": 39 }, + { "title": "23", "events": 72, "hours": 48 }, + { "title": "24", "events": 48, "hours": 21 }, + { "title": "25", "events": 89, "hours": 61 }, + { "title": "26", "events": 77, "hours": 44 } + ] + }, + { + "name": "Kitchen", + "color": "orange", + "data": [ + { "title": "01", "events": 94, "hours": 59 }, + { "title": "02", "events": 62, "hours": 48 }, + { "title": "03", "events": 76, "hours": 38 }, + { "title": "04", "events": 81, "hours": 62 }, + { "title": "05", "events": 64, "hours": 27 }, + { "title": "06", "events": 53, "hours": 31 }, + { "title": "07", "events": 92, "hours": 65 }, + { "title": "08", "events": 85, "hours": 42 }, + { "title": "09", "events": 74, "hours": 35 }, + { "title": "10", "events": 67, "hours": 55 }, + { "title": "11", "events": 49, "hours": 23 }, + { "title": "12", "events": 88, "hours": 75 }, + { "title": "13", "events": 93, "hours": 66 }, + { "title": "14", "events": 76, "hours": 34 }, + { "title": "15", "events": 59, "hours": 39 }, + { "title": "16", "events": 72, "hours": 51 }, + { "title": "17", "events": 83, "hours": 44 }, + { "title": "18", "events": 74, "hours": 33 }, + { "title": "19", "events": 69, "hours": 28 }, + { "title": "20", "events": 85, "hours": 56 }, + { "title": "21", "events": 53, "hours": 22 }, + { "title": "22", "events": 92, "hours": 70 }, + { "title": "23", "events": 71, "hours": 41 }, + { "title": "24", "events": 67, "hours": 25 }, + { "title": "25", "events": 86, "hours": 74 }, + { "title": "26", "events": 94, "hours": 68 } + ] + }, + { + "name": "Other", + "color": "hotpink", + "data": [ + { "title": "01", "events": 57, "hours": 27 }, + { "title": "02", "events": 74, "hours": 33 }, + { "title": "03", "events": 84, "hours": 53 }, + { "title": "04", "events": 95, "hours": 68 }, + { "title": "05", "events": 71, "hours": 48 }, + { "title": "06", "events": 92, "hours": 76 }, + { "title": "07", "events": 85, "hours": 62 }, + { "title": "08", "events": 49, "hours": 25 }, + { "title": "09", "events": 66, "hours": 38 }, + { "title": "10", "events": 63, "hours": 31 }, + { "title": "11", "events": 75, "hours": 47 }, + { "title": "12", "events": 94, "hours": 72 }, + { "title": "13", "events": 79, "hours": 49 }, + { "title": "14", "events": 72, "hours": 45 }, + { "title": "15", "events": 88, "hours": 61 }, + { "title": "16", "events": 83, "hours": 52 }, + { "title": "17", "events": 92, "hours": 76 }, + { "title": "18", "events": 73, "hours": 40 }, + { "title": "19", "events": 65, "hours": 28 }, + { "title": "20", "events": 76, "hours": 63 }, + { "title": "21", "events": 58, "hours": 30 }, + { "title": "22", "events": 84, "hours": 67 }, + { "title": "23", "events": 72, "hours": 41 }, + { "title": "24", "events": 79, "hours": 46 }, + { "title": "25", "events": 63, "hours": 29 }, + { "title": "26", "events": 68, "hours": 39 } + ] + } + ] + }, + { + "name": "6 Months", + "rooms": [ + { + "name": "Bathroom", + "color": "purple", + "data": [ + { "title": "October", "events": 62, "hours": 23 }, + { "title": "November", "events": 76, "hours": 42 }, + { "title": "December", "events": 85, "hours": 54 }, + { "title": "January", "events": 94, "hours": 67 }, + { "title": "February", "events": 63, "hours": 35 }, + { "title": "March", "events": 81, "hours": 46 } + ] + }, + { + "name": "Bedroom", + "color": "#3b82f6", + "data": [ + { "title": "October", "events": 64, "hours": 35 }, + { "title": "November", "events": 88, "hours": 71 }, + { "title": "December", "events": 79, "hours": 54 }, + { "title": "January", "events": 72, "hours": 49 }, + { "title": "February", "events": 53, "hours": 32 }, + { "title": "March", "events": 93, "hours": 67 } + ] + }, + { + "name": "Kitchen", + "color": "orange", + "data": [ + { "title": "October", "events": 92, "hours": 65 }, + { "title": "November", "events": 85, "hours": 62 }, + { "title": "December", "events": 74, "hours": 49 }, + { "title": "January", "events": 63, "hours": 33 }, + { "title": "February", "events": 78, "hours": 56 }, + { "title": "March", "events": 69, "hours": 41 } + ] + }, + { + "name": "Other", + "color": "hotpink", + "data": [ + { "title": "October", "events": 88, "hours": 54 }, + { "title": "November", "events": 72, "hours": 39 }, + { "title": "December", "events": 84, "hours": 63 }, + { "title": "January", "events": 76, "hours": 46 }, + { "title": "February", "events": 93, "hours": 72 }, + { "title": "March", "events": 68, "hours": 29 } + ] + } + ] + } + ] + } + + result_dictionary["chart_data"][0]["rooms"] = weekly_report + result_dictionary["chart_data"][1]["rooms"] = monthly_report + result_dictionary["chart_data"][2]["rooms"] = six_months_report + + + + payload = result_dictionary #{'result_dictionary': result_dictionary} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + + elif function == "dashboard_list": + # works in UTC only + + logger.error(f"------------------------------- dashboard_list ------------------------------------------") + + caretaker = user_name + #date_s = form_data.get('date') + time_s = form_data.get('time') + date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + filterr = form_data.get('filter') + if filterr == None: + filterr = 5 + + privileges = GetPriviledgesOnly(caretaker) + + deployments_list = GetUsersFromDeployments(privileges) + + #all_beneficiaries = ListBeneficiariesOfCaretaker(caretaker) #GetPriviledgesOnly + #AddToLog(all_beneficiaries) + + result_list = [] + + for deployment_id, first_name, last_name in deployments_list: + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + if details != {}: + + details["units"] = "°C" + if "America" in details["time_zone"]: + details["temperature"] = CelsiusToFahrenheit(details["temperature"]) + details["units"] = "°F" + devices_list, device_ids = GetProximityList(deployment_id, date_s) + # convert dates back to UTC + #details['bathroom_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bathroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['kitchen_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bathroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['bedroom_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bedroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['last_detected_time'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['last_detected_time'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + location_list = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if description == None or description == "": + location_list.append(location_name) + else: + location_list.append(location_name + " " + description) + details["deployment_id"] = deployment_id + details["location_list"] = location_list + result_list.append(details) + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + + elif function == "dashboard_single": + caretaker = user_name + #date_s = form_data.get('date') + date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + deployment_id = form_data.get('deployment_id') + filterr = form_data.get('filter') + if filterr == None: + filterr = 5 + + + #all_beneficiaries = ListBeneficiariesOfCaretaker(caretaker) #GetPriviledgesOnly + #AddToLog(all_beneficiaries) + + result_list = [] + + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr, False) + details["units"] = "°C" + if "America" in details["time_zone"]: + details["temperature"] = CelsiusToFahrenheit(details["temperature"]) + details["bedroom_temperature"] = CelsiusToFahrenheit(details["bedroom_temperature"]) + details["units"] = "°F" + devices_list, device_ids = GetProximityList(deployment_id, date_s) + location_list = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if description == None or description == "": + location_list.append(location_name) + else: + location_list.append(location_name + " " + description) + details["deployment_id"] = deployment_id + details["location_list"] = location_list + settings = {"wellness_score": False, "last_seen": False, "sleep_report": True, "activity_report": True, "temperature": True, "humidity": True, "air_pressure": True, "light": True, "air_quality": True, "radar": True, "other_activities": False} + details["settings"] = settings + result_list.append(details) + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + elif function == "request_node_red": + logger.error(f"------------------------------- {function} ------------------------------------------") + #this will: + # 1.prepare folder and settings.js + # 2.start instance on node-red and return it's return port + #caretaker = user_name + #date_s = form_data.get('date') + time_s = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + #deployment_id = form_data.get('deployment_id') + #redis_conn.set('node_red_requests', str([radar_threshold_signal, radar_threshold_value])) + # Hashes (dictionaries) + logger.error(f"Storing to node_red_requests {user_name}") + redis_conn.hset('node_red_requests', mapping={ + 'user_name': user_name, + 'token': token, + 'time': time_s, + 'requests': 1 + }) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "get_node_red_port": + #this will: + # 1.prepare folder and settings.js + # 2.start instance on node-red and return it's return port + hash_data = GetRedisMap(f'node_red_status_{user_name}') + port = 0 + if hash_data != {}: + port = hash_data['port'] + #date_s = form_data.get('date') + #date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + #deployment_id = form_data.get('deployment_id') + payload = {'port': port} + logger.debug(f"get_node_red_port: {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "activity_detected": + #this will: + # 1.store to REDIS time of last activity + time_s = form_data.get('time') + + hash_data = GetRedisMap(f'node_red_status_{user_name}') + port = 0 + if hash_data != {}: + port = hash_data['port'] + + redis_conn.hset(f'node_red_status_{user_name}', mapping={ + 'port': port, + 'last_activity': time_s + }) + + payload = {'ok': 1} + logger.debug(f"activity_detected: {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "store_flow": + #this will: + # 1.store flow into DB + time_s = form_data.get('time') + flow_json = form_data.get('flow') + logger.debug(f"store_flow: {flow_json}") + StoreFlow2DB(user_name, time_s, flow_json) + payload = {'ok': 1} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "store_alarms": + #this will: + # 1.store flow into DB + deployment_id = form_data.get('deployment_id') + device_id = form_data.get('device_id') + deployment_alarms_json = form_data.get('deployment_alarms') + device_alarms_json = form_data.get('device_alarms') + + logger.debug(f"store_alarms: {deployment_alarms_json}") + + + if privileges == "-1" or deployment_id in privileges: + ok = StoreAlarms2DB(deployment_id, device_id, deployment_alarms_json, device_alarms_json) + + redis_conn.set('alarm_device_settings_'+device_id, device_alarms_json) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('new_alarms', record_json) + + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + else: + payload = {'ok': 0, 'error': "not allowed"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + return + + elif function == "send_walarm": + # Extract data from form + deployment_id = form_data.get('deployment_id') + device_id = form_data.get('device_id') + location = form_data.get('location') + method = form_data.get('method') + feature = form_data.get('feature') + currentAlertTableMode = form_data.get('currentAlertTableMode') + time_s = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + content = form_data.get('content') + enabledCellContent = form_data.get('enabledCellContent') + currentUnits = form_data.get('currentUnits') + test_only = form_data.get('test_only') + action = form_data.get('action') + + logger.debug(f"send_requests: {user_name}") + + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'location': location, + 'method': method, + 'feature': feature, + 'currentAlertTableMode': currentAlertTableMode, + 'time': time_s, + 'content': content, + 'currentUnits': currentUnits, + 'test_only': test_only, + 'action': action, + 'enabledCellContent': enabledCellContent + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('send_requests', record_json) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "node-red_deployed": + #this will: + # 1.store flow into DB + time_s = form_data.get('time') + logger.debug(f"node-red_deployed: {user_name}") + redis_conn.hset('node-red_deployed', mapping={ + 'user_name': user_name, + 'token': token, + 'time': time_s, + 'requests': 1 + }) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + debug_string = "Error: function not recognized!" + AddToLog(debug_string) + payload = {'ok': 0, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + except Exception as e: + print(traceback.format_exc()) + resp.media = package_response(f"Error: {str(e)} {traceback.format_exc()}", HTTP_500) + + + def on_put(self, req, resp, path=""): + """Handle PUT requests""" + if path == "users": + logger.info("PUT request to users endpoint") + try: + # Parse the request body + request_data = json.loads(req.stream.read().decode('utf-8')) + + # TODO: Implement user update logic + + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"id": request_data.get("id"), "message": "User updated"}) + except json.JSONDecodeError: + resp.status = HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Invalid JSON"}) + else: + resp.media = package_response(f"PUT to /{path} not implemented", HTTP_400) + + def on_delete(self, req, resp, path=""): + """Handle DELETE requests""" + if path == "users": + logger.info("DELETE request to users endpoint") + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"message": "User deleted"}) + else: + resp.media = package_response(f"DELETE to /{path} not implemented", HTTP_400) + + +def SendWelcomeEmail(email, first_name, last_name, devices, phone_number, user_name, password): + logger.error(f"Requesting welcome email to {email}") + redis_conn.hset('messaging_requests', mapping={ + 'email': email, + 'user_name': user_name, + 'first_name': first_name, + 'last_name': last_name, + 'devices': devices, + 'phone_number': phone_number, + 'password': password, + 'requests': 1 + }) + + + +def DevicesNotUsed(devices, user_name): + """ + Check if devices exist in DB and if they are already deployed (excluding user's own deployments). + + Args: + devices (str): Comma-separated string of well_id or device_mac values + user_name (str): Username to check access permissions + + Returns: + tuple: (success_flag, result) + - If success: (1, {"deployed": [well_ids], "not_found": [device_identifiers]}) + - If error: (0, error_string) + """ + + if not devices: + return 1, {"deployed": [], "not_found": []} + + # Clean and split the devices string + device_list = [CleanObject(device.strip()) for device in devices.split(',') if device.strip()] + + if not device_list: + return 1, {"deployed": [], "not_found": []} + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + # First, get user's accessible deployments + user_deployments = [] + is_superuser = False + + if user_name: + user_sql = f""" + SELECT access_to_deployments + FROM public.person_details + WHERE user_name = '{CleanObject(user_name)}' + """ + + logger.debug(f"user_sql= {user_sql}") + cur.execute(user_sql) + user_result = cur.fetchone() + + if user_result and user_result[0]: + access_deployments = user_result[0].strip() + + if access_deployments == "-1": + # Superuser has access to all deployments + is_superuser = True + logger.debug("User is superuser - has access to all deployments") + else: + # Parse comma-separated deployment IDs + user_deployments = [int(dep_id.strip()) for dep_id in access_deployments.split(',') if dep_id.strip().isdigit()] + logger.debug(f"User has access to deployments: {user_deployments}") + + deployed_well_ids = [] + not_found_devices = [] + + for device in device_list: + # Check if this device is a well_id or device_mac by string length + if len(device) == 12: + # Exactly 12 characters = device_mac + device_mac = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE device_mac = '{device_mac}' + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + device_well_id = device_result[0] + + # Query to check if this device_mac is deployed (excluding user's accessible deployments) + if is_superuser: + # Superuser has access to all deployments, so no devices are "deployed elsewhere" + sql = "SELECT NULL WHERE FALSE" # Returns no results + elif user_deployments: + deployment_exclusion = f"AND dd.deployment_id NOT IN ({','.join(map(str, user_deployments))})" + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' {deployment_exclusion} + """ + else: + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' + """ + + else: + # Any other length = well_id + well_id = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE well_id = {well_id} + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + # Query to check if any device with this well_id is deployed (excluding user's accessible deployments) + if is_superuser: + # Superuser has access to all deployments, so no devices are "deployed elsewhere" + sql = "SELECT NULL WHERE FALSE" # Returns no results + elif user_deployments: + deployment_exclusion = f"AND dd.deployment_id NOT IN ({','.join(map(str, user_deployments))})" + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} {deployment_exclusion} + """ + else: + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + results = cur.fetchall() + + # Add any found well_id's to our list + for row in results: + if row[0] is not None and row[0] not in deployed_well_ids: + deployed_well_ids.append(row[0]) + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog(f"DevicesNotUsed check completed. Found {len(deployed_well_ids)} deployed devices, {len(not_found_devices)} not found.") + return 1, {"deployed": deployed_well_ids, "not_found": not_found_devices} + + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + if cur: + cur.close() + if conn: + conn.close() + return 0, error_string + +def CreatePassword(chat_len): + """ + Generate a reasonable password of specified length using common words + with character substitutions (leet speak style). + + Args: + chat_len (int): Desired password length + + Returns: + str: Generated password + """ + + # Seed random with current time for better randomness + random.seed(int(time.time() * 1000000) % 2**32) + + # Common words to use as base + words = [ + "password", "secure", "strong", "safe", "guard", "shield", "lock", + "key", "code", "access", "login", "enter", "open", "door", + "house", "home", "family", "friend", "happy", "sunny", "bright", + "quick", "fast", "smart", "clever", "power", "energy", "magic", + "super", "ultra", "mega", "cool", "awesome", "great", "best", + "gold", "silver", "diamond", "star", "moon", "sun", "fire", + "water", "earth", "wind", "storm", "thunder", "lightning", + "mountain", "ocean", "forest", "river", "bridge", "castle", + "knight", "dragon", "wizard", "hero", "champion", "winner" + ] + + # Character substitution mapping (leet speak) + substitutions = { + 'a': '@', 'A': '@', + 'e': '3', 'E': '3', + 'i': '1', 'I': '1', + 'o': '0', 'O': '0', + 's': '$', 'S': '$', + 't': '7', 'T': '7', + 'b': '8', 'B': '8', + 'g': '9', 'G': '9', + 'l': '!', 'L': '!', + 'z': '2', 'Z': '2' + } + + password = "" + + while len(password) < chat_len: + # Pick a random word + word = random.choice(words) + + # Apply random substitutions (not all characters, to keep it readable) + modified_word = "" + for char in word: + # 40% chance to substitute if substitution exists + if char in substitutions and random.random() < 0.4: + modified_word += substitutions[char] + else: + modified_word += char + + # Randomly capitalize some letters for variety + if random.random() < 0.3: + modified_word = modified_word.capitalize() + + # Add the word to password + if len(password) + len(modified_word) <= chat_len: + password += modified_word + else: + # If word is too long, take only what we need + remaining = chat_len - len(password) + password += modified_word[:remaining] + break + + # Add a random number or symbol between words (if space allows) + if len(password) < chat_len and random.random() < 0.5: + separators = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '#', '*', '+', '='] + separator = random.choice(separators) + if len(password) + 1 <= chat_len: + password += separator + + return password + +def DevicesNotUsedNoOwnershipCheck(devices): + """ + Check if devices exist in DB and if they are already deployed. + + Args: + devices (str): Comma-separated string of well_id or device_mac values + + Returns: + tuple: (success_flag, result) + - If success: (1, {"deployed": [well_ids], "not_found": [device_identifiers]}) + - If error: (0, error_string) + """ + + if not devices: + return 1, {"deployed": [], "not_found": []} + + # Clean and split the devices string + device_list = [CleanObject(device.strip()) for device in devices.split(',') if device.strip()] + + if not device_list: + return 1, {"deployed": [], "not_found": []} + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + deployed_well_ids = [] + not_found_devices = [] + + for device in device_list: + # Check if this device is a well_id or device_mac by string length + if len(device) == 12: + # Exactly 12 characters = device_mac + device_mac = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE device_mac = '{device_mac}' + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + #device_well_id = device_result[0] + + # Query to check if this device_mac is deployed + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' + """ + + else: + # Any other length = well_id + well_id = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE well_id = {well_id} + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + # Query to check if any device with this well_id is deployed + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + results = cur.fetchall() + + # Add any found well_id's to our list + for row in results: + if row[0] is not None and row[0] not in deployed_well_ids: + deployed_well_ids.append(row[0]) + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog(f"DevicesNotUsed check completed. Found {len(deployed_well_ids)} deployed devices, {len(not_found_devices)} not found.") + return 1, {"deployed": deployed_well_ids, "not_found": not_found_devices} + + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + if cur: + cur.close() + if conn: + conn.close() + return 0, error_string + + +def ExtractAddress(deployment_map): + address_map = {} + ''' +{ + "city": "mountain view", + "country": "usa", + "house_number": "760", + "postcode": "95070", + "road": "hope st.", + "state": "ca" +} + ''' + print(deployment_map) #{'deployment_id': 24, 'time_edit': 1753129300.0, 'user_edit': 32, 'persons': 2, 'gender': 1, 'race': 1, 'born': 1972, 'pets': 0, + # 'address_street': '', 'address_city': '', 'address_zip': '95070', 'address_state': '', 'address_country': '', 'wifis': '{"CBX_F": "69696969", "CBX": "69696969"}', 'lat': 37.267117, 'lng': -121.99548, 'gps_age': 0, 'note': 'me', 'overlapps': None}' + address_map["city"] = deployment_map["address_city"] + address_map["country"] = deployment_map["address_country"] + address_map["road"] = deployment_map["address_street"] + address_map["postcode"] = deployment_map["address_zip"] + address_map["state"] = deployment_map["address_state"] + + #address_map["city"] = "San Francisco" + #address_map["country"] = "USA" + #address_map["road"] = "230 Hope str." + #address_map["postcode"] = "95070" + #address_map["state"] = "CA" + + address_str = JoinAddress(address_map) + return(address_str["formatted_address"].strip()) + +# Initialize data files + +def PurgeDeployment(deployment): + deployment.pop('floor_plan', None) + deployment.pop('context', None) + deployment.pop('alarm_details', None) + deployment.pop('devices', None) + deployment.pop('floor_plan', None) + + deployment.pop('time_zone_s', None) + deployment.pop('beneficiary_id', None) + deployment.pop('caretaker_id', None) + deployment.pop('owner_id', None) + deployment.pop('installer_id', None) + #print(deployment) + deployment.pop('address_street', None) + deployment.pop('address_city', None) + deployment.pop('address_zip', None) + deployment.pop('address_state', None) + deployment.pop('address_country', None) + + deployment.pop('gps_age', None) + deployment.pop('note', None) + deployment.pop('overlapps', None) + deployment.pop('time_edit', None) + deployment.pop('user_edit', None) + + + return deployment + +def save_list_to_csv_method1(data_list, filename): + """Save list data to CSV with one item per row using csv module""" + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile) + # Optional: Add header + writer.writerow(['Value']) + # Write each item in a separate row + for item in data_list: + writer.writerow([item]) + print(f"Data saved to {filename} using csv module") + + +logger.error(f"------------------------------- STARTED ------------------------------------------") +try: + searches_text = read_file("searches.json") + searches_dict = json.loads(searches_text) if searches_text else {} + + dialogs_data = read_file("dialog.json") + dialog_dict = json.loads(dialogs_data) if dialogs_data else {"utterances": {}, "intents": {}} + + intent_map = dialog_dict.get("utterances", {}) + utterances = {} + for key in intent_map: + logger.debug(key) + list_of_utterances = intent_map[key] + for utterance in list_of_utterances: + utterances[utterance] = key + + intents = dialog_dict.get("intents", {}) +except Exception as e: + logger.error(f"Error initializing data files: {str(e)}") + searches_dict = {} + utterances = {} + intents = {} + +# Create Falcon application with middleware +middlewares = [CORSMiddleware(), RequestParser(), StripPathMiddleware()] +try: + # For newer Falcon versions + app = falcon.App(middleware=middlewares) +except: + # For older Falcon versions + app = falcon.API(middleware=middlewares) + +#logger.error(f"@1") +# Add routes for well-api +well_api_instance = WellApi() + +# New routes for well_api with multiple access paths +app.add_route('/function/well-api', well_api_instance) +app.add_route('/function/well-api/{path}', well_api_instance) +app.add_route('/api/well_api', well_api_instance) +app.add_route('/api/well_api/{path}', well_api_instance) +app.add_route('/healthz', well_api_instance, suffix='healthz') + +# Add routes for the standard API paths +app.add_route('/health', well_api_instance) +app.add_route('/users', well_api_instance) +app.add_route('/items', well_api_instance) + +# Keep the original routes for backward compatibility +app.add_route('/', well_api_instance) +app.add_route('/{path}', well_api_instance) + +MQTTSERVERL = "eluxnetworks.net" +MQTT_PortL = 443 + +MyName = "well-api" + +clientL = mqtt.Client(client_id=MyName+str(time.time()), transport="websockets") +clientL.tls_set(cert_reqs=ssl.CERT_NONE) # For self-signed certs, use proper CA in production +clientL.ws_set_options(path="/mqtt") # Important! Same path as in your JS code +clientL.username_pw_set("well_user","We3l1_best!") + +clientL.on_connect = on_connectL +clientL.on_message = on_messageL + +#clientL.connect(MQTTSERVERL, MQTT_PortL, 60) +#lientL.loop_start() +#logger.error(f"@2") + +# This code runs when executed directly (for development/debugging) +if __name__ == "__main__": + from wsgiref.simple_server import make_server + redis_conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB) + # Use port 8000 for local debugging + port = int(os.environ.get('PORT', 8000)) + #port = int(os.environ.get('PORT', 1998)) + + # Create a WSGI server + with make_server('', port, app) as httpd: + print(f'Serving on port {port}...') + + # Serve until process is killed + httpd.serve_forever() +else: + redis_conn = redis.Redis(host=redis_host, port=6379, db=0) diff --git a/well-api42-not-working-webhook-claude.py b/well-api42-not-working-webhook-claude.py new file mode 100644 index 0000000..538c869 --- /dev/null +++ b/well-api42-not-working-webhook-claude.py @@ -0,0 +1,21410 @@ +#!/usr/bin/env python3 +#Vesion 2.0.1 +import os +import sys +import ast +from ast import literal_eval +import falcon +from falcon import HTTP_200, HTTP_400, HTTP_401, HTTP_500 +import json +import logging +from dotenv import load_dotenv +import calendar +import io +import datetime +from datetime import timedelta, timezone +import jwt +import psycopg2 +import html +import re +import fnmatch +import traceback +import time +import pytz +from PIL import Image, ImageDraw, ImageFont +import paho.mqtt.client as mqtt +import ssl +import hashlib +import itertools +from collections import defaultdict, deque +import warnings +from io import BytesIO +import zipfile +from minio import Minio +from minio.error import S3Error +import numpy as np +import cv2 +from sklearn.mixture import GaussianMixture +import openai +from openai import OpenAI +from typing import List, Tuple +import redis +import base64 +import requests +import uuid +import csv +import random + +base_url = "http://192.168.68.70:5050" + +# Try to import the module +try: + from filter_short_groups import filter_short_groups_c + print("Successfully imported filter_short_groups_c") +except ImportError as e: + print(f"Error importing module: {e}") + exit(1) + + +device_lookup_cache = {} +threshold_cache = {} +humidity_offset = 34 +temperature_offset = -10 +st = 0 +if True: + + #from scipy import interpolate + from scipy.optimize import curve_fit + from scipy import stats + import pandas as pd + #from scipy.signal import savgol_filter + +EnablePlot = False #True + +if EnablePlot: + import matplotlib + matplotlib.use('Agg') # Set the backend before importing pyplot + import matplotlib.pyplot as plt + from matplotlib.colors import LinearSegmentedColormap + import matplotlib.dates as mdates + +# Configure logging +logging.basicConfig( + level=logging.DEBUG,# .ERROR, + format='%(asctime)s [%(levelname)s] %(message)s' +) +logger = logging.getLogger(__name__) + +location_names = {-1:"All",0:"?",5:"Office",6:"Hallway",7:"Garage",8:"Outside",9:"Conference Room",10:"Room",34:"Kitchen", + 56:"Bedroom",78:"Living Room",102:"Bathroom",103:"Dining Room",104:"Bathroom Main",105:"Bathroom Guest", + 106:"Bedroom Master", 107:"Bedroom Guest", 108:"Conference Room", 109:"Basement", 110:"Attic", 200:"Other"} + +#Loc2Color = {"?":(0,0,0),"Office":(255,255,0),"Hallway":(128,128,128),"Garage":(128,0,0),"Outside":(0,0,0),"Conference Room":(0,0,128), + #"Room":(64,64,64),"Kitchen":(255,0,0),"Bedroom":(16,255,16),"Living Room":(160,32,240),"Bathroom":(0,0,255), + #"Dining Room":(255,128,0),"Bathroom Main":(16,16,255), "Bedroom Master":(0,255,0),"Bathroom Guest":(32,32,255), + #"Bedroom Guest":(32,255,32), "Basement":(64,64,64), "Attic":(255,165,0), "Other":(192,192,192)} + +Loc2Color = {"Bedroom":((16,255,16),0),"Bedroom Master":((0,255,0),0),"Bedroom Guest":((32,255,32),0),"Bathroom":((0,0,255),1), + "Bathroom Main":((16,16,255),1),"Bathroom Guest":((32,32,255),1),"Kitchen":((255,0,0),2),"Dining Room":((255,128,0),3),"Dining":((255,128,0),3), + "Office":((255,255,0),4),"Conference Room":((0,0,128),5),"Conference":((0,0,128),5),"Room":((64,64,64),6),"Living Room":((160,32,240),7),"Living":((160,32,240),7),"Hallway":((128,128,128),8), + "Garage":((128,0,0),9),"Basement":((64,64,64), 10),"Attic":((255,165,0), 11),"Other":((192,192,192),12),"?":((0,0,0),13),"Outside":((0,0,0),14)} + + +s_table = ["temperature", "humidity", "pressure", "light", "radar", "voc0", "voc1", "voc2", "voc3", "voc4", "voc5", "voc6", "voc7", "voc8", "voc9"] # derived +smells_table = ["s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9"] # derived + +s_table_temp = [] + +Consolidataed_locations = {"?":"Room","Office":"Office","Hallway":"Hallway","Garage":"Garage","Outside":"Outside","Conference Room":"Office", + "Room":"Room","Kitchen":"Kitchen","Bedroom":"Bedroom","Living Room":"Living Room","Bathroom Guest":"Bathroom", + "Dining Room":"Dining Room","Bathroom":"Bathroom", "Bathroom Main":"Bathroom","Bedroom Master":"Bedroom", + "Bedroom Guest":"Bedroom", "Basement":"Basement", "Attic":"Attic", "Other":"Room"} + +AveragePercentPerLocation = {"Bedroom":[29, 37.5], "Bathroom":[2, 4], "Office":[10, 40],"Hallway":[0.1, 0.2],"Garage":[2, 3],"Outside":[5, 10], + "Room":[5, 10],"Kitchen":[5, 12.5], "Living Room":[5, 10], + "Dining Room":[5, 10], "Basement":[0, 0.2], "Attic":[0, 0.2]} + +location_indexes = {} + +for i in location_names: + location_indexes[location_names[i]] = i + + + +# HTTP Status codes +HTTP_200 = falcon.HTTP_200 +HTTP_201 = falcon.HTTP_201 +HTTP_400 = falcon.HTTP_400 +HTTP_401 = falcon.HTTP_401 +HTTP_404 = falcon.HTTP_404 +HTTP_500 = falcon.HTTP_500 + +load_dotenv() + +DB_NAME = os.getenv('DB_NAME') +DB_USER = os.getenv('DB_USER') +DB_PASSWORD = os.getenv('DB_PASSWORD') +DB_HOST = os.getenv('DB_HOST') +DB_PORT = os.getenv('DB_PORT') +MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') +MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY') +MINIO_HOST = os.getenv('MINIO_HOST') +MINIO_PORT = os.getenv('MINIO_PORT') +DAILY_MAPS_BUCKET_NAME = os.getenv('DAILY_MAPS_BUCKET_NAME') +JWT_SECRET = os.getenv('JWT_SECRET') +MASTER_ADMIN = os.getenv('MASTER_ADMIN') +MASTER_PS = os.getenv('MASTER_PS') + +OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') +model_engine = os.getenv('OPENAI_API_MODEL_ENGINE') + +# Redis Configuration +REDIS_HOST = os.getenv('REDIS_HOST', 'localhost') +REDIS_PORT = int(os.getenv('REDIS_PORT')) +REDIS_DB = int(os.getenv('REDIS_DB', 0)) +REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', None) + +ENABLE_AUDIO_PLAYBACK = True +OPT_IN_KEYWORD = "WELLNUOJOIN" +DEFAULT_TTS_VOICE = "female" +DEFAULT_TTS_LANGUAGE = "en-US" +CLIENT_STATE_PREFIX = "app_state" +TELNYX_API_KEY = os.getenv('TELNYX_API_KEY') +TELNYX_API_BASE_URL = os.getenv("TELNYX_API_BASE_URL") + +logger.debug(f"REDIS_PORT: {REDIS_PORT}") +logger.debug(f"TELNYX_API_KEY: {TELNYX_API_KEY}") +logger.debug(f"TELNYX_API_BASE_URL: {TELNYX_API_BASE_URL}") + +redis_host = os.getenv('REDIS_HOST', '192.168.68.70') +redis_host = '192.168.68.70' + +use_pdb = True +debug = False +debug_string = "" +logger.debug(f"Environment variables: {os.environ}") +filesDir = "/home/app/well_web_storage" #os.path.dirname(os.path.realpath(__file__)) +min_io_address = MINIO_HOST + ":" + MINIO_PORT + +miniIO_blob_client = Minio(min_io_address, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False) + +user_id_2_user = {} +smell_min = 1 +no_smell = 102400000 +smell_max = no_smell - 1 +sensor_legal_values = {"radar": (0,1000, 1), "co2": (smell_min, smell_max, 31), "humidity": (1,99, 31), "light": (0, 4095, 1), + "pressure": (0, 10000, 5), "temperature": (1, 60, 31), "voc": (smell_min, smell_max, 31), "voc0": (smell_min, smell_max, 31), + "voc1": (smell_min, smell_max, 31), "voc2": (smell_min, smell_max, 31), "voc3": (smell_min, smell_max, 31), "voc4": (smell_min, smell_max, 31), + "voc5": (smell_min, smell_max, 31), "voc6": (smell_min, smell_max, 31), "voc7": (smell_min, smell_max, 31), "voc8": (smell_min, smell_max, 31), "voc9": (smell_min, smell_max, 31), + "s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), + "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} + +smell_legal_values = {"s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), + "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} + + +def format_address_component(component, component_type): + """ + Apply proper capitalization rules based on component type + """ + if not component: + return "" + + component = component.strip() + + if component_type == 'street_number': + # Street numbers should remain as-is + return component + + elif component_type in ['street_name', 'city', 'country']: + # Title case with special handling for common patterns + return title_case_address(component) + + elif component_type == 'state': + # States should be uppercase if abbreviation, title case if full name + if len(component) == 2: + return component.upper() + else: + return title_case_address(component) + + elif component_type == 'zip_code': + # ZIP codes remain as-is + return component + + elif component_type in ['apt', 'unit', 'suite']: + # Apartment/unit numbers remain as-is + return component + + else: + # Default to title case + return title_case_address(component) + + +def title_case_address(text): + """ + Apply proper title case with address-specific rules + """ + # Words that should remain lowercase (unless at start) + lowercase_words = { + 'and', 'at', 'by', 'for', 'in', 'of', 'on', 'to', 'up', 'via', 'with' + } + + # Words that should be uppercase + uppercase_words = { + 'ne', 'nw', 'se', 'sw', 'n', 's', 'e', 'w', # Directions + 'st', 'nd', 'rd', 'th', # Ordinal suffixes + 'po', 'llc', 'inc', 'corp' # Business suffixes + } + + # Street type abbreviations that should be title case + street_types = { + 'st': 'St', 'ave': 'Ave', 'rd': 'Rd', 'dr': 'Dr', 'ln': 'Ln', + 'ct': 'Ct', 'pl': 'Pl', 'blvd': 'Blvd', 'pkwy': 'Pkwy', + 'hwy': 'Hwy', 'way': 'Way', 'circle': 'Circle', 'court': 'Court', + 'drive': 'Drive', 'lane': 'Lane', 'place': 'Place', 'road': 'Road', + 'street': 'Street', 'avenue': 'Avenue', 'boulevard': 'Boulevard' + } + + words = text.lower().split() + result = [] + + for i, word in enumerate(words): + # Remove punctuation for comparison + clean_word = word.rstrip('.,;:') + punctuation = word[len(clean_word):] + + if clean_word in uppercase_words: + result.append(clean_word.upper() + punctuation) + elif clean_word in street_types: + result.append(street_types[clean_word] + punctuation) + elif i > 0 and clean_word in lowercase_words: + result.append(clean_word + punctuation) + else: + # Handle special cases like "McDonald", "O'Connor" + formatted = format_special_cases(clean_word) + result.append(formatted + punctuation) + + return ' '.join(result) + + +def format_special_cases(word): + """ + Handle special capitalization cases + """ + # Handle names with apostrophes (O'Connor, D'Angelo) + if "'" in word: + parts = word.split("'") + return "'".join(part.capitalize() for part in parts) + + # Handle hyphenated words + if "-" in word: + parts = word.split("-") + return "-".join(part.capitalize() for part in parts) + + # Handle Mc/Mac prefixes + if word.startswith('mc') and len(word) > 2: + return 'Mc' + word[2:].capitalize() + elif word.startswith('mac') and len(word) > 3: + return 'Mac' + word[3:].capitalize() + + # Default capitalization + return word.capitalize() + + +def recreate_address(parsed_address): + """ + Recreate a properly formatted address string from parsed components + + Args: + parsed_address (dict): Dictionary containing parsed address components + OR list of tuples: [('value', 'type'), ...] + + Expected keys/types: + - street_number/house_number + - street_name/road + - apt/unit/suite (optional) + - city + - state + - zip_code/postcode + - country + + Returns: + str: Properly formatted address string + """ + + # Handle both dict and list of tuples formats + if isinstance(parsed_address, list): + # Convert list of tuples to dict + addr_dict = {} + for value, addr_type in parsed_address: + # Map the parser's field names to our expected names + if addr_type == 'house_number': + # Only use house_number if street_number doesn't already exist + if 'street_number' not in addr_dict: + addr_dict['street_number'] = value + elif addr_type == 'street_number': + # street_number takes priority over house_number + addr_dict['street_number'] = value + elif addr_type == 'road': + addr_dict['street_name'] = value + elif addr_type == 'postcode': + addr_dict['zip_code'] = value + else: + addr_dict[addr_type] = value + parsed_address = addr_dict + + # Format each component + street_number = format_address_component( + parsed_address.get('street_number', ''), 'street_number' + ) + + street_name = format_address_component( + parsed_address.get('street_name', ''), 'street_name' + ) + + # Handle apartment/unit/suite + apt_unit = '' + for key in ['apt', 'apartment', 'unit', 'suite', 'ste']: + if parsed_address.get(key): + apt_value = format_address_component(parsed_address[key], 'apt') + if key.lower() in ['apt', 'apartment']: + apt_unit = f"Apt {apt_value}" + elif key.lower() == 'unit': + apt_unit = f"Unit {apt_value}" + elif key.lower() in ['suite', 'ste']: + apt_unit = f"Suite {apt_value}" + break + + city = format_address_component( + parsed_address.get('city', ''), 'city' + ) + + state = format_address_component( + parsed_address.get('state', ''), 'state' + ) + + zip_code = format_address_component( + parsed_address.get('zip_code', ''), 'zip_code' + ) + + country = format_address_component( + parsed_address.get('country', ''), 'country' + ) + + # Build the address string - detect format based on available components + if country and not state: + # International format (like Croatian): Street Number, PostalCode, City, Country + address_parts = [] + + # Street address line + street_parts = [street_number, street_name] + street_line = ' '.join(filter(None, street_parts)) + + if apt_unit: + street_line += f", {apt_unit}" + + if street_line: + address_parts.append(street_line) + + # Add postal code, city, country as separate parts + if zip_code: + address_parts.append(zip_code) + if city: + address_parts.append(city) + if country: + address_parts.append(country) + + return ', '.join(address_parts) + + else: + # US format: Street\nCity, State ZIP + address_parts = [] + + # Street address line + street_parts = [street_number, street_name] + street_line = ' '.join(filter(None, street_parts)) + + if apt_unit: + street_line += f", {apt_unit}" + + if street_line: + address_parts.append(street_line) + + # City, State ZIP line + city_state_zip = [] + if city: + city_state_zip.append(city) + + if state and zip_code: + city_state_zip.append(f"{state} {zip_code}") + elif state: + city_state_zip.append(state) + elif zip_code: + city_state_zip.append(zip_code) + + if city_state_zip: + address_parts.append(', '.join(city_state_zip)) + + return '\n'.join(address_parts) + + +def GetRedisInt(key_name): + try: + result = int(redis_conn.get(key_name).decode('utf-8')) + except: + result = None + return result + +def GetRedisFloat(key_name): + try: + result = float(redis_conn.get(key_name).decode('utf-8')) + except: + result = None + + return result + +def GetRedisString(key_name): + try: + result = redis_conn.get(key_name).decode('utf-8') + except: + result = None + return result + +def GetRedisMap(key_name): + try: + result_bytes = redis_conn.hgetall(key_name) + result = {k.decode('utf-8'): v.decode('utf-8') for k, v in result_bytes.items()} + except: + result = {} + return result + + + +def read_file(file_name, source = "LOCAL", type_ = "TEXT", bucket_name="daily-maps"): + + blob_data = "" + if source == "MINIO": + blob_data = ReadObjectMinIO(bucket_name, file_name) + elif source == "LOCAL": + login_file = os.path.join(filesDir, file_name) + login_file = login_file.replace("\\","/") + logger.debug(f"Full file path: {login_file}") + logger.debug(f"File exists: {os.path.exists(login_file)}") + #print(login_file) + if type_ == "TEXT": + with open(login_file, encoding="utf8") as f: + blob_data = f.read() + else: + with open(login_file, 'rb') as f: + blob_data = f.read() + + elif source == "AZURE": + try: + blob_data = ""#container_client.download_blob(file_name).readall() + except Exception as err: + logger.error("Not reading Azure blob "+str(err)) + blob_data = "" + return blob_data + else: + pass + return blob_data + + +def match_with_wildcard(string, pattern): + return fnmatch.fnmatchcase(string, pattern) + +def extract_differing_part(string, pattern): + regex_pattern = re.escape(pattern).replace(r'\*', r'(.+)') + match = re.match(regex_pattern, string) + if match: + return match.group(1) + else: + return None + +def get_db_connection(): + + #when new_table created: + #GRANT ALL PRIVILEGES ON TABLE public.new_table TO well_app; + #GRANT ALL PRIVILEGES ON TABLE public.new_table TO postgres; + return psycopg2.connect(dbname=DB_NAME, user=DB_USER, password=DB_PASSWORD, host=DB_HOST, port=DB_PORT) + + +def generate_token(username): + expiration = datetime.datetime.now(timezone.utc) + timedelta(hours=24) + token = jwt.encode({"username": username, "exp": expiration}, JWT_SECRET, algorithm="HS256") + return token + + +def verify_token(token): + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) + return payload + except jwt.ExpiredSignatureError: + return None + except jwt.InvalidTokenError: + return None + +def SmartSplit(data_string): + """ + Splits a comma-separated string into a list, properly handling nested structures + and converting values to appropriate Python types using only the ast library. + """ + if not data_string: + return [] + + # Remove trailing comma if present + data_string = data_string.rstrip(',') + + items = [] + current_item = "" + bracket_count = 0 + in_quotes = False + quote_char = None + + i = 0 + while i < len(data_string): + char = data_string[i] + + # Handle quotes + if char in ('"', "'") and (i == 0 or data_string[i-1] != '\\'): + if not in_quotes: + in_quotes = True + quote_char = char + elif char == quote_char: + in_quotes = False + quote_char = None + + # Track brackets only when not in quotes + if not in_quotes: + if char in '[{(': + bracket_count += 1 + elif char in ']}': + bracket_count -= 1 + + # Split on comma only when not inside brackets/quotes + if char == ',' and bracket_count == 0 and not in_quotes: + items.append(current_item.strip()) + current_item = "" + else: + current_item += char + + i += 1 + + # Add the last item + if current_item.strip(): + items.append(current_item.strip()) + + # Convert each item using ast.literal_eval when possible + result = [] + for item in items: + if item == '': + result.append(None) + else: + try: + # Try to evaluate as Python literal + converted = ast.literal_eval(item) + result.append(converted) + except (ValueError, SyntaxError): + # If it fails, keep as string + result.append(item) + + return result + + +def SaveObjectInBlob(file_name, obj): + """ + Saves a Python object to MinIO blob storage using JSON serialization + + Args: + file_name (str): Name of the file to save in blob storage + obj: Python object to serialize and save + """ + try: + # Convert object to JSON string + json_str = json.dumps(obj) + # Convert string to bytes + json_bytes = json_str.encode('utf-8') + + # Save to MinIO + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(json_bytes), + len(json_bytes) + ) + return True + except Exception as e: + logger.error(f"Error saving object to blob: {traceback.format_exc()}") + return False + +def SaveGenericObjectInBlob(bucket_name, file_name, obj): + """ + Saves a Python object to MinIO blob storage using JSON serialization + + Args: + file_name (str): Name of the file to save in blob storage + obj: Python object to serialize and save + """ + try: + # Convert object to JSON string + json_str = json.dumps(obj) + # Convert string to bytes + json_bytes = json_str.encode('utf-8') + + # Save to MinIO + miniIO_blob_client.put_object( + bucket_name, + file_name, + io.BytesIO(json_bytes), + len(json_bytes) + ) + return True + except Exception as e: + logger.error(f"Error saving object to blob: {traceback.format_exc()}") + return False + + + +def ReadObjectMinIO(bucket_name, file_name, filter_date=None): + """ + Read object from MinIO with optional date filtering. + + Args: + bucket_name (str): Name of the MinIO bucket + file_name (str): Name of the file/object + filter_date (str, optional): Date string in format "YYYY-MM-DD". + If provided, returns empty string if object + was modified before or on this date. + + Returns: + str: Object content as string, empty string if filtered out, or None on error + """ + try: + # If date filtering is requested, check object's last modified date first + if filter_date: + try: + # Get object metadata to check last modified date + stat = miniIO_blob_client.stat_object(bucket_name, file_name) + last_modified = stat.last_modified + + # Parse filter date (assuming format YYYY-MM-DD) + target_date = datetime.datetime.strptime(filter_date, "%Y-%m-%d").date() + + # If object was modified before or on target date, return empty string + if last_modified.date() <= target_date: + return None + + except S3Error as e: + logger.error(f"Error getting metadata for {file_name}: {e}") + return None + except ValueError as e: + logger.error(f"Invalid date format '{filter_date}': {e}") + return None + + # Retrieve the object data + response = miniIO_blob_client.get_object(bucket_name, file_name) + # Read the data from response + data_bytes = response.read() + # Convert bytes to string + data_string = data_bytes.decode('utf-8') + # Don't forget to close the response + response.close() + response.release_conn() + return data_string + + except S3Error as e: + logger.error(f"An error occurred while reading {file_name}: {e}") + return None + except: + logger.error(f"An error occurred while decoding {file_name}") + return None + +#def ReadObjectMinIO(bucket_name, file_name): + #try: + ## Retrieve the object data + #response = miniIO_blob_client.get_object(bucket_name, file_name) + + ## Read the data from response + #data_bytes = response.read() + + ## Convert bytes to string and then load into a dictionary + #data_string = data_bytes.decode('utf-8') + + ## Don't forget to close the response + #response.close() + #response.release_conn() + + #return data_string + + #except S3Error as e: + #logger.error(f"An error occurred: {e}") + #return None + + +def package_response_C(payload, status_code=HTTP_200): + """Package response in a standard format""" + if status_code == HTTP_200: + return {"status": "success", "data": payload} + else: + return {"status": "error", "message": payload, "code": status_code} + +def package_response(content, status=falcon.HTTP_200): + """ + Format the HTTP response. + + :param content: The content to be returned in the response. + :param status: HTTP status code (default is 200 OK). + :return: A dictionary containing the formatted response. + """ + if isinstance(content, str): + # If content is a string, try to parse it as JSON + try: + response = json.loads(content) + except json.JSONDecodeError: + # If it's not valid JSON, use it as message + response = {"message": content} + elif isinstance(content, dict): + # If content is a dictionary, serialize it with datetime handling + try: + # First serialize to JSON string with datetime handling + json_str = json.dumps(content, default=datetime_handler) + # Then parse back to dict + response = json.loads(json_str) + except TypeError as e: + response = {"message": f"Serialization error: {str(e)}"} + else: + # For any other type, convert to string and use as message + response = {"message": str(content)} + + # Add status code to the response + response["status"] = status + + # Handle specific status codes + if status == falcon.HTTP_400: + response["error"] = "Bad Request" + elif status == falcon.HTTP_401: + response["error"] = "Unauthorized" + elif status == falcon.HTTP_500: + response["error"] = "Internal Server Error" + + return response + +def GetPriviledges(conn, user_name, password): + sql = "SELECT key, access_to_deployments, user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None and result != []: + if result[0][0] == password: + return result[0][1], result[0][2] + else: + return "0", "0" + else: + return "0", "0" + +def GetPriviledgesOnly(user): + with get_db_connection() as conn: + if isinstance(user, (int)) or user.isdigit(): + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_id = " + user + else: + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_name = '" + user + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetUserId(user_name): + with get_db_connection() as conn: + sql = "SELECT user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetNameFromUserId(user_id): + with get_db_connection() as conn: + sql = f"SELECT user_name, first_name, last_name FROM public.person_details WHERE user_id = {user_id}" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0] + else: + return None + + + +def ListDeployments(priviledges, user_id): + + global user_id_2_user + + conn = get_db_connection() + + if priviledges == "-1": + sql = "SELECT * FROM public.deployments ORDER BY deployment_id ASC;" + else: + sql = f"SELECT * FROM public.deployments WHERE deployment_id IN ({priviledges}) OR user_edit = {user_id} ORDER BY deployment_id ASC;" + + try: + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + complete_result = [] + else: + deployment_ids = [] + deployment_records_dict = {} + for record in result: + deployment_id = record[0] + deployment_ids.append(deployment_id) + deployment_records_dict[deployment_id] = record + + sql = f"SELECT * FROM public.deployment_details WHERE deployment_id IN ({','.join(map(str, deployment_ids))}) ORDER BY deployment_id ASC;" + cur.execute(sql) + details_result = cur.fetchall() + + beneficiary_ids = [] + + for record_details in details_result: + if record_details[1] != None and record_details[1] not in beneficiary_ids: + beneficiary_ids.append(record_details[1]) + + sql = f"SELECT * FROM public.person_details WHERE user_id IN ({','.join(map(str, beneficiary_ids))});" + + + cur.execute(sql) + user_id_2_user = {} + users = cur.fetchall()#cur.fetchone() + for usr_record in users: + user_id_2_user[usr_record[0]] = usr_record + + complete_result = [] + if details_result != None: + for record_details in details_result: + deployment_record = deployment_records_dict[record_details[0]] + complete_record = {'deployment_id': record_details[0], 'beneficiary_id': record_details[1], 'caretaker_id': record_details[2], + 'owner_id': record_details[3], 'installer_id': record_details[4], + 'address_street': record_details[6], 'address_city': record_details[7], 'address_zip': record_details[8], + 'address_state': record_details[9], 'address_country': record_details[10], + 'devices': record_details[5], 'wifis': record_details[11], 'persons': deployment_record[4], 'gender': deployment_record[5], + 'race': deployment_record[6], 'born': deployment_record[7], 'pets': deployment_record[8], 'time_zone': deployment_record[3] + } + complete_result.append(complete_record) + except: + logger.debug(f"Error: {traceback.format_exc()}") + return complete_result + +def ListCaretakers(privileges, user_name): + + conn = get_db_connection() + if privileges == "-1": + sql = "SELECT * FROM public.person_details WHERE role_ids LIKE '%2%' ORDER BY last_name;" #2 is caretaker + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + else: + #we need to check if + sql = f"SELECT * FROM public.person_details WHERE user_name = '{user_name}';" #2 is caretaker + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + + pass + return result + +def ListBeneficiaries(privilidges, user_info): + + conn = get_db_connection() + with conn.cursor() as cur: + + if (privilidges == "-1"): + sql = "SELECT * FROM public.person_details WHERE role_ids LIKE '%1%' ORDER BY last_name;" #1 is beneficiary + else: + #we need to find beneficiaries from list of deployments + sql = f"SELECT beneficiary_id FROM public.deployment_details WHERE deployment_id IN ({privilidges}) ORDER BY deployment_id ASC;" + cur.execute(sql) + result1 = cur.fetchall()#cur.fetchone() + if result1 == None: + result = [] + return result + beneficiaries = ",".join(str(x[0]) for x in result1) + sql = f"SELECT * FROM public.person_details WHERE user_id IN ({beneficiaries}) OR user_edit = {user_info} AND role_ids LIKE '%1%' ORDER BY last_name;" #1 is beneficiary + logger.debug(f"sql= {sql}") + + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + + return result + +def UserDetails(user_id): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'person_details';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.person_details WHERE user_id = "+user_id + + caretaker_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + caretaker_record[field[0]] = result[cnt] + cnt += 1 + + return caretaker_record + +def DeviceDetails(mac): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_mac = '" + mac + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + + return device_record + +def GetDeviceDetailsSingle(device_id): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_id = '" + device_id + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + + return device_record + +def GetDeviceDetailsSingleFromMac(device_mac): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_mac = '" + device_mac + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + else: + #device is not in DB so first lets find it in + pass + return device_record + +def DeploymentDetails(deployment_id): + + deployment_record = {} + + conn = get_db_connection() + + with conn.cursor() as cur: + + sql = "SELECT * FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'deployments';" + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.deployments WHERE deployment_id = '" + deployment_id + "'" + + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + deployment_record[field[3]] = result[cnt] + cnt += 1 + + sql = "SELECT * FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'deployment_details';" + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.deployment_details WHERE deployment_id = '" + deployment_id + "'" + + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + deployment_record[field[3]] = result[cnt] + cnt += 1 + + + return deployment_record + +def ValidUser(user_name, password): + + + if use_pdb: + with get_db_connection() as db_conn: + priviledges, user_id= GetPriviledges(db_conn, user_name, password) + return priviledges, user_id + + else: + pass + #container = GetReference("/MAC") + #try: + ## We can do an efficient point read lookup on partition key and id + ##response = container.read_item(item="64B708896BD8_temperature_2024-01-01_00", partition_key="64B708896BD8") #OK + ##items = query_items(container, '64B708896BD8') #Too slow + ##AddToLog("1!") + #privileges = GetCaretakers(container, email, password) + #return privileges + + #except Exception as err: + #AddToLog("Error !1 "+str(err)) + + +def GetMaxRole(user_name): + + with get_db_connection() as db_conn: + + sql = "SELECT role_ids FROM public.person_details WHERE user_name = '" + user_name + "'" + + with db_conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None and result != []: + return str(result[0]) + return "" + + +def SelectOption(html_code, select_id, selected_item): + """ + Modifies HTML code to set the selected attribute for a specific option in a select element. + + Args: + html_code (str): Original HTML code + select_id (str): ID of the select element to modify + selected_item (str or int): Value of the option to be selected + + Returns: + str: Modified HTML code with the selected attribute added + """ + # Convert selected_item to string for comparison + selected_item = str(selected_item) + + # Find the select element with the given ID + select_pattern = rf']*id=[\'"]?{select_id}[\'"]?[^>]*>(.*?)' + select_match = re.search(select_pattern, html_code, re.IGNORECASE | re.DOTALL) + + if not select_match: + return html_code # Return unchanged if select element not found + + select_content = select_match.group(0) + select_content_orig = select_content + # Remove any existing selected attributes + select_content = re.sub(r'\s+selected(?=[>\s])', '', select_content, flags=re.IGNORECASE) + + # Add selected attribute to the matching option + def replace_option(match): + value = re.search(r'value=[\'"]?([^\'">\s]+)', match.group(0)) + if value and value.group(1) == selected_item: + # Add selected attribute before the closing > + return match.group(0).rstrip('>') + ' selected>' + return match.group(0) + + modified_select = re.sub( + r']*>', + replace_option, + select_content + ) + + # Replace the original select element with the modified one + return html_code.replace(select_content_orig, modified_select) + +def FillFields(blob_data, record, form_type): + """ + Fill in the input fields in the HTML blob_data with values from the caretaker dictionary. + + :param blob_data: str - The initial HTML string containing empty or placeholder input fields. + :param caretaker: dict - The dictionary containing values to populate the fields. + :return: str - The HTML string with the input fields filled with the appropriate values. + """ + # Ensure blob_data is a string + #blob_data = str(blob_data) + + # Populate the fields + for field in record: + logger.debug(f"field= {field}") + if field == "user_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_user_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "deployment_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_deployment_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "device_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_device_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "user_name": + if record[field] != None: + escaped_string = html.escape(record[field]) + pattern = rf'(]+id="new_user_name"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="new_user_name"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + elif field == "location": + if record[field] != None: + blob_data = SelectOption(blob_data, 'location', record[field]) + + elif field == "gender": + if record[field] != None: + blob_data = SelectOption(blob_data, 'gender', record[field]) + + elif field == "race": + if record[field] != None: + blob_data = SelectOption(blob_data, 'race', record[field]) + + elif field == "time_zone_s": + if record[field] != None: + blob_data = SelectOption(blob_data, 'time_zone_s', record[field]) + + elif field == "time_edit" or field == "user_edit": + pass + else: + if record[field] != None: + escaped_string = html.escape(str(record[field])) + pattern = rf'(]+id="{field}"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="{field}"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + return blob_data + +def StoreThresholds2DB(device_id, TR, BR, TLIFE, BLIFE): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + + if device_id == None or device_id == 0: + return 1 + + try: + + + sql = f""" + UPDATE public.devices + SET + radar_threshold = '[{TR},{BR},{TLIFE},{BLIFE}]' + WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql}") + # Execute update query + print(sql) + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + logger.debug("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + + +def StoreBeneficiary2DB(parameters, editing_user_id, user_id): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + if editing_user_id == None or editing_user_id == "": + editing_user_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_user_id != "0": + sql = f""" + UPDATE public.person_details + SET + email = '{CleanObject(parameters.get('email'))}', + user_name = '{CleanObject(parameters.get('new_user_name'))}', + first_name = '{CleanObject(parameters.get('first_name'))}', + last_name = '{CleanObject(parameters.get('last_name'))}', + address_street = '{CleanObject(parameters.get('address_street'))}', + address_city = '{CleanObject(parameters.get('address_city'))}', + address_zip = '{CleanObject(parameters.get('address_zip'))}', + address_state = '{CleanObject(parameters.get('address_state'))}', + address_country = '{CleanObject(parameters.get('address_country'))}', + time_edit = {current_epoch_time}, + user_edit = {user_id}, + role_ids = '{CleanObject(parameters.get('role_ids'))}', + phone_number = '{CleanObject(parameters.get('phone_number'))}', + picture = '{CleanObject(parameters.get('picture'))}', + key = '{CleanObject(parameters.get('key'))}' + WHERE user_id = {editing_user_id}; -- replace 34 with the actual person_id you want to update + """ + + else: + sql = f""" + INSERT INTO public.person_details + (role_ids, email, user_name, first_name, last_name, address_street, address_city, address_zip, address_state, address_country, time_edit, user_edit, phone_number, picture, key) + VALUES + ('{CleanObject(parameters.get('role_ids'))}', '{CleanObject(parameters.get('email'))}', '{CleanObject(parameters.get('new_user_name'))}', + '{CleanObject(parameters.get('first_name'))}', '{CleanObject(parameters.get('last_name'))}', '{CleanObject(parameters.get('address_street'))}', + '{CleanObject(parameters.get('address_city'))}', '{CleanObject(parameters.get('address_zip'))}', '{CleanObject(parameters.get('address_state'))}', + '{CleanObject(parameters.get('address_country'))}', {current_epoch_time}, {user_id}, '{CleanObject(parameters.get('phone_number'))}', + '{CleanObject(parameters.get('picture'))}', '{CleanObject(parameters.get('key'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1, error_string + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + return 0, error_string + +def is_valid_email_strict(email): + """ + More comprehensive email validation with additional checks. + + Args: + email (str): Email address to validate + + Returns: + bool: True if email is valid, False otherwise + """ + if not isinstance(email, str) or len(email) > 254: + return False + + # Check for basic structure + if email.count('@') != 1: + return False + + local, domain = email.split('@') + + # Check local part (before @) + if not local or len(local) > 64: + return False + + # Check domain part (after @) + if not domain or len(domain) > 253: + return False + + # Comprehensive regex pattern + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + + # Additional checks + if email.startswith('.') or email.endswith('.'): + return False + + if '..' in email: + return False + + return bool(re.match(pattern, email)) + +def StoreDisclaimer2DB(parameters): + + email = CleanObject(parameters.get('email')) + if not is_valid_email_strict(email): + error_string = "email has wrong format" + #AddToLog(error_string) + return 0, error_string + + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + + sql = f""" + INSERT INTO public.disclaimers + (email, user_name, first_name, last_name, devices, date, policy_version) + VALUES + ('{CleanObject(parameters.get('email'))}', '{CleanObject(parameters.get('user_name'))}', + '{CleanObject(parameters.get('firstName'))}', '{CleanObject(parameters.get('lastName'))}', '{CleanObject(parameters.get('devices'))}', + '{CleanObject(parameters.get('agreementDate'))}', '{CleanObject(parameters.get('privacyPolicyVersion'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1, error_string + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + return 0, error_string + + +def AccountExists(user_name): + conn = get_db_connection() + + sql = "SELECT * FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + if len(result) > 0: + return True + else: + return False + return False + +def DeleteRecordFromDB(form_data): + + caretaker = form_data['user_name'] + privileges = GetPriviledgesOnly(caretaker) + + if privileges != "-1": + AddToLog("Forbidden!") + return 0 + + conn = get_db_connection() + cur = conn.cursor() + function = form_data.get('function') + if function == "deployment_delete": + user_id = form_data['user_id'] + editing_deployment_id = form_data['editing_deployment_id'] + priviledges = form_data['priviledges'] + if editing_deployment_id == None or editing_deployment_id == "" or editing_deployment_id == "0": + AddToLog("deployment_id is not defined") + cur.close() + conn.close() + return 0 + try: + if user_id == "-1": + sql = f""" + DELETE FROM public.deployments WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + sql = f""" + DELETE FROM public.deployment_details WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + else: + #lets check if user_edit = user_id + sql = f"SELECT user_edit FROM public.deployments WHERE deployment_id = '{editing_deployment_id}'" + cur.execute(sql) + result = cur.fetchone() + if priviledges != "-1": + if result[0] != int(user_id): + cur.close() + conn.close() + return 0 + + sql = f""" + DELETE FROM public.deployments WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + sql = f""" + DELETE FROM public.deployment_details WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + + elif function == "device_delete": + user_id = form_data['user_id'] + editing_device_id = form_data['editing_device_id'] + priviledges = form_data['priviledges'] + if editing_device_id == None or editing_device_id == "" or editing_device_id == "0": + AddToLog("editing_device_id is not defined") + cur.close() + conn.close() + return 0 + try: + if user_id == "-1": + sql = f""" + DELETE FROM public.deployments WHERE device_id = {editing_device_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + #else: + ##lets check if user_edit = user_id + #sql = f"SELECT user_edit FROM public.deployments WHERE deployment_id = '{editing_deployment_id}'" + #cur.execute(sql) + #result = cur.fetchone() + #if priviledges != "-1": + #if result[0] != int(user_id): + #cur.close() + #conn.close() + #return 0 + + #sql = f""" + #DELETE FROM public.deployments WHERE device_id = {editing_device_id} + #""" + #logger.debug(f"sql= {sql}") + ## Execute update query + #cur.execute(sql) + + #conn.commit() + + ## Close the cursor and connection + #cur.close() + #conn.close() + + #AddToLog("Deleted!") + #return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + else: + + #user_id = form_data['user_id'] + editing_user_id = form_data['delete_user_id'] + if editing_user_id == None or editing_user_id == "" or editing_user_id == "0": + AddToLog("user_id is not defined") + cur.close() + conn.close() + return 0 + try: + if privileges == "-1": #user_id == "-1": + sql = f""" + DELETE FROM public.person_details WHERE user_id = {editing_user_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + else: + #lets check if user_edit = user_id + sql = f"SELECT user_edit FROM public.person_details WHERE user_id = '{editing_user_id}'" + cur.execute(sql) + result = cur.fetchone() + if result[0] != int(user_id): + cur.close() + conn.close() + return 0 + + sql = f""" + DELETE FROM public.person_details WHERE user_id = {editing_user_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + +def StoreCaretaker2DB(parameters, editing_user_id, user_id): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + + if editing_user_id == None or editing_user_id == "": + editing_user_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_user_id != "0": + sql = f""" + UPDATE public.person_details + SET + role_ids = '{parameters.get('role_ids')}', + access_to_deployments = '{parameters.get('access_to_deployments')}', + email = '{parameters.get('email')}', + user_name = '{parameters.get('new_user_name')}', + first_name = '{parameters.get('first_name')}', + last_name = '{parameters.get('last_name')}', + address_street = '{parameters.get('address_street')}', + address_city = '{parameters.get('address_city')}', + address_zip = '{parameters.get('address_zip')}', + address_state = '{parameters.get('address_state')}', + address_country = '{parameters.get('address_country')}', + time_edit = {current_epoch_time}, + user_edit = {user_id}, + phone_number = '{parameters.get('phone_number')}', + picture = '{parameters.get('picture')}', + key = '{parameters.get('key')}' + WHERE user_id = {editing_user_id}; -- replace 34 with the actual person_id you want to update + """ + + else: + sql = f""" + INSERT INTO public.person_details + (role_ids, access_to_deployments, email, user_name, first_name, last_name, address_street, address_city, address_zip, address_state, address_country, time_edit, user_edit, phone_number, picture, key) + VALUES + ('{parameters.get('role_ids')}', '{parameters.get('access_to_deployments')}', '{parameters.get('email')}', '{parameters.get('new_user_name')}', '{parameters.get('first_name')}', + '{parameters.get('last_name')}', '{parameters.get('address_street')}', '{parameters.get('address_city')}', '{parameters.get('address_zip')}', '{parameters.get('address_state')}', + '{parameters.get('address_country')}', {current_epoch_time}, {user_id}, '{parameters.get('phone_number')}', '{parameters.get('picture')}', '{parameters.get('key')}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + return ok + +def StoreFlow2DB(user_name, time_s, flow_json): + + conn = get_db_connection() + cur = conn.cursor() + + query = f""" + INSERT INTO public.node_reds (user_name, last_activity, flow) + VALUES ('{user_name}', {time_s}, '{flow_json}') + ON CONFLICT (user_name) + DO UPDATE SET + last_activity = EXCLUDED.last_activity, + flow = EXCLUDED.flow + """ + logger.debug(f"sql= {query}") + + try: + #cur.execute(query, (user_name, time_s, flow_json)) + cur.execute(query) + conn.commit() + logger.debug(f"OK!") + return True + except Exception as e: + conn.rollback() + print(f"Error storing flow: {e}") + logger.debug(f"Error storing flow: {e}") + return False + finally: + cur.close() + conn.close() + logger.debug(f"Closing!") + +def StoreAlarms2DB(deployment_id, device_id, deployment_alarms_json, device_alarms_json): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + + try: + sql = f""" + UPDATE public.deployments SET alarm_details = '{CleanObject(deployment_alarms_json)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + + sql1 = f""" + UPDATE public.devices SET alert_details = '{CleanObject(device_alarms_json)}' WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def StoreAlarms2DBSimple(deployment_id, device_id, deployment_alarms_json, device_alarms_json): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + + try: + if deployment_id != 0: + sql = f""" + UPDATE public.deployments SET alarm_details = '{CleanObject(deployment_alarms_json)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + + if device_id != 0: + + sql1 = f""" + UPDATE public.devices SET alert_details = '{CleanObject(device_alarms_json)}' WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def GetAlarmSimple(deployment_id, device_id): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + deployment_alarms_json = "" + device_alarms_json = "" + + try: + if deployment_id != 0: + sql = f""" + SELECT alarm_details FROM public.deployments WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + deployment_alarms_json = cur.fetchone()[0] + except Exception as err: + pass + + + try: + if device_id != 0: + + sql1 = f""" + SELECT alert_details FROM public.devices WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + device_alarms_json = cur.fetchone()[0] + + + cur.close() + conn.close() + + return deployment_alarms_json, device_alarms_json + except Exception as err: + pass + return deployment_alarms_json, device_alarms_json + + +def CleanObject(object_in, typee = "s"): + + if typee == "n": + res = 0 + if object_in == None or object_in == "": + return 0 + if isinstance(object_in, str): + try: + res = object_in.replace("'", '"') + except: + pass + return res + else: + res = "" + if object_in == None: + return "" + if isinstance(object_in, str): + try: + res = object_in.replace("'", '"') + except: + pass + return res + return object_in + +def StoreDeployment2DB(parameters, editing_deployment_id): + conn = get_db_connection() + cur = conn.cursor() + + if editing_deployment_id == None or editing_deployment_id == "": + editing_deployment_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_deployment_id != "0": + sql = f""" + UPDATE public.deployments + SET + persons = {CleanObject(parameters.get('persons'), "n")}, + gender = {CleanObject(parameters.get('gender'), "n")}, + race = {CleanObject(parameters.get('race'), "n")}, + born = {CleanObject(parameters.get('born'), "n")}, + pets = {CleanObject(parameters.get('pets'), "n")}, + time_zone_s = '{CleanObject(parameters.get('time_zone_s'))}', + user_edit = {CleanObject(parameters.get('user_id'), "n")}, + time_edit = {current_epoch_time} + WHERE deployment_id = {CleanObject(editing_deployment_id, "n")}; + """ + + else: + sql = f""" + INSERT INTO public.deployments + (persons, gender, race, born, pets, time_zone_s, user_edit, time_edit) + VALUES + ({CleanObject(parameters.get('persons'), "n")}, {CleanObject(parameters.get('gender'), "n")}, {CleanObject(parameters.get('race'), "n")}, + {CleanObject(parameters.get('born'), "n")}, {CleanObject(parameters.get('pets'), "n")}, '{CleanObject(parameters.get('time_zone_s'))}', + {CleanObject(parameters.get('user_id'), "n")}, {current_epoch_time}) + RETURNING deployment_id; + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + if editing_deployment_id == "0": + new_deployment_id = cur.fetchone()[0] + # Commit the changes to the database + conn.commit() + + if editing_deployment_id != "0": + sql = f""" + UPDATE public.deployment_details + SET + beneficiary_id = {CleanObject(parameters.get('beneficiary_id'), "n")}, + caretaker_id = {CleanObject(parameters.get('caretaker_id'), "n")}, + owner_id = {CleanObject(parameters.get('owner_id'), "n")}, + installer_id = {CleanObject(parameters.get('installer_id'), "n")}, + address_street = '{CleanObject(parameters.get('address_street'))}', + address_city = '{CleanObject(parameters.get('address_city'))}', + address_zip = '{CleanObject(parameters.get('address_zip'))}', + address_state = '{CleanObject(parameters.get('address_state'))}', + address_country = '{CleanObject(parameters.get('address_country'))}', + + wifis = '{CleanObject(parameters.get('wifis'))}', + devices = '{CleanObject(parameters.get('devices'))}', + lat = {CleanObject(parameters.get('lat'), "n")}, + lng = {CleanObject(parameters.get('lng'), "n")}, + gps_age = {CleanObject(parameters.get('gps_age'), "n")} + + WHERE deployment_id = {editing_deployment_id}; + """ + + else: + sql = f""" + INSERT INTO public.deployment_details + (deployment_id, beneficiary_id, caretaker_id, owner_id, installer_id, address_street, address_city, address_zip, address_state, address_country) + VALUES + ({new_deployment_id}, {CleanObject(parameters.get('beneficiary_id'), "n")}, {CleanObject(parameters.get('caretaker_id'), "n")}, {CleanObject(parameters.get('owner_id'), "n")}, {CleanObject(parameters.get('installer_id'), "n")}, + '{CleanObject(parameters.get('address_street'))}', '{CleanObject(parameters.get('address_city'))}', '{CleanObject(parameters.get('address_zip'))}', '{CleanObject(parameters.get('address_state'))}', + '{CleanObject(parameters.get('address_country'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + + + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + return ok + +def StoreDevice2DB(parameters, editing_device_id): + import uuid + import time + call_id = str(uuid.uuid4())[:8] + + logger.debug(f"[{call_id}] StoreDevice2DB ENTRY - editing_device_id: {editing_device_id}") + logger.debug(f"[{call_id}] Parameters: {parameters}") + print(f"[{call_id}] StoreDevice2DB ENTRY - editing_device_id: {editing_device_id}") + + # Database connection + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + logger.debug(f"[{call_id}] Converted editing_device_id to: {editing_device_id}") + + try: + current_utc_time = datetime.datetime.now(timezone.utc) + + if editing_device_id != "0": + # Update specific device by device_id + logger.debug(f"[{call_id}] UPDATE mode - editing device_id: {editing_device_id}") + sql = f""" + UPDATE public.devices + SET + device_mac = '{CleanObject(parameters.get('device_mac'))}', + well_id = '{CleanObject(parameters.get('well_id'))}', + description = '{CleanObject(parameters.get('description'))}', + location = '{CleanObject(parameters.get('location'))}', + close_to = '{CleanObject(parameters.get('close_to'))}', + group_id = {CleanObject(parameters.get('group_id'))}, + radar_threshold = '{CleanObject(parameters.get('radar_threshold'))}', + temperature_calib = '{CleanObject(parameters.get('temperature_calib'))}', + humidity_calib = '{CleanObject(parameters.get('humidity_calib'))}' + WHERE device_id = {editing_device_id}; + """ + else: + # Use PostgreSQL's UPSERT with ON CONFLICT to handle duplicates safely + logger.debug(f"[{call_id}] INSERT/UPSERT mode") + well_id = CleanObject(parameters.get('well_id')) + device_mac = CleanObject(parameters.get('device_mac')) + + # Debug CleanObject results + logger.debug(f"[{call_id}] CleanObject results:") + logger.debug(f"[{call_id}] device_mac: '{device_mac}'") + logger.debug(f"[{call_id}] well_id: '{well_id}'") + logger.debug(f"[{call_id}] description: '{CleanObject(parameters.get('description'))}'") + logger.debug(f"[{call_id}] location: '{CleanObject(parameters.get('location'))}'") + logger.debug(f"[{call_id}] close_to: '{CleanObject(parameters.get('close_to'))}'") + logger.debug(f"[{call_id}] radar_threshold: '{CleanObject(parameters.get('radar_threshold'))}'") + logger.debug(f"[{call_id}] temperature_calib: '{CleanObject(parameters.get('temperature_calib'))}'") + logger.debug(f"[{call_id}] humidity_calib: '{CleanObject(parameters.get('humidity_calib'))}'") + logger.debug(f"[{call_id}] group_id: '{CleanObject(parameters.get('group_id'))}'") + + # Validate required fields + if not well_id or not device_mac: + logger.error(f"[{call_id}] Missing required fields: well_id={well_id}, device_mac={device_mac}") + raise ValueError("well_id and device_mac are required") + + # Use UPSERT with composite key (well_id, device_mac) + sql = f""" + INSERT INTO public.devices + (device_mac, well_id, description, location, close_to, radar_threshold, temperature_calib, humidity_calib, group_id) + VALUES + ('{device_mac}', '{well_id}', '{CleanObject(parameters.get('description'))}', + '{CleanObject(parameters.get('location'))}', '{CleanObject(parameters.get('close_to'))}', '{CleanObject(parameters.get('radar_threshold'))}', + '{CleanObject(parameters.get('temperature_calib'))}', '{CleanObject(parameters.get('humidity_calib'))}', {CleanObject(parameters.get('group_id'))}) + ON CONFLICT (well_id, device_mac) + DO UPDATE SET + description = EXCLUDED.description, + location = EXCLUDED.location, + close_to = EXCLUDED.close_to, + radar_threshold = EXCLUDED.radar_threshold, + temperature_calib = EXCLUDED.temperature_calib, + humidity_calib = EXCLUDED.humidity_calib, + group_id = EXCLUDED.group_id; + """ + + logger.debug(f"[{call_id}] Using UPSERT with composite key (well_id, device_mac)") + + logger.debug(f"[{call_id}] SQL: {sql}") + print(f"[{call_id}] SQL: {sql}") + + + logger.debug(f"[{call_id}] SQL: {sql}") + print(f"[{call_id}] SQL: {sql}") + + # === ADD THIS NEW DEBUGGING BLOCK === + final_well_id = CleanObject(parameters.get('well_id')) + final_device_mac = CleanObject(parameters.get('device_mac')) + logger.debug(f"[{call_id}] FINAL KEY VALUES FOR SQL:") + logger.debug(f"[{call_id}] well_id: {repr(final_well_id)} (Type: {type(final_well_id)})") + logger.debug(f"[{call_id}] device_mac: {repr(final_device_mac)} (Type: {type(final_device_mac)})") + + # Execute update/insert query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + logger.debug(f"[{call_id}] StoreDevice2DB SUCCESS - returning 1") + print(f"[{call_id}] StoreDevice2DB SUCCESS - returning 1") + AddToLog("Written/updated!") + return 1 + + except Exception as err: + logger.error(f"[{call_id}] Error in StoreDevice2DB: {traceback.format_exc()}") + AddToLog(traceback.format_exc()) + # Make sure to close connections in case of error + try: + if cur: + cur.close() + if conn: + conn.close() + except: + pass + logger.debug(f"[{call_id}] StoreDevice2DB ERROR - returning 0") + print(f"[{call_id}] StoreDevice2DB ERROR - returning 0") + return 0 + +def StoreGroupToDevice(editing_device_id_or_mac, group_id, user_name): + + max_wait = 30 + + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_group", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'group_id': group_id + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + + record = { + 'function': "set_group", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'group_id': group_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + logger.error(f"{(st + max_wait)-time.time()}: {queue_length}") + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.error(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.1) + + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + + try: + + #current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + #current_epoch_time = current_utc_time.timestamp() + + if editing_device_id != "0" and editing_device_id != None: + sql = f"UPDATE public.devices SET group_id = {CleanObject(group_id)} WHERE device_id = {editing_device_id}" + else: + cur.close() + conn.close() + return 0 + + logger.debug(f"sql= {sql}") + + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + + AddToLog("Written/updated!") + return str(record) + except Exception as err: + AddToLog(traceback.format_exc()) + return "" + +def StoreWellIdToDevice(editing_device_id_or_mac, well_id, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_well_id", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'well_id': well_id + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + + record = { + 'function': "set_well_id", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'well_id': well_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + logger.error(f"{(st + max_wait)-time.time()}: {queue_length}") + + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + + try: + + #current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + #current_epoch_time = current_utc_time.timestamp() + + if editing_device_id != "0": + sql = f"UPDATE public.devices SET well_id = {CleanObject(well_id)} WHERE device_id = {editing_device_id}" + else: + cur.close() + conn.close() + return 0 + + logger.debug(f"sql= {sql}") + + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + AddToLog("Written/updated!") + return str(record) + except Exception as err: + AddToLog(f"{traceback.format_exc()} {err}") + return "" + + +def GetDeviceLive(editing_device_id_or_mac, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "get_device_live", + 'user_name': user_name, + 'mac': editing_device_id_or_mac + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + record = { + 'function': "get_device_live", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + if editing_device_id != 0: + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + + return str(record) + else: + return("Error: Device not found!") + + +def StoreNetworkIdToDevice(editing_device_id_or_mac, network_id, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_network_id", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'network_id': network_id + } + MAC = editing_device_id_or_mac + else: + record = { + 'function': "set_network_id", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'network_id': network_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + logger.info(f"Cleared REDIS: from_alerter_{MAC}") + redis_conn.delete(f'from_alerter_{MAC}') + logger.info(f"Pushing to REDIS: send_requests {record_json}") + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + if queue_length > 0: + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + return str(record) + + +def DeviceReboot(editing_device_id_or_mac, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "reboot", + 'user_name': user_name, + 'mac': editing_device_id_or_mac + } + mac = editing_device_id_or_mac + else: + record = { + 'function': "reboot", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac + } + mac = DeviceId2MAC(editing_device_id_or_mac) + + try: + + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.delete(f'from_alerter_{mac}') + logger.info(f"Pushing to REDIS: send_requests {record_json}") + redis_conn.lpush('send_requests', record_json) + + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{mac}') + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{mac}') + logger.info(f"Received in REDIS: from_alerter_{mac} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + AddToLog(str(record)) + return str(record) + except Exception as err: + AddToLog(traceback.format_exc()) + return "" + +def ShowAge(seconds): + minutes = int(seconds // 60) + hours = int(minutes // 60) + days = int(hours // 24) + + if seconds >= 0: + hours = int(hours - (days * 24)) + minutes = int(minutes - (days * 24 * 60) - (hours * 60)) + seconds = int(seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60)) + + if days > 0: + report = f"{int(days)} d {int(hours)} h {int(minutes)} m {int(seconds)} s" + elif hours > 0: + report = f"{int(hours)} h {int(minutes)} m {int(seconds)} s" + elif minutes > 0: + report = f"{int(minutes)} m {int(seconds)} s" + else: + report = f"{int(seconds)} s" + + return report + else: + return "0 s" + +def UpdateDevicesTable(html_string, devices, users): + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + table_rows_string = "" + for device in devices: + #result = next(item for item in users if item[0] == device[6]) + + deployment_name = "?" + for item in users: + if item[0] == device[6]: + result = item + deployment_name = result[1] + if result[2] != None: + deployment_name = deployment_name + " " + result[2] + break + + + + + mac = device[2] + if mac == "64B7088903B4": + print("stop") + mac_row_string = f' {mac}\n' + age = time.time() - device[3] + + if (age < 300): + row_sting = f' \n {device[0]}\n' + else: + row_sting = f' \n {device[0]}\n' + + row_ending = f' \n \n' + for col_cnt in range(1, len(device)): + column_value = device[col_cnt] + #print(column_value) + if col_cnt == 2: + col_string_template = mac_row_string + elif col_cnt == 3: + if column_value > 0: + col_string_template = f' {ShowAge(age)}\n' + else: + col_string_template = f' No data\n' + elif col_cnt == 4: + col_string_template = f' {column_value}\n' + elif col_cnt == 5: + if column_value == None: + col_string_template = f' \n' + else: + col_string_template = f' {column_value}\n' + elif col_cnt == 6: + col_string_template = f' {column_value}\n'; + else: + if column_value == None: + column_value = "" + col_string_template = f' {column_value}\n' + row_sting = row_sting + col_string_template + row_sting = row_sting + row_ending + table_rows_string = table_rows_string + row_sting + #print(table_rows_string) + + html_string = html_string.replace("###ROWS###",table_rows_string) + return html_string + +def UpdateDeploymentsSelector(html_string, deployments, include_all=True, selected="1"): + # + if include_all: + selector_string = f' \n' + else: + selector_string = '' + + for deployment in deployments: + first_name = "" + last_name = "" + if deployment[1] != None: + first_name = deployment[1] + if deployment[2] != None: + last_name = deployment[2] + if deployment[0] == int(selected): + choice_string = f' \n' + else: + choice_string = f' \n' + selector_string = selector_string + choice_string + #print(selector_string) + + html_string = html_string.replace("###INSTALLS###",selector_string) + return html_string + +def GetDeviceDetails(cur, deployment_ids, location_id): + + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + macs = [mac for _, mac in deployment_ids] + #macs = list(deployment_ids.keys()) + macs_string_nq = ",".join(macs) + macs_string = "'" + "','".join(macs) + "'" + + if location_id == -1: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) + ORDER BY om.position; + """ + else: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) AND location = {location_id} + ORDER BY om.position; + """ + + cur.execute(sql) + print(sql) + devices_ids_records = cur.fetchall() + all_details = [] + + + devices_ids_list = [x[0] for x in devices_ids_records] + device_ids_string = ",".join(map(str, devices_ids_list)) + #sql = f"SELECT device_id, MAX(time) as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) GROUP BY device_id" #to slow + sql = f"SELECT DISTINCT ON (device_id) device_id, time as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) AND time > now() - INTERVAL '1 day' ORDER BY device_id, time DESC" + cur.execute(sql) + print(sql) + devices_times = cur.fetchall()#cur.fetchone() + found_device_details = {} + for device_record in devices_times: + device_id, last_message_time = device_record + found_device_details[device_id] = last_message_time + cnt = 0 + for device_table_record in devices_ids_records: + if len(devices_times) > 0: + device_id = device_table_record[0] + if device_id in found_device_details: + last_message_time = found_device_details[device_id] + last_message_epoch = int(last_message_time.timestamp()) + else: + try: + last_message_time = int(device_table_record[14]) + except: + last_message_time = 0 + last_message_epoch = last_message_time + else: + last_message_time = 0 + last_message_epoch = 0 + + #print(last_message_epoch) + #print(type(last_message_epoch)) + device_id = device_table_record[0] + mac = device_table_record[1] + well_id = device_table_record[2] + description = device_table_record[3] + if description == None: + description = "" + if device_table_record[5] != None: + if device_table_record[5] != "": + description = description + " Close to " + device_table_record[5] + location_id = device_table_record[4] + if location_id == None: + location_id = 0 + try: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + except: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + cnt += 1 + all_details.append(row_data) + + return all_details + +def GetDeviceDetailsComplete(cur, deployment_ids, location_id): + + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + macs = [mac for _, mac in deployment_ids] + #macs = list(deployment_ids.keys()) + macs_string_nq = ",".join(macs) + macs_string = "'" + "','".join(macs) + "'" + + if location_id == -1: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) + ORDER BY om.position; + """ + else: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) AND location = {location_id} + ORDER BY om.position; + """ + + cur.execute(sql) + print(sql) + devices_ids_records = cur.fetchall() + all_details = [] + + + devices_ids_list = [x[0] for x in devices_ids_records] + device_ids_string = ",".join(map(str, devices_ids_list)) + #sql = f"SELECT device_id, MAX(time) as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) GROUP BY device_id" #to slow + sql = f"SELECT DISTINCT ON (device_id) device_id, time as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) AND time > now() - INTERVAL '1 day' ORDER BY device_id, time DESC" + cur.execute(sql) + print(sql) + devices_times = cur.fetchall()#cur.fetchone() + found_device_details = {} + for device_record in devices_times: + device_id, last_message_time = device_record + found_device_details[device_id] = last_message_time + cnt = 0 + for device_table_record in devices_ids_records: + if len(devices_times) > 0: + + if device_id in found_device_details: + last_message_time = found_device_details[device_id] + last_message_epoch = int(last_message_time.timestamp()) + else: + try: + last_message_time = int(device_table_record[14]) + except: + last_message_time = 0 + last_message_epoch = last_message_time + else: + last_message_time = 0 + last_message_epoch = 0 + + #print(last_message_epoch) + #print(type(last_message_epoch)) + device_id = device_table_record[0] + mac = device_table_record[1] + well_id = device_table_record[2] + description = device_table_record[3] + alarm_details = device_table_record[16] + if description == None: + description = "" + if device_table_record[5] != None: + if device_table_record[5] != "": + description = description + " Close to " + device_table_record[5] + location_id = device_table_record[4] + if location_id == None: + location_id = 0 + #try: + # row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0], alarm_details] + #except: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0], alarm_details] + cnt += 1 + all_details.append(row_data) + + return all_details + +def GetVisibleDevices(deployments): + + devices_details = [] + stt = time.time() + with get_db_connection() as conn: + with conn.cursor() as cur: + #list all devices that user has access to + if deployments == "-1": + sql = "SELECT device_mac FROM public.devices ORDER BY device_id ASC"# SELECT deployment_id, devices FROM public.deployment_details" + macs_group = [] + deployment_ids = [] + print(sql) + cur.execute(sql) + macs_records = cur.fetchall()#cur.fetchone() + for record in macs_records: + deployment_ids.append((0, record[0])) + devices_details = GetDeviceDetails(cur, deployment_ids, -1) + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + print(sql) + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if "[" not in dev_group: + if "," not in dev_group: + dev_group = '["' + dev_group + '"]' + else: + dev_group = dev_group.replace(" ", "") + dev_group = dev_group.replace(",", '","') + dev_group = '["' + dev_group + '"]' + + macs_group = literal_eval(dev_group) + + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + else: + print(f"Deployment {deployment_id} has dev_group empty") + devices_details = GetDeviceDetails(cur, deployment_ids, -1) + #devices_details.append(devices_detail) + + return devices_details + +def GetVisibleDevicesPerLocation(deployments, location): + + devices_details = [] + + with get_db_connection() as conn: + #list all devices that user has access to + if deployments == "-1" or deployments == "0": + sql = "SELECT deployment_id, devices FROM public.deployment_details" + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + with conn.cursor() as cur: + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if dev_group[0] == "[": + macs_group = literal_eval(dev_group) + else: + macs_group = dev_group.split(',') + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + + devices_details = GetDeviceDetails(cur, deployment_ids, location_indexes[location]) + #devices_details.append(devices_detail) + + return devices_details + +def GetVisibleDevicesPerLocationComplete(deployments, location): + + devices_details = [] + + with get_db_connection() as conn: + #list all devices that user has access to + if deployments == "-1" or deployments == "0": + sql = "SELECT deployment_id, devices FROM public.deployment_details" + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + with conn.cursor() as cur: + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if dev_group[0] == "[": + macs_group = literal_eval(dev_group) + else: + macs_group = dev_group.split(',') + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + + devices_details = GetDeviceDetailsComplete(cur, deployment_ids, location_indexes[location]) + #devices_details.append(devices_detail) + + return devices_details + +def GetUsersFromDeployments(deployments): + #list all devices that user has access to + deployments_dets = [] + with get_db_connection() as conn: + try: + if deployments == "-1": + sql = f""" + SELECT dd.deployment_id, pd.first_name, pd.last_name + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + ORDER BY dd.deployment_id; + """ + else: + sql = f""" + SELECT dd.deployment_id, pd.first_name, pd.last_name + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + WHERE dd.deployment_id IN ({deployments}) + ORDER BY dd.deployment_id; + """ + with conn.cursor() as cur: + cur.execute(sql) + deployments_dets = cur.fetchall()#cur.fetchone() + except Exception as err: + logger.error("GetUsersFromDeployments "+str(err) +" "+sql) + + return deployments_dets + +def GetPreviousDate(current_date): + date_obj = datetime.datetime.strptime(current_date, "%Y-%m-%d") + # Subtract one day + previous_date = date_obj - timedelta(days=1) + # Convert back to string format + previous_date_str = previous_date.strftime("%Y-%m-%d") + return(previous_date_str) + +def CovertToIsoTime(date_s, n_minute): + + hours = n_minute // 60 # Integer division + minutes = n_minute % 60 + + base_date = datetime.datetime.strptime(date_s, "%Y-%m-%d") + final_datetime = base_date + timedelta(hours=hours, minutes=minutes) + iso_timestamp = final_datetime.isoformat() + return iso_timestamp + + +def sleep_length(presence_list, short_absence_threshold=15): + """ + Calculate the total sleep duration and wake time based on presence data. + + This function correctly interprets the presence_list to determine sleep duration by: + 1. Properly aggregating the total sleep time from all significant in-bed periods + 2. Considering short absences as part of the same sleep session + 3. Determining the wake time when the main sleep session ended + + Args: + presence_list (list): List of tuples indicating bed presence/absence + short_absence_threshold (int, optional): Maximum duration in decas to consider + an absence "short" and still count as sleep. + Default is 15 (2.5 minutes) + + Returns: + tuple: (sleep_duration_minutes, wake_time_minutes) + sleep_duration_minutes: Total sleep duration in minutes + wake_time_minutes: Minute in the day when person was determined to be + done sleeping (minutes since midnight) + """ + # Extract in-bed periods and out-of-bed periods + in_bed_periods = [] + out_bed_periods = [] + + # First process the raw data into periods + for i in range(len(presence_list)): + deca_index, deca_count = presence_list[i] + + # Skip separator tuples where deca_count is 0 + if deca_count == 0: + continue + + if deca_count > 0: # In bed + # Special case for the midnight (first) tuple + if i == 0 and deca_index == 0: + # This is time in bed before midnight + start_deca = -deca_count # Negative because it's before midnight + end_deca = 0 # Midnight + else: + start_deca = deca_index + end_deca = deca_index + deca_count + + in_bed_periods.append({ + 'start': start_deca, + 'end': end_deca, + 'duration': deca_count + }) + else: # Out of bed + out_bed_periods.append({ + 'start': deca_index, + 'end': deca_index + abs(deca_count), + 'duration': abs(deca_count) + }) + + # Sort periods to ensure chronological order + in_bed_periods.sort(key=lambda p: p['start']) + out_bed_periods.sort(key=lambda p: p['start']) + + # Merge in-bed periods that are separated by short absences + merged_periods = [] + current_period = None + + for period in in_bed_periods: + # If we're at the start or after a long break, begin a new period + if current_period is None: + current_period = period.copy() + else: + # Check if this period starts shortly after the previous one ends + gap = period['start'] - current_period['end'] + + # If the gap is negative, the periods overlap (data error), treat as continuous + if gap < 0: + gap = 0 + + # If the gap is short enough, merge the periods + if gap <= short_absence_threshold: + # Extend the current period + current_period['end'] = period['end'] + current_period['duration'] += period['duration'] + gap # Include gap + else: + # Gap too long, add the completed period and start a new one + merged_periods.append(current_period) + current_period = period.copy() + + # Add the last period if there is one + if current_period is not None: + merged_periods.append(current_period) + + # Find significant sleep periods (at least 30 minutes) - REMOVED night-time restriction + significant_sleep_threshold = 180 # 30 minutes (180 decas) + significant_periods = [p for p in merged_periods if p['duration'] >= significant_sleep_threshold] + + if significant_periods: + # Find the LATEST significant sleep period (for multi-day data) + main_sleep_period = max(significant_periods, key=lambda p: p['start']) + + # Calculate total sleep duration + sleep_duration_minutes = round(main_sleep_period['duration'] / 6) # Convert to minutes + + # Wake time is when this period ended + # Handle multi-day scenarios by taking modulo 24 hours + wake_time_deca = main_sleep_period['end'] + wake_time_minutes = round(wake_time_deca / 6) + + # If wake time is beyond 24 hours, wrap it to the current day + if wake_time_minutes >= 1440: # 1440 = 24 hours in minutes + wake_time_minutes = wake_time_minutes % 1440 + + return (sleep_duration_minutes, wake_time_minutes) + + # No significant sleep periods found + return (0, 0) + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + +def filter_short_groups_c_wc_old(presence_list, filter_size, device_id_str, from_date, to_date, time_zone_s, refresh = False): + + #days = presence_list + #for from_date, to_date + + tz = pytz.timezone(time_zone_s) + # Get current time in that timezone + current_time = datetime.datetime.now(tz) + # Return just the date part as string + now_date_str = current_time.strftime("%Y-%m-%d") + + start_date = datetime.datetime.strptime(from_date, "%Y-%m-%d") + end_date = datetime.datetime.strptime(to_date, "%Y-%m-%d") + + last_offset = 0 + #if to_date == now_date_str: + # last_offset = 1 + + # Loop through each date (including end_date) + current_date = start_date + dates_list = [] + days_difference = 1 + (end_date - start_date).days + whole_result = [0] * 6 * 1440 * (days_difference) + + is_long = False + if len(presence_list)/(6 * 1440) > (days_difference): #long version + is_long = True + + while current_date <= end_date: + current_date_str = current_date.strftime("%Y-%m-%d") + print(current_date_str) + dates_list.append(current_date_str) + current_date += timedelta(days=1) + + for day in range(1, days_difference-last_offset+1): + print(day) + end_index = (1 + day) * 6 * 1440 + if end_index > len(presence_list): + end_index = len(presence_list) + + if is_long: + start_index = end_index - 2 * 6 * 1440 + else: + start_index = end_index - 6 * 1440 + + current_date_str = dates_list[day-1] + filename_day_presence = f"/{device_id_str}/{device_id_str}_{current_date_str}_{filter_size}_presence.bin" + filtered_day_str = None + if refresh == False: + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence) + + if filtered_day_str == None: + filtered_day = filter_short_groups_c(presence_list[start_index:end_index], filter_size, device_id_str, from_date) + SaveGenericObjectInBlob("filtered-presence", filename_day_presence, filtered_day) + else: + filtered_day = json.loads(filtered_day_str) + whole_result[start_index:end_index] = filtered_day + + if current_date_str != to_date: + end_index = len(presence_list) + start_index = end_index - 2 * 6 * 1440 + filtered_day = filter_short_groups_c(presence_list[start_index:end_index], filter_size, device_id_str, from_date) + whole_result[start_index:end_index] = filtered_day + + return whole_result + +def filter_short_groups_c_wc(presence_list, filter_size, device_id_str, from_date, to_date, time_zone_s, refresh=False): + """ + Filter out short groups across multiple days. + For each target day, processes [previous_day + target_day] and extracts only the target_day result. + """ + # Setup timezone and current time + tz = pytz.timezone(time_zone_s) + current_time = datetime.datetime.now(tz) + now_date_str = current_time.strftime("%Y-%m-%d") + + start_date = datetime.datetime.strptime(from_date, "%Y-%m-%d") + end_date = datetime.datetime.strptime(to_date, "%Y-%m-%d") + + # Build dates list + current_date = start_date + dates_list = [] + while current_date <= end_date: + current_date_str = current_date.strftime("%Y-%m-%d") + print(current_date_str) + dates_list.append(current_date_str) + current_date += timedelta(days=1) + + days_difference = len(dates_list) + + # Handle current day limitation + samples_per_day = 6 * 1440 + total_samples = samples_per_day * days_difference + + # If today is the last day, limit the data + effective_total_samples = total_samples + if to_date == now_date_str: + current_minute_of_day = current_time.hour * 60 + current_time.minute + current_sample_of_day = min(current_minute_of_day * 6, samples_per_day) + effective_total_samples = (days_difference - 1) * samples_per_day + current_sample_of_day + print(f"Today detected: limiting to {current_sample_of_day} samples for last day") + + # Initialize result - use effective total samples + whole_result = [0] * effective_total_samples + + # Process each day (0-indexed to avoid confusion) + for day_idx in range(days_difference): + current_date_str = dates_list[day_idx] + print(f"Processing day {day_idx + 1}: {current_date_str}") + + # Calculate result array indices for this day + result_start_idx = day_idx * samples_per_day + result_end_idx = (day_idx + 1) * samples_per_day + + # For the last day, if it's today, limit the end index + if day_idx == days_difference - 1 and to_date == now_date_str: + result_end_idx = result_start_idx + current_sample_of_day + + # Skip if this day's range is beyond our result array + if result_start_idx >= len(whole_result): + break + + # Ensure we don't exceed result array bounds + result_end_idx = min(result_end_idx, len(whole_result)) + + # Calculate input data range - FIXED LOGIC + if day_idx == 0: + # First day: no previous day available in our data, process only current day + input_start_idx = 0 + input_end_idx = min(len(presence_list), samples_per_day) + has_previous_day = False + else: + # Other days: use previous day + current day + input_start_idx = (day_idx - 1) * samples_per_day + input_end_idx = min(len(presence_list), (day_idx + 1) * samples_per_day) + has_previous_day = True + + # Skip if no input data available + if input_start_idx >= input_end_idx or input_start_idx >= len(presence_list): + print(f"No input data available for {current_date_str}") + continue + + # Try to load cached data + filename_day_presence = f"/{device_id_str}/{device_id_str}_{current_date_str}_{filter_size}_presence.bin" + filtered_day_str = None + + if not refresh: + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence, current_date_str) + + if filtered_day_str is not None and filtered_day_str != "": + has_larger = bool(re.search(r'\b(?:[2-9]|\d{2,})\.\d+\b', filtered_day_str)) + if has_larger: + filtered_day_str = None + + if filtered_day_str is None or filtered_day_str == "": + # Filter the input data + input_data = presence_list[input_start_idx:input_end_idx] + print(f"Input range: {input_start_idx}:{input_end_idx}, length: {len(input_data)}, has_previous_day: {has_previous_day}") + + filtered_data = filter_short_groups_c(input_data, filter_size, device_id_str, from_date) + + # Calculate how much data we need for this day + needed_samples = result_end_idx - result_start_idx + + # Determine extraction offset - FIXED LOGIC + if has_previous_day and len(filtered_data) >= 2 * samples_per_day: + # We processed [previous_day + current_day], take the second day (current_day) + day_data_start = samples_per_day # Skip the first day (previous day context) + print(f"Extracting day 1 from 2-day filtered result: [{day_data_start}:{day_data_start + needed_samples}]") + elif has_previous_day and len(filtered_data) >= samples_per_day: + # We have previous day context but less than 2 full days + # Take from the portion that corresponds to current day + available_current_day_samples = len(filtered_data) - samples_per_day + day_data_start = samples_per_day + needed_samples = min(needed_samples, available_current_day_samples) + print(f"Extracting partial day 1: [{day_data_start}:{day_data_start + needed_samples}]") + else: + # First day or single day processing, take from beginning + day_data_start = 0 + print(f"Extracting day 0 (first/single day): [{day_data_start}:{day_data_start + needed_samples}]") + + day_data_end = day_data_start + needed_samples + + # Extract the day's portion, ensuring we don't exceed bounds + if day_data_start < len(filtered_data): + filtered_day = filtered_data[day_data_start:min(day_data_end, len(filtered_data))] + else: + filtered_day = [] + + print(f"Filtered data length: {len(filtered_data)}") + print(f"Extracted day data: start={day_data_start}, end={day_data_end}, length={len(filtered_day)}") + + # Cache the result + SaveGenericObjectInBlob("filtered-presence", filename_day_presence, filtered_day) + else: + filtered_day = json.loads(filtered_day_str) + + # Copy to result array + copy_length = min(len(filtered_day), result_end_idx - result_start_idx) + if copy_length > 0: + whole_result[result_start_idx:result_start_idx + copy_length] = filtered_day[:copy_length] + + print(f"Completed {current_date_str}: copied {copy_length} samples") + + return whole_result + + +def GetLastDurationMinutes(deployment_id, selected_devices, filter, ddate): + + global threshold_cache, device_lookup_cache + + max_sleep = 0 + max_device_id = 0 + max_woke_up = 0 + presence_list = [] + to_date = ddate + + + date_obj = datetime.datetime.strptime(ddate, "%Y-%m-%d") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + # Convert back to string + prev_date = previous_day.strftime("%Y-%m-%d") + + data_type = "z-graph" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + + + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Lets filter bedrooms only + just_selected_devices = [] + for device_details in devices_list: + if device_details[1] in selected_devices: + just_selected_devices.append(device_details) + + devices_list = just_selected_devices + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + #time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + #epoch_time = calendar.timegm(time_from_z.utctimetuple()) + time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from.utctimetuple()) + + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + # Calculate the difference in days + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + + if data_type == "all" or data_type == "z-graph" or data_type == "multiple": + + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + threshold_str = details[5] + if "," in threshold_str: + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + else: + if is_number(threshold_str): + threshold_lst = ["s3",float(threshold_str)] + + print(threshold_lst) + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + if len(devices_list) > 0: + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #print(sql) + if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zsql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + cur.execute(zsql) + myz_data = cur.fetchall() + + #if my_data != None: + + #device_id_2_threshold = {} + #device_id_2_location = {0: "Outside"} + #row_nr_2_device_id = {} + #cnt = 0 + #row_nr_2_device_id[0] = 0 + + ##presence_map['longpresence'] and temporary_map_day_plus are similar, except one is used for Z-graph, and another for multiple persons detection + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'] = {} + #presence_map['longpresence'] = {} + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'] = {} + + #for details in devices_list: + ##(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + #well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'][well_id] = [0] * 6 * 1440 * days_difference + + + ##presence_map[][well_id] = zeros_list + #cnt += 1 + #row_nr_2_device_id[cnt] = well_id + + #if radar_threshold_group_st == None: + #radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + #if len(radar_threshold_group_st) > 8: + #radar_threshold_group = json.loads(radar_threshold_group_st) + #else: + #radar_threshold_group = ["s3",12] + + #device_id_2_location[well_id] = location_name + #device_id_2_threshold[well_id] = radar_threshold_group + #if len(my_data) > 1: + + #start_time_ = my_data[0][0] + #parsed_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + #) + + #presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + presence_map['longpresence'] = {} + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + + print(deployment_id) + print(time_from_z_str) + print(devices_list) + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + start_time = datetime.datetime( + parsed_time.year, + parsed_time.month, + parsed_time.day, + parsed_time.hour, # Adjust for UTC-7 + parsed_time.minute, + parsed_time.second, + tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + ) + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + presence_map['longpresence'][well_id] = temporary_map_day_plus[well_id] + + for device_id in ids_list: + device_id_str = str(device_id) + if data_type == "presence" or data_type == "all" or data_type == "z-graph": + if filter > 1: + #presence_list = filter_short_groups_numpy(presence_map["presence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + #presence_list = filter_short_groups_c_wc(presence_map["presence"][id2well_id[device_id]], filter, device_id_str, ddate, to_date, time_zone_s) + #presence_map["presence"][id2well_id[device_id]] = presence_list + #longpresence_list = filter_short_groups_numpy(presence_map["longpresence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s, True) + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + + max_sleep = 0 + max_device_id = 0 + max_woke_up = 0 + for device_id in ids_list: + #print(device_id_2_threshold[id2well_id[device_id]]) + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + sleep_minutes, woke_up = sleep_length(z_graph) + if sleep_minutes > max_sleep: + max_sleep = sleep_minutes + max_device_id = device_id + max_woke_up = woke_up + presence_map = {} + return max_sleep, max_device_id, max_woke_up, z_graph + +def is_number(s): + try: + float(s) + return True + except ValueError: + return False + +def GetTempOffset(device_id): + result = 0 + sql = f""" + SELECT temperature_calib + FROM public.devices + WHERE device_id = {device_id}; + """ + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + try: + if isinstance(result, tuple): + if "," in result[0]: + string_nr = result[0].split(',')[-1].strip() + if is_number(string_nr): + return float(string_nr) + else: + return temperature_offset + else: + if is_number(result[0]): + return float(result[0]) + else: + return temperature_offset + else: + return temperature_offset + except: + return temperature_offset + + +def GetTemperature(bedroom_device_id): + result = 0 + sql = f""" + SELECT * + FROM public.sensor_readings + WHERE device_id = {bedroom_device_id} and temperature > 0 + ORDER BY "time" DESC + LIMIT 1; + """ + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + + if result == None: + return 0 + else: + return result[2] + +def GetSensorsDetailsFromDeployment(deployment_id, ddate, filter_minutes, fast=False): + #list all devices that user has access to + deployments_dets = [] + with get_db_connection() as conn: + try: + sql = f""" + SELECT pd.user_id, pd.first_name, pd.last_name, pd.address_street, pd.picture + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + WHERE dd.deployment_id ={deployment_id}; + """ + with conn.cursor() as cur: + cur.execute(sql) + deployments_dets = cur.fetchone() + except Exception as err: + logger.error("GetSensorsDetailsFromDeployment "+str(err) +" "+sql) + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Which sensor is in: Bathroom, Kitchen, Bedroom ? + bathrooms = [] + kitchens = [] + bedrooms = [] + dev_id_to_location = {0: "Outside/?"} + for device in devices_list: + dev_id_to_location[device[1]] = device[2] + if Consolidataed_locations[device[2]] == "Bathroom": + bathrooms.append(device[1]) + elif Consolidataed_locations[device[2]] == "Kitchen": + kitchens.append(device[1]) + elif Consolidataed_locations[device[2]] == "Bedroom": + bedrooms.append(device[1]) + + #we need to determine where user is seen last, and user sensor data from there... + locations_file = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + logger.debug(f"locations_file1 ={locations_file}") + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + force_recreate = False + file_exists1, file_modified_utc1 = check_file_exists(locations_file+".bin") + if file_exists1: + file_modified_local = file_modified_utc1.astimezone(pytz.timezone(time_zone_s)) + file_modified_date_local = file_modified_local.date() #local date + file_modified_date_utc = file_modified_utc1.date() + file_date_utc = MapFileToDate(locations_file) #locations_file is UTC + #if file_modified_date_local < file_date_utc: + if file_modified_utc1.date() < file_date_utc: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - file_modified_local + if time_passed.seconds > 30: #recreate if older than 5 minutes + force_recreate = True + else: + force_recreate = True + + + logger.debug(f"force_recreate={str(force_recreate)}") + + if force_recreate: + CreateLocationsStripe(locations_file, time_zone_s) + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + + last_present_device = 0 + last_present = 0 + last_bathroom = 0 + last_kitchen = 0 + last_bedroom = 0 + last_bathroom_date = ddate + last_kitchen_date = ddate + last_bedroom_date = ddate + before_last_present_device = 0 + last_present_duration = 0 + + if locations_list_s is not None: + locations_list = json.loads(locations_list_s) + + if len(locations_list) > 1: + if locations_list[-1][0] > 0: + last_present_device = locations_list[-1][0] + last_present = locations_list[-1][1] + locations_list[-1][2] + before_last_present_device, last_present_duration = FindPreviousLocation(locations_list, last_present_device, -1) + #before_last_present_device = locations_list[-3][0] + #last_present_duration = locations_list[-1][2] + + else: + last_present_device = locations_list[-2][0] + last_present = locations_list[-2][1] + locations_list[-2][2] + before_last_present_device, last_present_duration = FindPreviousLocation(locations_list, last_present_device, -2) + #before_last_present_device = locations_list[-4][0] + #last_present_duration = locations_list[-2][2] + + elif len(locations_list) == 1: + last_present_device = locations_list[0][0] + + #Lets find last bathroom presence time + + if len(locations_list) > 0 and len(bathrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bathrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bathroom: + last_bathroom = loc_time[1] + loc_time[2] + last_bathroom_date = ddate + + + #Lets find last kitchen presence time + + if len(locations_list) > 0 and len(kitchens) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in kitchens: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_kitchen: + last_kitchen = loc_time[1] + loc_time[2] + last_kitchen_date = ddate + + + #Lets find last bedroom presence time + + if len(locations_list) > 0 and len(bedrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bedrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bedroom: + last_bedroom = loc_time[1] + loc_time[2] + last_bedroom_date = ddate + + + if last_bathroom == 0 or last_kitchen == 0 or last_bedroom == 0: + ddate = GetPreviousDate(ddate) + + locations_file = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + logger.debug(f"locations_file2 ={locations_file}") + + force_recreate = False + file_exists1, file_modified_utc1 = check_file_exists(locations_file+".bin") + logger.debug(f"file_exists1={str(file_exists1)}") + logger.debug(f"file_modified_utc1={str(file_modified_utc1)}") + #file_exists1, file_modified_utc1 + if file_exists1: + file_modified_local = file_modified_utc1.astimezone(pytz.timezone(time_zone_s)) + file_modified_date_local = file_modified_local.date() + file_date_utc = MapFileToDate(locations_file) + logger.debug(f"file_modified_utc1={str(file_modified_utc1.date())} file_date_utc={str(file_date_utc)}") + if file_modified_utc1.date() < file_date_utc: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - file_modified_local + logger.debug(f"current_time={current_time} file_modified_local={file_modified_local} time_passed={time_passed}") + if time_passed.seconds > 30: #recreate if older than 5 minutes + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + CreateLocationsStripe(locations_file, time_zone_s) + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + + + logger.debug(f"locations_list_s={locations_list_s}") + if (locations_list_s is not None): + + locations_list = json.loads(locations_list_s) + + if last_present_device == 0: + if len(locations_list) > 1: + if locations_list[-1][0] > 0: + last_present_device = locations_list[-1][0] + else: + last_present_device = locations_list[-2][0] + + elif len(locations_list) == 1: + last_present_device = locations_list[0][0] + + if last_bathroom == 0: + if len(locations_list) > 0 and len(bathrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bathrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bathroom: + last_bathroom = loc_time[1] + loc_time[2] + last_bathroom_date = ddate + + if last_kitchen == 0: + if len(locations_list) > 0 and len(kitchens) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in kitchens: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_kitchen: + last_kitchen = loc_time[1] + loc_time[2] + last_kitchen_date = ddate + + if last_bedroom == 0: + if len(locations_list) > 0 and len(bedrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bedrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bedroom: + last_bedroom = loc_time[1] + loc_time[2] + last_bedroom_date = ddate + + + + last_bathroom_time = "2023-01-01T00:00:00" + if last_bathroom > 0: + last_bathroom_time = CovertToIsoTime(last_bathroom_date, last_bathroom) + + last_kitchen_time = "2023-01-01T00:00:00" + if last_kitchen > 0: + last_kitchen_time = CovertToIsoTime(last_kitchen_date, last_kitchen) + + last_bedroom_time = "2023-01-01T00:00:00" + if last_bedroom > 0: + last_bedroom_time = CovertToIsoTime(last_bedroom_date, last_bedroom) + + last_present_time = "2023-01-01T00:00:00" + if last_present > 0: + last_present_time = CovertToIsoTime(ddate, last_present) + + # debug for 48h bug + if last_bathroom_time == "2023-01-01T00:00:00" or last_kitchen_time == "2023-01-01T00:00:00" or last_bedroom_time == "2023-01-01T00:00:00": + #last_bathroom_time = "48h" if last_bathroom_time == "2023-01-01T00:00:00" else f"{last_bathroom-last_bathroom_time}" + #last_kitchen_time = "48h" if last_kitchen_time == "2023-01-01T00:00:00" else f"{last_kitchen-last_kitchen_time}" + #last_bedroom_time = "48h" if last_bedroom_time == "2023-01-01T00:00:00" else f"{last_bedroom-last_bedroom_time}" + + logger.debug(f"48h-> deployment_id={str(deployment_id)}, ddate={str(ddate)}") + logger.debug(f"48h-> force_recreate={force_recreate}") + logger.debug(f"48h-> last_bathroom_time={last_bathroom_time}|last_kitchen_time={last_kitchen_time}|last_bedroom_time={last_bedroom_time}") + logger.debug(f"48h-> devices_list={str(devices_list)}") + logger.debug(f"48h-> bathrooms={str(bathrooms)}") + logger.debug(f"48h-> kitchens={str(kitchens)}") + logger.debug(f"48h-> bedrooms={str(bedrooms)}") + logger.debug(f"48h-> locations_list_s={str(locations_list_s)}") + + + # wellness_score_percent + wellness_score_percent = 90 + + sleep_filter_minutes = 5 + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + + sleep_bathroom_visit_count = 0 + # bedroom_co2 + bedroom_co2 = 500 + device_detail = None + bedroom_temperature = 0 + sleep_hours = 0 + if fast == False: + + if len(bedrooms) > 0: + sleep_minutes, bedroom_device_id, woke_up, presence_list = GetLastDurationMinutes(deployment_id, bedrooms, sleep_filter_minutes, ddate) + sleep_hours = sleep_minutes/ 60 + # bedroom_temperature + temp_offset = GetTempOffset(bedroom_device_id) + + bedroom_temperature = GetTemperature(bedroom_device_id) + temp_offset + + # sleep_bathroom_visit_count + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + if sleep_minutes < woke_up: # went to sleep after midnight + date_sleep = ddate + to_sleep = woke_up - sleep_minutes + else:# went to sleep before midnight + to_sleep = 1440 + woke_up - sleep_minutes + + # Convert string to datetime object + previous_day = date_obj - timedelta(days=1) + date_sleep = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + + # shower_detected_time + shower_detected_time = last_bathroom_time + + # breakfast_detected_time + breakfast_detected_time = 0 + + # living_room_detected_time + living_room_time_spent = 0 + + # outside_hours + outside_hours = 0 + + #lets find last time seen at Bathroom, Kitchen, Bedroom pd.first_name, pd.last_name, pd.address_street, pd.picture + + picture_url = deployments_dets[4] + + temp_offset = GetTempOffset(last_present_device) + temperature = GetTemperature(last_present_device) + temp_offset + + + report = {} + + try: + + report = {"user_id":deployments_dets[0], + "name":deployments_dets[1] + " " + deployments_dets[2], + "address":deployments_dets[3], + "time_zone":time_zone_s, + "picture":picture_url, + "bathroom_at": last_bathroom_time, + "kitchen_at": last_kitchen_time, + "bedroom_at": last_bedroom_time, + "temperature": temperature, + "smell": "clean", + "bathroom_delayed": [6, 12], + "kitchen_delayed": [6, 12], + "bedroom_delayed": [13, 16], + "last_location": dev_id_to_location[last_present_device], + "last_detected_time": last_present_time, + "before_last_location": dev_id_to_location[before_last_present_device], + "last_present_duration": last_present_duration, + "wellness_score_percent": wellness_score_percent, + "wellness_descriptor_color": "bg-green-100 text-green-700", + "bedroom_temperature": round(bedroom_temperature, 2), + "sleep_bathroom_visit_count": sleep_bathroom_visit_count, + "bedroom_co2": bedroom_co2, + "shower_detected_time": shower_detected_time, + "breakfast_detected_time": breakfast_detected_time, + "living_room_time_spent": round(living_room_time_spent, 2), + "outside_hours": round(outside_hours, 2), + "wellness_descriptor": "Great!", + "last_seen_alert": "Alert = None", + "last_seen_alert_colors": "bg-green-100 text-green-700", #https://tailwindcss.com/docs/colors + "most_time_spent_in": "Bedroom", + "sleep_hours": round(sleep_hours, 2) + } + except Exception as e: + print(traceback.format_exc()) + return report + +def FindPreviousLocation(locations_list, last_present_device, start_index): + """ + Find the previous device_id (not last_present_device and not 0) counting backwards from start_index. + Also sum the minutes for all entries except the one being reported. + + Args: + locations_list: List of [device_id, start_time, minutes] entries + last_present_device: Device ID to exclude from search + start_index: Index to start searching backwards from + + Returns: + tuple: (device_id, total_minutes) or (None, total_minutes) if no valid device found + """ + total_minutes = 0 + found_device = None + end_index = -len(locations_list) - 1 + print(locations_list[-4:]) + # Count backwards from start_index + for i in range(start_index, end_index, -1): + device_id = locations_list[i][0] + minutes = locations_list[i][2] + + # If we haven't found our target device yet, check if this one qualifies + if found_device is None and device_id != last_present_device and device_id != 0: + found_device = device_id + break + else: + # Add minutes for all entries except the one we're reporting + total_minutes += minutes + + return found_device, total_minutes + + +def ToList(input_data): + # If input is already a list + if isinstance(input_data, list): + return [str(x).strip() for x in input_data] + + # If input is string + if isinstance(input_data, str): + # Remove outer brackets if present + cleaned = input_data.strip('()') + cleaned = cleaned.strip('[]') + # Remove extra quotes + cleaned = cleaned.replace('"', '').replace("'", '') + # Split by comma and clean each element + return [x.strip() for x in cleaned.split(',')] + + raise ValueError(f"Unsupported input type: {type(input_data)}") + +def MACsToWellIds(cur, macs_list): + + device_ids = [] + device_list = [] + + macs_string = ",".join(f"'{mac}'" for mac in macs_list) + if macs_string != "'None'": + sqlr = f"SELECT well_id, device_mac, device_id, location, description, radar_threshold, close_to FROM public.devices WHERE device_mac IN ({macs_string})" + + print (sqlr) + macs_map = {} + cur.execute(sqlr) + proximitys_list = cur.fetchall() + for well_id, mac, device_id, location, description, radar_threshold, close_to in proximitys_list: + if location == None: + location = -1 + if radar_threshold == None: + radar_threshold = "50" + if close_to == None: + close_to = "" + + macs_map[mac] = (well_id, device_id, location_names[location], description, mac, radar_threshold, close_to) + + + for mac in macs_list: + if mac != "": + print(mac) + device_ids.append(macs_map[mac][1]) + device_list.append(macs_map[mac]) + + return device_ids, device_list + +def MACsToDeviceId(MAC): + + sqlr = f"SELECT device_id FROM public.devices WHERE device_mac = '{MAC}'" + device_id = 0 + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchone() + if result != None: + device_id = result[0] + + + return device_id + +def DeviceId2MAC(device_id): + + try: + + with get_db_connection() as conn: + + sqlr = f"SELECT device_mac FROM public.devices WHERE device_id ='{device_id}'" + with conn.cursor() as cur: + + cur.execute(sqlr) + mac = cur.fetchone()[0] + return mac + except: + return "" + + +def WellId2Details(well_ids): + + try: + + with get_db_connection() as conn: + #sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + #device_ids_string = ",".join(map(str, devices_ids_list)) + sqlr = f"SELECT well_id, device_id, device_mac, location, description FROM public.devices WHERE well_id IN ({well_ids})" + print(sqlr) + with conn.cursor() as cur: + + cur.execute(sqlr) + details = cur.fetchall() + return details + except: + return "" + + + +def MACsStrToDevIds(cur, macs): + + device_ids = [] + #we need to repcakage string to contain ' + macs_list = ToList(macs) + macs_string = ",".join(f"'{mac}'" for mac in macs_list) + if macs_string != "'None'": + + sqlr = f"SELECT device_mac, device_id FROM public.devices WHERE device_mac IN ({macs_string})" + print (sqlr) + macs_map = {} + cur.execute(sqlr) + proximitys_list = cur.fetchall() + for mac, device_id in proximitys_list: + device_ids.append((mac, device_id)) + + return device_ids + + + +def ReadCleanStringDB(cur, sql): + cur.execute(sql) + temp_string = cur.fetchone() + if temp_string == None: + return "" + else: + return str(temp_string[0]).strip() + +# obtain device_list, device_ids for deployment_id on time as epoch_from_file_s (usually today) +# it tries first overridden/newly-installed (FROM public.deployment_history) +# then if none found there searches FROM public.deployment_details +def GetProximityList(deployment_id, epoch_from_file_s): + + #both are valid: + #64B70888FA84,64B70888F6F0,64B70888F860,64B70889062C,64B70888FAB0,64B708896BDC,64B708897428 + #['64B70888FA84', '64B70888F6F0', '64B70888F860', '64B70889062C', '64B70888FAB0', '64B708896BDC', '64B708897428'] + + #result_list = [] + #well_ids = [] + with get_db_connection() as conn: + + sqlr = f""" + SELECT * FROM ( + SELECT proximity + FROM public.deployment_history + WHERE deployment_id = {deployment_id} + AND time <= {epoch_from_file_s} + ORDER BY time DESC + LIMIT 1 + ) AS latest_deployment + """ + #print (sqlr) + with conn.cursor() as cur: + devices_string = ReadCleanStringDB(cur, sqlr) + + if devices_string == "": + sqlr = f"SELECT devices from public.deployment_details WHERE deployment_id ={deployment_id}" + #print (sqlr) + devices_string = ReadCleanStringDB(cur, sqlr) + + + if devices_string == "": + return [], [] + + macs_list = ToList(devices_string) + device_ids, device_list = MACsToWellIds(cur, macs_list) + return device_list, device_ids + +def FilterList(to_filter: str, allowed: str) -> str: + # Convert comma-separated strings to sets + filter_set = set(to_filter.split(',')) + allowed_set = set(allowed.split(',')) + + # Find intersection and sort the result + filtered = sorted(filter_set.intersection(allowed_set), key=int) + + # Join back to comma-separated string + return ','.join(filtered) + + +def GetMatchingDevices(privileges, group, deployment, location): + + global LocationsMap + + results=[] + if privileges != "-1": + if deployment == "" or deployment == "0": + deployment = privileges + + privileges_list = privileges.split(',') + if deployment != "0": + if "," in deployment: + deployment = FilterList(deployment, privileges) + else: + if deployment not in privileges_list: + return results + else: + if deployment == "0": + deployment = "-1" + + devices = GetVisibleDevicesPerLocation(deployment, location) + return devices + +def GetMatchingDevicesComplete(privileges, group, deployment, location): + + global LocationsMap + + results=[] + if privileges != "-1": + if deployment == "" or deployment == "0": + deployment = privileges + + privileges_list = privileges.split(',') + if deployment != "0": + if "," in deployment: + deployment = FilterList(deployment, privileges) + else: + if deployment not in privileges_list: + return results + else: + if deployment == "0": + deployment = "-1" + + devices = GetVisibleDevicesPerLocationComplete(deployment, location) + return devices + +def getOldestDeploymentHistoryFromBeneficiary(deployment_id): + #this will return oldest entry as well as last proximity (devices) + st = time.time() + print(f"*0 ----{time.time() - st}") + results=[] + well_ids_last = [] #this needs to be list of tuples (well_id, Location_st, Description) + oldest_time = None + try: + print(f"*0a ----{time.time() - st}") + with get_db_connection() as conn: + sqlr = f""" + SELECT * FROM ( + SELECT time, proximity + FROM public.deployment_history + WHERE deployment_id = {deployment_id} + ORDER BY time ASC + ) AS latest_deployment + """ + print (sqlr) + print(f"*1 ----{time.time() - st}") + with conn.cursor() as cur: + cur.execute(sqlr) + print(f"*2 ----{time.time() - st}") + results = cur.fetchall() + print(f"*3 ----{time.time() - st}") + #lets find which of historical sets has data in DB + if results == None or results == []: #look in deployment_details + sqlr = f"SELECT devices from public.deployment_details WHERE deployment_id ={deployment_id}" + #print (sqlr) + print(f"*4 ----{time.time() - st}") + devices_string = ReadCleanStringDB(cur, sqlr) + print(f"*5 ----{time.time() - st}") + macs_list = ToList(devices_string) + print(f"*6 ----{time.time() - st}") + device_ids_last, device_alls_last = MACsToWellIds(cur, macs_list) + sql_query = """ + SELECT device_id, first_seen_at + FROM device_first_seen + WHERE device_id = ANY(%s) + GROUP BY device_id; + """ + print(f"*7 ----{time.time() - st}") + try: + cur.execute(sql_query, (device_ids_last,)) + results1 = cur.fetchall() + if results1 == []: + try: + # Find the oldest timestamp for each device from sensor_readings + oldest_query = """ + SELECT device_id, MIN(time) as first_seen_at + FROM sensor_readings + WHERE device_id = ANY(%s) + GROUP BY device_id + """ + + cur.execute(oldest_query, (device_ids_last,)) + oldest_results = cur.fetchall() + + if oldest_results: + # Insert the oldest records into device_first_seen + insert_query = """ + INSERT INTO device_first_seen (device_id, first_seen_at) + VALUES (%s, %s) + ON CONFLICT (device_id) DO NOTHING + """ + + # Insert each device's first seen timestamp + for device_id, first_seen_at in oldest_results: + cur.execute(insert_query, (device_id, first_seen_at)) + + # Commit the inserts + conn.commit() + + # Set oldest_time to the earliest timestamp found + oldest_time = min(result[1] for result in oldest_results) + + AddToLog(f"Inserted {len(oldest_results)} device first seen records") + else: + # No sensor readings found for these devices + AddToLog("No sensor readings found for the specified devices") + oldest_time = None + + except Exception as e: + AddToLog(f"Error determining oldest records: {traceback.format_exc()}") + AddToLog(str(e)) + oldest_time = None + # Rollback in case of error + conn.rollback() + else: + oldest_time = results1[0][1] + except Exception as e: + AddToLog(traceback.format_exc()) + AddToLog(str(e)) + print(f"*8 ----{time.time() - st}") + else: + history_entry = results[-1] + macs_list = ToList(history_entry[1]) + print(f"*9 ----{time.time() - st}") + device_ids_last, device_alls_last = MACsToWellIds(cur, macs_list) + + + for history_entry in results: + macs_list = ToList(history_entry[1]) + print(f"*10 ----{time.time() - st}") + device_ids, device_alls = MACsToWellIds(cur, macs_list) + #print(f"*11 ----{time.time() - st}") + #sql_query = """ + #SELECT time as oldest_record_time + #FROM sensor_readings + #WHERE device_id = ANY(%s) + #ORDER BY time ASC + #LIMIT 1; + #""" + print(f"*12 ----{time.time() - st}") + print("Getting oldest record time for devices:", device_ids_last) + + #print(sql_query, device_ids_last) + #try: + #cur.execute(sql_query, (device_ids_last,)) + #results1 = cur.fetchall() + #oldest_time = results1[0][0] + #if oldest_time != None: + #break + + + #except Exception as e: + #print(str(e)) + + try: + oldest_time = get_oldest_record_time_optimized(cur, device_ids_last) + if oldest_time is not None: + break + except Exception as e: + print(str(e)) + + print(f"*13 ----{time.time() - st}") + + except Exception as e: + print(f"*0b ----{time.time() - st}") + AddToLog(traceback.format_exc()) + + print(f"*14 ----{time.time() - st}") + return oldest_time, device_alls_last + +def get_oldest_record_time_optimized(cur, device_ids): + """ + Get the oldest record time for devices, using device_first_seen cache + and only querying sensor_readings for devices not in cache. + """ + if not device_ids: + return None + + # First, check which devices already have cached first_seen times + device_ids_tuple = tuple(device_ids) + cache_query = """ + SELECT device_id, first_seen_at + FROM device_first_seen + WHERE device_id = ANY(%s) + """ + + cur.execute(cache_query, (device_ids,)) + cached_results = cur.fetchall() + cached_device_times = {device_id: first_seen for device_id, first_seen in cached_results} + + # Find devices that are NOT in cache + uncached_device_ids = [device_id for device_id in device_ids if device_id not in cached_device_times] + + # For uncached devices, query sensor_readings and populate cache + for device_id in uncached_device_ids: + try: + # Query oldest record for this specific device + oldest_query = """ + SELECT time as oldest_record_time + FROM sensor_readings + WHERE device_id = %s + ORDER BY time ASC + LIMIT 1; + """ + + cur.execute(oldest_query, (device_id,)) + result = cur.fetchone() + + if result and result[0] is not None: + oldest_time = result[0] + + # Insert into cache table (use ON CONFLICT in case of race conditions) + insert_cache_query = """ + INSERT INTO device_first_seen (device_id, first_seen_at) + VALUES (%s, %s) + ON CONFLICT (device_id) DO NOTHING; + """ + cur.execute(insert_cache_query, (device_id, oldest_time)) + + # Add to our local cache dict + cached_device_times[device_id] = oldest_time + + except Exception as e: + print(f"Error processing device_id {device_id}: {str(e)}") + continue + + # Return the earliest time among all devices that have data + valid_times = [time for time in cached_device_times.values() if time is not None] + return min(valid_times) if valid_times else None + + +def getLastEditedBeneficiary(beneficiary): + + #lets generate token here to elliminate issues with outdated token... + token = generate_token(beneficiary) + url = 'https://well-api.azurewebsites.net/api/well_api' + params = { + "name": "beneficiary_detail", + "beneficiary": beneficiary, + "token": token + } + #{"id": "user_beneficiary_bernhard@wellnuo.com", "MAC": "BENEFICIARY", "email": "bernhard@wellnuo.com", "edit_date": "Fri Aug 16 06:45:01 2024", "c_password": "bern1", "first_name": "Bernhard", "last_name": "Knigge", "address": "776 Dubanski Dr.", "address_city": "San Jose", "address_state": "CA", "address_zip": "95123", "address_country": "United States", "phone_number": "4087055709", "persons": "2", "gender": "M", "race": "W", "born": "1972", "pets": "1", "creds": "", "devs": "[[203, 'Living Room', '', '64B708890B14'], [251, 'Bathroom', '', '64B7088909E8'], [252, 'Bedroom', '', '64B708890734'], [204, 'Bathroom', 'Guest', '64B708890288'], [201, 'Kitchen', 'toaster', '64B708890584'], [202, 'Kitchen', 'stove', '64B7088906D8'], [205, 'Office', '', '64B708897018']]", "tzone": "America/Los_Angeles", "ttl": -1, "_rid": "R60hANIG-K+qTQIAAAAAAg==", "_self": "dbs/R60hAA==/colls/R60hANIG-K8=/docs/R60hANIG-K+qTQIAAAAAAg==/", "_etag": "\"3500a0ae-0000-0800-0000-66bef56d0000\"", "_attachments": "attachments/", "_ts": 1723790701} + response = requests.get(url, params=params) + if response.status_code == 200: + + text = response.text + #print(text) + if text == "Log-Out": + return text + if text[0] == "{": + data = json.loads(response.text) + date_string = data["edit_date"] + parsed_date = datetime.datetime.strptime(date_string, '%c') + # Convert the datetime object to a timestamp (epoch time) + epoch_str = str(time.mktime(parsed_date.timetuple())) + devices = data["devs"] + return(epoch_str, devices) + else: + return text,"" + else: + logger.debug((f"Failed to retrieve the data, status code: {response.status_code}")) + + return "","" + +def GetDeploymentNameFromId(Id): + + con = sqlite3.connect(main_db) + con.text_factory = str + cur = con.cursor() + results=[] + SQL = "SELECT name FROM deployments WHERE id =" + Id + df = cur.execute(SQL) + results = cur.fetchall() + if len(results) > 0: + return results[0][0] + else: + return "" + +def GetTimeZoneOfDeployment(deployment_id): + time_zone_st = 'America/Los_Angeles' + with get_db_connection() as conn: + with conn.cursor() as cur: + sqlr = f"SELECT time_zone_s from public.deployments WHERE deployment_id ={deployment_id}" + time_zone_st = ReadCleanStringDB(cur, sqlr) + return time_zone_st + +def StringToEpoch(date_string, time_zone_s): + """ + Convert a date string to epoch timestamp for start of day (midnight) in specified timezone + + Args: + date_string (str): Date in 'YYYY-MM-DD' format + time_zone_s (str): Timezone string (e.g. 'America/Los_Angeles') + + Returns: + float: Epoch timestamp in seconds + """ + # Parse the date string + date_format = '%Y-%m-%d' + naive_date = datetime.datetime.strptime(date_string, date_format) + + # Get the timezone + timezone = pytz.timezone(time_zone_s) + + # Localize the date to midnight in the specified timezone + local_date = timezone.localize(naive_date) + + # Convert to epoch timestamp + epoch_time = local_date.timestamp() + + return epoch_time + +def LocalDateToUTCEpoch(local_date_str, time_zone_s): + """ + Convert a date string to epoch timestamp for start of day (midnight) in UTC + + Args: + local_date_str (str): Date in 'YYYY-MM-DD' format + time_zone_s (str): Timezone string (e.g. 'America/Los_Angeles') + + Returns: + float: Epoch UTC timestamp in seconds + """ + timezone = pytz.timezone(time_zone_s) + # Parse the date string + date_format = '%Y-%m-%d' + local_datetime = datetime.datetime.strptime(local_date_str, date_format) + local_datetime = timezone.localize(local_datetime) + + utc_datetime = local_datetime.astimezone(pytz.UTC) + epoch_time = int(utc_datetime.timestamp()) + + return epoch_time + +def GetDeploymentDatesBoth(deployment_in): + + #when looking at the date, date is defined in TZ where device is! + #Lets take oldest data from first member of deployment + st = time.time() + date_list = [] + print(f"&0 ----{time.time() - st}") + time_zone_st = GetTimeZoneOfDeployment(deployment_in) + print(f"&1 ----{time.time() - st}") + oldest_date_dt_utc, devices_all = getOldestDeploymentHistoryFromBeneficiary(deployment_in) + print(f"&2 ----{time.time() - st}") + if oldest_date_dt_utc != None: + #get date in local time zone from UTC datetime + + #oldest_date_dt + # Get today's date + local_timezone = pytz.timezone(time_zone_st) # Replace with your local timezone + oldest_date_dt_local = oldest_date_dt_utc.astimezone(local_timezone) + today_date = datetime.datetime.now(local_timezone) + + # Generate a list of date strings from oldest_date to today in inverted order + date_list = [(today_date - timedelta(days=x)).strftime('%Y-%m-%d') for x in range((today_date - oldest_date_dt_local).days + 1)] + print(f"&3 ----{time.time() - st}") + return date_list, devices_all, time_zone_st + +def check_file_exists(file_name, bucket_name="daily-maps"): + try: + # Try to get the object's stats - this will raise an exception if the object doesn't exist + stat_result = miniIO_blob_client.stat_object(bucket_name, file_name) + last_modified_utc = stat_result.last_modified + return True, last_modified_utc + except S3Error as e: + if e.code == 'NoSuchKey': + return False, 0 + # Re-raise if it's a different error + raise + +def get_text_dimensions(text, font, font_scale, thickness): + (width, height), baseline = cv2.getTextSize(text, font, font_scale, thickness) + return { + 'width': width, + 'height': height, + 'baseline': baseline, + 'total_height': height + baseline + } + +def save_to_minio(image, filename, bucket_name="daily-maps", content_type="image/png"): + """ + Save a PIL Image directly to MinIO + + Args: + image (PIL.Image): Image to save + filename (str): Filename to use in MinIO + bucket_name (str): MinIO bucket name + content_type (str): Content type of the file + + Returns: + bool: True if successful, False otherwise + """ + logger = logging.getLogger(__name__) + try: + # Convert PIL image to bytes + img_byte_arr = io.BytesIO() + image.save(img_byte_arr, format='PNG') + img_byte_arr.seek(0) # Move to start of the BytesIO buffer + + # Upload to MinIO + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + filename, + img_byte_arr, + length=len(img_byte_arr.getvalue()), + content_type=content_type + ) + return True + + except Exception as e: + logger.error(f"Error saving to MinIO: {traceback.format_exc()}") + return False + + +def SaveImageInBlob(file_name, arr_stretched, labels = []): + + #labels=[(caption,(x,y),font,scale,color,thickness,line_type)] + try: + + image_with_text = arr_stretched.copy() + + for label in labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + # Encode the image to a memory buffer using imencode + success, encoded_image = cv2.imencode('.png', image_with_text) + AddToLog(f"success={success}") + if not success: + raise Exception("Could not encode image!") + + #AddToLog(f"DAILY_MAPS_BUCKET_NAME={DAILY_MAPS_BUCKET_NAME}") + + image_bytes = encoded_image.tobytes() + AddToLog(f"len(image_bytes)={len(image_bytes)}") + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(image_bytes), + len(image_bytes)) + return True + except Exception as e: + AddToLog(f"{traceback.format_exc()}") + logger.error(f"{traceback.format_exc()}") + return False + +def SaveImageInBlobLabelsOut(file_name, arr_stretched, labels, title_labels): + + #labels=[(caption,(x,y),font,scale,color,thickness,line_type)] + try: + + image_with_text = arr_stretched.copy() + + for label in labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + for label in title_labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + + # Encode the image to a memory buffer using imencode + success, encoded_image = cv2.imencode('.png', image_with_text) + AddToLog(f"success={success}") + if not success: + raise Exception("Could not encode image!") + + #AddToLog(f"DAILY_MAPS_BUCKET_NAME={DAILY_MAPS_BUCKET_NAME}") + + image_bytes = encoded_image.tobytes() + AddToLog(f"len(image_bytes)={len(image_bytes)}") + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(image_bytes), + len(image_bytes)) + return True + except Exception as e: + AddToLog(f"{traceback.format_exc()}") + logger.error(f"{traceback.format_exc()}") + return False + +def GetLocalTimeForDate(selected_date, time_zone_s, minutes_padding = 0): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + if minutes_padding > 0: + local_start = local_start - timedelta(minutes=minutes_padding) + local_next = local_next + timedelta(minutes=minutes_padding) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Format as strings + time_from_str = utc_start.strftime("%Y-%m-%d %H:%M:%S") + time_to_str = utc_next.strftime("%Y-%m-%d %H:%M:%S") + + return time_from_str + "+0000", time_to_str + "+0000" + +def GetLocalTimeForDateSimple(selected_date, time_zone_s, minutes_padding = 0): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + if minutes_padding > 0: + local_start = local_start - timedelta(minutes=minutes_padding) + local_next = local_next + timedelta(minutes=minutes_padding) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + return utc_start, utc_next + +def GetLocalTimeEpochsForDate(selected_date, time_zone_s): + """ + Get start and end of day epochs for a given date in a specific timezone. + + Args: + selected_date (str): Date in "YYYY-MM-DD" format + time_zone_s (str): Timezone string (e.g., "America/New_York") + + Returns: + tuple: (start_epoch, end_epoch) - Unix timestamps for start and end of day + """ + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Convert to epochs (Unix timestamps) + start_epoch = int(utc_start.timestamp()) + end_epoch = int(utc_next.timestamp()) + + return start_epoch, end_epoch + +def UTC2Local(utc_time, time_zone_s): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + #local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(selected_date) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Format as strings + time_from_str = utc_start.strftime("%Y-%m-%d %H:%M:%S") + time_to_str = utc_next.strftime("%Y-%m-%d %H:%M:%S") + + return time_from_str + "+0000", time_to_str + "+0000" + +def get_timezone_aware_datetime(time_str, timezone_str="America/Los_Angeles"): + """ + Convert a naive datetime string to a timezone-aware datetime object. + + Parameters: + time_str: String in format 'YYYY-MM-DD HH:MM:SS' + timezone_str: String representing the timezone (default: "America/Los_Angeles") + + Returns: + datetime: A timezone-aware datetime object + """ + # Parse the naive datetime + naive_dt = datetime.datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S') + + # Get the timezone + tz = pytz.timezone(timezone_str) + + # Localize the datetime (make it timezone-aware) + # localize() is the correct way to do this, as it handles DST transitions properly + aware_dt = tz.localize(naive_dt) + return aware_dt + +def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6 + } + # Add sensor columns dynamically + cols_len = len(columns) + for i in range(10): + columns[f'sensor_min_s{i}'] = i + cols_len #smell * 10 + 5 + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_smell_array_from_timescale(day_data, time_from_str, device_to_index, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Define column mappings (sensor type to position in record) + columns = { + 's0': 2, + 's1': 3, + 's2': 4, + 's3': 5, + 's4': 6, + 's5': 7, + 's6': 8, + 's7': 9, + 's8': 10, + 's9': 11 + } + ## Add sensor columns dynamically + #cols_len = len(columns) + #for i in range(10): + #columns[f'sensor_min_s{i}'] = i + cols_len #smell * 10 + 5 + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_radar_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) + columns = { + 's2': 2, + 's3': 3, + 's4': 4, + 's5': 5, + 's6': 6, + 's7': 7, + 's8': 8 + } + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def ScaleTemperature(temperature_in_f): + # Define our key temperature points and their corresponding color values + temp_points = [30, 50, 70, 90, 110] + color_values = [768, 640, 384, 128, 0] # Color values in the rainbow scale + + # Clamp the temperature to our range + clamped_temp = max(min(temperature_in_f, 110), 30) + + # Find which segment the temperature falls into + for i in range(len(temp_points) - 1): + if temp_points[i] <= clamped_temp <= temp_points[i + 1]: + # Linear interpolation between the two nearest points + t = (clamped_temp - temp_points[i]) / (temp_points[i + 1] - temp_points[i]) + color_value = int(color_values[i] + t * (color_values[i + 1] - color_values[i])) + return color_value + + # Fallback (should never reach here due to clamping) + return 0 + +def GetTemperatureColor(temperature_in_f): + color_value = ScaleTemperature(temperature_in_f) + return BestColor(color_value) + +def BestColor(in_val): + #this function uses numbers from 0 to 1279 to convert to rainbow from Blue to Red(1024) to Violet 1279 + r,g,b=0,0,0 + in_val = int(in_val) + if(in_val > 1279): + in_val = 1279 + if (in_val < 256): + r = 255 + g = in_val + elif (in_val < 512): + r = 511 - in_val + g = 255 + elif (in_val < 768): + g = 255 + b = in_val-512 + elif (in_val < 1024): + g = 1023 - in_val + b = 255 + else: + r = in_val - 1024 + b = 255 + + #if (r > 255): + # print(in_val) + # print(int(r),int(g),int(b)) + return(int(r),int(g),int(b)) + +def GrayColor(in_val): + #this function uses numbers from 0 to 1279 to convert to rainbow from Blue to Red(1024) to Violet 1279 + r,g,b=0,0,0 + in_val = int(in_val) + if(in_val < 0): + in_val = 0 + if(in_val > 255): + in_val = 255 + + r = in_val + g = r + b = r + return(int(r),int(g),int(b)) + +def fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str): + """ + Fill numpy array with data from TimeScaleDB query results. + + Parameters: + day_data: List of tuples from database query + time_from_str: Starting datetime string in format 'YYYY-MM-DD HH:MM:SS' + devices_list: List of device IDs + arr_source: Pre-initialized numpy array to fill + + Returns: + numpy.ndarray: Filled array + """ + # Parse the start time + #start_time = get_timezone_aware_datetime(time_from_str, timezone_str) + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + # Create mapping of device_ids to their index positions + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define columns and their positions in the result tuple + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Process each record + for record in day_data: + # Get minute and device_id from record + record_time = record[0] # minute column + device_id = record[1] # device_id column + + if record_time and device_id: + # Calculate minute delta + minute_delta = int((record_time - start_time).total_seconds() / 60) + + if 0 <= minute_delta < arr_source.shape[1]: + # Calculate base index for this device + base_idx = device_to_index[device_id] * len(columns) + + # Fill data for each sensor/measurement type + for col_name, col_offset in columns.items(): + value = record[col_offset] + if value is not None: # Skip NULL values + row_idx = base_idx + list(columns.keys()).index(col_name) + arr_source[row_idx, minute_delta] = value + + return arr_source + +def fast_fill_array_from_timescale_bad(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) - KEEP EXACT SAME ORDER as original + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Pre-compute column keys list for consistent indexing + column_keys = list(columns.keys()) + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + # Use pre-computed column_keys list for consistent indexing + row_idx = base_idx + column_keys.index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) - KEEP EXACT SAME ORDER as original + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Pre-compute column keys list for consistent indexing + column_keys = list(columns.keys()) + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + # Use pre-computed column_keys list for consistent indexing + row_idx = base_idx + column_keys.index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_array_from_timescale_single(day_data, time_from_str, devices_list, arr_source, sensor, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + if sensor != None: + columns = { + 'avg_temperature': 2, + 'avg_humidity': 2, + 'pressure_amplitude': 2, + 'max_light': 2, + 'radar': 2, + 'sensor_min_s0': 2, + 'sensor_min_s1': 2, + 'sensor_min_s2': 2, + 'sensor_min_s3': 2, + 'sensor_min_s4': 2, + 'sensor_min_s5': 2, + 'sensor_min_s6': 2, + 'sensor_min_s7': 2, + 'sensor_min_s8': 2, + 'sensor_min_s9': 2 + } + else: + + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + column_keys = list(columns.keys()) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] #* len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + # Use pre-computed column_keys list for consistent indexing + + + #row_idx = base_idx + 2#column_keys.index(col_name) + #values = records_array[:, column_keys.index(col_name)] + + ## Filter out None values + #valid_values = ~np.equal(values, None) + #if not np.any(valid_values): + #continue + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def CalcExtremes(arr_source, length, height): + """ + Calculate min and max values for each row within legal bounds. + Optimized version using numpy vectorized operations. + + Parameters: + arr_source: numpy array of shape (height, length+4) containing data and bounds + length: number of data points to process (typically 1440 for minutes in a day) + height: number of rows in the array + + Returns: + numpy array with min/max values stored in columns 1442 and 1443 + """ + # Extract the data portion and bounds + data = arr_source[:, :length] + ignore_below = arr_source[:, 1440:1441] # Keep 2D shape for broadcasting + ignore_above = arr_source[:, 1441:1442] # Keep 2D shape for broadcasting + + # Create masks for valid values + above_min_mask = data >= ignore_below + below_max_mask = data <= ignore_above + valid_mask = above_min_mask & below_max_mask + + # Create a masked array to handle invalid values + masked_data = np.ma.array(data, mask=~valid_mask) + + # Calculate min and max values for each row + row_mins = np.ma.min(masked_data, axis=1).filled(-0.001) + row_maxs = np.ma.max(masked_data, axis=1).filled(-0.001) + + # Store results + arr_source[:, 1442] = row_mins + arr_source[:, 1443] = row_maxs + + return arr_source + +def plot(arr, filename="histogram.png", title="Histogram Plot", figsize=(12, 6), + color='blue', style='histogram', bins=1000): + """ + Plot a 1D numpy array as a line or scatter plot + + Parameters: + arr : 1D numpy array + title : str, plot title + figsize : tuple, figure size in inches + color : str, line/point color + style : str, 'line' or 'scatter' + """ + title = filename + plt.figure(figsize=figsize) + x = np.arange(len(arr)) + + if style == 'line': + x = np.arange(len(arr)) + plt.plot(x, arr, color=color) + elif style == 'scatter': + x = np.arange(len(arr)) + plt.scatter(x, arr, color=color, alpha=0.6) + elif style == 'histogram': + plt.hist(arr.ravel(), bins=bins, range=(0, 100), color=color, alpha=0.8) + plt.yscale('log') # Using log scale for better visualization + plt.xlabel('Signal Value') + plt.ylabel('Frequency') + + plt.title(title) + plt.xlabel('Index') + plt.ylabel('Value') + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig(filename) + plt.close() + print(f"Plot saved to: {filename}") + #plt.show() + +def ShowArray(arr, threshold, filename="histogram.png", title="Histogram Plot", figsize=(12, 6), + color='blue', style='histogram', bins=1000): + """ + Plot a 1D numpy array as a line or scatter plot + + Parameters: + arr : 1D numpy array + title : str, plot title + figsize : tuple, figure size in inches + color : str, line/point color + style : str, 'line' or 'scatter' + """ + title = filename + plt.figure(figsize=figsize) + x = np.arange(len(arr)) + + if style == 'line': + x = np.arange(len(arr)) + plt.plot(x, arr, color=color) + plt.axhline(y=threshold, color='red', linestyle='--', + label=f'Threshold: {threshold:.3f}') + plt.xlabel('Index') + plt.ylabel('Value') + elif style == 'scatter': + x = np.arange(len(arr)) + plt.scatter(x, arr, color=color, alpha=0.6) + elif style == 'histogram': + plt.hist(arr.ravel(), bins=bins, range=(0, 100), color=color, alpha=0.8) + plt.yscale('log') # Using log scale for better visualization + plt.xlabel('Signal Value') + plt.ylabel('Frequency') + + plt.title(title) + plt.xlabel('Index') + plt.ylabel('Value') + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig(filename) + plt.close() + print(f"Plot saved to: {filename}") + #plt.show() + +def AddLimits_optimized(arr_source, devices_c, sensors_c, percentile): + """ + Vectorized version of AddLimits that processes all sensors at once. + + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + + # Create arrays of sensor indices for all rows + sensor_indices = np.arange(total_sensors) % sensors_c + + # Convert sensor_legal_values into arrays for vectorized access + sensor_types = np.array([s_table[i] for i in range(sensors_c)]) + min_vals = np.array([sensor_legal_values[t][0] for t in sensor_types]) + max_vals = np.array([sensor_legal_values[t][1] for t in sensor_types]) + windows = np.array([sensor_legal_values[t][2] for t in sensor_types]) + + # Get values for each row based on sensor type + row_windows = windows[sensor_indices] + row_mins = min_vals[sensor_indices] + row_maxs = max_vals[sensor_indices] + + # Process rows that need cleaning (window > 2) + clean_mask = row_windows > 2 + if np.any(clean_mask): + # Clean each row with its corresponding window size + for window in np.unique(row_windows[clean_mask]): + # Get indices of rows that need this window size + rows_to_clean = np.where(clean_mask & (row_windows == window))[0] + + # Clean each row individually (since clean_data_vectorized expects 1D input) + for row_idx in rows_to_clean: + arr_source[row_idx, :1440] = clean_data_vectorized( + arr_source[row_idx, :1440], + window, + percentile + ) + + # Set min/max values for all rows at once + arr_source[:, 1440] = row_mins + arr_source[:, 1441] = row_maxs + + return arr_source + +def AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile): + """ + Vectorized version of AddLimits that processes all sensors at once. + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + # Create arrays of sensor indices for all rows + sensor_indices = np.arange(total_sensors) % sensors_c + # Convert sensor_legal_values into arrays for vectorized access + sensor_types = np.array([smells_table[i] for i in range(sensors_c)]) + min_vals = np.array([smell_legal_values[t][0] for t in sensor_types]) + max_vals = np.array([smell_legal_values[t][1] for t in sensor_types]) + # Get values for each row based on sensor type + row_mins = min_vals[sensor_indices] + row_maxs = max_vals[sensor_indices] + + # Replace values smaller than smell_min and larger than smell_max with no_smell + # Create a mask for the data points (first 1440 columns) + data_mask_below = arr_source[:, :1440] < smell_min + data_mask_above = arr_source[:, :1440] > smell_max + data_mask_invalid = data_mask_below | data_mask_above + + # Replace invalid values with no_smell + arr_source[:, :1440][data_mask_invalid] = no_smell + + # Set min/max values for all rows at once + arr_source[:, 1440] = row_mins + arr_source[:, 1441] = row_maxs + return arr_source + +def AddLimits(arr_source, devices_c, sensors_c, percentile): + for y in range(devices_c*sensors_c): + sensor_index = y % sensors_c + min_ok, max_ok, window = sensor_legal_values[s_table[sensor_index]] + #if EnablePlot: + #if (y == 33): + #print("stop") + #plot(arr_source[y, :1440], "before_clean_sensor.png") + if window > 2: + arr_source[y, :1440] = clean_data_vectorized(arr_source[y, :1440], window, percentile) + + #if EnablePlot: + #if (y == 33): + #print("stop") + #plot(arr_source[y, :1440], "after_clean_sensor.png") + + arr_source[y][1440] = min_ok + arr_source[y][1441] = max_ok + return arr_source + +def clean_data_vectorized(data, window, percentile): + """ + Vectorized version of clean_data function using pure numpy + First removes zeros, then cleans outliers + + Parameters: + data: numpy array of sensor readings + window: int, size of rolling window + percentile: float, percentile threshold for deviation filtering + """ + # Create a copy to avoid modifying original data + working_data = data.copy() + + # Replace zeros with NaN + zero_mask = working_data == 0 + working_data[zero_mask] = np.nan + + # Create rolling window view of the data + def rolling_window(a, window): + shape = a.shape[:-1] + (a.shape[-1] - window + 1, window) + strides = a.strides + (a.strides[-1],) + return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides) + + # Pad array for edge handling + pad_width = window // 2 + padded = np.pad(working_data, pad_width, mode='edge') + + # Create rolling windows + windows = rolling_window(padded, window) + + # Calculate rolling median (ignoring NaN values) + medians = np.nanmedian(windows, axis=1) + + # Forward/backward fill any NaN in medians + # Forward fill + mask = np.isnan(medians) + idx = np.where(~mask, np.arange(mask.shape[0]), 0) + np.maximum.accumulate(idx, out=idx) + medians[mask] = medians[idx[mask]] + + # Backward fill any remaining NaNs + mask = np.isnan(medians) + idx = np.where(~mask, np.arange(mask.shape[0]), mask.shape[0] - 1) + idx = np.minimum.accumulate(idx[::-1])[::-1] + medians[mask] = medians[idx[mask]] + + # Calculate deviations (ignoring NaN values) + deviations = np.abs(working_data - medians) + + # Calculate threshold (ignoring NaN values) + threshold = np.nanpercentile(deviations, percentile) + + # Create mask and replace outliers with median values + # Points are good if they're not NaN and deviation is within threshold + good_points = (~np.isnan(working_data)) & (deviations <= threshold) + + # Replace all bad points (including zeros and outliers) with median values + result = np.where(good_points, working_data, medians) + + return result + + +def process_chunk(args): + """ + Process a chunk of rows + """ + chunk, sensors_c, sensor_legal_values, s_table, window, percentile = args + result = np.copy(chunk) + + # Process all time series in the chunk at once + result[:, :1440] = np.array([ + clean_data_vectorized(row[:1440], window, percentile) + for row in chunk + ]) + + # Set limits for all rows in chunk using vectorized operations + sensor_indices = np.arange(len(chunk)) % sensors_c + min_values = np.array([sensor_legal_values[s_table[i]][0] for i in sensor_indices]) + max_values = np.array([sensor_legal_values[s_table[i]][1] for i in sensor_indices]) + + result[:, 1440] = min_values + result[:, 1441] = max_values + + return result + + +def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw): + """ + Optimized version of FillImage function that fills the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + sensors_c: number of sensors per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + group_by: grouping strategy ("sensortype" or other) + bw: boolean flag for black and white output + + Returns: + arr_stretched: Filled array with RGB values + and vocs_scaled aray from 0 to 1280 + """ + stripes = devices_c * sensors_c + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Pre-calculate VOC rows mask + if group_by != "sensortype": + voc_rows = np.arange(stripes) >= 5 * devices_c + else: + voc_rows = (np.arange(stripes) % sensors_c) >= 5 + + # Pre-calculate destination row mapping for sensortype grouping + if group_by == "sensortype": + row_indices = np.arange(stripes) + sensor_indices = row_indices % sensors_c + device_indices = row_indices // sensors_c + dest_rows = sensor_indices * devices_c + device_indices + dest_rows = dest_rows[:, np.newaxis] * stretch_by + np.arange(stretch_by) + else: + row_indices = np.arange(stripes)[:, np.newaxis] * stretch_by + np.arange(stretch_by) + + # Optimize color calculation functions + def best_color_vectorized(vals): + """Vectorized version of BestColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 1279).astype(np.int32) + r = np.zeros_like(vals, dtype=np.int32) + g = np.zeros_like(vals, dtype=np.int32) + b = np.zeros_like(vals, dtype=np.int32) + + # Region 0-255 + mask1 = vals < 256 + r[mask1] = 255 + g[mask1] = vals[mask1] + + # Region 256-511 + mask2 = (vals >= 256) & (vals < 512) + r[mask2] = 511 - vals[mask2] + g[mask2] = 255 + + # Region 512-767 + mask3 = (vals >= 512) & (vals < 768) + g[mask3] = 255 + b[mask3] = vals[mask3] - 512 + + # Region 768-1023 + mask4 = (vals >= 768) & (vals < 1024) + g[mask4] = 1023 - vals[mask4] + b[mask4] = 255 + + # Region 1024-1279 + mask5 = vals >= 1024 + r[mask5] = vals[mask5] - 1024 + b[mask5] = 255 + + return r, g, b + + def gray_color_vectorized(vals): + """Vectorized version of GrayColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 255).astype(np.int32) + return vals, vals, vals + + color_func = gray_color_vectorized if bw else best_color_vectorized + + # Process all rows at once + valid_mask = scaled_day[:, :minutes] != -0.001 + big_min = scaled_day[:, 1442:1443] # Keep 2D shape for broadcasting + big_max = scaled_day[:, 1443:1444] + + # Calculate k factors where max > min + valid_range_mask = big_max > big_min + k = np.zeros_like(big_min) + k[valid_range_mask] = (1280 if not bw else 255) / (big_max[valid_range_mask] - big_min[valid_range_mask]) + + # Calculate normalized values for all rows at once + normalized_vals = np.zeros_like(scaled_day[:, :minutes]) + valid_range_indices = np.where(valid_range_mask)[0] + + normalized_vals[valid_range_indices] = ( + k[valid_range_indices] * + (scaled_day[valid_range_indices, :minutes] - big_min[valid_range_indices]) + ) + + # Invert VOC rows + normalized_vals[voc_rows] = (1279 if not bw else 255) - normalized_vals[voc_rows] + + # Apply valid mask + normalized_vals[~valid_mask] = 0 + + # Convert to RGB + r, g, b = color_func(normalized_vals) + + # Create RGB array + rgb_values = np.stack([r, g, b], axis=-1) + + # Handle special case where max == min + equal_range_mask = ~valid_range_mask + if np.any(equal_range_mask): + rgb_values[equal_range_mask.ravel()] = 128 + + # Fill the stretched array efficiently + if group_by == "sensortype": + arr_stretched[dest_rows] = rgb_values[:, None] + else: + arr_stretched[row_indices] = rgb_values[:, None] + + return arr_stretched, normalized_vals[voc_rows] + + +def FillSmellImage_optimized(scaled_day, arr_stretched, y_offset): + """ + Fill the stretched array with colored sensor data from scaled_day. + + Parameters: + scaled_day: 2D array of shape (70, 1444) containing sensor readings + arr_stretched: 3D array of shape (2685, 1640, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stretch_by = 20 + x_offset = 200 + + def best_color_vectorizedS(vals): + """Vectorized version of BestColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 1279).astype(np.int32) + r = np.zeros_like(vals, dtype=np.int32) + g = np.zeros_like(vals, dtype=np.int32) + b = np.zeros_like(vals, dtype=np.int32) + + # Region 0-255 + mask1 = vals < 256 + r[mask1] = 255 + g[mask1] = vals[mask1] + + # Region 256-511 + mask2 = (vals >= 256) & (vals < 512) + r[mask2] = 511 - vals[mask2] + g[mask2] = 255 + + # Region 512-767 + mask3 = (vals >= 512) & (vals < 768) + g[mask3] = 255 + b[mask3] = vals[mask3] - 512 + + # Region 768-1023 + mask4 = (vals >= 768) & (vals < 1024) + g[mask4] = 1023 - vals[mask4] + b[mask4] = 255 + + # Region 1024-1279 + mask5 = vals >= 1024 + r[mask5] = vals[mask5] - 1024 + b[mask5] = 255 + + return r, g, b + + # Process each row in scaled_day + for row_idx in range(scaled_day.shape[0]): + # Extract min and max for this row + row_min = scaled_day[row_idx, 1442] + row_max = scaled_day[row_idx, 1443] + + # Get data for this row (first 1440 elements) + row_data = scaled_day[row_idx, :1440] + + # Check if min and max are the same + if row_min == row_max: + # Create gray stripe + stripe = np.ones((stretch_by, 1440, 3), dtype=np.int32) * 128 + else: + # Normalize the data between 0 and 1279 + k = 1280 / (row_max - row_min) + normalized_vals = k * (row_data - row_min) + normalized_vals = np.clip(normalized_vals, 0, 1279) + + # Convert to RGB + r, g, b = best_color_vectorizedS(normalized_vals) + + # Create RGB stripe + stripe = np.zeros((stretch_by, 1440, 3), dtype=np.int32) + + # Fill stripe with the same color pattern for all stretch_by rows + for i in range(stretch_by): + stripe[i, :, 0] = r + stripe[i, :, 1] = g + stripe[i, :, 2] = b + + # Calculate the y position for this stripe + y_pos = y_offset + row_idx * stretch_by + + # Place the stripe into arr_stretched + print(stretch_by, stripe.shape, arr_stretched.shape, y_pos) + arr_stretched[y_pos:y_pos+stretch_by, x_offset:x_offset+1440, :] = stripe + + return arr_stretched + +def FillImage(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw): + """ + Fill the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + sensors_c: number of sensors per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stripes = devices_c * sensors_c + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Create a boolean mask for VOC sensors + if group_by != "sensortype": + voc_rows = np.array([i for i in range(stripes) if int(i/devices_c) >= 5]) + else: + voc_rows = np.array([i for i in range(stripes) if int(i % sensors_c) >= 5]) + # Vectorize the BestColor function + if not bw: + vectorized_best_color = np.vectorize(BestColor) + else: + vectorized_best_color = np.vectorize(GrayColor) + + # Process each row + for row in range(stripes): + + row_data = scaled_day[row, :minutes] # Get minute data + + #if row == 33: + # print("stop") + # plot(row_data, "row_data.png") + big_min = scaled_day[row, 1442] # min value + big_max = scaled_day[row, 1443] # max value + + # Create mask for valid values + valid_mask = row_data != -0.001 + + # Initialize RGB row with zeros + rgb_row = np.zeros((minutes, 3), dtype=np.uint8) + + if big_max > big_min: + # Scale factor + if not bw: + k = 1280/(big_max-big_min) + else: + k = 255/(big_max-big_min) + # Calculate normalized values + normalized_vals = k * (row_data - big_min) + + # Invert if it's a VOC row + if row in voc_rows: + if not bw: + normalized_vals = 1279 - normalized_vals + else: + normalized_vals = 255 - normalized_vals + + # Apply valid mask + normalized_vals = np.where(valid_mask, normalized_vals, 0) + #if row == 33: + # plot(normalized_vals, "normalized_vals.png") + + # Convert to RGB colors (vectorized) + r, g, b = vectorized_best_color(normalized_vals) + + # Combine into RGB array + rgb_row[valid_mask] = np.stack([r[valid_mask], + g[valid_mask], + b[valid_mask]], axis=1) + else: + # Set to gray where valid + rgb_row[valid_mask] = 128 + + + if group_by == "sensortype": + + # Fill the stretched rows + sensor_index = row % sensors_c + device_index = int(row/sensors_c) + dest_row = sensor_index * devices_c + device_index #0-0, 1- + start_idx = dest_row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + else: + # Fill the stretched rows + start_idx = row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + + return arr_stretched + +def FillRadarImage(scaled_day, devices_c, bands, arr_stretched, group_by, map_type): + """ + Fill the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + bands: number of bands per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stripes = devices_c * bands + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Create a boolean mask for VOC sensors + if group_by != "sensortype": + voc_rows = np.array([i for i in range(stripes) if int(i/devices_c) >= 5]) + else: + voc_rows = np.array([i for i in range(stripes) if int(i % bands) >= 5]) + # Vectorize the BestColor function + if map_type == 3: + vectorized_best_color = np.vectorize(BestColor) + else: + vectorized_best_color = np.vectorize(GrayColor) + + # Process each row + for row in range(stripes): + + row_data = scaled_day[row, :minutes] # Get minute data + + #if row == 33: + # print("stop") + # plot(row_data, "row_data.png") + big_min = 0 #scaled_day[row, 1442] # min value + big_max = 255 #scaled_day[row, 1443] # max value + + # Create mask for valid values + valid_mask = row_data != -0.001 + + # Initialize RGB row with zeros + rgb_row = np.zeros((minutes, 3), dtype=np.uint8) + + if big_max > big_min: + # Scale factor + if map_type == 3: + k = 1280/(big_max-big_min) + else: + k = 255/(big_max-big_min) + # Calculate normalized values + normalized_vals = k * (row_data - big_min) + + # Invert if it's a VOC row + if row in voc_rows: + if map_type == 3: + normalized_vals = 1279 - normalized_vals + else: + normalized_vals = 255 - normalized_vals + + # Apply valid mask + normalized_vals = np.where(valid_mask, normalized_vals, 0) + #if row == 33: + # plot(normalized_vals, "normalized_vals.png") + + # Convert to RGB colors (vectorized) + r, g, b = vectorized_best_color(normalized_vals) + + # Combine into RGB array + rgb_row[valid_mask] = np.stack([r[valid_mask], + g[valid_mask], + b[valid_mask]], axis=1) + else: + # Set to gray where valid + rgb_row[valid_mask] = 128 + + + if group_by == "sensortype": + + # Fill the stretched rows + band_index = row % bands + device_index = int(row/bands) + dest_row = band_index * devices_c + device_index #0-0, 1- + start_idx = dest_row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + else: + # Fill the stretched rows + start_idx = row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + + return arr_stretched + +def GetFullLocMapDetails(map_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + local_timezone = pytz.timezone('America/Los_Angeles') # Replace with your local timezone + dest_path = os.path.dirname(map_file) + + parts = map_file.split("/") + deployment = parts[-2] + parts1 = parts[-1].split("_") + date_string = parts1[1] + + deployments = GetDeploymentB(deployment, -1) #All + last_locations_file = "" + last_per_minute_file = "" + + today = datetime.today() + deployment_details = deployments[0] + deployment_pair = deployment_details[0] + proximity_lst = deployment_details[1] + + date_object = datetime.strptime(date_string, "%Y-%m-%d") + date_object_midnight = local_timezone.localize(date_object.replace(hour=0, minute=0, second=0, microsecond=0)) + selected_epoch = int(date_object_midnight.timestamp()) + + sel_date = datetime.fromtimestamp(selected_epoch) + devices_list_str = GetDevicesList(deployment_details, sel_date)#.split(',') + devices_list = ast.literal_eval(devices_list_str) + return devices_list, selected_epoch, dest_path + +def median_filter(data, window_size): + filtered_data = [] + print(len(data)) + window = deque(maxlen=window_size) + last_value = -1 + offset = 0 + added_old = 0 + for value in data: + if value != '': + added_old = 0 + last_value = value + window.append(value) + if len(window) == window_size: + # Sort the window and get the median value + sorted_window = sorted(window) + median = sorted_window[window_size // 2] + filtered_data.append(median) + else: + if last_value != -1: + if added_old < window_size: + added_old = added_old + 1 + window.append(last_value) + else: + window.append(-1) + + if len(window) == window_size: + # Sort the window and get the median value + sorted_window = sorted(window) + median = sorted_window[window_size // 2] + filtered_data.append(median) + + else: + offset +=1 + + if len(filtered_data) > 0: + offset += (window_size // 2) + #if starts empty, just leav it such, do not fake backwards from midnight + first_val = -1# filtered_data[0] + last_val = filtered_data[-1] + front_padding = [first_val] * offset + remaining = len(data) - len(filtered_data) - len(front_padding) + back_padding = [last_val] * remaining + out_data = front_padding + filtered_data + back_padding + else: + out_data = data + #add front and back padding + + return out_data + +def FilterGlitches(wave_in, filter_minutes): + + if(filter_minutes > 0): + notfiltered_wave = [i[0] for i in wave_in] + filtered_wave = median_filter(notfiltered_wave, filter_minutes) + for i, value in enumerate(filtered_wave): + wave_in[i][0] = value + + return wave_in + +def setup_timezone_converter(time_zone_st): + """ + Setup timezone converter to be reused + + Parameters: + time_zone_st (str): Timezone string (e.g. 'Europe/Berlin') + + Returns: + pytz.timezone: Timezone object for conversion + """ + return pytz.timezone(time_zone_st) + +def ReadDailyRadar(MAC, current_date): + + #This will return all 1 Minute radar data for each gate in the file + #Will return list (2 items) of lists: Maxes, Mins + + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + start_of_day = ToLocal(calendar.timegm(datetime(current_date.year, current_date.month,current_date.day, 0, 0).timetuple())) + end_of_day = start_of_day + 1440 * 60 + file = os.path.join(scriptDir, "DB/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+".db") + file = file.replace("\\","/") + file1 = os.path.join(scriptDir, "DB/processed_db/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+".db") + file1 = file1.replace("\\","/") + if (not path.exists(file) and not path.exists(file1)): + print(file + " and " + file1 + " are not found") + return [] + result = [] + min_OK = "0" + sqlr = "SELECT * FROM radars WHERE time >= "+str(start_of_day) +" and time < "+str(end_of_day) +" ORDER BY time ASC" + #sqlr = "SELECT Date, high, low from "+sensor.lower()+"s1Min"+" WHERE low >= "+min_OK+" and Date >= "+str(start_of_day) +" and Date < "+str(end_of_day) + print(sqlr) + if os.path.exists(file): + result = QuerrySql(file, sqlr) + elif os.path.exists(file1): + result = QuerrySql(file1, sqlr) + + # M0 ............M8 S2 ........S8 + #day_minutes_data = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]] * (24 * 60 + 2) + day_minutes_data = [[0] * 16 for _ in range(24 * 60)] + #for each gate lets find maximum value per minute + for mgate in range(9): + max_per_min = 0 + for minute_data in result: + seconde = minute_data[0] + date_time_minute = datetime.fromtimestamp(seconde) + minute_m = 60*date_time_minute.hour+date_time_minute.minute + if minute_data[mgate + 6] > day_minutes_data[minute_m][mgate]: + day_minutes_data[minute_m][mgate] = minute_data[mgate + 6] + + for sgate in range(7): + for minute_data in result: + seconde = minute_data[0] + date_time_minute = datetime.fromtimestamp(seconde) + minute_m = 60*date_time_minute.hour+date_time_minute.minute + if minute_data[sgate + 17] > day_minutes_data[minute_m][sgate+9]: + day_minutes_data[minute_m][sgate+9] = minute_data[sgate + 17] + + return day_minutes_data + + +def FromLocalMidnight(epoch_time, local_delta): + + # Convert epoch time to UTC datetime object + print(type(epoch_time)) + print(epoch_time) + local_datetime = datetime.datetime.utcfromtimestamp(epoch_time+local_delta).replace(tzinfo=pytz.UTC) + + # Calculate minute count from midnight + minutes_from_midnight = (local_datetime - local_datetime.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60 + return minutes_from_midnight + +def process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest): + """ + NumPy-based version of wave processing + + Parameters: + my_data: List of tuples containing (time_val, device_id, other radar_fields_of_interest) + time_zone_s: Target timezone string + device_id_2_threshold: Dictionary mapping device_ids to their thresholds + + Returns: + List of [device_id, max_val] pairs for each minute + """ + wave_m = None + tz = pytz.timezone(time_zone_s) + if not my_data: + return [["", -1] for _ in range(1440)] + + vectorized_BestColor = np.vectorize(BestColor) + stripes = len(device_id_2_threshold) + stretch_by = 5 + minutes = 1440 + arr_source = np.zeros((int(stripes), minutes), dtype=np.float32) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + index_map = {word: idx for idx, word in enumerate(radar_fields_of_interest)} + devices_map = {word: idx for idx, word in enumerate(device_id_2_threshold)} + times = [] + start_time = 0 + for data_set in my_data: + time_stamp = data_set[0] + if start_time == 0: + + # Convert timestamp to a datetime object in UTC + local_tz = pytz.timezone(time_zone_s) + local_time = time_stamp.astimezone(local_tz) + # Set the time to the start of the day in the local time zone + start_of_day_local = local_time.replace(hour=0, minute=0, second=0, microsecond=0) + # Convert the start of the day back to UTC + start_time = start_of_day_local.astimezone(pytz.utc) + + diff = time_stamp - start_time + minute = int(diff.total_seconds() / 60) + device_id = data_set[1] + field_name = device_id_2_threshold[device_id][0] + field_index = index_map[field_name] + threshold = device_id_2_threshold[device_id][1] + value = data_set[2+field_index] + if value > threshold: + arr_source[devices_map[device_id]][minute] = value + #np.savetxt('output.csv', arr_source, delimiter=',') + if False: + for yy in range(stripes): + rgb_row = vectorized_BestColor(1280*arr_source[yy]/100) + rgb_reshaped = np.array(rgb_row).reshape(3, minutes).T + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_reshaped + + SaveImageInBlob(image_file, arr_stretched, []) + + + max_values = np.max(arr_source, axis=0) + + # Get indices (0-based) + wave_m = np.argmax(arr_source, axis=0) + + # Add 1 to convert to 1-based indexing + wave_m = wave_m + 1 + + # Set to 0 where the column was all zeros + wave_m[max_values == 0] = 0 + + return wave_m + + + +def ReadDailyCollapsedFastRadar(MAC, time_from_str, time_to_str): + + #This will return all 1 Minute radar data for each gate in the file + #Will return list (2 items) of lists: Maxes, Mins based on s28 (stationary[2] to [8]) + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + result = [] + min_OK = "0" + sqlr = "SELECT radar_max FROM devices WHERE MAC = '"+MAC +"'" + print(sqlr) + DB_to_be_found_in_full = os.path.join(scriptDir, "main.db") + DB_to_be_found_in_full = DB_to_be_found_in_full.replace("\\","/") + result = QuerrySql(DB_to_be_found_in_full, sqlr) + sqlr = "SELECT date, low FROM radars1Min WHERE date >= "+str(start_of_day) +" and date < "+str(end_of_day) + " ORDER BY date" + if len(result)>0: + if result[0][0] == 1: + sqlr = "SELECT date, high FROM radars1Min WHERE date >= "+str(start_of_day) +" and date < "+str(end_of_day) + " ORDER BY date" + + print(sqlr) + if os.path.exists(file): + result = QuerrySql(file, sqlr) + elif os.path.exists(file1): + result = QuerrySql(file1, sqlr) + + return result + +def vectorized_best_color_numpy(values): + """Vectorized version of BestColor using pure NumPy""" + # Ensure values are within range + values = np.clip(values, 0, 1279) + + # Initialize output arrays + r = np.zeros_like(values, dtype=np.uint8) + g = np.zeros_like(values, dtype=np.uint8) + b = np.zeros_like(values, dtype=np.uint8) + + # Create masks for each range + mask_0_255 = values < 256 + mask_256_511 = (values >= 256) & (values < 512) + mask_512_767 = (values >= 512) & (values < 768) + mask_768_1023 = (values >= 768) & (values < 1024) + mask_1024_plus = values >= 1024 + + # Set values for each range using masks + r[mask_0_255] = 255 + g[mask_0_255] = values[mask_0_255] + + r[mask_256_511] = 511 - values[mask_256_511] + g[mask_256_511] = 255 + + g[mask_512_767] = 255 + b[mask_512_767] = values[mask_512_767] - 512 + + g[mask_768_1023] = 1023 - values[mask_768_1023] + b[mask_768_1023] = 255 + + r[mask_1024_plus] = values[mask_1024_plus] - 1024 + b[mask_1024_plus] = 255 + + return np.stack([r, g, b], axis=-1) + +def create_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val, max_val): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] - min_val / (max_val - min_val)) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Calculate color values + color_values = np.zeros_like(values[batch_slice]) + + color_values[:, :] = ((values[batch_slice, :] - min_val) / (max_val - min_val)) * 1279.0 + #color_values[:, :] = (values[batch_slice, :] / 100.0) * 1279.0 # other fields + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Convert to RGB colors + for i in range(end_idx - start_idx): + rgb_values = vectorized_best_color_numpy(color_values[i]) + wave_m[y_coords[i], x_coords[batch_slice][i]] = rgb_values + + return wave_m + +def create_light_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=4095): + """ + Create an optimized heatmap for light data (range 0-4095) + + Parameters: + my_data (list): Data from the database query + bw (bool): Whether to create a black and white (True) or color (False) heatmap + fields (list): List of field names + wave_m (numpy.ndarray): The image array to fill + device_to_index (dict): Mapping from device_id to index + base_minute (datetime): The base minute for time calculations + timezone_st (str): Timezone string + min_val (float): Minimum value for normalization (default: 0) + max_val (float): Maximum value for normalization (default: 4095) + + Returns: + numpy.ndarray: The filled image array + """ + if len(my_data) < 1: + return wave_m + + import numpy as np + import pytz + + # Get the local timezone + local_tz = pytz.timezone(timezone_st) + + # Number of fields (should be 1 for light data) + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float - light data is in column 2 + # Reshape to match expected format (n_samples, n_fields) + values = data_array[:, 2].astype(np.float32).reshape(-1, 1) + + # Process in batches to avoid memory issues + batch_size = 1000 + + if bw: + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Normalize light values (0-4095) to grayscale (0-255) + gray_values = ((values[batch_slice] - min_val) / (max_val - min_val) * 255.0) + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + + # Assign values to the image array + for i in range(end_idx - start_idx): + # Create RGB grayscale (same value for R, G, B) + gray_rgb = np.full(3, gray_values[i, 0], dtype=np.uint8) + wave_m[y_coords[i, 0], x_coords[batch_slice][i]] = gray_rgb + else: + # Color mode + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Normalize light values (0-4095) to color range (0-1279) + color_values = ((values[batch_slice] - min_val) / (max_val - min_val) * 1279.0) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + + # For each value, calculate its RGB color and assign to the image + for i in range(end_idx - start_idx): + # Convert normalized value to RGB using vectorized_best_color_numpy + rgb_value = vectorized_best_color_numpy(np.array([color_values[i, 0]]))[0] + wave_m[y_coords[i, 0], x_coords[batch_slice][i]] = rgb_value + + return wave_m + +def create_temperature_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=4095): + """ + Create an optimized heatmap for temperature data with alarm levels + + Parameters: + my_data (list): Data from the database query with columns for minute, device_id, temperature_avg, alarm_level + bw (bool): Whether to create a black and white (True) or color (False) heatmap + fields (list): List of field names - should be ['temperature', 'temperature_state'] + wave_m (numpy.ndarray): The image array to fill + device_to_index (dict): Mapping from device_id to index + base_minute (datetime): The base minute for time calculations + timezone_st (str): Timezone string + min_val (float): Minimum value for temperature normalization + max_val (float): Maximum value for temperature normalization + + Returns: + numpy.ndarray: The filled image array + """ + if len(my_data) < 1: + return wave_m + + import numpy as np + import pytz + + # Get the local timezone + local_tz = pytz.timezone(timezone_st) + + # Number of fields (should be 2 for temperature data: temperature and alarm state) + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Process in batches to avoid memory issues + batch_size = 1000 + + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + for i in range(end_idx - start_idx): + # Get data for this record + temperature = data_array[batch_slice][i, 2] + if temperature == None: + temperature = min_val + alarm_level = 0 + + # If we have an alarm_level column (index 3), use it + if data_array.shape[1] > 3: + alarm_level = data_array[batch_slice][i, 3] + + # Calculate base y-coordinate for this device + base_y = device_indices[batch_slice][i] * n_fields + + # Temperature row (even row - index 0, 2, 4...) + # Normalize temperature to the color range and create color + if not bw: + # For color mode + normalized_temp = np.clip((temperature - min_val) / (max_val - min_val) * 1279.0, 0, 1279) + temp_rgb = vectorized_best_color_numpy(np.array([normalized_temp]))[0] + else: + # For B&W mode + normalized_temp = np.clip((temperature - min_val) / (max_val - min_val) * 255.0, 0, 255) + gray_value = int(normalized_temp) + temp_rgb = np.array([gray_value, gray_value, gray_value], dtype=np.uint8) + + # Set the temperature color in the even row + wave_m[base_y, x_coords[batch_slice][i]] = temp_rgb + + # Alarm level row (odd row - index 1, 3, 5...) + # Set color based on alarm level (0=green, 1=yellow, 2=red) + if alarm_level == 0: + # Green for normal + alarm_rgb = np.array([0, 255, 0], dtype=np.uint8) + elif alarm_level == 1: + # Yellow for warning + alarm_rgb = np.array([0, 255, 255], dtype=np.uint8) + else: # alarm_level == 2 + # Red for critical + alarm_rgb = np.array([0, 0, 255], dtype=np.uint8) + + # Set the alarm color in the odd row + wave_m[base_y + 1, x_coords[batch_slice][i]] = alarm_rgb + + return wave_m + + +def create_humidity_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=100): + """ + Create a heatmap with the exact blue-cyan-green-yellow-red-violet spectrum + matching Image 2, with green at position 40 + """ + if len(my_data) < 1: + return wave_m + + # Number of fields + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Process in batches to avoid memory issues + batch_size = 1000 + + # Define the color mapping function based on the exact spectrum we want + def get_color(t): + """Get RGB color from humidity 0-100""" + # Define color stops - exact RGB values at each step + # Format: (position, (r, g, b)) + #color_stops = [ + #(0, (0, 0, 255)), # Blue + #(20, (0, 255, 255)), # Cyan + #(40, (0, 255, 0)), # Green (centered at 40) + #(60, (255, 255, 0)), # Yellow + #(80, (255, 0, 0)), # Red + #(100, (255, 0, 255)) # Violet + #] + + color_stops = [ + (0, (0, 0, 255)), # Blue + (16, (0, 255, 255)), # Cyan + (32, (0, 255, 0)), # Green (now centered at 32) + (60, (255, 255, 0)), # Yellow + (80, (255, 0, 0)), # Red + (100, (255, 0, 255)) # Violet + ] + + # Ensure t is within range + t = max(0, min(100, t)) + + # Find the two stops to interpolate between + for i in range(len(color_stops) - 1): + pos1, color1 = color_stops[i] + pos2, color2 = color_stops[i+1] + + if pos1 <= t <= pos2: + # Linear interpolation between the two color stops + ratio = (t - pos1) / (pos2 - pos1) + r = int(color1[0] + ratio * (color2[0] - color1[0])) + g = int(color1[1] + ratio * (color2[1] - color1[1])) + b = int(color1[2] + ratio * (color2[2] - color1[2])) + return r, g, b + + # Should never reach here + return 0, 0, 0 + + humidity = min_val + + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + for i in range(end_idx - start_idx): + # Get data for this record + if data_array[batch_slice][i, 2] != None: + humidity = float(data_array[batch_slice][i, 2]) + + # Map humidity from min_val-max_val to 0-100 for our color function + normalized_temp = 100.0 * (humidity - min_val) / (max_val - min_val) if max_val > min_val else 0 + normalized_temp = max(0, min(100, normalized_temp)) # Clamp to 0-100 + + alarm_level = 0 + # If we have an alarm_level column (index 3), use it + if data_array.shape[1] > 3: + alarm_level = data_array[batch_slice][i, 3] + + # Calculate base y-coordinate for this device + base_y = device_indices[batch_slice][i] * n_fields + + # Temperature row (even row) + if not bw: + # Get RGB color from our direct mapping function + r, g, b = get_color(normalized_temp) + + # OpenCV uses BGR ordering, not RGB + temp_rgb = np.array([b, g, r], dtype=np.uint8) + else: + # For B&W mode + gray_value = int(normalized_temp * 2.55) # 0-100 to 0-255 + gray_value = max(0, min(255, gray_value)) + temp_rgb = np.array([gray_value, gray_value, gray_value], dtype=np.uint8) + + # Set the humidity color in the even row + wave_m[base_y, x_coords[batch_slice][i]] = temp_rgb + + # Alarm level row (odd row) + if alarm_level == 0: + # Green for normal + alarm_rgb = np.array([0, 255, 0], dtype=np.uint8) #thisis B,G,R !!! + elif alarm_level == 1: + # Yellow for warning + alarm_rgb = np.array([0, 255, 255], dtype=np.uint8) + else: # alarm_level == 2 + # Red for critical + alarm_rgb = np.array([0, 0, 255], dtype=np.uint8) + + # Set the alarm color in the odd row + wave_m[base_y + 1, x_coords[batch_slice][i]] = alarm_rgb + + return wave_m + +def create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields, device_to_index, base_minute, timezone_st, smell_component_stretch_by, selected_date, y_offset): + """ + Create a heatmap with the exact blue-cyan-green-yellow-red-violet spectrum + matching Image 2, with green at position 40 + """ + if len(my_data) < 1: + return + + minutes = 1440 + devices_c = len(device_to_index) + sensors_c = len(fields) + stripes = devices_c * sensors_c #2 for upper maxes, lower mins + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) + st = time.time() + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, timezone_st) + st = time.time() + arr_source = fast_fill_smell_array_from_timescale(my_data, time_from_str, device_to_index, arr_source_template, timezone_st) + arr_source = AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + + arr_stretched = FillSmellImage_optimized(scaled_day, arr_stretched, y_offset) + + return + + +def create_optimized_heatmap_simple(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val, max_val): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] - min_val / (max_val - min_val)) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for i in range(0, len(data_array)): + rgb_value = ((values[i] - min_val) / (max_val - min_val)) * 1279.0 + wave_m[i, x_coords[i]] = rgb_value + + return wave_m + + +def create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] / 100.0) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Calculate color values + color_values = np.zeros_like(values[batch_slice]) + color_values[:, :] = (values[batch_slice, :] / 100.0) * 1279.0 # other fields + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Convert to RGB colors + for i in range(end_idx - start_idx): + rgb_values = vectorized_best_color_numpy(color_values[i]) + wave_m[y_coords[i], x_coords[batch_slice][i]] = rgb_values + + return wave_m + +def visualize_gmm_fit(stationary_signal, output_file='gmm_explanation.png'): + """ + Visualize how GMM separates the stationary signal into components + """ + # Prepare data + X = stationary_signal.reshape(-1, 1) + + # Fit GMM + gmm = GaussianMixture(n_components=2, random_state=42) + gmm.fit(X) + + # Get parameters + means = gmm.means_.flatten() + stds = np.sqrt(gmm.covariances_.flatten()) + weights = gmm.weights_ + + # Create histogram of actual data + plt.figure(figsize=(12, 6)) + + # Plot histogram of actual data + plt.hist(X, bins=50, density=True, alpha=0.6, color='gray', + label='Actual Signal Distribution') + + # Generate points for GMM curves + x = np.linspace(X.min(), X.max(), 200) + + # Plot individual components + for i in range(len(means)): + plt.plot(x, weights[i] * stats.norm.pdf(x, means[i], stds[i]), + label=f'Component {i+1}: mean={means[i]:.2f}, std={stds[i]:.2f}') + + # Plot combined GMM + gmm_curve = np.zeros_like(x) + for i in range(len(means)): + gmm_curve += weights[i] * stats.norm.pdf(x, means[i], stds[i]) + plt.plot(x, gmm_curve, 'r--', linewidth=2, label='Combined GMM') + + # Add vertical lines for threshold + baseline = min(means) + threshold = baseline + 3 * np.sqrt(gmm.covariances_.flatten()[np.argmin(means)]) + plt.axvline(x=baseline, color='g', linestyle='--', label='Baseline') + plt.axvline(x=threshold, color='r', linestyle='--', label='Threshold') + + plt.title('Gaussian Mixture Model Components of Stationary Signal') + plt.xlabel('Signal Value') + plt.ylabel('Density') + plt.legend() + plt.grid(True) + + # Save and close + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.close() + +def process_location_data(location_data): + """ + Convert raw location data into aligned time series. + """ + timestamps = np.array([t[0] for t in location_data]) + stationary = np.array([t[1] for t in location_data]) + motion = np.array([t[2] for t in location_data]) + return timestamps, stationary, motion + +def detect_presence_for_location(stationary_signal, motion_signal, + motion_threshold=5, gmm_components=2): + """ + Simplified presence detection for a single location. + Returns presence mask and parameters. + """ + # Fit GMM to stationary signal + gmm = GaussianMixture(n_components=gmm_components, random_state=42) + X = stationary_signal.reshape(-1, 1) + gmm.fit(X) + + visualize_gmm_fit(stationary_signal, output_file='gmm_explanation.png') + + # Get baseline and threshold + baseline = min(gmm.means_)[0] + components_sorted = sorted(zip(gmm.means_.flatten(), gmm.covariances_.flatten())) + baseline_std = np.sqrt(components_sorted[0][1]) + threshold = baseline + 3 * baseline_std + + # Detect presence + presence_mask = (motion_signal > motion_threshold) | (stationary_signal > threshold) + + # Smooth presence detection (15 seconds window = 1.5 samples at 10sec sampling) + smooth_window = 3 + presence_mask = np.convolve(presence_mask.astype(int), + np.ones(smooth_window)/smooth_window, + mode='same') > 0.5 + + return presence_mask, threshold + +def find_current_location(data_sets, start_time, end_time, motion_threshold=10): + """ + Analyze presence across multiple locations for each minute. + + Parameters: + ----------- + data_sets : dict + Dictionary of location_name: data_tuples pairs + start_time : datetime + Start time for analysis + end_time : datetime + End time for analysis + motion_threshold : float + Threshold for significant motion detection + + Returns: + -------- + dict + Minute by minute analysis of presence and movement + """ + # Process each location's data + location_data = {} + for location, data in data_sets.items(): + timestamps, stationary, motion = process_location_data(data) + presence, threshold = detect_presence_for_location(stationary, motion, motion_threshold) + location_data[location] = { + 'timestamps': timestamps, + 'presence': presence, + 'motion': motion, + 'stationary': stationary, + 'threshold': threshold + } + + # Create minute-by-minute analysis + current_time = start_time + results = [] + + while current_time < end_time: + minute_end = current_time + timedelta(minutes=1) + + # Analysis for current minute + minute_status = { + 'timestamp': current_time, + 'locations': [], + 'moving_locations': [], + 'presence_values': {}, + 'motion_values': {}, + 'status': 'nobody_present' + } + + # First pass: collect all presence and motion values + for location, data in location_data.items(): + # Find indices for current minute + mask = (data['timestamps'] >= current_time) & (data['timestamps'] < minute_end) + if not any(mask): + continue + + presence_in_minute = data['presence'][mask] + motion_in_minute = data['motion'][mask] + stationary_in_minute = data['stationary'][mask] + + if any(presence_in_minute): + minute_status['presence_values'][location] = np.max(stationary_in_minute) + minute_status['motion_values'][location] = np.max(motion_in_minute) + + # If no presence detected anywhere + if not minute_status['presence_values']: + minute_status['status'] = 'nobody_present' + results.append(minute_status) + current_time += timedelta(minutes=1) + continue + + + # Find location with strongest presence + primary_location = max(minute_status['presence_values'].items(), + key=lambda x: x[1])[0] + + # Count locations with significant motion + moving_locations = [loc for loc, motion in minute_status['motion_values'].items() + if motion > motion_threshold] + + plot(motion, filename=f"motion.png", title=f"Motion", style='line') + # Update status based on motion and presence + if len(moving_locations) > 1: + # Multiple locations with significant motion indicates multiple people + minute_status['status'] = 'multiple_people_moving' + minute_status['locations'] = moving_locations + minute_status['moving_locations'] = moving_locations + else: + # Single or no motion - assign to location with strongest presence + minute_status['locations'] = [primary_location] + if moving_locations: + minute_status['status'] = f'single_person_moving_in_{primary_location}' + minute_status['moving_locations'] = moving_locations + else: + minute_status['status'] = f'single_person_stationary_in_{primary_location}' + + results.append(minute_status) + current_time += timedelta(minutes=1) + + return results + +def get_size(obj, seen=None): + # Recursively find size of objects and their contents + if seen is None: + seen = set() + + obj_id = id(obj) + if obj_id in seen: + return 0 + + seen.add(obj_id) + size = sys.getsizeof(obj) + + if isinstance(obj, (list, tuple, set, dict)): + if isinstance(obj, (list, tuple, set)): + size += sum(get_size(i, seen) for i in obj) + else: # dict + size += sum(get_size(k, seen) + get_size(v, seen) for k, v in obj.items()) + + return size + +def CreatePresenceMap(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, + fast, filter_minutes, time_zone_s): + + #global Id2MACDict + data_sets = {} + ids_list = [] + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + time_from, time_to = GetLocalTimeForDateSimple(selected_date, time_zone_s) + + for details in devices_list: + + sql = get_device_radar_only_query(str(details[1]), time_from_str, time_to_str, [details[1]]) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + data_sets[details[2]] = cur.fetchall()#cur.fetchone() + + + + + # Get minute-by-minute analysis + location_analysis = find_current_location(data_sets, time_from, time_to) + + # Example of printing results + for minute in location_analysis: + print(f"Time: {minute['timestamp']}") + print(f"Status: {minute['status']}") + print(f"Present in: {', '.join(minute['locations'])}") + if minute['moving_locations']: + print(f"Movement in: {', '.join(minute['moving_locations'])}") + print("---") + + print(f"Dictionary size: {get_size(data_sets)} bytes") + devices_list_str = ','.join(str(device[1]) for device in devices_list) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + sql = get_device_radar_only_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #thresholds_dict = {} + + #stretch_to_min_max = True + #devices_c = len(devices_list) + + #data_sets = { + #'living_room': my_data1, + #'kitchen': my_data2, + #'bedroom1': my_data3, + #'bedroom2': my_data4, + #'hallway': my_data5, + #'bathroom': my_data6, + #'office': my_data7 + #} + + sensors_c = 1#len(sensors_table) + + + image_file = location_image_file + + minutes = 1440 + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] #Why 'm8_max' and 'm08_max' ?because m08 is m0 + m1 .. to 8! + + fields_n = len(fields) + + stripes = len(devices_list) * fields_n + + #device_counter = 0 + stretch_by = 5 + + #arr_source = np.zeros((stripes, minutes), dtype=float) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + ids_list = [] + + labels = [] + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_scale = 1 + label_font_color = (255, 255, 255) + label_font_thickness = 2 + label_font_line = cv2.LINE_AA + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, 10 + text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + + + # Get start and end times from your data + start_time = min(data_sets['living_room'][0][0], + data_sets['kitchen'][0][0], + # ... add other locations + ) + end_time = max(data_sets['living_room'][-1][0], + data_sets['kitchen'][-1][0], + # ... add other locations + ) + + # Get minute-by-minute analysis + location_analysis = find_current_location(data_sets, start_time, end_time) + + # Example of printing results + for minute in location_analysis: + print(f"Time: {minute['timestamp']}") + print(f"Status: {minute['status']}") + print(f"Present in: {', '.join(minute['locations'])}") + if minute['moving_locations']: + print(f"Movement in: {', '.join(minute['moving_locations'])}") + print("---") +#---------------------------------------------------------------------------------------------------- + + + + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #device_ids = sorted(set(record[1] for record in my_data)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + + base_minute = ConvertToBase(time_from_str, time_zone_s) + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + if False: + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + for record in my_data: + + #(minute,device_id,absent_min,stationary_max,moving_max,both_max,m0_max,m1_max,m2_max,m3_max,m4_max, + # m5_max,m6_max,m7_max,m8_max,m08_max,s2_max,s3_max,s4_max,s5_max,s6_max,s7_max,s8_max,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the max/min values + x = int((minute - base_minute).total_seconds()/60) + device_idx = device_to_index[device_id] + + if bw: + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 255.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = [gray_value, gray_value, gray_value] + else: #color + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * 22 + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 1279.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = BestColor(gray_value) + + print(time.time()-st) + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + + SaveImageInBlob(image_file, arr_stretched, labels) + #arr_source[2*gate, :] = wave_m + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_m[col] + #if sens_val != 0: + #r,g,b=BestColor(km*(sens_val-m_min)) + #if r > 255 or g > 255 or b > 255: + #print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + ##print(y, row, devices_c, sensor_index, location_index, stretch_index) + ##arr_stretched[y, :] = rgb_row + + #if gate > 1: + #ks = 0 + #if(s_max > s_min): + #if bw: + #ks = 255/(s_max - s_min) + #else: + #ks = 1280/(s_max - s_min) + ##wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + #wave_s = np.array([0.0] * 1440) + + #for minute_m in range(1440): + #wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + ##wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + ##DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + #arr_source[2*gate + 1, :] = wave_s + + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_s[col] + #if sens_val != 0: + #if bw: + #r = ks*(sens_val-s_min) + #g = r + #b = r + #else: + #r,g,b=BestColor(ks*(sens_val-s_min)) + ##print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + #y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + + print("stop") + +def ConvertToBase(time_from_str, time_zone_s): + print(time_from_str) + dt = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + return dt + +def GetTimeAndEvents(data): + """ + Calculates non-zero elements and consecutive non-zero groups using itertools. + This is often the most readable and efficient pure Python approach. + """ + # Fast way to count non-zeros since they are all 1.0 + #non_zeros = int(sum(data)) + non_zeros = sum(1 for x in data if x != 0) + # Count groups of non-zero elements + events = sum(1 for key, group in itertools.groupby(data) if key != 0.0) + return non_zeros, events + +def current_date_at_tz(timezone_str): + """ + Returns the current date in the specified timezone in yyyy-mm-dd format. + + Args: + timezone_str (str): Timezone string like "America/Los_Angeles" + + Returns: + str: Current date in yyyy-mm-dd format + """ + # Get the timezone object + tz = pytz.timezone(timezone_str) + + # Get current datetime in the specified timezone + current_dt = datetime.datetime.now(tz) + + # Format as yyyy-mm-dd + return current_dt.strftime('%Y-%m-%d') + + +def GetActivities(device_id, well_id, date_str, filter_size, refresh, timezone_str, radar_threshold_group_st): + #filtered_day has non 0 points that exceeded threshold of radar reads + device_id_str = str(device_id) + + try: + + time_from_str, time_to_str = GetLocalTimeForDate(date_str, timezone_str) + filename_day_presence = f"/{device_id_str}/{device_id_str}_{date_str}_{filter_size}_presence.bin" + filtered_day_str = None + if refresh == False and date_str != current_date_at_tz(timezone_str): + has_larger = False + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence, date_str) + if filtered_day_str != None and filtered_day_str != "": + has_larger = bool(re.search(r'\b(?:[2-9]|\d{2,})\.\d+\b', filtered_day_str)) + if has_larger: + filtered_day_str = None + if filtered_day_str == None: + + radar_fields_of_interest = [] + + try: + threshold_lst = json.loads(radar_threshold_group_st) + except: + threshold_lst = ["s3_max",12] + radar_fields_of_interest = [threshold_lst[0]] + ids_list = [int(device_id)] + devices_list_str = device_id_str + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + sql = get_deployment_radar_10sec_snapped_query_min_max(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = None + my_data = cur.fetchall() + + days_difference_long = 2 + + presence_map = {'longpresence': {}, 'raw': {}} + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference_long + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3",12] + + #device_id_2_location = {device_id: ""} + device_id_2_threshold = {device_id: radar_threshold_group} + device_field_indexes = {radar_threshold_group[0].split("_")[0]: 1} #len(radar_fields_of_interest) + id2well_id = {device_id: well_id} + + if len(my_data) > 1: + + start_time_ = my_data[0][0] + parsed_time_ = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + #) + + presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, "presence") + + presence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter_size, device_id_str, date_str, date_str, timezone_str) + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence) + filtered_day = json.loads(filtered_day_str) + else: + filtered_day = json.loads(filtered_day_str) + + non_zeros, events = GetTimeAndEvents(filtered_day) + + return(non_zeros / 360, events) #decas to hours + except Exception as e: + print(filename_day_presence) + print(filtered_day_str) + print(traceback.format_exc()) + return(0, 0) +def CreateFullLocationMap(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s): + #global Id2MACDict + + thresholds_dict = {} + + stretch_to_min_max = True + devices_c = len(devices_list) + + if devices_c == 0: + return + + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + if scale_global and chart_type != 3 and chart_type != 4: #"digital" and chart_type != "collapsed" + + max_gate={} + for gate in range(9): + max_gate[str(gate)+"_m"] = 0 + max_gate[str(gate)+"_s"] = 0 + + device_counter = 0 + for details in devices_list: + MAC, threshold, location_name, description = details + if threshold == None: + threshold = '["s3_max",12]' + + + #day_minutes_data = [(0,0)] * (24 * 60 + 2) + #day_minutes_data_l = [[0] * 10 for _ in range(24 * 60 + 2)] + + minute_radar_lists = ReadDailyRadar(MAC, current_date) + + for gate in range(9): + for minute_m in range(1440): + if (minute_radar_lists[minute_m][gate] > max_gate[str(gate)+"_m"]): + max_gate[str(gate)+"_m"] = minute_radar_lists[minute_m][gate] + + if gate > 1: + if (minute_radar_lists[minute_m][gate + 7] > max_gate[str(gate)+"_s"]): + max_gate[str(gate)+"_s"] = minute_radar_lists[minute_m][gate + 7] + + + if (chart_type == 2): #"analog" + + + #fields = ['absent_min', 'stationary_max', 'moving_max', 'both_max', + #'m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + #'m6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + #'s4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max'] + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] + + + fields_n = len(fields) + + stripes = len(devices_list) * fields_n + + device_counter = 0 + stretch_by = 5 + + arr_source = np.zeros((stripes, minutes), dtype=float) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + + labels = [] + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_scale = 1 + label_font_color = (255, 255, 255) + label_font_thickness = 2 + label_font_line = cv2.LINE_AA + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, 10 + text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #device_ids = sorted(set(record[1] for record in my_data)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + if False: + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + for record in my_data: + + #(minute,device_id,absent_min,stationary_max,moving_max,both_max,m0_max,m1_max,m2_max,m3_max,m4_max, + # m5_max,m6_max,m7_max,m8_max,m08_max,s2_max,s3_max,s4_max,s5_max,s6_max,s7_max,s8_max,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the max/min values + x = int((minute - base_minute).total_seconds()/60) + device_idx = device_to_index[device_id] + + if bw: + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 255.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = [gray_value, gray_value, gray_value] + else: #color + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * 22 + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 1279.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = BestColor(gray_value) + + print(time.time()-st) + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + + SaveImageInBlob(image_file, arr_stretched, labels) + #arr_source[2*gate, :] = wave_m + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_m[col] + #if sens_val != 0: + #r,g,b=BestColor(km*(sens_val-m_min)) + #if r > 255 or g > 255 or b > 255: + #print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + ##print(y, row, devices_c, sensor_index, location_index, stretch_index) + ##arr_stretched[y, :] = rgb_row + + #if gate > 1: + #ks = 0 + #if(s_max > s_min): + #if bw: + #ks = 255/(s_max - s_min) + #else: + #ks = 1280/(s_max - s_min) + ##wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + #wave_s = np.array([0.0] * 1440) + + #for minute_m in range(1440): + #wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + ##wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + ##DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + #arr_source[2*gate + 1, :] = wave_s + + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_s[col] + #if sens_val != 0: + #if bw: + #r = ks*(sens_val-s_min) + #g = r + #b = r + #else: + #r,g,b=BestColor(ks*(sens_val-s_min)) + ##print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + #y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + + print("stop") + elif (chart_type == 3): #"digital" + device_counter = 0 + for details in devices_list: + dev_id = details[0] + MAC, threshold, location_id, description = GetMacThrFromId(dev_id) + if threshold == None: + threshold = 30 + + sensor = "Radar" + location_name = location_names[location_id] + pickle_file = os.path.join(scriptDir, "scratch/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_radarM.pkl") + pickle_file = pickle_file.replace("\\","/") + #day_minutes_data = [(0,0)] * (24 * 60 + 2) + #day_minutes_data_l = [[0] * 10 for _ in range(24 * 60 + 2)] + + minute_radar_lists = ReadDailyRadar(MAC, current_date) + + y = 0 + sensor_index = 0 + + #location_index = 0 + + for gate in range(9): + threshold = 15 + if (gate > 1): + threshold = thresholds_dict[dev_id][gate-2] + + for minute_m in range(1440): + if (minute_radar_lists[minute_m][gate] > threshold): + minute_radar_lists[minute_m][gate] = 100 + else: + minute_radar_lists[minute_m][gate] = 0 + + if gate > 1: + if (minute_radar_lists[minute_m][gate + 7] > threshold): + minute_radar_lists[minute_m][gate + 7] = 100 + else: + minute_radar_lists[minute_m][gate + 7] = 0 + + m_max = 100 + m_min = 0 + s_max = 100 + s_min = 0 + + km = 0 + if(m_max > m_min): + km = 1280/(m_max - m_min) + #wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + wave_m = np.array([0.0] * 1440) + + for minute_m in range(1440): + wave_m[minute_m] = minute_radar_lists[minute_m][gate] + + if gate < 2: + DoDisplay(wave_m, location_name+" "+ description+" " + str(gate)) + #wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + arr_source[2*gate, :] = wave_m + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + for col in range(1440): + sens_val = wave_m[col] + if sens_val != 0: + r,g,b=BestColor(km*(sens_val-m_min)) + #print(r,g,b) + rgb_row[col] = r,g,b + + for stretch_index in range(stretch_by): + y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + #print(y, row, devices_c, sensor_index, location_index, stretch_index) + + #arr_stretched[y, :] = rgb_row + + if gate > 1: + ks = 0 + if(s_max > s_min): + if bw: + ks = 255/(s_max - s_min) + else: + ks = 1280/(s_max - s_min) + #wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + wave_s = np.array([0.0] * 1440) + + for minute_m in range(1440): + wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + #wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + arr_source[2*gate + 1, :] = wave_s + + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + for col in range(1440): + sens_val = wave_s[col] + if sens_val != 0: + if bw: + r = ks*(sens_val-s_min) + g = r + b = r + else: + r,g,b=BestColor(ks*(sens_val-s_min)) + #print(r,g,b) + rgb_row[col] = r,g,b + + for stretch_index in range(stretch_by): + y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + device_counter += 1 + print("stop") + + elif (chart_type == 4): #"collapsed" + + stretch_by = 50 + arr_source = np.zeros((1, minutes), dtype=float) + arr_stretched = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + device_counter = 0 + wave_m = [["", -1] for _ in range(1440)] + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + radar_fields_of_interest = [] + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3_max",12] + + + if isinstance(threshold_lst, int): + threshold_lst = ["s3_max",threshold_lst] + + radar_field = threshold_lst[0] + if radar_field not in radar_fields_of_interest: + radar_fields_of_interest.append(radar_field) + + threshold = threshold_lst[1] + dev_id = details[0] + ids_list.append(details[1]) + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + cnt += 1 + row_nr_2_device_id[cnt] = device_id + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + device_id_2_location[device_id] = location_name + + device_id_2_threshold[device_id] = radar_threshold_group + + target_tz = pytz.timezone(time_zone_s) + st = time.time() + + #each record in my_data has time, device_id and radar_fields_of_interest in it + result_np = None + try: + result_np = process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest) + print(time.time() - st) + except Exception as err: + print(str(err)) + + if False: + for record in my_data: + time_val, device_id, min_val, max_val = record + radar_threshold = device_id_2_threshold[device_id] + local_time = time_val.astimezone(target_tz) + minute_m = int((local_time - local_time.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60) + + if (wave_m[minute_m][0] == ""): + if max_val > radar_threshold: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + else: + if max_val > radar_threshold: + if max_val > wave_m[minute_m][1]: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + + + + print(time.time()-st) + if result_np is not None: + wave_m = result_np + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + #wave_m = FilterGlitches(wave_m, filter_minutes) + r = 0 + g = 0 + b = 0 + + if isinstance(wave_m[0], np.int64): + inital_device_id = row_nr_2_device_id[wave_m[0]] + else: + inital_device_id = 0 + + present_at = [[inital_device_id, 0, 1]] #device_id, minute, duration + + for minute_m in range(1440): + try: + + if isinstance(wave_m[minute_m], np.int64): + device_id = row_nr_2_device_id[wave_m[minute_m]] + else: + device_id = 0 + + if device_id != "" and device_id != -1: + r,g,b = Loc2Color[device_id_2_location[device_id]][0] + rgb_row[minute_m] = b,g,r + + if minute_m > 0: + if present_at[-1][0] != device_id: + present_at.append([device_id, minute_m, 1]) + else: + present_at[-1][2] += 1 + + except Exception as err: + print(str(err)) + + for stretch_index in range(stretch_by): + y = stretch_index + arr_stretched[y, :] = rgb_row + + #print("stop") + #print(r,g,b) + SaveObjectInBlob(image_file+".bin", present_at) + SaveImageInBlob(image_file, arr_stretched) + +def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s): + #global Id2MACDict + + thresholds_dict = {} + + stretch_to_min_max = True + devices_c = len(devices_list) + + if devices_c == 0: + return + + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + + if (chart_type == 8): #"all graphs" + + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] + + + show_radar = True + show_light = True + show_temperature = True + show_humidity = True + show_smell = True + + labels_width = 200 + title_labels_height = 40 + title_label_width = 100 + + #common + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_line = cv2.LINE_AA + + #different + title_label_font_scale = 1 + title_label_font_color = (0, 0, 0)#(128, 255, 255) + title_label_font_thickness = 2 + + label_font_scale = 0.5 + label_font_color = (0, 0, 0)#(0, 255, 255) + label_font_thickness = 1 + + fields_n = len(fields) + + radar_stripes = len(devices_list) * fields_n + radar_stretch_by = 5 + + light_stripes = len(devices_list) + light_stretch_by = 20 + + smell_sensors_stripes = 10 * len(devices_list) + other_sensors_stripes = len(devices_list) + + temp_stripe_width = 15 + alarm_stripe_width = 5 + temperature_stretch_by = temp_stripe_width + alarm_stripe_width # Total height per device + + humidity_stripe_width = 15 + humidity_stretch_by = humidity_stripe_width + alarm_stripe_width + smell_component_stretch_by = 20 + + text_dimensions = get_text_dimensions("TEST", label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + + all_maps_height = 0 + + # radar, light, temperature, humidity, smell*10 + + if show_radar: + all_maps_height = title_labels_height + radar_stripes*radar_stretch_by + + if show_light: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*light_stretch_by + + if show_temperature: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*temperature_stretch_by + + if show_humidity: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*humidity_stretch_by + + if show_smell: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*smell_component_stretch_by * 10 + + + if all_maps_height == 0: + return + + vertical_offset = 0 + arr_stretched = np.full((all_maps_height, minutes+labels_width, 3), [255, 174, 70], dtype=np.uint8) + + #Lets add divider lines + x = 190 + if show_radar: + stretch_by = radar_stretch_by + cnt = 0 + for details in devices_list: + y = vertical_offset + title_labels_height + (cnt)*fields_n*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + + section_height = title_labels_height + radar_stripes*radar_stretch_by + vertical_offset = vertical_offset + section_height + if show_light: + stretch_by = light_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + + section_height = title_labels_height + other_sensors_stripes*stretch_by + vertical_offset = vertical_offset + section_height + + if show_temperature: + stretch_by = temperature_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + section_height = title_labels_height + other_sensors_stripes*stretch_by + vertical_offset = vertical_offset + section_height + if show_humidity: + stretch_by = humidity_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + section_height = title_labels_height + other_sensors_stripes*humidity_stretch_by + vertical_offset = vertical_offset + section_height + if show_smell: + stretch_by = smell_component_stretch_by + + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*10*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + #section_height = title_labels_height + other_sensors_stripes**stretch_by * 10 + #vertical_offset = vertical_offset + section_height + + #all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*stretch_by * 10 + + + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + labels = [] + title_labels = [] + vertical_offset = 0 + ######################################## RADAR ################################################################## + if show_radar: + title_label_text = "RADAR" + fields_s = fields + stripes = radar_stripes + stretch_by = radar_stretch_by + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), vertical_offset + 10 + title_text_height), label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data != None: + + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields_s, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(radar_stretch_by): + y = yy * radar_stretch_by + stretch_index + arr_stretched[title_labels_height+y, 200:] = rgb_row + + print(time.time()-st) + vertical_offset = vertical_offset + title_labels_height + stripes*radar_stretch_by + ######################################## LIGHT ################################################################## + if show_light: + title_label_text = "LIGHT" + fields_s = ['light'] + min_val = 0 + max_val = 4095 + stretch_by = light_stretch_by + stripes = len(devices_list) * len(fields_s) # Calculate number of rows needed + # Calculate the correct vertical offset for light section + + # Draw the light section title at the correct position + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for light section + cnt = 0 + light_ids_list = [] # Create a separate list for light section + for details in devices_list: + dev_id = details[0] + light_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the light section + labels.append((descriptor, (10, vertical_offset + title_labels_height + text_height + (cnt)*len(fields_s)*stretch_by), + label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get light data using the existing query function + sql = get_deployment_light_only_query(devices_list_str, time_from_str, time_to_str, light_ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(light_ids_list)} + + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process light data + st = time.time() + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + # Use the light-specific function + wave_m = create_light_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Light heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap vertically + st = time.time() + section_start = vertical_offset + title_labels_height + + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + target_y = section_start + y + + # Make sure we're within bounds of the array + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = rgb_row + else: + print(f"Warning: Row {target_y} is out of bounds (max: {arr_stretched.shape[0]-1})") + + vertical_offset = vertical_offset + title_labels_height + stripes*stretch_by + print(f"Light stretching time: {time.time()-st:.4f} seconds") + + ######################################## TEMPERATURE ################################################################## + if show_temperature: + title_label_text = "TEMPERATURE" + fields_s = ['temperature', 'temperature_state'] + + # Define different stripe widths for temperature and alarm + + temp_offset = -10#GetTempOffset(device_id) + min_val = 20 + max_val = 30 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height + text_height + cnt * temperature_stretch_by + #y_pos = vertical_offset + title_labels_height + text_height + (cnt)*len(fields_s)*stretch_by) + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get temperature data + sql = get_deployment_temperature_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, temp_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process temperature data + st = time.time() + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + if False: + # Simulate data for testing + for i in range(min(len(my_data), 500)): + if i >= 100: # Only modify indices 100-500 + t = (i - 100) / 4.0 # Temperature value + + # Set correct alarm levels based on temperature + if CelsiusToFahrenheit(t) <= 50 or CelsiusToFahrenheit(t) >= 90: + alarm_level = 2 # Critical - should be red + elif CelsiusToFahrenheit(t) <= 60 or CelsiusToFahrenheit(t) >= 80: + alarm_level = 1 # Warning - should be yellow + else: + alarm_level = 0 # Normal - should be green + + # Replace the tuple with new values + my_data[i] = (my_data[i][0], my_data[i][1], t, alarm_level) + + # Create the heatmap data + wave_m = create_temperature_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Temperature heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap with different heights for temperature and alarm + st = time.time() + section_start = vertical_offset + title_labels_height + + # Loop through each device + for device_idx in range(len(temp_ids_list)): + # Get the data rows for this device + temp_row = wave_m[device_idx * 2] # Temperature row (even index) + alarm_row = wave_m[device_idx * 2 + 1] # Alarm row (odd index) + + # Calculate the starting y-position for this device + device_y_start = section_start + device_idx * temperature_stretch_by + + # Draw the temperature stripe (15 pixels) + for stretch_index in range(temp_stripe_width): + target_y = device_y_start + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = temp_row + + # Draw the alarm stripe (5 pixels) + for stretch_index in range(alarm_stripe_width): + target_y = device_y_start + temp_stripe_width + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = alarm_row + + print(f"Temperature stretching time: {time.time()-st:.4f} seconds") + + ######################################## HUMIDITY ################################################################## + ''' + Ideal indoor humidity: 30-50% + Too dry: Below 30% - Can cause dry skin, irritated eyes, and respiratory issues + Too humid: Above 60% - Feels warmer than actual temperature, promotes mold growth + ''' + if show_humidity: + title_label_text = "HUMIDITY" + fields_s = ['humidity', 'humidity_state'] + + # Define different stripe widths for humidity and alarm + + humidity_offset = 0 + min_val = 40 + max_val = 90#60 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + if show_temperature: + vertical_offset += title_labels_height + other_sensors_stripes * temperature_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height + text_height + cnt * humidity_stretch_by + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get humidity data + sql = get_deployment_humidity_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, humidity_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process temperature data + st = time.time() + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + if False: + # Simulate data for testing + for i in range(min(len(my_data), 500)): + if i >= 100: # Only modify indices 100-500 + h = (i - 100) / 4.0 # Temperature value + + # Set correct alarm levels based on temperature + if h <= 20 or h >= 60: + alarm_level = 2 # Critical - should be red + elif h <= 30 or h >= 50: + alarm_level = 1 # Warning - should be yellow + else: + alarm_level = 0 # Normal - should be green + + # Replace the tuple with new values + my_data[i] = (my_data[i][0], my_data[i][1], h, alarm_level) + + # Create the heatmap data + wave_m = create_humidity_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Humidity heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap with different heights for humidity and alarm + st = time.time() + section_start = vertical_offset + title_labels_height + + # Loop through each device + for device_idx in range(len(temp_ids_list)): + # Get the data rows for this device + humidity_row = wave_m[device_idx * 2] # Humidity row (even index) + alarm_row = wave_m[device_idx * 2 + 1] # Alarm row (odd index) + + # Calculate the starting y-position for this device + device_y_start = section_start + device_idx * humidity_stretch_by + + # Draw the humidity stripe (15 pixels) + for stretch_index in range(humidity_stripe_width): + target_y = device_y_start + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = humidity_row + + # Draw the alarm stripe (5 pixels) + for stretch_index in range(alarm_stripe_width): + target_y = device_y_start + temp_stripe_width + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = alarm_row + + print(f"Temperature stretching time: {time.time()-st:.4f} seconds") + + ######################################## SMELL ################################################################## + if show_smell: + title_label_text = "SMELL" + fields_s = ['S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8', 'S9'] + + # Define different stripe widths for humidity and alarm + + smell_offset = 0 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + if show_temperature: + vertical_offset += title_labels_height + other_sensors_stripes * temperature_stretch_by + if show_humidity: + vertical_offset += title_labels_height + other_sensors_stripes * humidity_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height +80+ text_height + cnt * smell_component_stretch_by * 10 + #y_pos = vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by) + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get smell data + sql = get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, smell_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Create the heatmap data + create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields_s, device_to_index, base_minute, time_zone_s, smell_component_stretch_by, selected_date, vertical_offset + 18 + title_text_height) + + + + SaveImageInBlobLabelsOut(image_file, arr_stretched, labels, title_labels) + + print("stop") + + + + + +def CreateDailyLocationMap(location_image_file, devices_list, selected_date, filter_minutes, time_zone_s, stretch_by): + + devices_c = len(devices_list) + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + arr_source = np.zeros((1, minutes), dtype=float) + arr_stretched = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + arr_stretched_sorted = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + device_counter = 0 + wave_m = [["", -1] for _ in range(1440)] + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + radar_fields_of_interest = [] + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + if len(threshold_lst) > 2: + threshold_lst = ["s3_max",12] + radar_field = threshold_lst[0] + except: + threshold_lst = ["s3_max",12] + + radar_field = threshold_lst[0] + if radar_field not in radar_fields_of_interest: + radar_fields_of_interest.append(radar_field) + + threshold = threshold_lst[1] + dev_id = details[0] + ids_list.append(details[1]) + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + cnt += 1 + row_nr_2_device_id[cnt] = device_id + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + device_id_2_location[device_id] = location_name + + device_id_2_threshold[device_id] = radar_threshold_group + + target_tz = pytz.timezone(time_zone_s) + st = time.time() + + #each record in my_data has time, device_id and radar_fields_of_interest in it + + try: + result_np = process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest) + print(time.time() - st) + except Exception as err: + print(str(err)) + + if False: + for record in my_data: + time_val, device_id, min_val, max_val = record + radar_threshold = device_id_2_threshold[device_id] + local_time = time_val.astimezone(target_tz) + minute_m = int((local_time - local_time.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60) + + if (wave_m[minute_m][0] == ""): + if max_val > radar_threshold: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + else: + if max_val > radar_threshold: + if max_val > wave_m[minute_m][1]: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + + + + print(time.time()-st) + wave_m = result_np + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + rgbsorted_row = np.zeros(( 1440, 3), dtype=np.uint8) + #wave_m = FilterGlitches(wave_m, filter_minutes) + r = 0 + g = 0 + b = 0 + + presence_minutes = {} + + #we want to generate present_at array + if isinstance(wave_m[0], np.int64): + inital_device_id = row_nr_2_device_id[wave_m[0]] + else: + inital_device_id = 0 + present_at = [[inital_device_id, 0, 1]] #device_id, minute, duration + for minute_m in range(1440): + try: + if isinstance(wave_m[minute_m], np.int64): + device_id = row_nr_2_device_id[wave_m[minute_m]] + else: + device_id = 0 + + if device_id != "" and device_id != -1: + r,g,b = Loc2Color[device_id_2_location[device_id]][0] + rgb_row[minute_m] = b,g,r + + if Loc2Color[device_id_2_location[device_id]][1] in presence_minutes: + presence_minutes[Loc2Color[device_id_2_location[device_id]][1]] = [presence_minutes[Loc2Color[device_id_2_location[device_id]][1]][0] + 1, Loc2Color[device_id_2_location[device_id]][0]] + else: + presence_minutes[Loc2Color[device_id_2_location[device_id]][1]] = [1, Loc2Color[device_id_2_location[device_id]][0]] + + if minute_m > 0: + if present_at[-1][0] != device_id: + present_at.append([device_id, minute_m, 1]) + else: + present_at[-1][2] += 1 + + except Exception as err: + print(str(err)) + + start_minute = 0 + for color_key in sorted(presence_minutes): + print(color_key, presence_minutes[color_key]) + rgbsorted_row[start_minute:start_minute+presence_minutes[color_key][0]] = presence_minutes[color_key][1][::-1] + start_minute += presence_minutes[color_key][0] + + #we need to save present_at list to blob + SaveObjectInBlob(image_file+".bin", present_at) + #present_at_back_s = ReadObjectMinIO("daily-maps", image_file+".bin") + #present_at_back = json.loads(present_at_back_s) + #print(present_at_back) + for stretch_index in range(stretch_by): + y = stretch_index + arr_stretched[y, :] = rgb_row + arr_stretched_sorted[y, :] = rgbsorted_row + #print("stop") + #print(r,g,b) + SaveImageInBlob(image_file, arr_stretched) + SaveImageInBlob(image_file[:-4]+"S.png", arr_stretched_sorted) + + +def GenerateFullLocationMap(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreateFullLocationMap(map_file, devices_list, ddate, 1, recreate_or_not, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + +def GenerateFullLocationMapLabelsOut(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreateFullLocationMapLabelsOut(map_file, devices_list, ddate, 1, recreate_or_not, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + +def CreateMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by): + global Id2MACDict, s_table_temp + + st = time.time() + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + try: + + #stretch_to_min_max = True + + #current_date_p = selected_date.replace("-", "_") + #current_date_s = selected_date + + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + for details in devices_list[0]: + dev_id = details[0] + ids_list.append(details[1]) + if dev_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return False, [] + + if larger_than200 > 0: + sensors_c = len(s_table_temp) + else: #old sensors not supported + return False, [] + + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + image_file = map_file + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + temp_offset = -10 + if sensors_c > 1: + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + else: + sql = get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, s_table_temp[0]) + print(sql) + #print(sql) + #st = time.time() + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + day_data = cur.fetchall()#cur.fetchone() + #print(result) + if day_data == None: + return False, [] + + stretch_by = 10 + minutes = 1440 + stripes = devices_c * sensors_c #2 for upper maxes, lower mins + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) + arr_stretched_template = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) # 3 for RGB channels + arr_source = fast_fill_array_from_timescale(day_data, time_from_str, devices_list[1], arr_source_template, time_zone_s) + arr_source = AddLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) + SaveImageInBlob(image_file, arr_stretched) + return True, vocs_scaled + + except Exception as e: + AddToLog(traceback.format_exc()) + return False, [] + +def get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+ {temp_offset} as avg_temperature, + sr.avg_humidity, + sr.pressure_amplitude, + sr.max_light, + rr.radar, + sr.min_s0 as sensor_min_s0, + sr.min_s1 as sensor_min_s1, + sr.min_s2 as sensor_min_s2, + sr.min_s3 as sensor_min_s3, + sr.min_s4 as sensor_min_s4, + sr.min_s5 as sensor_min_s5, + sr.min_s6 as sensor_min_s6, + sr.min_s7 as sensor_min_s7, + sr.min_s8 as sensor_min_s8, + sr.min_s9 as sensor_min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS pressure_amplitude, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, sensor_in): + """ + Generate a TimeScaleDB query for a single sensor reading based on device IDs. + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + temp_offset (float): Temperature offset to apply + sensor (str): Single sensor to query from s_table + Returns: + str: Generated SQL query + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + if "_" in sensor_in: + sensor = sensor_in.split("_")[1] + else: + sensor = sensor_in + + # Handle different sensor types + if sensor == "radar": + # Query only radar readings + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS {sensor_in} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "temperature": + # Query temperature with offset + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) + {temp_offset} AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "humidity": + # Query humidity + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(humidity) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "pressure": + # Query pressure + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(pressure) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "light": + # Query light + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(light) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor.startswith("voc"): + # Query VOC sensors (voc0-voc9) - these correspond to s0-s9 in the original query + voc_num = sensor[3:] # Extract number from "voc0", "voc1", etc. + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(CASE WHEN s{voc_num} > 0 THEN s{voc_num} END) AS {sensor} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + else: + raise ValueError(f"Unknown sensor type: {sensor}. Must be one of: temperature, humidity, pressure, light, radar, voc0-voc9") + + return sql + +def get_deployment_single_query_rz(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, sensor): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + + Returns: + str: Generated SQL query + """ + + #table_sens = {"temperature": (f"sr.avg_temperature+ {temp_offset} as avg_temperature", "avg_temperature"), + #"humidity": ("sr.avg_humidity", "avg_humidity"), + #"pressure": ("sr.pressure_amplitude", "pressure_amplitude"), + #"light":("sr.max_light", "max_light"), + #"radar":("rr.radar") + #"voc0": + #"voc1": + #"voc2": + #"voc3": + #"voc4": + #"voc5": + #"voc6": + #"voc7": + #"voc8": + #"voc9": , + #} + # derived + + #if sensor == + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+ {temp_offset} as avg_temperature, + sr.avg_humidity, + sr.pressure_amplitude, + sr.max_light, + rr.radar, + sr.min_s0 as sensor_min_s0, + sr.min_s1 as sensor_min_s1, + sr.min_s2 as sensor_min_s2, + sr.min_s3 as sensor_min_s3, + sr.min_s4 as sensor_min_s4, + sr.min_s5 as sensor_min_s5, + sr.min_s6 as sensor_min_s6, + sr.min_s7 as sensor_min_s7, + sr.min_s8 as sensor_min_s8, + sr.min_s9 as sensor_min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS pressure_amplitude, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + + +def get_deployment_rd_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+{temp_offset} as avg_temperature, + sr.avg_humidity, + sr.avg_pressure, + sr.max_light, + sr.min_s0 as smell_s0, + sr.min_s1 as smell_s1, + sr.min_s2 as smell_s2, + sr.min_s3 as smell_s3, + sr.min_s4 as smell_s4, + sr.min_s5 as smell_s5, + sr.min_s6 as smell_s6, + sr.min_s7 as smell_s7, + sr.min_s8 as smell_s8, + sr.min_s9 as smell_s9, + rr.absent as radar_absent, + rr.moving as radar_moving, + rr.stationary as radar_stationary, + rr.both as radar_both, + rr.m0 as radar_m0, + rr.m1 as radar_m1, + rr.m2 as radar_m2, + rr.m3 as radar_m3, + rr.m4 as radar_m4, + rr.m5 as radar_m5, + rr.m6 as radar_m6, + rr.m7 as radar_m7, + rr.m8 as radar_m8, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS avg_pressure, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(absent) AS absent, + MAX(moving) AS moving, + MAX(stationary) AS stationary, + MAX(\"both\") AS both, + MAX(m0) AS m0, + MAX(m1) AS m1, + MAX(m2) AS m2, + MAX(m3) AS m3, + MAX(m4) AS m4, + MAX(m5) AS m5, + MAX(m6) AS m6, + MAX(m7) AS m7, + MAX(m8) AS m8, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_radar_only_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_device_radar_s28_only_query(time_from_str, time_to_str, device_id): + sql = f""" + SELECT + time, + (s2+s3+s4+s5+s6+s7+s8)/7 AS s28, + (m2+m3+m4+m5+m6+m7+m8)/7 AS m28 + FROM + radar_readings + WHERE + device_id = {device_id} + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + time ASC + """ + return sql + +def get_device_radar_only_query(devices_list_str, time_from_str, time_to_str, device_id): + sql = f""" + SELECT + time, + (s2+s3+s4+s5+s6+s7+s8)/7 AS s28, + (m2+m3+m4+m5+m6+m7+m8)/7 AS m28 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + time ASC + """ + return sql + + +def get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list) list of different unique fields required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + radar_fields_to_get = "" + q_parts = "" + for field in radar_fields_of_interest: + if field == "s28_min": + q_part = "MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min" + elif field == "s28_max": + q_part = "MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max" + elif field == "m08_max": + q_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max" + elif field == "s2_max": + q_part = "MAX(s2) AS s2_max" + elif field == "s3_max": + q_part = "MAX(s3) AS s3_max" + elif field == "s4_max": + q_part = "MAX(s4) AS s4_max" + elif field == "s5_max": + q_part = "MAX(s5) AS s5_max" + elif field == "s6_max": + q_part = "MAX(s6) AS s6_max" + elif field == "s7_max": + q_part = "MAX(s7) AS s7_max" + elif field == "s8_max": + q_part = "MAX(s8) AS s8_max" + elif field == "m0_max": + q_part = "MAX(m0) AS m0_max" + elif field == "m1_max": + q_part = "MAX(m1) AS m1_max" + elif field == "m2_max": + q_part = "MAX(m2) AS m2_max" + elif field == "m3_max": + q_part = "MAX(m3) AS m3_max" + elif field == "m4_max": + q_part = "MAX(m4) AS m4_max" + elif field == "m5_max": + q_part = "MAX(m5) AS m5_max" + elif field == "m6_max": + q_part = "MAX(m6) AS m6_max" + elif field == "m7_max": + q_part = "MAX(m7) AS m7_max" + elif field == "m8_max": + q_part = "MAX(m8) AS m8_max" + else: + q_part = field + + if q_parts == "": + q_parts = q_part + else: + q_parts = q_parts + ", " + q_part + if radar_fields_to_get == "": + radar_fields_to_get = field + else: + radar_fields_to_get = radar_fields_to_get + ", " + field + sql = f""" + SELECT + minute, + device_id, + {radar_fields_to_get} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {q_parts} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_radar_only_colapsed_query_wid(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + radar_fields_to_get = "" + q_parts = "" + for field in radar_fields_of_interest: + if field == "s28_min": + q_part = "MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min" + elif field == "s28_max": + q_part = "MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max" + elif field == "m08_max": + q_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max" + elif field == "s2_max": + q_part = "MAX(s2) AS s2_max" + elif field == "s3_max": + q_part = "MAX(s3) AS s3_max" + elif field == "s4_max": + q_part = "MAX(s4) AS s4_max" + elif field == "s5_max": + q_part = "MAX(s5) AS s5_max" + elif field == "s6_max": + q_part = "MAX(s6) AS s6_max" + elif field == "s7_max": + q_part = "MAX(s7) AS s7_max" + elif field == "s8_max": + q_part = "MAX(s8) AS s8_max" + elif field == "m0_max": + q_part = "MAX(m0) AS m0_max" + elif field == "m1_max": + q_part = "MAX(m1) AS m1_max" + elif field == "m2_max": + q_part = "MAX(m2) AS m2_max" + elif field == "m3_max": + q_part = "MAX(m3) AS m3_max" + elif field == "m4_max": + q_part = "MAX(m4) AS m4_max" + elif field == "m5_max": + q_part = "MAX(m5) AS m5_max" + elif field == "m6_max": + q_part = "MAX(m6) AS m6_max" + elif field == "m7_max": + q_part = "MAX(m7) AS m7_max" + elif field == "m8_max": + q_part = "MAX(m8) AS m8_max" + else: + q_part = field + + if q_parts == "": + q_parts = q_part + else: + q_parts = q_parts + ", " + q_part + if radar_fields_to_get == "": + radar_fields_to_get = field + else: + radar_fields_to_get = radar_fields_to_get + ", " + field + sql = f""" + SELECT + minute, + device_id, + {radar_fields_to_get} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {q_parts} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + This is looking for presence, NOT absence... othervise all MAXes need to be converted to MINs + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + m0_max, + m1_max, + m2_max, + m3_max, + m4_max, + m5_max, + m6_max, + m7_max, + m8_max, + m08_max, + s2_max, + s3_max, + s4_max, + s5_max, + s6_max, + s7_max, + s8_max, + s28_max, + s28_min + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(m0) AS m0_max, + MAX(m1) AS m1_max, + MAX(m2) AS m2_max, + MAX(m3) AS m3_max, + MAX(m4) AS m4_max, + MAX(m5) AS m5_max, + MAX(m6) AS m6_max, + MAX(m7) AS m7_max, + MAX(m8) AS m8_max, + MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max, + MAX(s2) AS s2_max, + MAX(s3) AS s3_max, + MAX(s4) AS s4_max, + MAX(s5) AS s5_max, + MAX(s6) AS s6_max, + MAX(s7) AS s7_max, + MAX(s8) AS s8_max, + MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max, + MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_light_only_query(devices_list_str, time_from_str, time_to_str, ids_list): + #light detailed + """ + Generate a TimeScaleDB query for light readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + light_max + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(light) AS light_max + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_temperature_only_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + """ + Generate a TimeScaleDB query for temperature readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with temperature and alarm level + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + temperature_avg, + CASE + WHEN (temperature_avg * 9/5 + 32) <= 50 OR (temperature_avg * 9/5 + 32) >= 90 THEN 2 + WHEN (temperature_avg * 9/5 + 32) <= 60 OR (temperature_avg * 9/5 + 32) >= 80 THEN 1 + ELSE 0 + END AS alarm_level + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature)+{temp_offset} AS temperature_avg + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_humidity_only_query(devices_list_str, time_from_str, time_to_str, ids_list, humidity_offset): + """ + Generate a TimeScaleDB query for humidity readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with humidity and alarm level + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + col_expr = f"2.3592 * AVG(humidity) + 23.5546" #= 2.3592 * J2 + 33.5546 + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + humidity_avg, + CASE + WHEN humidity_avg <= 20 OR humidity_avg >= 60 THEN 2 + WHEN humidity_avg <= 30 OR humidity_avg >= 50 THEN 1 + ELSE 0 + END AS alarm_level + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {col_expr} AS humidity_avg + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str, ids_list, humidity_offset): + """ + Generate a TimeScaleDB query for smell readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with smell components + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + min_s0, + min_s1, + min_s2, + min_s3, + min_s4, + min_s5, + min_s6, + min_s7, + min_s8, + min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + + +def get_deployment_radar_only_detailed_all_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + This is looking for presence, NOT absence... othervise all MAXes need to be converted to MINs + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + absent_min, + stationary_max, + moving_max, + both_max, + m0_max, + m1_max, + m2_max, + m3_max, + m4_max, + m5_max, + m6_max, + m7_max, + m8_max, + m08_max, + s2_max, + s3_max, + s4_max, + s5_max, + s6_max, + s7_max, + s8_max, + s28_max + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(absent) AS absent_min, + MAX(stationary) AS stationary_max, + MAX(moving) AS moving_max, + MAX("both") AS both_max, + MAX(m0) AS m0_max, + MAX(m1) AS m1_max, + MAX(m2) AS m2_max, + MAX(m3) AS m3_max, + MAX(m4) AS m4_max, + MAX(m5) AS m5_max, + MAX(m6) AS m6_max, + MAX(m7) AS m7_max, + MAX(m8) AS m8_max, + MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max, + MAX(s2) AS s2_max, + MAX(s3) AS s3_max, + MAX(s4) AS s4_max, + MAX(s5) AS s5_max, + MAX(s6) AS s6_max, + MAX(s7) AS s7_max, + MAX(s8) AS s8_max, + MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql +def get_deployment_deca_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+{temp_offset} as avg_temperature, + sr.avg_humidity, + sr.avg_pressure, + sr.max_light, + sr.min_s0 as smell_s0, + sr.min_s1 as smell_s1, + sr.min_s2 as smell_s2, + sr.min_s3 as smell_s3, + sr.min_s4 as smell_s4, + sr.min_s5 as smell_s5, + sr.min_s6 as smell_s6, + sr.min_s7 as smell_s7, + sr.min_s8 as smell_s8, + sr.min_s9 as smell_s9, + rr.absent as radar_absent, + rr.moving as radar_moving, + rr.stationary as radar_stationary, + rr.both as radar_both, + rr.m0 as radar_m0, + rr.m1 as radar_m1, + rr.m2 as radar_m2, + rr.m3 as radar_m3, + rr.m4 as radar_m4, + rr.m5 as radar_m5, + rr.m6 as radar_m6, + rr.m7 as radar_m7, + rr.m8 as radar_m8, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('10 seconds', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS avg_pressure, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('10 seconds', time) AS minute, + device_id, + MAX(absent) AS absent, + MAX(moving) AS moving, + MAX(stationary) AS stationary, + MAX(\"both\") AS both, + MAX(m0) AS m0, + MAX(m1) AS m1, + MAX(m2) AS m2, + MAX(m3) AS m3, + MAX(m4) AS m4, + MAX(m5) AS m5, + MAX(m6) AS m6, + MAX(m7) AS m7, + MAX(m8) AS m8, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + """ + Generate a TimeScaleDB query for radar readings based on device IDs with time snapped to 10-second intervals. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list): List of field names required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + # Handle fields processing + select_fields = [] + for field in radar_fields_of_interest: + if field == "s28": + select_fields.append("(s2+s3+s4+s5+s6+s7+s8)/7 AS s28") + else: + select_fields.append(field) + + fields_str = ", ".join(select_fields) + + sql = f""" + SELECT + time_bucket('10 seconds', time) AS ten_seconds, + device_id, + {fields_str} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + CASE device_id + {case_order} + END, + ten_seconds + """ + return sql + +def get_deployment_radar_10sec_snapped_query_min_max(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + """ + Generate a TimeScaleDB query for radar readings based on device IDs with time snapped to 10-second intervals. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list): List of field names required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + # Handle fields processing + select_fields = [] + for field in radar_fields_of_interest: + + radar_fields = field.split("_") + field_t = radar_fields[0] + if field_t == "s28": + if radar_fields[1] == "max": + select_fields.append("MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28") + else: + select_fields.append("MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28") + else: + if radar_fields[1] == "max": + select_fields.append(f"MAX({field_t}) as {field}") + else: + select_fields.append(f"MIN({field_t}) as {field}") + + fields_str = ", ".join(select_fields) + + sql = f""" + SELECT + time_bucket('10 seconds', time) AS ten_seconds, + device_id, + {fields_str} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + ten_seconds, + device_id + ORDER BY + CASE device_id + {case_order} + END, + ten_seconds + """ + return sql + +def export_query_to_minio_chunked(connection_params, query, minio_client, bucket_name, blob_name=None, chunksize=10000): + """ + Export query results to MinIO as CSV in chunks to handle large datasets + + Parameters: + connection_params (dict): Database connection parameters + query (str): SQL query to execute + minio_client: Initialized MinIO client + bucket_name (str): Name of the MinIO bucket + blob_name (str): Name for the blob in MinIO. If None, generates timestamped name + chunksize (int): Number of rows to process at a time + + Returns: + str: Name of the created blob + """ + try: + # Create direct connection using psycopg2 + conn = psycopg2.connect(**connection_params) + + # Generate blob name if not provided + if blob_name is None: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + blob_name = f'query_results_{timestamp}.csv' + + # Create a buffer to store CSV data + csv_buffer = io.StringIO() + + # Stream the query results in chunks + first_chunk = True + for chunk_df in pd.read_sql_query(query, conn, chunksize=chunksize): + # Write header only for the first chunk + chunk_df.to_csv( + csv_buffer, + index=False, + header=first_chunk, + mode='a' + ) + first_chunk = False + + # Get the CSV data as bytes + csv_buffer.seek(0) + csv_bytes = csv_buffer.getvalue().encode('utf-8') + + # Upload to MinIO + minio_client.put_object( + bucket_name, + blob_name, + io.BytesIO(csv_bytes), + len(csv_bytes) + ) + + print(f"Data exported successfully to MinIO: {bucket_name}/{blob_name}") + return blob_name + + except Exception as e: + print(f"Error exporting data: {str(e)}") + print(f"Traceback: {traceback.format_exc()}") + raise + + finally: + if 'conn' in locals(): + conn.close() + if 'csv_buffer' in locals(): + csv_buffer.close() + +def export_query_to_csv_pandas(connection_params, query, output_path=None): + """ + Export query results to CSV using pandas with psycopg2 connection + + Parameters: + connection_params (dict): Database connection parameters + query (str): SQL query to execute + output_path (str): Path for output CSV file. If None, generates timestamped filename + + Returns: + str: Path to the created CSV file + """ + try: + # Create direct connection using psycopg2 + conn = psycopg2.connect(**connection_params) + + # Generate output path if not provided + if output_path is None: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + output_path = f'query_results_{timestamp}.csv' + + # Read query directly into DataFrame using the psycopg2 connection + df = pd.read_sql_query(query, conn) + + # Export to CSV with all headers + df.to_csv(output_path, index=False) + print(f"Data exported successfully to {output_path}") + return output_path + + except Exception as e: + print(f"Error exporting data: {str(e)}") + raise + + finally: + if 'conn' in locals(): + conn.close() + + +def CreateDailyCSV(csv_file, devices_list, selected_date, vocs_scaled, time_zone_s, radar_part, consolidated_by, temp_offset): + global Id2MACDict + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + try: + + #stretch_to_min_max = True + + #current_date_p = selected_date.replace("-", "_") + #current_date_s = selected_date + + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + for details in devices_list[0]: + dev_id = details[0] + ids_list.append(details[1]) + if dev_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return "" + + if larger_than200 > 0: + sensors_c = len(s_table) + else: #old sensors not supported + return "" + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + if consolidated_by == "by_minute_rc": + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + elif consolidated_by == "by_deca_rd": + sql = get_deployment_deca_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset) + elif consolidated_by == "by_minute_rd": + sql = get_deployment_rd_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset) + + print(sql) + + connection_params = { + 'host': DB_HOST, + 'database': DB_NAME, + 'user': DB_USER, + 'password': DB_PASSWORD, + 'port': DB_PORT + } + + # Using pandas approach (recommended) + output_file = export_query_to_minio_chunked( + connection_params, + sql, + miniIO_blob_client, + "data-downloads", + csv_file, + chunksize=10000 + ) + return output_file + except Exception as e: + logging.error(str(traceback.format_exc())) + return "" + +def GetBlob(file_name, bucket_name="daily-maps"): + """ + Retrieve image from blob storage + + Args: + file_name (str): Name of the file to retrieve from blob storage + + Returns: + tuple: (image_bytes, content_type) + Returns None, None if image not found or error occurs + """ + logger.debug(f"GetBlob({file_name})") + try: + # Get the object from blob storage + data = miniIO_blob_client.get_object( + bucket_name, + file_name + ) + + # Read the data into bytes + data_bytes = data.read() + #logger.debug(f"len(data_bytes)={len(data_bytes)}") + + if bucket_name == "daily-maps": + return data_bytes, 'image/png' + else: + return data_bytes, 'application/zip' + + except Exception as e: + logger.error(f"Error: {traceback.format_exc()}") + return None, None + + +def MapFileToDate(map_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + parts = map_file.split("/") + parts = parts[-1].split("_") + + if "-" in parts[0]: + date_string = parts[0] + elif "-" in parts[1]: + date_string = parts[1] + + date_object = datetime.datetime.strptime(date_string, "%Y-%m-%d") + date_only = date_object.date() + return date_only + +def CSVFileToDate(csv_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + parts = csv_file.split("/") + parts = parts[-1].split("_") + + if "-" in parts[0]: + date_string = parts[0] + elif "-" in parts[1]: + date_string = parts[1] + + date_object = datetime.datetime.strptime(date_string, "%Y-%m-%d") + date_only = date_object.date() + return date_only + +def GetMACsListSimple(list_of_lists): + + result = [] + if len(list_of_lists) > 0: + result = [sublist[3] for sublist in list_of_lists] + + return(result) + +def datetime_handler(obj): + """Handle datetime serialization for JSON""" + if isinstance(obj, datetime.datetime): + if obj.tzinfo: + return obj.isoformat() + return obj.strftime('%Y-%m-%d %H:%M:%S.%f') + raise TypeError(f"Object of type {type(obj)} is not JSON serializable") + +def ReadCandles(file, sensor, period, time_from, time_to): + result = [] + if sensor == "voc0": + sqlr = "SELECT * from vocs_0"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc1": + sqlr = "SELECT * from vocs_1"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc2": + sqlr = "SELECT * from vocs_2"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc3": + sqlr = "SELECT * from vocs_3"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc4": + sqlr = "SELECT * from vocs_4"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc5": + sqlr = "SELECT * from vocs_5"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc6": + sqlr = "SELECT * from vocs_6"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc7": + sqlr = "SELECT * from vocs_7"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc8": + sqlr = "SELECT * from vocs_8"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc9": + sqlr = "SELECT * from vocs_9"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + else: + sqlr = "SELECT * from "+sensor+"s"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + logger.debug(f"sqlr = {sqlr}") + + with get_db_connection() as conn: + with conn.cursor() as cur: + devices_string = ReadCleanStringDB(cur, sqlr) + result = QuerrySql(file, sqlr) + return result + +def ReadSensor(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + result = [] + if sensor == "radar": + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor[0] == "s": + sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + if sensor == "temperature": + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype=17 ORDER BY time ASC" + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadSensorDeltas(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + 'for now pressure and light only' + sensor_2_mtype = {"light": 4, "pressure": 1, "temperature": 2, "humidity": 3} + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + result = [] + if sensor == "radar": + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor[0] == "s": + sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' and mtype=4 ORDER BY time ASC" + else: + if sensor == "temperature": + sqlr = f"SELECT time, {sensor} - 16 from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor == "pressure": + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype=1 ORDER BY time ASC" + else: + mtype = sensor_2_mtype[sensor] + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype={mtype} ORDER BY time ASC" + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadSensor3(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + import datetime + from datetime import timezone + + # Convert epoch to datetime and format as ISO 8601 strings with timezone + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + # If bucket_size is provided (i.e. not "no"), then use time bucketing. + use_bucket = bucket_size != "no" + if use_bucket: + # Map the shorthand bucket sizes to PostgreSQL interval strings. + mapping = { + "10s": "10 seconds", + "1m": "1 minute", + "5m": "5 minutes", + "10m": "10 minutes", + "15m": "15 minutes", + "30m": "30 minutes", + "1h": "1 hour" + } + bucket_interval = mapping.get(bucket_size, bucket_size) + + avgmax = "AVG" + # Build the SQL query based on sensor type. + if sensor == "radar": + # For radar sensors, a special aggregation is needed. + avgmax = "MAX" + if radar_part == "s28": + radar_expr = "(s2+s3+s4+s5+s6+s7+s8)/7" + else: + radar_expr = radar_part + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({radar_expr}) AS radar + FROM radar_readings + WHERE device_id = {device_id} + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {radar_expr} AS radar + FROM radar_readings + WHERE device_id = {device_id} + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor[0] == "s": + # For sensors whose name starts with "s" (for example, smell sensors) + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({sensor}) AS smell + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {sensor} AS smell + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "co2": + alias = sensor + sensor = "s4" + sqlr = f""" + WITH s4_values AS ( + SELECT s4 + FROM public.sensor_readings + WHERE device_id = 559 + AND s4 IS NOT NULL + ), + s4_percentile AS ( + SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY s4 DESC) AS s4_25_percentile + FROM s4_values + ) + SELECT s4_25_percentile + FROM s4_percentile; + """ + co2_max = 22536000#102400000 + co2_min = 2400000#16825674 #387585 + + real_co2_max = 2000 + real_co2_min = 430 + + + #logger.debug(f"sqlr = {sqlr}") + #with get_db_connection() as conn: + #with conn.cursor() as cur: + #cur.execute(sqlr) + #result = cur.fetchall() + #co2_max = result[0][0] + #co2_min = result[0][1] + #=E17+E20*(102400000-A24)/B18 + #col_expr = f"{real_co2_min}+({real_co2_max}-{real_co2_min})*(102400000-{sensor})/({co2_min}-{co2_max})" + col_expr = f"GREATEST({real_co2_min},{real_co2_min}+({real_co2_max}-{real_co2_min})*({co2_max}-percentile_cont(0.5) WITHIN GROUP (ORDER BY {sensor}))/({co2_max}-{co2_min}))" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + ({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "voc": + sensor = "s9" + alias = sensor + col_expr = f"{sensor} - 0" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "humidity": + alias = sensor + #col_expr = f"2.3592 * {sensor} + 32.5546" #= 2.3592 * J2 + 33.5546 + col_expr = f"1* {sensor} + 5.0" #= 2.3592 * J2 + 33.5546 + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + else: + # For other sensors (including temperature, which requires a subtraction) + alias = sensor + col_expr = sensor + if sensor == "temperature": + col_expr = f"{sensor}" + alias = "temperature" + elif sensor == "light": + avgmax = "MAX" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + AND mtype = 17 + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadRadarDetail(device_id, sensor, time_from_epoch, time_to_epoch, alt_key_state): + + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + #sensor_index = int(sensor_index) + + + result = [] + + #time_period_sec can be "10" (RAW) or "60" + if alt_key_state == "1": #"RAW = 10 sec" + radar_part = sensor + if sensor == "m08_max": + radar_part = "(m0+m1+m2+m3+m4+m5+m6+m7+m8)/9" + elif sensor == "s28_max" or sensor == "s28_min": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + if sensor == "m08_max": + radar_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/7) AS m08_max" + elif sensor == "s28_max": + radar_part = f"MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS {sensor}" + elif sensor == "s28_min": + radar_part = f"MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS {sensor}" + else: + radar_part = f"MAX({sensor}) AS {sensor}" + + sqlr = f""" + SELECT + minute, + {sensor} as {sensor} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + {radar_part} + FROM + radar_readings + WHERE + device_id = {device_id} + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute + ) rr + + ORDER BY + minute + """ + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def check_and_parse(data_str): + # Remove whitespace to handle cases with spaces + cleaned = data_str.strip() + # Check if second character is '[' + is_list_of_lists = cleaned[1] == '[' + if cleaned[0] == '[': + # Parse the string regardless of type + parsed = json.loads(cleaned) + else: + #parsed = cleaned.split(",") + parsed = SmartSplit(cleaned) + return is_list_of_lists, parsed + +def clean_data_with_rolling_spline(line_part_t, window=5, threshold=2.0): + """ + Filter outliers using rolling median and replace with spline interpolation + Returns data in the same format as input: [(timestamp, value), ...] + """ + # Unzip the input tuples + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) # explicitly convert to float + + # Calculate rolling median and MAD using a safer approach + rolling_median = [] + rolling_mad = [] + + for i in range(len(y)): + start_idx = max(0, i - window//2) + end_idx = min(len(y), i + window//2 + 1) + window_values = y[start_idx:end_idx] + + # Skip if window is empty or contains invalid values + if len(window_values) == 0 or np.any(np.isnan(window_values)): + rolling_median.append(y[i]) + rolling_mad.append(0) + continue + + med = np.median(window_values) + mad = np.median(np.abs(window_values - med)) + + rolling_median.append(med) + rolling_mad.append(mad) + + rolling_median = np.array(rolling_median) + rolling_mad = np.array(rolling_mad) + + # Identify outliers (protect against division by zero) + outlier_mask = np.abs(y - rolling_median) > threshold * (rolling_mad + 1e-10) + good_data_mask = ~outlier_mask + + if np.sum(good_data_mask) < 4: + return line_part_t # return original data if we can't interpolate + + try: + # Create and apply spline + spline = interpolate.InterpolatedUnivariateSpline( + x[good_data_mask], + y[good_data_mask], + k=3 + ) + + y_cleaned = y.copy() + y_cleaned[outlier_mask] = spline(x[outlier_mask]) + except Exception as e: + print(f"Spline interpolation failed: {e}") + return line_part_t + + # Return in the same format as input + return list(zip(x, y_cleaned)) + +def DatesSpan(date_from: str, date_to: str) -> list: + """ + Generate a list of dates between date_from and date_to (inclusive). + Handles cases where date_from is later than date_to. + + Args: + date_from (str): Start date in 'YYYY-MM-DD' format + date_to (str): End date in 'YYYY-MM-DD' format + + Returns: + list: List of dates in 'YYYY-MM-DD' format + """ + # Convert string dates to datetime objects + start_date = datetime.datetime.strptime(date_from, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + start_date, end_date = end_date, start_date + + # Generate list of dates + dates_list = [] + current_date = start_date + + while current_date <= end_date: + dates_list.append(current_date.strftime('%Y-%m-%d')) + current_date += timedelta(days=1) + + # Reverse the list if original date_from was later than date_to + #if datetime.datetime.strptime(date_from, '%Y-%m-%d') > datetime.datetime.strptime(date_to, '%Y-%m-%d'): + # dates_list.reverse() + + return dates_list + +def zip_blobs(blob_paths, zip_blob_name, bucket_name, minio_client=None): + """ + Zip multiple blobs from MinIO storage into a single zip file without saving locally. + + Args: + blob_paths (list): List of blob paths to zip + zip_blob_name (str): Name/path for the output zip file in MinIO + bucket_name (str): MinIO bucket name + minio_client (Minio, optional): Existing MinIO client instance + + Returns: + bool: True if successful, False otherwise + """ + try: + # Create zip file in memory + zip_buffer = BytesIO() + + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + # Process each blob + for blob_path in blob_paths: + # Get file name from path for zip entry + file_name = blob_path.split('/')[-1] + + # Get blob data into memory + data = minio_client.get_object(bucket_name, blob_path.lstrip('/')) + + # Add file to zip + zip_file.writestr(file_name, data.read()) + + # Close the object to free memory + data.close() + + # Seek to start of zip file + zip_buffer.seek(0) + + # Upload zip file to MinIO + minio_client.put_object( + bucket_name, + zip_blob_name.lstrip('/'), + zip_buffer, + length=zip_buffer.getbuffer().nbytes + ) + + return True + + except Exception as e: + print(f"Error creating zip file: {str(e)}") + return False + finally: + # Clean up + zip_buffer.close() + +def clean_data_with_spline(x, y, threshold=2.0): + """ + Filter outliers and replace with spline interpolation + + Parameters: + x : array-like, timestamps or x-coordinates + y : array-like, values to be filtered + threshold : float, number of median absolute deviations for outlier detection + + Returns: + array-like : cleaned data with outliers replaced by spline interpolation + """ + # Convert inputs to numpy arrays + x = np.array(x) + y = np.array(y) + + # Calculate median and median absolute deviation + median = np.median(y) + mad = stats.median_abs_deviation(y) + + # Identify outliers + outlier_mask = np.abs(y - median) > threshold * mad + good_data_mask = ~outlier_mask + + # If we have too few good points for interpolation, adjust threshold + min_points_needed = 4 # minimum points needed for cubic spline + if np.sum(good_data_mask) < min_points_needed: + return y # return original data if we can't interpolate + + # Create spline with non-outlier data + spline = interpolate.InterpolatedUnivariateSpline( + x[good_data_mask], + y[good_data_mask], + k=3 # cubic spline + ) + + # Replace outliers with interpolated values + y_cleaned = y.copy() + y_cleaned[outlier_mask] = spline(x[outlier_mask]) + + return y_cleaned + +def clean_data(line_part_t, window=5, threshold=2.0): + """ + Remove obvious outliers based on window comparison + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) + + cleaned_data = [] + + for i in range(len(y)): + # Get window around current point + start_idx = max(0, i - window//2) + end_idx = min(len(y), i + window//2 + 1) + window_values = y[start_idx:end_idx] + + # Calculate median and MAD for the window + window_median = np.median(window_values) + deviation = abs(y[i] - window_median) + + # Keep point if it's not too far from window median + if deviation <= threshold * window_median: + cleaned_data.append((x[i], y[i])) + #else: + #print(window_values) + return cleaned_data + +def clean_data_fast(line_part_t, window=5, threshold=2.0): + """ + Remove obvious outliers based on window comparison - vectorized version + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) + + # Calculate rolling median using numpy + half_window = window // 2 + medians = np.array([ + np.median(y[max(0, i-half_window):min(len(y), i+half_window+1)]) + for i in range(len(y)) + ]) + + # Calculate deviations for all points at once + deviations = np.abs(y - medians) + + # Create mask for good points + good_points = deviations <= threshold * medians + + # Return filtered data using boolean indexing + return list(zip(x[good_points], y[good_points])) + +def clean_data_pd(line_part_t, window=5, percentile=99): + """ + Remove obvious outliers based on window comparison - pandas version + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + #line_part_t = line_part_t[2000:2100] + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + + # Create pandas Series and calculate rolling median + series = pd.Series(y) + medians = series.rolling(window=window, center=True, min_periods=1).median() + + # Calculate deviations + deviations = np.abs(series - medians) + + largest_deviations = deviations.nlargest(10) + #print(largest_deviations) + + # Create mask for good points + deviation_threshold = np.percentile(deviations, percentile) + good_points = deviations <= deviation_threshold + + # Convert back to numpy arrays for filtering + x = np.array(x) + y = np.array(y) + + # Return filtered data + return list(zip(x[good_points], y[good_points])) + +def CombineStripes(result_filename, stripes_files): + try: + # Open the first image to get the width and initialize the height + first_image = Image.open(stripes_files[0]) + width, height = first_image.size + + # Calculate the total height of the combined image + total_height = height * len(stripes_files) + + # Create a new blank image with the same width and the calculated height + result_image = Image.new('RGB', (width, total_height)) + + # Paste each image onto the result image vertically + y_offset = 0 + for file_name in stripes_files: + image = Image.open(file_name) + result_image.paste(image, (0, y_offset)) + y_offset += height + + # Save the result image + result_image.save(result_filename) + + # Return success flag + return True + + except Exception as e: + print("Error:", e) + return False + +def FindFirstLocalMinimum(counts, bins): + """ + Find the first local minimum in a histogram after the main peak and calculate its offset. + + Parameters: + hist: tuple of (counts, bin_edges) from np.histogram() + The histogram data to analyze + + Returns: + tuple: (TR, THR_OFFSET) + TR: float, the bin value (position) of the first local minimum after the main peak + THR_OFFSET: int, number of bins between the global maximum and the local minimum + """ + + # Find the main peak (global maximum) + main_peak_idx = np.argmax(counts) + + # Look for the first local minimum after the main peak + for i in range(main_peak_idx + 1, len(counts) - 1): + # Check if current point is less than or equal to both neighbors + if counts[i] <= counts[i-1] and counts[i] <= counts[i+1]: + # Calculate the bin center value for TR + TR = (bins[i] + bins[i+1]) / 2 + # Calculate offset from main peak in number of bins + THR_OFFSET = (bins[i] + bins[i+1]) / 2 - (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2 + return (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2, TR, THR_OFFSET + + # If no local minimum is found, return None for both values + return None, None + +def process_raw_data(data_tuples): + """ + Convert list of (timestamp, stationary, motion) tuples to separate arrays + + Parameters: + ----------- + data_tuples : list of tuples + Each tuple contains (datetime, stationary_value, motion_value) + + Returns: + -------- + timestamps : array of datetime + stationary : array of float + motion : array of float + """ + timestamps = np.array([t[0] for t in data_tuples]) + stationary = np.array([t[1] for t in data_tuples]) + motion = np.array([t[2] for t in data_tuples]) + + return timestamps, stationary, motion + +def rolling_std_fast(arr, window_size): + """ + Fast calculation of rolling standard deviation using NumPy's stride tricks. + + Parameters: + ----------- + arr : numpy array + Input array + window_size : int + Size of rolling window + + Returns: + -------- + numpy array + Rolling standard deviation + """ + # Compute rolling sum of squares + r = np.array(arr, dtype=float) + r2 = np.array(arr, dtype=float) ** 2 + + # Calculate cumulative sums + cum = np.cumsum(np.insert(r, 0, 0)) + cum2 = np.cumsum(np.insert(r2, 0, 0)) + + # Get rolling windows + x = (cum[window_size:] - cum[:-window_size]) + x2 = (cum2[window_size:] - cum2[:-window_size]) + + # Calculate standard deviation + n = window_size + return np.sqrt((x2/n) - (x/n) ** 2) + +def detect_presence(timestamps, stationary_signal, motion_signal, window_size=100, + motion_threshold=5, gmm_components=2): + """ + Detect presence using both stationary and motion signals with adaptive thresholding. + + Parameters: + ----------- + timestamps : array-like + Array of datetime objects + stationary_signal : array-like + Time series of stationary signal (0-100) + motion_signal : array-like + Time series of motion signal (0-100) + window_size : int + Size of rolling window for statistics (used only for temporal smoothing) + motion_threshold : float + Threshold for significant motion + gmm_components : int + Number of components for Gaussian Mixture Model + + Returns: + -------- + presence_mask : numpy array + Boolean array indicating presence + baseline : float + Computed baseline for stationary signal + threshold : float + Computed threshold for stationary signal + """ + + # Convert inputs to numpy arrays + stationary_signal = np.array(stationary_signal) + motion_signal = np.array(motion_signal) + + # 1. Fit Gaussian Mixture Model to stationary signal + gmm = GaussianMixture(n_components=gmm_components, random_state=42) + X = stationary_signal.reshape(-1, 1) + gmm.fit(X) + + # Get the component with lowest mean as baseline + baseline = min(gmm.means_)[0] + + # 2. Calculate adaptive threshold using GMM components + components_sorted = sorted(zip(gmm.means_.flatten(), gmm.covariances_.flatten())) + baseline_std = np.sqrt(components_sorted[0][1]) + threshold = baseline + 3 * baseline_std # 3 sigma rule + + # 3. Combine motion and stationary detection + presence_mask = np.zeros(len(stationary_signal), dtype=bool) + + # Vectorized operations instead of loop + presence_mask = (motion_signal > motion_threshold) | (stationary_signal > threshold) + + # 4. Apply temporal smoothing to reduce false transitions + smooth_window = min(window_size // 4, 10) # Smaller window for smoothing + presence_mask = np.convolve(presence_mask.astype(int), + np.ones(smooth_window)/smooth_window, + mode='same') > 0.5 + + return presence_mask, baseline, threshold + +def visualize_detection(timestamps, stationary_signal, motion_signal, presence_mask, + baseline, threshold, output_file='presence_detection.png'): + """ + Visualize the detection results and save to file. + + Parameters: + ----------- + timestamps : array-like + Array of datetime objects + stationary_signal : array-like + Time series of stationary signal + motion_signal : array-like + Time series of motion signal + presence_mask : array-like + Boolean array indicating presence + baseline : float + Computed baseline for stationary signal + threshold : float + Computed threshold for stationary signal + output_file : str + Path to save the output PNG file + """ + plt.figure(figsize=(15, 10)) + + # Configure time formatting + date_formatter = mdates.DateFormatter('%H:%M:%S') + + # Plot signals + plt.subplot(3, 1, 1) + plt.plot(timestamps, stationary_signal, label='Stationary Signal') + plt.axhline(y=baseline, color='g', linestyle='--', label='Baseline') + plt.axhline(y=threshold, color='r', linestyle='--', label='Threshold') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.legend() + plt.title('Stationary Signal with Baseline and Threshold') + plt.grid(True) + + plt.subplot(3, 1, 2) + plt.plot(timestamps, motion_signal, label='Motion Signal') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.legend() + plt.title('Motion Signal') + plt.grid(True) + + plt.subplot(3, 1, 3) + plt.plot(timestamps, presence_mask, label='Presence Detection') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.ylim(-0.1, 1.1) + plt.legend() + plt.title('Presence Detection Result') + plt.grid(True) + + plt.tight_layout() + + # Save to file and close figure to free memory + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.close() + +def FindZeroIntersection(counts, bins, save_plot, device_id): + """ + Find the zero intersection point by fitting a parabola to the descending slope + between 50% and 10% of the maximum peak height. Also returns the peak position. + + Parameters: + counts: array-like + The histogram counts + bins: array-like + The histogram bin edges + save_plot: str or None, optional + If provided, saves the visualization to the specified file path + + Returns: + tuple: (zero_intersections, peak_position) + zero_intersections: list of floats, x-coordinates where parabola intersects y=0 + peak_position: float, x-coordinate of the histogram maximum peak + """ + # Find the main peak + main_peak_idx = np.argmax(counts) + peak_height = counts[main_peak_idx] + peak_position = (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2 + + # Calculate 75% and 25% of peak height + height_50 = 0.50 * peak_height + height_10 = 0.10 * peak_height + + # Find indices where counts cross these thresholds after the peak + idx_50 = main_peak_idx + idx_10 = main_peak_idx + + for i in range(main_peak_idx, len(counts)): + if counts[i] <= height_50 and idx_50 == main_peak_idx: + idx_50 = i + if counts[i] <= height_10: + idx_10 = i + break + + # If we couldn't find valid points, return None + if idx_50 == main_peak_idx or idx_10 == main_peak_idx: + return None, peak_position + + # Get x and y coordinates for fitting + # Use bin centers for x coordinates + x_points = np.array([(bins[i] + bins[i+1])/2 for i in range(idx_50, idx_10+1)]) + y_points = counts[idx_50:idx_10+1] + + # Define quadratic function for fitting + def quadratic(x, a, b, c): + return a*x**2 + b*x + c + + try: + popt, pcov = curve_fit(quadratic, x_points, y_points) + a, b, c = popt + + # Find zeros using quadratic formula + if a != 0: + discriminant = b**2 - 4*a*c + if discriminant >= 0: + x1 = (-b + np.sqrt(discriminant)) / (2*a) + x2 = (-b - np.sqrt(discriminant)) / (2*a) + zero_intersections = sorted([x1, x2]) + # Filter zeros to only include those after the peak + zero_intersections = [x for x in zero_intersections if x > peak_position] + else: + zero_intersections = [] + else: + # If a ≈ 0, fallback to linear solution + if b != 0: + zero_intersections = [-c/b] + else: + zero_intersections = [] + + if save_plot: + plt.figure(figsize=(10, 6)) + + # Plot histogram + bin_centers = [(bins[i] + bins[i+1])/2 for i in range(len(counts))] + plt.bar(bin_centers, counts, width=bins[1]-bins[0], alpha=0.6, + color='skyblue', label='Histogram') + + # Plot peak height lines + plt.axhline(y=height_50, color='g', linestyle='--', alpha=0.5, + label='50% Peak Height') + plt.axhline(y=height_10, color='r', linestyle='--', alpha=0.5, + label='10% Peak Height') + + # Plot fitted parabola + x_fit = np.linspace(min(x_points), max(x_points), 100) + y_fit = quadratic(x_fit, a, b, c) + plt.plot(x_fit, y_fit, 'r-', label='Fitted Parabola') + + # Plot points used for fitting + plt.plot(x_points, y_points, 'ro', alpha=0.5, label='Fitting Points') + + # Plot zero intersections + for x_zero in zero_intersections: + plt.plot(x_zero, 0, 'ko', label='Zero Intersection') + + # Plot peak position + plt.axvline(x=peak_position, color='purple', linestyle='--', alpha=0.5, + label='Peak Position') + + # Add labels and legend + plt.xlabel('Bin Values') + plt.ylabel('Counts') + plt.title(f'Histogram Analysis with Parabolic Fit {device_id}') + plt.legend() + + # Show zero line + plt.axhline(y=0, color='k', linestyle='-', alpha=0.2) + + # Add text with intersection and peak values + text = f'Peak Position: {peak_position:.2f}\n' + if zero_intersections: + text += f'Zero Intersection(s): {", ".join([f"{x:.2f}" for x in zero_intersections])}' + plt.text(0.02, 0.98, text, transform=plt.gca().transAxes, + verticalalignment='top', + bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)) + + # Save plot to file + plt.savefig(save_plot, dpi=300, bbox_inches='tight') + plt.close() + + return zero_intersections, peak_position + except RuntimeError: + print("Warning: Failed to fit parabola") + return None, peak_position + + +def GeneratePresenceHistory(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + #maps_dates, proximity = GetDeploymentDatesBoth(deployment_id) + minutes = 1440 + stripes_files = [] + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + maps_dates.reverse() + days = len(maps_dates) + stretch_by = int(1000 / days) + if stretch_by > 50: + stretch_by = 50 + + #lets use 1000 pixels + #arr_stretched = np.zeros((int(days*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + result_image = Image.new('RGB', (minutes, int(days*stretch_by))) + + + # Paste each image onto the result image vertically + y_offset = 0 + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + + file_exists, time_modified_utc = check_file_exists(filename_day) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + file_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if file_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) + + + image_bytes, content_type = GetBlob(filename_day) + if image_bytes != None: + image_stream = io.BytesIO(image_bytes) + image = Image.open(image_stream) + + #image = Image.open(file_name) + result_image.paste(image, (0, y_offset)) + image.close() + image_stream.close() + + y_offset += stretch_by + + # Save directly to MinIO instead of local file + success = save_to_minio(result_image, filename, DAILY_MAPS_BUCKET_NAME) + # Clean up + result_image.close() + return success + +def AddText(room_image_cv2, x, y, room_name, font_size): + pil_im = Image.fromarray(room_image_cv2) + draw = ImageDraw.Draw(pil_im) + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + #print(f"Attempting to load font from: {font_path}") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error(f"Poppins font not found in {font_path}. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + draw.text((x, y), room_name, font=font, fill=(150, 150, 150)) # Black color in RGB + room_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + return room_image_cv2 + +def AddTextList(room_image_cv2, strings_list, font_size): + pil_im = Image.fromarray(room_image_cv2) + draw = ImageDraw.Draw(pil_im) + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error("Poppins font not found. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + + for x, y, room_name in strings_list: + draw.text((x, y), room_name, font=font, fill=(150, 150, 150)) # Black color in RGB + + room_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + return room_image_cv2 + + +def AddRoomData(room_image, room_name, data): + + # Example usage: + radius = 10 + color_t = data["color"] # BGR format for red + color = (color_t[2], color_t[1], color_t[0]) + x_offset = 12 + + room_image = AddText(room_image, 13, 20, room_name, 50) + print(data) + for present in data["presence"]: + device_id, minute, duration = present + #duration = 10 + top_left = (x_offset + minute, 140) #bottom_right = (300, 200) + bottom_right = (x_offset + minute + duration, 260) + draw_rounded_rectangle(room_image, top_left, bottom_right, radius, color) + return room_image + +def AddFooterData(image): + '12, 370, 736, 1092, 1452' + step_size = 1440 / 4 + string_width = 60 + offset = 12 + yoffset = 30 + step = 0 + font_size = 40 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "3 AM", font_size) + step = 1 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "9 AM", font_size) + step = 2 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "3 PM", font_size) + step = 3 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "9 PM", font_size) + return image + +def draw_rounded_rectangle(image, top_left, bottom_right, radius, color): + """ + Draw a filled rectangle with rounded corners, using simple rectangle for small dimensions + :param image: Image to draw on + :param top_left: Top-left corner coordinates (x, y) + :param bottom_right: Bottom-right corner coordinates (x, y) + :param radius: Desired corner radius (will be adjusted if needed) + :param color: Rectangle color in BGR format + """ + x1, y1 = top_left + x2, y2 = bottom_right + + # Calculate width + width = x2 - x1 + + + # Adjust radius if width or height is too small + # Maximum radius should be half of the smaller dimension + max_radius = abs(width) // 2 + radius = min(radius, max_radius) + + # If width is too small, fallback to regular rectangle + if width <= 4 or radius <= 1: + cv2.rectangle(image, top_left, bottom_right, color, -1) + return + + # Adjust radius if needed + radius = min(radius, width // 2) + + # Create points for the main rectangle + pts = np.array([ + [x1 + radius, y1], + [x2 - radius, y1], + [x2, y1 + radius], + [x2, y2 - radius], + [x2 - radius, y2], + [x1 + radius, y2], + [x1, y2 - radius], + [x1, y1 + radius] + ], np.int32) + + # Fill the main shape + cv2.fillPoly(image, [pts], color) + + # Fill the corners + cv2.ellipse(image, (x1 + radius, y1 + radius), (radius, radius), 180, 0, 90, color, -1) + cv2.ellipse(image, (x2 - radius, y1 + radius), (radius, radius), 270, 0, 90, color, -1) + cv2.ellipse(image, (x1 + radius, y2 - radius), (radius, radius), 90, 0, 90, color, -1) + cv2.ellipse(image, (x2 - radius, y2 - radius), (radius, radius), 0, 0, 90, color, -1) + +def filter_device(locations_list, device_id): + result = [] + for entry in locations_list: + if entry[0] == device_id: + result.append(entry) + + return result + +def GenerateLocationsMap(date_st, devices_list, devices_map, locations_list, time_zone_s): + + devices_list_t = [("date",date_st)] + + + for mac in devices_list: + well_id, device_id, room = devices_map[mac] + #room = devices[well_id][0] + if room in Loc2Color: + color = Loc2Color[room][0] + else: + color = Loc2Color[room.split()[0]][0] + presence_data = filter_device(locations_list, device_id) + room_details = (room, {"color": color, "presence": presence_data}) + devices_list_t.append(room_details) + + well_id = 0 + device_id = 0 + room = "Outside/?" + color = (0, 0, 0) + + #lets's not draw future unknown! + presence_data = filter_device(locations_list, device_id) + current_utc = datetime.datetime.now(pytz.UTC) + current_date_local = current_utc.astimezone(pytz.timezone(time_zone_s)) + current_minute_of_day = current_date_local.hour * 60 + current_date_local.minute + + if date_st == current_date_local.strftime('%Y-%m-%d'): + filtered_presence_data = [] + for entry in presence_data: + if entry[1] < current_minute_of_day : + if entry[1] + entry[2] < current_minute_of_day: + filtered_presence_data.append(entry) + else: + entry[2] = (current_minute_of_day - entry[2]) + if entry[2] > 0: + filtered_presence_data.append(entry) + #print(presence_data) + else: + filtered_presence_data = presence_data + + room_details = (room, {"color": color, "presence": filtered_presence_data}) + devices_list_t.append(room_details) + + return devices_list_t + +def CreateDailyLocationChart(filename_chart_image_day, locations): + result = False + header_image_file = "header.png" + room_image_file = "room.png" + footer_image_file = "footer.png" + + #ToDo: change it so it reads files from MinIo + header_image_file = os.path.join(filesDir, header_image_file) + header_image_file = header_image_file.replace("\\","/") + header_image = cv2.imread(header_image_file) + #header_height, header_width = header_image.shape[:2] + + room_image_file = os.path.join(filesDir, room_image_file) + room_image_file = room_image_file.replace("\\","/") + room_image = cv2.imread(room_image_file) + #room_height, room_width = room_image.shape[:2] + + footer_image_file = os.path.join(filesDir, footer_image_file) + footer_image_file = footer_image_file.replace("\\","/") + footer_image = cv2.imread(footer_image_file) + + all_images = [header_image] + for item_c in locations: + item = item_c[0] + if item == "date": + date = item_c[1] + else: + room_image = cv2.imread(room_image_file) + data = item_c[1] + room_image = AddRoomData(room_image, item, data) + all_images.append(room_image) + + footer_image = AddFooterData(footer_image) + all_images.append(footer_image) + final_image = np.vstack(all_images) + #this needs to write straight to MinIo ! + SaveImageInBlob(filename_chart_image_day, final_image) + result = True + #cv2.imwrite(filename_chart_image_day, final_image) + #print(rooms_count) + return result + + +def GetOptimumFontSize(target_width, text="00", min_size=1, max_size=100, tolerance=1): + """ + Find optimal font size to fit text within target width using binary search. + + Args: + target_width (int): Desired width in pixels + text (str): Text to measure (default "00") + min_size (int): Minimum font size to try + max_size (int): Maximum font size to try + tolerance (int): Acceptable difference from target width + + Returns: + int: Optimal font size + """ + while min_size <= max_size: + current_size = (min_size + max_size) // 2 + width, _ = GetStringSize(text, current_size) + + if abs(width - target_width) <= tolerance: + return current_size + elif width > target_width: + max_size = current_size - 1 + else: + min_size = current_size + 1 + + # Return the largest size that fits within target width + width, _ = GetStringSize(text, min_size) + return min_size if width <= target_width else min_size - 1 + +def GetStringSize(some_string, font_size): + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error("Poppins font not found. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + + bbox = font.getbbox(some_string) + return bbox[2] - bbox[0], bbox[3] - bbox[1] + +def GeneratePresenceHistoryChart(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + #maps_dates, proximity = GetDeploymentDatesBoth(deployment_id) + minutes = 1440 + stripes_files = [] + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + #maps_dates.reverse() + days = len(maps_dates) + #stretch_by = int(1000 / days) + #if stretch_by > 50: + #stretch_by = 50 + stretch_by = 30 + + #background_image_file = os.path.join(filesDir, "multi_day_template.png") + background_image_file = os.path.join(filesDir, "multi_day_template2.png") + background_image_file = background_image_file.replace("\\","/") + background_image = cv2.imread(background_image_file) + + rgb_image = background_image #cv2.cvtColor(background_image, cv2.COLOR_BGR2RGB) + result_image = Image.fromarray(rgb_image) # Convert to PIL Image + #result_image = Image.new('RGB', (minutes, int(days*stretch_by))) + + # Paste each image onto the result image vertically + y_offset = 0 + locations_list = [] + font_size = 50 + string_width, string_height = GetStringSize("00", font_size) + + + success = False + if len(maps_dates) == 1: + + filename_chart_image_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations_chart.png" + force_recreate = recreate_in + #it is faster to resize existing daily location chart (length is always 1440), than having to re-create it each time... + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + filename_chart_data_day = filename_day+".bin" + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_chart_image_day) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = ddate + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + if not force_recreate: + file_exists1, time_modified_utc1 = check_file_exists(filename_chart_data_day) + if file_exists1: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = ddate + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list_a, device_ids = GetProximityList(deployment_id, timee) + CreateDailyLocationMap(filename_day, devices_list_a, ddate, filter_minutes, time_zone_s, stretch_by) + locations_list_s = ReadObjectMinIO("daily-maps", filename_chart_data_day) + locations_list = json.loads(locations_list_s) + + devices_map = {} + devices_list = [] + for device_entry in devices_list_a: + #if T: + if device_entry[3] == None or device_entry[3].strip() == "": + devices_map[device_entry[4]] = [device_entry[0], device_entry[1], device_entry[2]] + else: + devices_map[device_entry[4]] = [device_entry[0], device_entry[1], device_entry[2] + " " + device_entry[3]] + devices_list.append(device_entry[4]) + + locations = GenerateLocationsMap(ddate, devices_list, devices_map, locations_list, time_zone_s) + success = CreateDailyLocationChart(filename, locations) + else: + + day_counter = 0 + day_step_width = int(1780 / days) + x_offset = 563 + y_offset = 1615 + h_labels_bottom = 1720 + day_width = int(0.9 * day_step_width) + day_height = 1440 + font_size = GetOptimumFontSize(day_width, "00", 10, 50, 0) + string_width, string_height = GetStringSize("00", font_size) + #logger.debug(f"font_size={font_size} string_width={string_width}") + + y_offset = y_offset - day_height + filename_chart_image_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations_chart.png" + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_day) + file_existsS, time_modifiedS_utc = check_file_exists(filename_day[:-4]+"S.png") + if file_exists and file_existsS: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) + + #here we need to rotate and resize to: + + image_bytes, content_type = GetBlob(filename_day) + image_bytes_s, content_type_s = GetBlob(filename_day[:-4]+"S.png") + if image_bytes != None: + image_stream = io.BytesIO(image_bytes) + image = Image.open(image_stream) + numpy_image = np.array(image) + rotated_image = cv2.rotate(numpy_image, cv2.ROTATE_90_COUNTERCLOCKWISE) + scaled_image = cv2.resize(rotated_image, (day_width, day_height), interpolation=cv2.INTER_AREA) + + # Convert from BGR to RGB + rgb_image = cv2.cvtColor(scaled_image, cv2.COLOR_BGR2RGB) + # Convert to PIL Image + pil_image = Image.fromarray(rgb_image) + + #image = Image.open(file_name) + x_origin = x_offset + day_step_width * day_counter + int(0.05 * day_step_width) + result_image.paste(pil_image, (x_origin, y_offset)) + + image_stream = io.BytesIO(image_bytes_s) + image = Image.open(image_stream) + numpy_image = np.array(image) + rotated_image = cv2.rotate(numpy_image, cv2.ROTATE_90_COUNTERCLOCKWISE) + scaled_image = cv2.resize(rotated_image, (day_width, day_height), interpolation=cv2.INTER_AREA) + + # Convert from BGR to RGB + rgb_image = cv2.cvtColor(scaled_image, cv2.COLOR_BGR2RGB) + # Convert to PIL Image + pil_image = Image.fromarray(rgb_image) + + #image = Image.open(file_name) + x_origin = x_offset + day_step_width * day_counter + int(0.05 * day_step_width) + result_image.paste(pil_image, (x_origin, 1807+y_offset)) + + + image.close() + image_stream.close() + + day_counter += 1 + + pil_im = result_image + #result_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + result_image_cv2 = np.array(pil_im)#cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + strings_list = [] + day_counter = 0 + for ddate in maps_dates: + if string_width <= day_width: + date_str = ddate[8:10] + x_origin = x_offset + int(day_step_width * (day_counter + 0.5)) - int(string_width / 2) + strings_list.append((x_origin, h_labels_bottom, date_str)) + day_counter += 1 + result_image_cv2 = AddTextList(result_image_cv2, strings_list, font_size) + + + #Y 124 to 1636 + labels_bottom = 1636 - 1.5 * string_height + x_offset = 340 + step = -4 * 60 #4 hours + font_size = 50 + strings_list = [] + count = 0 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 AM")) + count = 1 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "4 AM")) + count = 2 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "8 AM")) + count = 3 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 PM")) + count = 4 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "4 PM")) + count = 5 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "8 PM")) + count = 6 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 AM")) + + result_image_cv2 = AddTextList(result_image_cv2, strings_list, font_size) + + numpy_image = np.array(result_image_cv2) + + success = SaveImageInBlob(filename, numpy_image) + #SaveImageInBlob(filename, result_image) + # Save directly to MinIO instead of local file + #if success: + # success = save_to_minio(result_image, filename, DAILY_MAPS_BUCKET_NAME) + # Clean up + if success: + return filename + else: + return "" + +def GeneratePresenceHistoryFiles(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + stretch_by = 30 + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + + day_counter = 0 + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_day+".bin") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, 10) + + day_counter += 1 + + return filename + +def CalcStdevs(row, stdev_range, stdevs): + half_range = stdev_range // 2 + data_len = len(row) + + # Calculate standard deviations with proper window alignment + for i in range(data_len): + # Calculate window boundaries + start = max(0, i - half_range) + end = min(data_len, i + half_range + 1) + + # Get data within window + window_data = row[start:end] + + # Calculate standard deviation if we have data + if len(window_data) > 0: + stdevs[i] = np.std(window_data) + + # Find amplitude (max - min of standard deviations) + amplitude = np.max(stdevs) - np.min(stdevs) + + # Scale to range 0-1279 + if amplitude > 0: # Avoid division by zero + stdevs = ((stdevs - np.min(stdevs)) / amplitude * 1279).astype(np.float32) + + return stdevs, amplitude + +def CalcLife(row, stdev_range, stdevs): + half_range = stdev_range // 2 + data_len = len(row) + + # Calculate standard deviations with proper window alignment + for i in range(data_len): + # Calculate window boundaries + start = max(0, i - half_range) + end = min(data_len, i + half_range + 1) + + # Get data within window + window_data = row[start:end] + + # Calculate standard deviation if we have data + if len(window_data) > 0: + stdevs[i] = np.std(window_data) + + # Find amplitude (max - min of standard deviations) + amplitude = np.max(stdevs) - np.min(stdevs) + + # Scale to range 0-1279 + if amplitude > 0: # Avoid division by zero + stdevs = ((stdevs - np.min(stdevs)) / amplitude * 1279).astype(np.float32) + + return stdevs, amplitude + +def FindCalibrationDate(device_ids, ddate): + PCD = 50 #% (Peak Contained Data %) + PHB = 50 #% (Peak Height from Base %) + MPW = 10 #? (Max Peak Width) + MPSD =10 #? (Minimum Presence signal Standard Deviation) + #Find first day with, for all devices: + #- enough radar data points collected + #-Single histogram peak containing more than PCD% of data and peak width (at PHB% height) is < MPW + #Stdev of Data larger > MPSD + return ddate + +def FindThreshold(data, percent_list): + """ + Find the threshold value above which lies the specified percentage of points. + + Args: + data: numpy array of values + percent: percentage of points that should be above the threshold (0-100) + + Returns: + threshold value + """ + percent_from, percent_to = percent_list + # Sort data in descending order + sorted_data = np.sort(data)[::-1] + + # Calculate the index corresponding to the desired percentage + index_from = int((percent_from / 100) * len(data)) + index_to = int((percent_to / 100) * len(data)) + + # Return the threshold value + return sorted_data[index_from], sorted_data[index_to] + +def ShowThresholdGraph(data, filename, threshold_low, threshold_high, title, AveragePercentSpendsThere, location): + """ + Create and save a threshold analysis graph with maximum curvature point. + """ + dpi=600 + # Get min and max values + min_val = np.min(data) + max_val = np.max(data) + + # Create 1000 threshold levels from max to min + thresholds = np.linspace(max_val, min_val, 1000) + threshold_percentages = np.linspace(0, 100, 1000) + + # Calculate percentage of points above each threshold + points_above = [] + total_points = len(data) + + for thresh in thresholds: + above_count = np.sum(data > thresh) + percentage = (above_count / total_points) * 100 + points_above.append(percentage) + + points_above = np.array(points_above) + + # Calculate derivatives and smooth them + first_derivative = np.gradient(points_above) + second_derivative = np.gradient(first_derivative) + + #first_derivative = savgol_filter(np.gradient(points_above), window_length=51, polyorder=3) + #second_derivative = savgol_filter(np.gradient(first_derivative), window_length=51, polyorder=3) + + + # Find the point of maximum absolute second derivative + # Exclude edges (first and last 5% of points) to avoid edge effects + edge_margin = len(second_derivative) // 20 # 5% of points + valid_range = slice(edge_margin, -edge_margin) + max_curve_idx = edge_margin + np.argmax(np.abs(second_derivative[valid_range])) + + max_curve_x = threshold_percentages[max_curve_idx] + max_curve_y = points_above[max_curve_idx] + max_curve_second_deriv = second_derivative[max_curve_idx] + + # Calculate the actual threshold value for this point + threshold2 = max_val - (max_curve_x/100) * (max_val - min_val) + + # Create subplot figure + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[3, 2]) + + # Plot 1: Original curve with thresholds + ax1.plot(threshold_percentages, points_above, 'b-', linewidth=2, label='Distribution') + ax1.grid(True, linestyle='--', alpha=0.7) + + # Add original threshold line if provided + if threshold_low is not None: + threshold_percent = ((max_val - threshold_low) / (max_val - min_val)) * 100 + percent_above = (np.sum(data > threshold_low) / total_points) * 100 + + ax1.axvline(x=threshold_percent, color='r', linestyle='--', label=f'Minimum % spent in {location}: {AveragePercentSpendsThere[0]:.3f}') + ax1.axhline(y=percent_above, color='r', linestyle='--') + + #ax1.annotate(f'Threshold 1: {threshold_low:.3f}\nPoints above: {percent_above:.1f}%', + #xy=(threshold_percent, percent_above), + #xytext=(10, 10), textcoords='offset points', + #bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5), + #arrowprops=dict(arrowstyle='->')) + + if threshold_high is not None: + threshold_percent = ((max_val - threshold_high) / (max_val - min_val)) * 100 + percent_above = (np.sum(data > threshold_high) / total_points) * 100 + + ax1.axvline(x=threshold_percent, color='b', linestyle='--', label=f'Maximum % spent in {location}: {AveragePercentSpendsThere[1]:.3f}') + ax1.axhline(y=percent_above, color='b', linestyle='--') + + #ax1.annotate(f'Threshold 1: {threshold_high:.3f}\nPoints above: {percent_above:.1f}%', + #xy=(threshold_percent, percent_above), + #xytext=(10, 10), textcoords='offset points', + #bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5), + #arrowprops=dict(arrowstyle='->')) + + # Add maximum curvature point threshold + ax1.axvline(x=max_curve_x, color='g', linestyle='--', label=f'Threshold 2: {threshold2:.3f}') + ax1.axhline(y=max_curve_y, color='g', linestyle='--') + ax1.plot(max_curve_x, max_curve_y, 'go', markersize=10) + + ax1.annotate(f'Threshold 2: {threshold2:.3f}\nPoints above: {max_curve_y:.1f}%', + xy=(max_curve_x, max_curve_y), + xytext=(10, -20), textcoords='offset points', + bbox=dict(boxstyle='round,pad=0.5', fc='lightgreen', alpha=0.5), + arrowprops=dict(arrowstyle='->')) + + ax1.set_xlabel('Threshold Level (%)\n0% = Maximum, 100% = Minimum') + ax1.set_ylabel('Points Above Threshold (%)') + ax1.set_title(title) + ax1.set_xlim(0, 100) + ax1.set_ylim(0, 100) + ax1.legend() + + # Plot 2: Rate of change + ax2.plot(threshold_percentages, first_derivative, 'g-', label='First derivative', alpha=0.7) + ax2.plot(threshold_percentages, second_derivative, 'r-', label='Second derivative', alpha=0.7) + ax2.grid(True, linestyle='--', alpha=0.7) + + # Mark maximum curvature point on derivative plot + ax2.axvline(x=max_curve_x, color='g', linestyle='--') + # Plot point exactly on the second derivative curve + ax2.plot(max_curve_x, max_curve_second_deriv, 'go', markersize=10, + label=f'Max curvature at {max_curve_x:.1f}%') + + ax2.set_xlabel('Threshold Level (%)') + ax2.set_ylabel('Rate of Change') + ax2.set_title('Rate of Change Analysis') + ax2.legend() + + plt.tight_layout() + plt.savefig(filename, dpi=dpi, bbox_inches='tight') + plt.close() + + return threshold2, max_curve_x, max_curve_y + +def add_boundary_points(line_part_t, time_zone): + """ + Add boundary points (00:00:00 and 23:59:59) to a time series list. + + Args: + line_part_t: List of tuples (timestamp, value) + time_zone: String representing the timezone (e.g., "America/Los_Angeles") + + Returns: + List of tuples with added boundary points + """ + if not line_part_t: + return line_part_t + + tz = pytz.timezone(time_zone) + + # Get the date from the first point + first_dt = datetime.datetime.fromtimestamp(line_part_t[0][0], tz) + date = first_dt.date() + + last_dt = datetime.datetime.fromtimestamp(line_part_t[-1][0], tz) + last_date = last_dt.date() + + + # Create datetime objects for start and end of the day + start_dt = tz.localize(datetime.datetime.combine(date, datetime.datetime.min.time())) + end_dt = tz.localize(datetime.datetime.combine(last_date, datetime.datetime.max.time())) + + # Convert to timestamps + start_ts = start_dt.timestamp() + end_ts = end_dt.timestamp() + + result = list(line_part_t) + + # Handle start point (00:00:00) + first_point_dt = datetime.datetime.fromtimestamp(line_part_t[0][0], tz) + time_diff = first_point_dt - start_dt + + start_value = line_part_t[0][1] + + # Add start point at the beginning + #result.insert(0, (start_ts, start_value)) + + # Handle end point (23:59:59) + last_point_dt = datetime.datetime.fromtimestamp(line_part_t[-1][0], tz) + end_value = line_part_t[-1][1] + # Add end point + result.append((end_ts, end_value)) + + return result + +def calculate_life_and_average(my_data1, stdev_range=5): + # Convert data to numpy array for faster operations + data_array = np.array(my_data1) + + # Calculate half range + stdev_range_h = stdev_range // 2 + + # Pre-calculate indices for the sliding window + indices = np.arange(len(data_array) - 2 * stdev_range_h)[:, None] + np.arange(2 * stdev_range_h + 1) + + # Get sliding windows of data + windows = data_array[indices] + + # Calculate average (using column 3) + average = np.mean(windows[:, :, 3], axis=1) + + # Calculate life (using columns 2, 3, and 4) + deltas = windows[:, :, 3] - windows[:, :, 2] + windows[:, :, 4] + life = np.mean(deltas, axis=1) + + return life.tolist(), average.tolist() + +def TryJulia(prompt): + + if len(prompt) > 0: + + if prompt[0] == "#": + return prompt.upper() + + if prompt not in utterances: + return "" + else: + intent = utterances[prompt] + action = intents[intent] + + return action[0] + else: + return "" + +def AskGPT(in_prompt, language_from, language_to): + + if len(in_prompt) > 4: + + prompt = in_prompt.lower() + + if language_to.lower() not in language_from.lower(): + prompt = in_prompt + " Answer in " + language_to + + + print(prompt) + + #lets see if question is looking for OSM query + pattern = "what is only the node line for query for * on openstreetmap api? do not answer with url to nominatim, but with query!" + + + if match_with_wildcard(prompt, pattern): + differing_part = extract_differing_part(prompt, pattern) + + if differing_part != "": + + print(differing_part) + + if differing_part in searches_dict: + response = searches_dict[differing_part] + print(response) + return response, language_to + else: + #check if one of synonims: + if differing_part in searches_dict["synonims"]: + differing_part = searches_dict["synonims"][differing_part] + if differing_part != "": + if differing_part in searches_dict[differing_part]: + response = searches_dict[differing_part] + print(response) + return response, language_to + + hash_string = hashlib.sha256(str(prompt).encode('utf-8')).hexdigest() + #filename=os.path.join(cache_path, "chgpt_query_" + hash_string+".pkl") + + julia_present = False + if prompt.startswith("julia"): + prompt = prompt[len("julia") + 1:] + julia_present = True + + completion = "" + if julia_present == False: + completion = TryJulia(prompt) + #if completion == "": + # if os.path.exists(filename): + # #completion = pickle.load(open( filename, "rb" )) + # completion = (completion.choices[0].message.content.strip(), language_to)[0] + else: + completion = TryJulia(prompt) + + + if completion == "": + + st = time.time() + #import wandb + + #run = wandb.init(project='GPT-4 in Python') + #prediction_table = wandb.Table(columns=["prompt", "prompt tokens", "completion", "completion tokens", "model", "total tokens"]) + + print(time.time() - st) + + openai.api_key = OPENAI_API_KEY + client = OpenAI( + # This is the default and can be omitted + api_key = OPENAI_API_KEY + ) + + completion = client.chat.completions.create( + messages=[ + { + "role": "user", + "content": prompt, + } + ], + model="gpt-3.5-turbo", + ) + + + #with open(filename, 'wb') as handle: + #pickle.dump(completion, handle, protocol=pickle.HIGHEST_PROTOCOL) + + response = (completion.choices[0].message.content.strip(), language_to) + else: + response = (completion, language_to) + + else: + response = ("question is too short", language_to) + + + print(response) + return response + +def AskGPTPure(in_prompt): + + if len(in_prompt) > 4: + + prompt = in_prompt.lower() + + print(prompt) + + st = time.time() + + print(time.time() - st) + + openai.api_key = OPENAI_API_KEY + client = OpenAI( + # This is the default and can be omitted + api_key = OPENAI_API_KEY + ) + + completion = client.chat.completions.create( + messages=[ + { + "role": "user", + "content": prompt, + } + ], + model="gpt-3.5-turbo", + ) + + response = completion.choices[0].message.content.strip() + + + else: + response = "question is too short" + + + print(response) + return response + +def get_last_n_days(n=14, timezone_str='America/Los_Angeles'): + # Get current UTC time + utc_now = datetime.datetime.now(pytz.UTC) + + # Convert to the specified timezone + local_now = utc_now.astimezone(pytz.timezone(timezone_str)) + + # Get the current date in the specified timezone + current_date = local_now.date() + + # Determine the last whole day + if local_now.hour > 0 or local_now.minute > 0 or local_now.second > 0: + # Yesterday in the specified timezone + last_whole_day = current_date - timedelta(days=1) + else: + # If it's exactly midnight, the last whole day is two days ago + last_whole_day = current_date - timedelta(days=2) + + # Generate list of n days, ending with the last whole day + date_list = [] + for i in range(n-1, -1, -1): + day = last_whole_day - timedelta(days=i) + date_list.append(day.strftime('%Y-%m-%d')) + + return date_list + + +def numpy_to_json(arr, devices_list): + """ + Convert numpy array to JSON-serializable format + + Args: + arr (numpy.ndarray): 2D numpy array to serialize + + Returns: + str: JSON string containing array data and metadata + """ + if not isinstance(arr, np.ndarray): + raise TypeError("Input must be a numpy array") + + array_dict = { + 'dtype': str(arr.dtype), + 'shape': arr.shape, + 'devices_list': devices_list, + 'data': arr.tolist() # Convert to nested Python lists + } + + return json.dumps(array_dict) + +def format_time_difference(minutes): + # Calculate days, hours, minutes + days = int(minutes // (24 * 60)) + remaining_minutes = minutes % (24 * 60) + hours = int(remaining_minutes // 60) + mins = int(remaining_minutes % 60) + + parts = [] + + # Add days if any + if days > 0: + parts.append(f"{days} day{'s' if days != 1 else ''}") + + # Add hours if any + if hours > 0: + parts.append(f"{hours} hour{'s' if hours != 1 else ''}") + + # Add minutes if any + if mins > 0 or (days == 0 and hours == 0): + parts.append(f"{mins} minute{'s' if mins != 1 else ''}") + + # Combine the parts into a sentence + if len(parts) == 1: + return parts[0] + elif len(parts) == 2: + return f"{parts[0]} and {parts[1]}" + else: + return f"{parts[0]}, {parts[1]}, and {parts[2]}" + +def RunCommand(commmand, args_dictionary, deployment_id): + + to_return = "" + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + local_tz = pytz.timezone(time_zone_s) + + filter_minutes = 5 + dates = get_last_n_days(28, time_zone_s) + ddate = dates[0] #2025-02-02 req.params.get("date") + to_date = dates[-1] + date_s = datetime.datetime.now(pytz.UTC).astimezone(local_tz).date().strftime("%Y-%m-%d") + + if commmand == "#STATUS#": + force_recreate_orig = False #True + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{to_date}_{filter_minutes}_history_image.png" + filename = GeneratePresenceHistoryFiles(filename, force_recreate_orig, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + stretch_by = 30 + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + + day_counter = 0 + minutes_spent_there_list = [] + minutes_locations_list = [] + filename_4w = f"/{deployment_id}/{deployment_id}_{maps_dates[0]}_{maps_dates[-1]}_{filter_minutes}_{stretch_by}_4w_locations.png.bin" + for ddate in maps_dates: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + Id2Location = {} + for device in devices_list: + Id2Location[device[1]] = device[2] + Id2Location[0] = "Outside/?" + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png.bin" + locations_list_s = ReadObjectMinIO("daily-maps", filename_day) + locations_list = ast.literal_eval(locations_list_s) + minutes_locations_list.append((ddate, locations_list)) + #print(locations_list_s) + minutes_spent_there = {} + + + for loc in Id2Location: + minutes_spent_there[Id2Location[loc]] = 0 + minutes_spent_there[Id2Location[0]] = 0 + + for loc in locations_list: + #print(loc[0]) + #if loc[0] == 559: + # print("Stop") + if loc[0] in Id2Location: + print(Id2Location[loc[0]]) + minutes_spent_there[Id2Location[loc[0]]] += loc[2] + + for loc in minutes_spent_there: + minutes_spent_there[loc] = int(1000 * minutes_spent_there[loc] / 1440) / 10 + minutes_spent_there_list.append((ddate, minutes_spent_there)) + data_part = str(minutes_spent_there_list) + minutes_locations_list_str = str(minutes_locations_list) + obj_to_save = {"Location_indexes": str(Id2Location), "Locations": minutes_locations_list_str} + print(obj_to_save) + #SaveObjectInBlob(filename_4w, obj_to_save) + #print(data_part) + + #prompt = "Attached is 4 weeks of data representing % of time where person living alone is spending each day" + #prompt = prompt + " Assess his last week compared to previous 3 weeks. Comment only on significant changes." + #prompt = prompt + " Ignore days where data is all (or mostly) 0!" + #prompt = prompt + " Consider that office and living room are equivalent for this individual. Entertainment is consumed on computer (office) and in living room TV." + #prompt = prompt + " But he is also napping in living room. Comment on his sleeping pattern as well" + #prompt = prompt + " Can you summarize all in 1 sentence?" + #prompt = prompt + " " + data_part + #result = AskGPTPure(prompt) + + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + #current_time = datetime.datetime.now() + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + before_last_location = details["before_last_location"] + last_present_duration = details["last_present_duration"] + detected_time = datetime.datetime.fromisoformat(details["last_detected_time"]) + local_time = local_tz.localize(detected_time) + + result = f"There are no significant changes in his routines. He slept {details['sleep_hours']} hours last night and woke 1 time for bathroom. He is in the {last_location} for last {last_present_duration} minutes, and I can smell coffee in the kitchen" + to_return = result + #to_return = "Your father appears to be fine. He was walking around the house 10 minutes ago and is currently in the living room. And I can smell coffee" + + elif commmand == "#STATUS_F#": + to_return = "Your mother is doing well. She slept 8hr and 23min last night. She used the restroom twice last night. She is now in the kitchen. I can smell coffee." + elif commmand == "#HELP#": + to_return = "There is number of things you can ask me about. For example: 'how is my dad doing?' Or 'How is his environment' or any other question you like" + elif commmand == "#SLEEP#": + to_return = "Your dad slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#SLEEP_F#": + to_return = "Your mom slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#ENVIRONMENT#": + to_return = "The temperature in the house is 23 degrees Celsius, CO2 level is 662 ppm, and I can smell coffee brewing. Your dad slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#WEEK#": + to_return = "Showing his weekly activity" + elif commmand == "#WEEK_F#": + to_return = "Showing her weekly activity" + elif commmand == "#ACTIVITY#": + to_return = "Your dad has been less active this week than usual. He spent more time sitting in the living room and he got up later than usual by 38min. He also did not go outside as frequently and had less visitors. He only showered once this week." + elif commmand == "#ACTIVITY_F#": + to_return = "Your mom has been less active this week than usual. She spent more time sitting in the living room and she got up later than usual by 38min. She also did not go outside as frequently and had less visitors. She only showered once this week." + elif commmand == "#ACTIVITY_COMPARE#": + to_return = "Overall your dad is less active this year compared to last year. He slept longer in the mornings and had less visitors. Also his shower activity is reduced from typically 2 times a week to once a week." + elif commmand == "#ACTIVITY_COMPARE_F#": + to_return = "Overall your mom is less active this year compared to last year. She slept longer in the mornings and had less visitors. Also her shower activity is reduced from typically 2 times a week to once a week." + elif commmand == "#LOCATION#": + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + #current_time = datetime.datetime.now() + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + before_last_location = details["before_last_location"] + last_present_duration = details["last_present_duration"] + detected_time = datetime.datetime.fromisoformat(details["last_detected_time"]) + local_time = local_tz.localize(detected_time) + # Convert to UTC + detected_utc_time = local_time.astimezone(pytz.UTC) + + time_diff = current_time - detected_utc_time + minutes = time_diff.total_seconds() / 60 + #patch... needs investigating todo + if minutes > 1400: + minutes = 0 + time_sentence = format_time_difference(minutes) + if minutes < 2: + to_return = f"He is now in the {last_location} for {last_present_duration} minutes. Before that he was in {before_last_location}" + else: + to_return = f"He was last detected in the {last_location} {time_sentence} ago" + elif commmand == "#SHOWER#": + to_return = "In the last 7 days, your Dad took a shower on Friday, Sunday and Tuesday" + elif commmand == "#SHOWER_F#": + to_return = "The last time your mom took a shower was Yesterda at 9:33AM" + elif commmand == "#BATHROOM#": + to_return = "Last night your Dad used the restroom only once at 6.10am" + elif commmand == "#KITCHEN#": + to_return = "Your Dad only cooked Dinner on Wednesday and he turned off the stove afterwards" + elif commmand == "#MOLD#": + to_return = "I cannot smell any mold. Also, the humidity is very low. In any of the rooms never exceeded 27% RH in the last 7 days." + elif commmand == "#VISITORS#": + to_return = "Yes, on Tuesday, I could detect motion in both office and kitchen at the same time and CO2 levels in the living room exceeded 900ppm." + elif commmand == "#TEMPERATURE#": + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + temperature = int(details["temperature"]) + if "America" in time_zone_s: + temperature_sentence = f"{int(CelsiusToFahrenheit(temperature))} degrees Farenhight" + else: + temperature_sentence = f"{temperature} degrees Celsius." + to_return = f"The temperature in the {last_location} is {temperature_sentence}." + elif commmand == "#TEMPERATURE_B#": + to_return = "The temperature in the main bathroom is 80 degrees Farenhight." + elif commmand == "#OXYGEN#": + to_return = "His last oxygen level was at 95%." + elif commmand == "#OXYGEN_F#": + to_return = "Her last oxygen level was at 95%." + elif commmand == "#HEART_RATE#": + to_return = "His last heart rate was 74 bpm." + elif commmand == "#BLOOD_PRESSURE#": + to_return = "His latest blood pressure was measured 5 hours ago and it was 137 over 83." + elif commmand == "#BLOOD_PRESSURE_F#": + to_return = "Her latest blood pressure was measured 5 hours ago and it was 137 over 83." + elif commmand == "#EKG#": + to_return = "His latest HeartBeam EKG was done on Monday and it was within his baseline!" + elif commmand == "#EKG_F#": + to_return = "Her latest HeartBeam EKG was done on Monday and it was within her baseline!" + return to_return + +def ScaleToCommon(data, sensor): + + if sensor == "temperature": + new_min = 0 + new_max = 100 + elif sensor == "humidity": + new_min = 100 + new_max = 200 + elif sensor == "light": + new_min = 200 + new_max = 300 + elif sensor == "radar": + new_min = 300 + new_max = 400 + elif sensor == "s0": + new_min = 400 + new_max = 500 + elif sensor == "s1": + new_min = 500 + new_max = 600 + elif sensor == "s2": + new_min = 600 + new_max = 700 + elif sensor == "s3": + new_min = 700 + new_max = 800 + elif sensor == "s4": + new_min = 800 + new_max = 900 + elif sensor == "s5": + new_min = 900 + new_max = 1000 + elif sensor == "s6": + new_min = 1000 + new_max = 1100 + elif sensor == "s7": + new_min = 1100 + new_max = 1200 + elif sensor == "s8": + new_min = 1200 + new_max = 1300 + else: #s9 + new_min = 1300 + new_max = 1400 + + # Split timestamps and values into separate arrays + timestamps = np.array([x[0] for x in data]) + values = np.array([x[1] for x in data]) + + # Get current min and max + if len(values) > 0: + current_min = np.min(values) + current_max = np.max(values) + else: + current_min = 0; + current_max = 0; + + # Scale the values using the min-max formula + if current_max - current_min > 0: + scaled_values = (values - current_min) * (new_max - new_min) / (current_max - current_min) + new_min + else: + mid_val = (new_max + new_min) / 2 + scaled_values = np.full_like(values, mid_val) + + # Zip back together with original timestamps + return list(zip(timestamps, scaled_values)) + +def CreateLocationsStripe(locations_file, time_zone_s): + + parts = locations_file.split("/") + parts1 = parts[2].split("_") + ddate = parts1[1] + deployment_id = parts1[0] + filter_minutes = parts1[2] + bw = False + chart_type = 4 + force_recreate = True + motion = False + scale_global = False + fast = True + GenerateFullLocationMap(locations_file, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes) + + +def CelsiusToFahrenheit(C): + F = (C * 9/5) + 32 + return F + +def CelsiusToFahrenheitList(compressed_readings: List[Tuple[datetime.datetime, np.float64]]) -> List[Tuple[datetime.datetime, np.float64]]: + + # Create a new list with converted temperatures + converted_readings = [ + [reading[0], CelsiusToFahrenheit(reading[1])] + for reading in compressed_readings + ] + + return converted_readings + +def GetPriviledgesOnly(user_name): + with get_db_connection() as conn: + if isinstance(user_name, (int)) or user_name.isdigit(): + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_id = " + user_name + else: + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetPriviledgesAndUserId(user_name): + with get_db_connection() as conn: + sql = "SELECT access_to_deployments, user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0] + else: + return "[0,0]" + +def AddToLog(message): + """Add message to log""" + logger.info(message) + +def FillFields(blob_data, record, form_type): + """ + Fill in the input fields in the HTML blob_data with values from the caretaker dictionary. + + :param blob_data: str - The initial HTML string containing empty or placeholder input fields. + :param caretaker: dict - The dictionary containing values to populate the fields. + :return: str - The HTML string with the input fields filled with the appropriate values. + """ + # Ensure blob_data is a string + #blob_data = str(blob_data) + + # Populate the fields + for field in record: + logger.debug(f"field= {field}") + if field == "user_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_user_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "deployment_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_deployment_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "device_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_device_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "user_name": + if record[field] != None: + escaped_string = html.escape(record[field]) + pattern = rf'(]+id="new_user_name"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="new_user_name"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + elif field == "location": + if record[field] != None: + blob_data = SelectOption(blob_data, 'location', record[field]) + + elif field == "gender": + if record[field] != None: + blob_data = SelectOption(blob_data, 'gender', record[field]) + + elif field == "race": + if record[field] != None: + blob_data = SelectOption(blob_data, 'race', record[field]) + + elif field == "time_zone_s": + if record[field] != None: + blob_data = SelectOption(blob_data, 'time_zone_s', record[field]) + + elif field == "time_edit" or field == "user_edit": + pass + else: + if record[field] != None: + escaped_string = html.escape(str(record[field])) + pattern = rf'(]+id="{field}"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="{field}"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + return blob_data + +def convert_timestamps_lc(data, time_zone_s): + target_tz = pytz.timezone(time_zone_s) + return [[datetime.datetime.fromtimestamp(epoch, pytz.UTC).astimezone(target_tz), value] + for epoch, value in data] + + +subbedToL = [("/wellget",1),("/wellget_cmp",1),("/well_hub",1)] +def on_connectL(client_, userdata, flags, rc): + print(MQTTSERVERL + " L. Connected with result code "+str(rc)) + + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client_.subscribe(subbedToL) + print("SubscribedL to: "+str(subbedToL)) + +def on_messageL(client_, userdata, msg): #message from GUI + print(msg.topic+" "+str(msg.payload)) + #msga = msg.payload.decode("ascii") + #print(msg.timestamp) + #in_queue.append((str(time.time()), msg.topic, msg.payload)) + +def MQSendL(topic, content, qos=1): + print(topic, content[0:100]) + #return MQSend(topic, content) + #currentTime = int(time.time()) + try: + if "_cmp" in topic: + enc_msg = zlib.compress(content.encode('utf-8')) + else: + enc_msg = content + clientL.publish(topic, enc_msg, qos=qos, retain=False) + except Exception as err: + print ("Err2B:", err) + try: + clientL.disconnect() + #client.username_pw_set('telegraf', 'well18') + clientL.connect(MQTTSERVERL, MQTT_PortL, 60) + except Exception as e: + print ("Err3b:", e) + +def StoreFloorPlan(deployment_id, layout): + + conn = get_db_connection() + cur = conn.cursor() + print(layout) + data = json.loads(layout) + + # Extract the overlapping list + overlapping_list = str(data["overlapping"]) + + try: + sql = f""" + UPDATE public.deployment_details SET floor_plan = '{CleanObject(layout)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + sql1 = f""" + INSERT INTO public.deployment_details (deployment_id, "overlapps") + VALUES ({deployment_id}, '{CleanObject(overlapping_list)}') + ON CONFLICT (deployment_id) + DO UPDATE SET "overlapps" = '{CleanObject(overlapping_list)}'; + """ + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def GetFloorPlan(deployment_id): + + conn = get_db_connection() + + try: + sql = f""" + SELECT floor_plan FROM public.deployment_details WHERE deployment_id = {deployment_id}; + """ + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "" + + logger.debug(f"sql= {sql}") + conn.close() + return 1 + except Exception as err: + return 0 + +# CORS Middleware +class CORSMiddleware: + def process_request(self, req, resp): + resp.set_header('Access-Control-Allow-Origin', '*') + resp.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + resp.set_header('Access-Control-Allow-Headers', '*') + + def process_response(self, req, resp, resource, req_succeeded): + if req.method == 'OPTIONS': # Handle preflight requests + resp.status = falcon.HTTP_200 + +# Add this class to your code +class RequestParser: + def __init__(self): + # Detect if we're running in debug/development mode + self.debug_mode = __name__ == "__main__" or os.environ.get('DEBUG', 'false').lower() in ('true', '1', 'yes') + logger.debug(f"RequestParser initialized in {'DEBUG' if self.debug_mode else 'PRODUCTION'} mode") + + def process_request(self, req, resp): + """Pre-process the request to ensure media is parsed early""" + logger.debug(f"RequestParser processing: {req.method} {req.path}") + + # Initialize an empty form_data dict + req.context.form_data = {} + + # Only process POST requests with the right content type + if req.method != 'POST' or not req.content_type or 'form-urlencoded' not in req.content_type: + logger.debug("RequestParser: Skipping (not a form POST)") + return + + try: + # Different handling based on environment + if self.debug_mode: + self._process_debug(req) + else: + self._process_production(req) + + except Exception as e: + logger.error(f"RequestParser error: {str(e)}") + logger.error(traceback.format_exc()) + + def _process_debug(self, req): + """Process request in debug mode - optimized for local development""" + logger.debug("RequestParser: Using DEBUG mode processing") + + # In debug mode, we can use Content-Length and know it's reliable + content_length = req.get_header('content-length') + + if content_length: + # Content-Length is present + content_length = int(content_length) + logger.debug(f"RequestParser: Reading {content_length} bytes using Content-Length") + + raw_body = req.stream.read(content_length) + if raw_body: + body_text = raw_body.decode('utf-8') + logger.debug(f"RequestParser: Successfully read {len(body_text)} chars") + + # Parse the form data + import urllib.parse + form_data = dict(urllib.parse.parse_qsl(body_text)) + + # Store in context + req.context.form_data = form_data + logger.debug(f"RequestParser: Parsed form data: {form_data}") + + # Reset the stream with the original content + import io + req.stream = io.BytesIO(raw_body) + else: + logger.debug("RequestParser: No body data read") + else: + logger.debug("RequestParser (debug): No Content-Length header") + + def _process_production(self, req): + """Process request in production mode - optimized for OpenFaaS/faasd deployment""" + logger.debug("RequestParser: Using PRODUCTION mode processing") + + # Simple direct read approach for production (OpenFaaS/faasd) + # We'll limit the read to 1MB for safety + MAX_SIZE = 1024 * 1024 # 1MB + + # Just read directly from the stream without checking + raw_body = req.stream.read(MAX_SIZE) + if raw_body: + body_text = raw_body.decode('utf-8') + logger.debug(f"RequestParser: Successfully read {len(body_text)} chars") + + # Parse the form data + import urllib.parse + form_data = dict(urllib.parse.parse_qsl(body_text)) + + # Store in context + req.context.form_data = form_data + logger.debug(f"RequestParser: Parsed form data: {form_data}") + + # Reset the stream with the original content + import io + req.stream = io.BytesIO(raw_body) + else: + logger.debug("RequestParser: No body data read") + + +def FindDeviceByRole(deployment_id, location_list): + + #For purposes of activity report, Bedroom and Bathroom are determined in order of priority: + #Bedroom: "Bedroom Master", "Bedroom", "Bedroom Guest" (106, 56, 107) + #Bathroom: ""Bathroom Main","Bathroom","Bathroom Guest" (104, 103, 105) + + #location_names_inverted = {"All":-1 ,"?": 0,"Office": 5,"Hallway": 6,"Garage": 7,"Outside": 8,"Conference Room": 9,"Room": 10,"Kitchen": 34, + # "Bedroom": 56,"Living Room": 78,"Bathroom": 102,"Dining Room": 103,"Bathroom Main": ,104,"Bathroom Guest": 105, + # "Bedroom Master": 106, "Bedroom Guest": 107, "Conference Room": 108, "Basement": 109, "Attic": 110, "Other": 200} + + + ttime = datetime.datetime.utcnow().timestamp() + + devices_list, device_ids = GetProximityList(deployment_id, ttime) + + if location_list != []: + for location in location_list: + for device in devices_list: + well_id = device[0] + device_id = device[1] + location_t = device[2] + if location_t == location: + return (device_id, location, well_id) + + else: + conn = get_db_connection() + with conn.cursor() as cur: + + #we need to find beneficiaries from list of deployments + #sql = f'SELECT device_id FROM public.devices where device_id in {device_ids} and other="other"' + sql = "SELECT device_id, location, well_id FROM public.devices WHERE device_id = ANY(%s) AND other = %s" + #print(sql) + cur.execute(sql, (device_ids, "other")) + result = cur.fetchall()#cur.fetchone() + if len(result) > 0: + return result[0] + else: + + devices_list, device_ids = GetProximityList(deployment_id, ttime) + for device in devices_list: + well_id = device[0] + device_id = device[1] + location_t = device[2] + if "Bathroom" in location_t or "Bedroom" in location_t or "Kitchen" in location_t: + pass + else: + return (device_id, location_t, well_id) + + return (0, 0, 0) + + +def ensure_date_order(from_date, to_date): + """ + Ensures that from_date is earlier than to_date. + If not, swaps the dates. + + Args: + from_date: Date string in format 'YYYY-MM-DD' + to_date: Date string in format 'YYYY-MM-DD' + + Returns: + Tuple of (from_date, to_date) in correct order + """ + # Compare the date strings + # This works because the 'YYYY-MM-DD' format allows for string comparison + if from_date > to_date: + # Swap the dates + return to_date, from_date + else: + # Dates are already in correct order + return from_date, to_date + +def signum(x): + return (x > 0) - (x < 0) + + +def get_week_days_and_dates(days_back, timezone_str="America/Los_Angeles"): + """ + Generate weekdays and dates from 7 days ago until today for a given timezone. + + Args: + timezone_str (str): Timezone string like "America/Los_Angeles" + + Returns: + list: List of tuples containing (weekday_name, date_string) + """ + # Get the timezone object + tz = pytz.timezone(timezone_str) + + # Get current date in the specified timezone + today = datetime.datetime.now(tz).date() + + # Generate dates from days_back days ago to today + result = [] + for i in range(days_back-1, -1, -1): # days_back days ago to today (inclusive) + date = today - timedelta(days=i) + weekday_name = date.strftime("%A") # Full weekday name + date_string = date.strftime("%Y-%m-%d") # ISO format date + day_of_month = date.day + result.append((date_string, weekday_name, day_of_month)) + + return result + + +def filter_short_groups_numpy_orig(presence_list, filter_size, device_id, dates_str): + """ + Optimized version using NumPy to remove groups of consecutive zeros + or consecutive non-zeros (based on sign) shorter than filter_size. + Mimics the iterative, shortest-first logic of filter_short_groupss. + + Args: + presence_list: List of numbers (can include floats, ints, 0s). + filter_size: Minimum size of consecutive groups (by sign) to keep. + + Returns: + Filtered list with short groups removed. Output contains 0s and 1s. + (Note: Differs slightly from filter_short_groupss if negative numbers + were present, as this version converts them to 0, not 2). + """ + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"NumPy: Early exit/no processing time: {time.time() - st:.6f}s") + # Return a copy to avoid modifying the original list + return presence_list[:] if isinstance(presence_list, list) else list(presence_list) + + # Work with a NumPy array for efficiency, ensure float type for consistency + result = np.array(presence_list, dtype=float) + n = len(result) + + # Use a set to store hashable representations (tuples) of previous states for cycle detection + previous_states = set() + + while True: + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("NumPy: Cycle detected, breaking.") + break + previous_states.add(current_state_tuple) + + # 1. Calculate the sign of each element (-1, 0, 1) + signs = np.sign(result) + + # 2. Find indices where the sign changes + # np.diff calculates the difference between adjacent elements. + # A non-zero difference means the sign changed. + # np.where returns the indices *before* the change. Add 1 to get the start of the new run. + change_indices = np.where(np.diff(signs) != 0)[0] + 1 + + # 3. Define the boundaries of all consecutive runs (start and end indices) + # Include the start (0) and end (n) of the array. + boundaries = np.concatenate(([0], change_indices, [n])) + + # 4. Identify short runs + short_runs_to_process = [] + for i in range(len(boundaries) - 1): + start = boundaries[i] + end = boundaries[i+1] # Slicing is exclusive of the end index + length = end - start + + if length > 0: # Ensure the run is not empty + # Determine the characteristic sign of the run (use the first element) + run_sign = signs[start] + + if length < filter_size: + # --- Verification Step (Crucial) --- + # Check if the segment *still* consists of elements with the same sign. + # This handles cases where a previous modification might have altered + # part of what *was* a longer run. + current_segment_signs = np.sign(result[start:end]) + if np.all(current_segment_signs == run_sign): + # If the run is short and its sign consistency is verified, + # add it to the list of candidates for modification. + short_runs_to_process.append({ + 'start': start, + 'end': end, + 'sign': run_sign, + 'length': length + }) + # --- End Verification --- + + # 5. Check if any short runs were found + if not short_runs_to_process: + # No modifiable short runs found in this pass, the list is stable. + break + + # 6. Sort the short runs: shortest first, then by start index for determinism + # This ensures we process the same run as the original iterative function would. + short_runs_to_process.sort(key=lambda r: (r['length'], r['start'])) + + # 7. Process ONLY the *first* (shortest) identified run in this pass + run_to_process = short_runs_to_process[0] + start = run_to_process['start'] + end = run_to_process['end'] + run_sign = run_to_process['sign'] + + # Determine the replacement value based on the sign of the run being removed + # Short runs of 0 become 1 + # Short runs of non-zero (positive or negative) become 0 + replacement_value = 1.0 if run_sign == 0 else 0.0 + + # 8. Apply the replacement to the segment using NumPy slicing + result[start:end] = replacement_value + # Loop continues because a change was made + + print(f"filter_short_groups_numpy time: {time.time() - st:.6f}s") + if (time.time() - st) > 40: + print(presence_list) + # Convert back to a standard Python list for the return value + return result.tolist() + + + +def filter_short_groups_numpy(presence_list, filter_size, device_id, dates_str): + """ + Optimized version using NumPy to remove groups of consecutive zeros + or consecutive non-zeros (based on sign) shorter than filter_size. + Mimics the iterative, shortest-first logic. + + Optimization: + - Vectorized extraction of segment properties. + - Removed redundant sign verification within the segment analysis loop. + """ + # Start timer (optional, for benchmarking) + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"NumPy Optimized: Early exit/no processing time: {time.time() - st:.6f}s") + return presence_list[:] if isinstance(presence_list, list) else list(presence_list) + + result = np.array(presence_list, dtype=float) + n = len(result) + + previous_states = set() + + while True: + # Cycle detection + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("NumPy Optimized: Cycle detected, breaking.") + break + previous_states.add(current_state_tuple) + + # 1. Calculate the sign of each element (-1, 0, 1) + signs = np.sign(result) + + # 2. Find indices where the sign changes + change_indices = np.where(np.diff(signs) != 0)[0] + 1 + + # 3. Define the boundaries of all consecutive runs + boundaries = np.concatenate(([0], change_indices, [n])) + + # If there's only one segment (e.g., all zeros, all ones, or array is too short to have changes), + # or if the array was empty (n=0 leading to boundaries=[0,0]), no further processing is needed. + if len(boundaries) <= 2: # e.g., boundaries is [0, n] or [0,0] + break + + # 4. Vectorized extraction of run properties + run_starts = boundaries[:-1] + run_ends = boundaries[1:] + run_lengths = run_ends - run_starts + # The sign of the first element of a run (from the 'signs' array computed at the + # start of this 'while' iteration) is representative of the entire run's sign, + # by definition of how 'boundaries' were created. + run_signs = signs[run_starts] + + # 5. Identify short runs and collect their properties + short_runs_to_process = [] + for i in range(len(run_starts)): # Iterate over all identified runs + # Ensure run_length is positive (should be, due to boundary logic, but good check) + if run_lengths[i] > 0 and run_lengths[i] < filter_size: + short_runs_to_process.append({ + 'start': run_starts[i], + 'end': run_ends[i], + 'sign': run_signs[i], + 'length': run_lengths[i] + }) + + # 6. Check if any modifiable short runs were found + if not short_runs_to_process: + # No short runs found in this pass, the list is stable. + break + + # 7. Sort the short runs: shortest first, then by start index for determinism + short_runs_to_process.sort(key=lambda r: (r['length'], r['start'])) + + # 8. Process ONLY the *first* (shortest) identified run in this pass + run_to_process = short_runs_to_process[0] + start = run_to_process['start'] + end = run_to_process['end'] + run_sign = run_to_process['sign'] + + # Determine the replacement value + replacement_value = 1.0 if run_sign == 0 else 0.0 + + # 9. Apply the replacement + result[start:end] = replacement_value + # A change was made, so the 'while True' loop continues (unless a cycle is detected next) + + # End timer and print (optional) + # Your original print statements for timing: + print(f"filter_short_groups_numpy time: {time.time() - st:.6f}s") + # if (time.time() - st) > 40: + # print(presence_list) # This would print the original input on long runs + + return result.tolist() + +def filter_short_groups(presence_list, filter_size): + """ + Corrected version to perform the same task as filter_short_groupss, + including handling of non-zero/non-one values based on signum. + Iteratively removes the shortest group < filter_size by flipping its + signum representation (0->1, pos->0, neg->2). + + Args: + presence_list: List of numbers (0s, 1s, or any other number). + filter_size: Minimum size of groups (based on signum) to keep. + Returns: + Filtered list with short groups removed, potentially containing 0, 1, 2. + """ + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"filter_short_groups: Early exit/no processing time: {time.time() - st:.6f}s") + return presence_list.copy() + + result = presence_list.copy() + n = len(result) + + # Using a set for faster cycle detection lookups + previous_states = set() + + while True: + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("Cycle detected in filter_short_groups, breaking.") + break + previous_states.add(current_state_tuple) + + # --- Start of logic mimicking filter_short_groupss --- + changes_made_outer = False + + # 1. Find all segments based on signum + segments = [] + i = 0 + while i < n: + start = i + # Use signum to define the characteristic value of the run + current_signum = signum(result[i]) + + # Find the end of the group based on *consistent signum* + while i < n and signum(result[i]) == current_signum: + i += 1 + + group_length = i - start + # Store the signum value associated with the run + segments.append((start, i - 1, current_signum, group_length)) + + # 2. Sort segments by length (ascending) to process shortest first + segments.sort(key=lambda x: x[3]) + + # 3. Process the segments (find the first short one to modify) + for start, end, run_signum, length in segments: + if length < filter_size: + # Verify the segment hasn't been fundamentally altered (signum-wise) + # This check mirrors filter_short_groupss's intent, using signum consistently. + is_still_original_signum_segment = True + for k_idx in range(start, end + 1): + if signum(result[k_idx]) != run_signum: + is_still_original_signum_segment = False + break + + if is_still_original_signum_segment: + # Calculate replacement value based on signum (0->1, pos->0, neg->2) + replacement_value = 1 - run_signum + + # Apply replacement + segment_modified = False + for j in range(start, end + 1): + # Use direct comparison as replacement values are integers (0, 1, 2) + if result[j] != replacement_value: + result[j] = replacement_value + segment_modified = True + + if segment_modified: + changes_made_outer = True + # Break after making *one* change and restart the whole process + # (finding segments, sorting, finding shortest modifiable) + break # Break from the 'for segment in segments' loop + + # --- End of logic mimicking filter_short_groupss --- + + if not changes_made_outer: + # If we went through all segments and made no changes, we're done. + break + + print(f"filter_short_groups time: {time.time() - st:.6f}s") + return result + + +def filter_short_groupss(presence_list, filter_size): + """ + Iteratively remove groups of consecutive 0s or 1s that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of 0s and 1s + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + st = time.time() + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = signum(result[i]) + + # Find the end of the group + while i < n and signum(result[i]) == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + # Sort segments by length (ascending) to process shortest first + segments.sort(key=lambda x: x[3]) + + # Process the segments + for start, end, value, length in segments: + # If segment is too short, replace with opposite value + if length < filter_size: + # Verify the segment hasn't been modified by previous replacements + if all(result[j] == value for j in range(start, end+1)): + replacement = 1 - value # Toggle between 0 and 1 + for j in range(start, end+1): + result[j] = replacement + changes_made = True + #print(start, end) + break # Break after making a change and restart + + print("s", time.time()-st) + return result + +def filter_short_segments(segments, filter_size): + """ + Iteratively remove segments that are shorter than filter_size, + replacing them with data from the previous segment. + + Args: + segments: List of tuples (start_time, end_time, num_persons, duration) + filter_size: Minimum duration to keep a segment + + Returns: + Filtered list of segments covering the entire time range + """ + if not segments or filter_size <= 0: + return segments.copy() + + result = segments.copy() + changes_made = True + + while changes_made: + changes_made = False + + i = 1 # Start from the second segment + while i < len(result): + _, _, _, duration = result[i] + + if duration < filter_size: + # Get the previous segment's person count + if i > 0: + _, _, prev_persons, _ = result[i-1] + start, end, _, dur = result[i] + + # Replace with previous person count + result[i] = (start, end, prev_persons, dur) + changes_made = True + + # Check if we can merge with previous segment + if i > 0: + prev_start, prev_end, prev_persons, prev_dur = result[i-1] + curr_start, curr_end, curr_persons, curr_dur = result[i] + + if prev_persons == curr_persons and prev_end + 1 == curr_start: + # Merge segments + merged = (prev_start, curr_end, prev_persons, prev_dur + curr_dur) + result[i-1] = merged + result.pop(i) + i -= 1 # Adjust index after removing an element + changes_made = True + + i += 1 + + # Sort segments by start time to ensure proper order + result.sort(key=lambda x: x[0]) + + return result + +def filter_out_short_high_segments(segments, filter_size): + """ + Iteratively remove segments that are shorter than filter_size, + replacing them with data from the previous segment. + + Args: + segments: List of tuples (start_time, end_time, num_persons, duration) + filter_size: Minimum duration to keep a segment + + Returns: + Filtered list of segments covering the entire time range + """ + if not segments: + return segments.copy() + + result = segments.copy() + changes_made = True + + while changes_made: + changes_made = False + + i = 1 # Start from the second segment + while i < len(result): + _, _, _, duration = result[i] + + if duration < filter_size: + # Get the previous segment's person count + if i > 0: + _, _, prev_persons, _ = result[i-1] + start, end, _, dur = result[i] + + # Replace with previous person count + result[i] = (start, end, prev_persons, dur) + changes_made = True + + # Check if we can merge with previous segment + if i > 0: + prev_start, prev_end, prev_persons, prev_dur = result[i-1] + curr_start, curr_end, curr_persons, curr_dur = result[i] + + if prev_persons == curr_persons and prev_end + 1 == curr_start: + # Merge segments + merged = (prev_start, curr_end, prev_persons, prev_dur + curr_dur) + result[i-1] = merged + result.pop(i) + i -= 1 # Adjust index after removing an element + changes_made = True + + i += 1 + + # Sort segments by start time to ensure proper order + result.sort(key=lambda x: x[0]) + + return result + +def filter_out_short_same_groups_iterative(presence_list, filter_size): + """ + Iteratively remove groups of consecutive sames that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of values + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list: + return presence_list.copy() + + result = presence_list.copy() + + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + + result = filter_out_short_high_segments(segments, filter_size) + + return result + +def filter_out_short_highs_iterative(presence_list, filter_size): + """ + Iteratively remove groups of consecutive sames that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of values + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list: + return presence_list.copy() + + result = presence_list.copy() + + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + + result = filter_out_short_high_segments(segments, filter_size) + + return result + +def filter_short_groups_iterative_analog(presence_list, filter_size): + """ + Iteratively remove groups of consecutive similar values that are shorter than filter_size. + For non-zero values, replaces with 0. For zero values, needs context to determine replacement. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # Identify all segments of consecutive similar values + segments = [] + i = 0 + n = len(result) + + while i < n: + start = i + is_zero = (result[i] == 0) + + # Find the end of the group with same characteristic (zero or non-zero) + while i < n and ((result[i] == 0) == is_zero): + i += 1 + + group_length = i - start + segments.append((start, i-1, is_zero, group_length)) + + # Process segments from shortest to longest + segments.sort(key=lambda x: x[3]) + + for start, end, is_zero, length in segments: + if length < filter_size: + # For short non-zero groups, replace with zeros + if not is_zero: + for j in range(start, end+1): + result[j] = 0 + changes_made = True + break + else: + # For short zero groups, replace with average of surrounding non-zero values + # First, find surrounding values + left_value = 0 + right_value = 0 + + # Look for non-zero value on the left + for j in range(start-1, -1, -1): + if result[j] != 0: + left_value = result[j] + break + + # Look for non-zero value on the right + for j in range(end+1, n): + if result[j] != 0: + right_value = result[j] + break + + # Calculate replacement value + if left_value > 0 and right_value > 0: + replacement = (left_value + right_value) / 2 + elif left_value > 0: + replacement = left_value + elif right_value > 0: + replacement = right_value + else: + replacement = 0 # No surrounding non-zero values + + # Apply replacement + for j in range(start, end+1): + result[j] = replacement + + if replacement != 0: # Only mark as changed if we actually changed something + changes_made = True + break + return result + + +def filter_short_high_groups_iterative_analog(presence_list, filter_size): + st = time.time() + """ + More efficient implementation that still handles cascading effects. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + i = 0 + n = len(result) + + # Use a single pass to find all non-zero segments + segments = [] + while i < n: + # Skip zeros + if result[i] == 0: + i += 1 + continue + + # Found non-zero, find the end of this segment + start = i + while i < n and result[i] != 0: + i += 1 + + # Add segment to our list + segments.append((start, i)) + + # Process all short segments in one iteration + for start, end in segments: + length = end - start + if length < filter_size: + # Set all elements in this segment to zero + for j in range(start, end): + result[j] = 0 + changes_made = True + # Don't break - process all short segments in this pass + + # If we've made changes, we need to check again for newly formed short segments + print(f"filter_short_high_groups_iterative_analog time: {time.time() - st:.6f}s") + return result + +def filter_short_high_groups_iterative_analog_orig(presence_list, filter_size): + """ + Iteratively remove groups of consecutive similar values that are shorter than filter_size. + For non-zero values, replaces with 0. For zero values, needs context to determine replacement. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + st = time.time() + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # Identify all segments of consecutive similar values + segments = [] + i = 0 + n = len(result) + + while i < n: + start = i + is_zero = (result[i] == 0) + + # Find the end of the group with same characteristic (zero or non-zero) + while i < n and ((result[i] == 0) == is_zero): + i += 1 + + group_length = i - start + segments.append((start, i-1, is_zero, group_length)) + + # Process segments from shortest to longest + segments.sort(key=lambda x: x[3]) + + for start, end, is_zero, length in segments: + if length < filter_size: + # For short non-zero groups, replace with zeros + if not is_zero: + for j in range(start, end+1): + result[j] = 0 + changes_made = True + break + + print(f"filter_short_high_groups_iterative_analog time: {time.time() - st:.6f}s") + #if (time.time() - st) > 40: + # print(presence_list) + return result + + + +def filter_short_groupsWhat(presence_list, filter_size): + """ + Remove groups of consecutive 0s or 1s that are shorter than filter_size. + For short groups of 0s, replace with 1s. + For short groups of 1s, replace with 0s. + + Args: + presence_list: List of 0s and 1s + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + n = len(result) + + # Find groups and process them + i = 0 + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + + # If group is too short, replace with opposite value + if group_length < filter_size: + replacement = 1 - current_value # Toggle between 0 and 1 + for j in range(start, i): + result[j] = replacement + + return result + + +def GetOverlapps(deployment_id): + + with get_db_connection() as db_conn: + with db_conn.cursor() as cur: + sql = f"SELECT overlapps FROM public.deployment_details WHERE deployment_id = '{deployment_id}'" + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + return result[0] + + +def GetAmpitude(point_val, segment_lenght): + if point_val == 0: + return -segment_lenght + else: + return segment_lenght + +def CreateZGraph(well_id, presence_list): + """ + return size and position of consecutive groups of 0s and 1s + + Args: + presence_list: List of 0s and 1s + + Returns: + list of times and lengths + """ + if not presence_list: + return presence_list.copy() + + #if well_id == 290: + # print("Stop") + dekas_in_day = 6 * 1440 + result = [] + print(well_id) + #result will look like this: [(0,34),(34,-56),(92,6),...] where (A,B) + #A: is minute of section, B: height of section +=presence -=absence + #lets find point 0 first moving backward in time + segment_lenght = 0 + point_zero_val = signum(presence_list[dekas_in_day]) + for i in range(dekas_in_day-1, 0, -1): + if point_zero_val != signum(presence_list[i]): + segment_lenght = dekas_in_day -1 - i + break + x = 0 + y = GetAmpitude(point_zero_val, segment_lenght) + result.append((x, y)) + #x = x + segment_lenght + last_y = y + last_val = point_zero_val + last_source_minute = dekas_in_day + 1 + for i in range(last_source_minute, len(presence_list)): + if last_val != signum(presence_list[i]): + segment_lenght = i - dekas_in_day - x + x = x + segment_lenght + y = last_y + GetAmpitude(last_val, segment_lenght) + result.append((x, y)) + result.append((x, 0)) + last_y = 0 + last_val = signum(presence_list[i]) + + #last point i is NOT 1 + last above... it is last above so 2879! + segment_lenght = i - dekas_in_day - x + x = i - dekas_in_day #last point + y = GetAmpitude(last_val, segment_lenght) + result.append((x, y)) + + return result +def CreateZGraphAI(presence_list): + """ + Creates a step graph representation where: + - Presence periods: step up to +count, then drop to 0 + - Absence periods: step down to -count, then rise to 0 + - X-coordinate advances by the duration of each segment + + Pattern: [0,0] → [duration1, +/-count1] → [duration1, 0] → [duration1+duration2, +/-count2] → [duration1+duration2, 0] → ... + + Args: + presence_list: List of values (0 = absence, non-zero = presence) + + Returns: + List of [minute, height] coordinates for step graph + """ + if not presence_list: + return [] + + result = [[0, 0]] # Start at origin + + i = 0 + current_x = 0 + + while i < len(presence_list): + if presence_list[i] != 0: # Start of presence period + # Count consecutive non-zero values + count = 0 + while i < len(presence_list) and presence_list[i] != 0: + count += 1 + i += 1 + + # Move x forward by count, step up to +count + current_x += count + result.append([current_x, count]) + + # Add vertical line down to 0 + result.append([current_x, 0]) + + else: # Start of absence period + # Count consecutive zero values + count = 0 + while i < len(presence_list) and presence_list[i] == 0: + count += 1 + i += 1 + + # Move x forward by count, step down to -count + current_x += count + result.append([current_x, -count]) + + # Add vertical line up to 0 + result.append([current_x, 0]) + + return result + + +# Add this function to your code +def get_form_data(req): + """Helper function to get form data from either context or req.media""" + # First check if we pre-parsed the form data + if hasattr(req.context, 'form_data') and req.context.form_data: + logger.debug("Using pre-parsed form data from context") + return req.context.form_data + + # Otherwise try to get from req.media (for json) + try: + if req.content_type and ( + falcon.MEDIA_JSON in req.content_type or + falcon.MEDIA_URLENCODED in req.content_type + ): + logger.debug("Attempting to get form data from req.media") + return req.media or {} + except Exception as e: + logger.error(f"Error getting req.media: {str(e)}") + + logger.debug("No form data available, returning empty dict") + return {} + +def DetectMultiple(temporary_map_day_plus, overlaps_str_lst): + """ + Written by Robert Zmrzli + Detects time intervals of multiple vs single/no presence and outputs + the signed duration of each interval at its end time. + + Args: + temporary_map_day_plus: Map for each device radar reads that were detected to be above threshold + overlaps_lst: List of pairs of devices that have overlapping area + Returns: + A list of tuples representing the multiple presence timeline segments. + Each segment is represented by two tuples: + 1. (end_minute, signed_duration): signed_duration is the length of the + interval ending at end_minute (+ multiple, - single/none). + 2. (end_minute, 0): A marker for visualization. + """ + for location_id, data_list in temporary_map_day_plus.items(): + minutes_in_data = len(data_list) + break + events = [] + min_time = 0 + max_time = 0 + + + #['267:273', '273:291'] + seen_list = [0] * minutes_in_data + seen_where_list = [[] for _ in range(minutes_in_data)] + + for location_id, data_list in temporary_map_day_plus.items(): + for i in range(minutes_in_data): + if data_list[i] > 0: # Presence interval + seen_where_list[i].append(location_id) + + seen_where_list_uf = seen_where_list.copy() + overlap_pairs = set() + for overlap_str in overlaps_str_lst: + nums = [int(x) for x in overlap_str.split(':')] + # Add both orderings of the pair for easier checking + overlap_pairs.add((nums[0], nums[1])) + overlap_pairs.add((nums[1], nums[0])) + + # Process each sub-list in seen_where_list + for i in range(len(seen_where_list)): + locations = seen_where_list[i] + + # Skip empty lists and lists with only 0 or 1 item + if len(locations) <= 1: + continue + + has_non_overlapping_pair = False + + for j in range(len(locations)): + for k in range(j+1, len(locations)): + loc1, loc2 = locations[j], locations[k] + + # If this pair is not in our overlap_pairs, then they don't overlap + if (loc1, loc2) not in overlap_pairs: + has_non_overlapping_pair = True + break + + if has_non_overlapping_pair: + break + + # If all pairs overlap (no non-overlapping pairs found), clear the list + if not has_non_overlapping_pair: + seen_where_list[i] = [] + + + variations = [] + variation_index = {} + + for i in range(minutes_in_data): + if len(seen_where_list[i]) > 1: # Presence interval + if seen_where_list[i] not in variations: + variations.append(seen_where_list[i]) + variation_index[str(seen_where_list[i])] = len(variations) - 1 + + seen_list[i] = variation_index[str(seen_where_list[i])] + + + return seen_list, seen_where_list_uf + +def minutes_to_time(minutes): + """ + Convert minutes in a day (0-1439) to HH:MM format + + Args: + minutes (int): Minutes since midnight (0-1439) + + Returns: + str: Time in HH:MM format + """ + # Ensure the input is within valid range + #if not 0 <= minutes <= 1439: + # raise ValueError("Minutes must be between 0 and 1439") + + # Calculate hours and remaining minutes + minutes = minutes % 1440 + hours = minutes // 60 + mins = minutes % 60 + + # Format as HH:MM with leading zeros + return f"{hours:02d}:{mins:02d}" + +def decas_to_time(decas): + """ + Convert decas in a day (0-8639) to HH:MM format + + Args: + decas (int): decas since midnight (0-1439) + + Returns: + str: Time in HH:MM format + """ + # Ensure the input is within valid range + #if not 0 <= minutes <= 1439: + # raise ValueError("Minutes must be between 0 and 1439") + + # Calculate hours and remaining minutes + decas = decas % 8640 + hours = decas // (6 * 60) + mins = (decas // 6) % 60 + secs = 10 * (decas % 10) + + # Format as HH:MM with leading zeros + return f"{hours:02d}:{mins:02d}:{secs:02d}" + +def ClearOverlaps(temporary_map_day_plus, overlaps_str_lst): + """ + Detects reads that came from same person read by multiple devices that overlap, and removes weaker reads + Args: + temporary_map_day_plus: Map for each device radar reads that were detected to be above threshold + overlaps_lst: List of pairs of devices that have overlapping area + Returns: + An original temporary_map_day_plus with some reads removed + """ + ## Get the number of minutes + #for location_id, data_list in temporary_map_day_plus.items(): + #decas_in_data = len(data_list) + #break + + if temporary_map_day_plus: + decas_in_data = len(next(iter(temporary_map_day_plus.values()))) + else: + decas_in_data = 0 + + # Create seen_where_list with device-signal pairs + seen_where_list = [[] for _ in range(decas_in_data)] + for location_id, data_list in temporary_map_day_plus.items(): + for i in range(decas_in_data): + if data_list[i] > 0: # Presence interval + #if i == (8721): + # print("stop") + seen_where_list[i].append((location_id, data_list[i])) + + # Parse overlap pairs + overlap_pairs = set() + for overlap_str in overlaps_str_lst: + nums = [int(x) for x in overlap_str.split(':')] + overlap_pairs.add((nums[0], nums[1])) + overlap_pairs.add((nums[1], nums[0])) + + # Process each time slot + for i in range(len(seen_where_list)): + locations = seen_where_list[i] + + if len(locations) <= 1: + continue + + #if i == (5713 + 8640): + # print("stop") + #if i == (8721): + # print("stop") + # Create a new list to store the filtered results + filtered_list = [] + + # Make a copy of locations to process + to_process = locations.copy() + + # Process each device and decide whether to keep it + while to_process: + current = to_process.pop(0) + device_id, signal_strength = current + + should_keep = True + devices_to_remove = [] + + # Compare with all other devices (including those already in filtered_list) + for other in locations: + other_device_id, other_signal_strength = other + + # Skip if comparing with itself + if device_id == other_device_id: + continue + + # Check if these devices overlap + if (device_id, other_device_id) in overlap_pairs: + # They overlap, keep only the stronger signal + if signal_strength < other_signal_strength: + # Other device is stronger, don't keep current + should_keep = False + break + elif signal_strength == other_signal_strength and device_id > other_device_id: + # For equal signals, use device_id as tiebreaker + should_keep = False + break + + # If we should keep this device, add it to filtered list + if should_keep: + filtered_list.append(current) + + # Update the original list with filtered results + + #if i == (8721): + # print("stop") + seen_where_list[i] = filtered_list + + # Create a new temporary_map_day_plus with the filtered data + result = {} + for location_id, data_list in temporary_map_day_plus.items(): + result[location_id] = [0] * decas_in_data + + # Fill in the filtered data + for i in range(decas_in_data): + #if len(seen_where_list[i]) > 1: + #if i == (8721): + # print("stop") + #print(i, decas_to_time(i), seen_where_list[i]) + + for device_id, signal_strength in seen_where_list[i]: + result[device_id][i] = signal_strength + + return result + +# Path handling middleware +class StripPathMiddleware: + def process_request(self, req, resp): + # Strip the '/function/well-api' prefix if present + path = req.path + logger.info(f"Original request path: {path}") + + # Define patterns to match different URL formats + patterns = [ + r'^/function/well-api', # Standard OpenFaaS path + r'^/api/well_api', # API path + ] + + for pattern in patterns: + if re.match(pattern, path): + # Strip the matched prefix + path = re.sub(pattern, '', path) + # Ensure path starts with a slash + if not path.startswith('/'): + path = '/' + path + # Update the request path + req.path = path + logger.info(f"Modified request path: {path}") + break + + +def optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type): + last_device_id = None + + # Pre-compute seconds per minute + seconds_per_deka = 10 + + # Check if we need to process all data or just specific types + #process_all = data_type in ("all", "z-graph", "multiple") + process_all = True + + for radar_read in myz_data: + local_time = radar_read[0] + device_id = radar_read[1] + + # Calculate deca once + deca = int((local_time - start_time).total_seconds() / seconds_per_deka) + + # Use cached lookups when possible + if device_id != last_device_id: + last_device_id = device_id + + # Check if we've cached this device info + if device_id not in device_lookup_cache: + well_id = id2well_id[device_id] + radar_threshold_group_st = device_id_2_threshold[device_id]#well_id] + threshold_sig, threshold = radar_threshold_group_st + threshold_sig = threshold_sig.split("_")[0] + + # Cache the values + device_lookup_cache[device_id] = { + 'well_id': well_id, + 'threshold_sig': threshold_sig, + 'threshold': threshold + } + else: + # Use cached values + cached = device_lookup_cache[device_id] + well_id = cached['well_id'] + threshold_sig = cached['threshold_sig'] + threshold = cached['threshold'] + + days_decas = len(temporary_map_day_plus[well_id]) + else: + # Use already loaded values from last iteration + cached = device_lookup_cache[device_id] + well_id = cached['well_id'] + threshold_sig = cached['threshold_sig'] + threshold = cached['threshold'] + days_decas = len(temporary_map_day_plus[well_id]) + + # Get radar value using cached index + radar_val = radar_read[2 + device_field_indexes[threshold_sig]] + + # Process data if needed + if process_all and radar_val > threshold and deca < days_decas: + temporary_map_day_plus[well_id][deca] = radar_val + #if well_id == 269: + # print(local_time) + + return temporary_map_day_plus + +def optimized_radar_processing(my_data, start_time, id2well_id, device_id_2_threshold, + device_field_indexes, presence_map, data_type): + last_device_id = 0 + + # Cache for threshold_sig calculation which is expensive due to dictionary lookups and string splitting + threshold_sig_cache = {} + field_index_cache = {} + + for radar_read in my_data: + local_time = radar_read[0] + device_id = radar_read[1] + + # Calculate deca once + deca = int((local_time - start_time).total_seconds() / 10) + + # Device changed - update values that depend on device + if device_id != last_device_id: + last_device_id = device_id + well_id = id2well_id[device_id] + + # Calculate days_decas exactly like original + #if data_type == "raw" or data_type == "all": + days_decas = len(presence_map['raw'][well_id]) + #else: + # days_decas = len(presence_map['presence'][well_id]) + + # Calculate threshold_sig with caching + if device_id not in threshold_sig_cache: + radar_threshold_group_st = device_id_2_threshold[device_id] + threshold_sig, threshold = radar_threshold_group_st + threshold_sig = threshold_sig.split("_")[0] + threshold_sig_cache[device_id] = (threshold_sig, threshold) + else: + threshold_sig, threshold = threshold_sig_cache[device_id] + + # Calculate field index with caching + if threshold_sig not in field_index_cache: + field_index = 2 + device_field_indexes[threshold_sig] + field_index_cache[threshold_sig] = field_index + else: + field_index = field_index_cache[threshold_sig] + else: + # Use values from previous iteration for same device + #well_id = id2well_id[device_id] + + # Calculate days_decas exactly like original + #if data_type == "raw" or data_type == "all": + #days_decas = len(presence_map['raw'][well_id]) + #else: + # days_decas = len(presence_map['presence'][well_id]) + + # Use cached values + threshold_sig, threshold = threshold_sig_cache[device_id] + field_index = field_index_cache[threshold_sig] + + # Get radar value using cached field index + if field_index >= len(radar_read): + radar_val = radar_read[-1] + else: + radar_val = radar_read[field_index] + + + if well_id == 475: + print(".") + # Process presence data + if radar_val > threshold: + if deca < days_decas: + presence_map['longpresence'][well_id][deca] = 1 + + # Process raw data if needed + if data_type == "raw" or data_type == "all": + if deca < days_decas: + presence_map['raw'][well_id][deca] = radar_val + + return presence_map + +def CompressList(presence_devices_map): + + for key in presence_devices_map: + presence_map_list = presence_devices_map[key] + presence_map_list_compressed = Compress(presence_map_list) + presence_devices_map[key] = presence_map_list_compressed + + return presence_devices_map + +def Compress(presence_map_list): + + presence_map_list_compressed = [] + l = len(presence_map_list) + if l > 1: + last_data_point = presence_map_list[0] + presence_map_list_compressed.append([0, last_data_point]) + for i in range(1, l): + data_point = presence_map_list[i] + if data_point != last_data_point: + presence_map_list_compressed.append([i - 1, last_data_point]) + presence_map_list_compressed.append([i, data_point]) + last_data_point = data_point + presence_map_list_compressed.append([i, data_point]) + return presence_map_list_compressed + +def Decompress(pers_in_deka): + + last = pers_in_deka[-1] + last_index = 1 + last[1] + result = [0] * last_index + for points in pers_in_deka: + start_deca = points[0] + end_deca = points[1] + value_deca = points[2] + for i in range(start_deca, 1+end_deca): + result[i] = value_deca + return result + +def store_to_file(my_list, filename): + + try: + with open(filename, 'w') as f: + json.dump(my_list, f, indent=4) # indent for pretty printing + print(f"List saved to {filename} using JSON") + except IOError: + print(f"Error: Could not write to file {filename}") + except TypeError as e: + print(f"Error: Could not serialize list to JSON. {e}") # e.g. if list contains unsupported types like sets + +def find_custom_header(headers, name): + """Helper to find a custom header value (case-insensitive name).""" + if not headers: return None + for header in headers: + if header.get('name', '').lower() == name.lower(): return header.get('value') + return None + +def encode_state(parts): + """Joins parts with a pipe and base64 encodes the result.""" + plain_state = "|".join(map(str, parts)) + base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii') + # Assuming 'logger' is your app's logger instance + logger.debug(f"Encoded state: '{plain_state}' -> '{base64_state}'") + return base64_state + +def decode_state(b64_state): + """Decodes a base64 state and splits it by pipe.""" + if not b64_state: return [] + try: + decoded_plain = base64.b64decode(b64_state).decode('utf-8') + parts = decoded_plain.split('|') + logger.debug(f"Decoded state: '{b64_state}' -> '{decoded_plain}' -> {parts}") + return parts + except Exception as e: + logger.error(f"Failed to decode client_state '{b64_state}': {e}") + return [] + +def create_client_state(base_event, call_control_id, prefix): + """Create a base64 encoded client state string as required by Telnyx API""" + # Create the plain text client state string + plain_state = f"{prefix}_{base_event}_{call_control_id[:8]}" if call_control_id else f"{prefix}_{base_event}_unknownccid" + + # Encode to base64 as required by Telnyx API + base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii') + + logger.debug(f"Client state created: '{plain_state}' -> base64: '{base64_state}'") + return base64_state + +def send_telnyx_command(action_path, params, api_key): + """ + Sends a command to the Telnyx Call Control API actions endpoint. + This function should REPLACE your existing send_telnyx_command. + """ + if not api_key: + logger.error(f"CMDFAIL ('{action_path}'): API_KEY not available.") + return None + + ccid = params.get("call_control_id") + if not ccid: + logger.error(f"CMDFAIL ('{action_path}'): call_control_id missing in params.") + return None + + # Correct endpoint construction for V2 actions + endpoint = f"{TELNYX_API_BASE_URL}/calls/{ccid}/{action_path}" + + # Body should not contain call_control_id for actions API + body = {k: v for k, v in params.items() if k != 'call_control_id'} + + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + "Accept": "application/json" + } + + logger.info(f"SENDCMD ('{action_path}')") + logger.debug(f" Endpoint: POST {endpoint}") + logger.debug(f" JSON Payload: {json.dumps(body, indent=2)}") + + try: + response = requests.post(endpoint, json=body, headers=headers, timeout=10) + response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) + logger.info(f"CMDOK ('{action_path}'): Telnyx accepted. Status: {response.status_code}") + return response.json() + except requests.exceptions.HTTPError as e: + logger.error(f"CMDFAIL ('{action_path}'): Telnyx rejected. Status: {e.response.status_code}") + try: + logger.error(f" Telnyx Err Detail: {json.dumps(e.response.json(), indent=2)}") + except json.JSONDecodeError: + logger.error(f" Raw Err Body: {e.response.text[:500]}") + except requests.exceptions.RequestException as e: + logger.exception(f"CMDFAIL ('{action_path}'): Network error") + + return None + +def ParseAddress(address_string): + try: + payload = {"address": address_string} + + response = requests.post( + f"{base_url}/parse_address", + data=json.dumps(payload), + headers={"Content-Type": "application/json"} + ) + return response.json() + except Exception as e: + print(f"Error: {e}") + return {} + +def JoinAddress(address_map): + try: + payload = address_map + + response = requests.post( + f"{base_url}/join_address", + data=json.dumps(payload), + headers={"Content-Type": "application/json"} + ) + return response.json() + except Exception as e: + print(f"Error: {e}") + return {} + + +def StoreToDB(data): + + try: + + event_type = data.get('event_type') + sql = "" + + payload_json = json.dumps(data["payload"]) + call_session_id = data["payload"]['call_session_id'] + if event_type == "call.initiated": + timee = data.get('occurred_at') + sql = f""" + INSERT INTO public.alarms_voice ( + index, + "time", + call_session_id, + initiated + ) + VALUES ( + (SELECT COALESCE(MAX(index), 0) + 1 FROM public.alarms_voice), -- Auto-increment index + '{timee}'::timestamptz, -- occurred_at value + '{call_session_id}', -- call_session_id value + '{payload_json}' + ); """ + + elif event_type == "call.answered": + sql = f""" + UPDATE public.alarms_voice + SET answered = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.playback.started": + sql = f""" + UPDATE public.alarms_voice + SET playback_started = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.playback.ended": + sql = f""" + UPDATE public.alarms_voice + SET playback_ended = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.hangup": + sql = f""" + UPDATE public.alarms_voice + SET hangup = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + + if sql != "": + with get_db_connection() as conn: + with conn.cursor() as cur: + print(sql) + cur.execute(sql) + except Exception as e: + print ("Error in StoreToDB:", e) + +def handle_telnyx_webhook(webhook_data, remote_addr, request_id): + """Process Telnyx webhook events - Enhanced version with DTMF support""" + logger = logging.getLogger(__name__) + logger.info(f"Processing Enhanced Telnyx webhook from {remote_addr}, Request-ID: {request_id}") + + try: + data = webhook_data.get('data', {}) + event_type = data.get('event_type') + record_type = data.get('record_type') + payload = data.get('payload', {}) + + logger.info(f"Event: {event_type}, Record Type: {record_type}") + + if not event_type or not record_type: + logger.error("Missing event_type or record_type in webhook data") + return False + + call_control_id = payload.get('call_control_id') + call_session_id = payload.get('call_session_id') + + # Store to DB (existing functionality) + StoreToDB(data) + + # Handle SMS events (existing functionality) + if record_type == 'message': + logger.info(f"Processing SMS event: {event_type}") + return True + + # Handle voice events + if record_type == 'event': + logger.info(f"Processing voice event: {event_type}") + + # Get current client state and decode if needed + client_state_str = payload.get("client_state", "") + + # Parse state - check if it's a complex encoded state + state_parts = [] + state_name = "unknown" + + if client_state_str: + # Try to extract state name from client_state + if client_state_str.startswith(f"{CLIENT_STATE_PREFIX}_"): + state_suffix = client_state_str[len(f"{CLIENT_STATE_PREFIX}_"):] + + # Check if it's base64 encoded (complex state) + try: + import base64 + decoded = base64.b64decode(state_suffix).decode('utf-8') + state_parts = decoded.split('|') + state_name = state_parts[0] if state_parts else "unknown" + logger.debug(f"Decoded complex state: {state_parts}") + except: + # Simple state (existing format) + state_name = state_suffix + state_parts = [state_name] + logger.debug(f"Simple state: {state_name}") + else: + state_name = client_state_str + state_parts = [state_name] + + logger.info(f"Current state: {state_name}") + + # --- EVENT HANDLERS --- + + if event_type == 'call.initiated': + logger.info(f"Call initiated: From: {payload.get('from')}, To: {payload.get('to')}") + return True + + elif event_type == 'call.answered': + logger.info(f"Call answered: From: {payload.get('from')}, To: {payload.get('to')}") + + # Get custom headers and log them + custom_headers = payload.get('custom_headers', []) + logger.debug(f"Custom headers: {json.dumps(custom_headers)}") + + # Check for audio URL and TTS payload (existing logic) + audio_url = find_custom_header(custom_headers, 'X-Audio-Url') + tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') + + logger.info(f"Audio URL: {audio_url}, TTS Payload: {tts_payload}") + + # Play audio or TTS if URL/payload is provided + if ENABLE_AUDIO_PLAYBACK and audio_url: + logger.info(f"Playing audio: {audio_url}") + + # Create complex state for DTMF functionality + import base64 + complex_state = base64.b64encode("MAIN_MEDIA_PLAYED|audio|{}".format(audio_url).encode('utf-8')).decode('ascii') + client_state = f"{CLIENT_STATE_PREFIX}_{complex_state}" + + play_params = { + "call_control_id": call_control_id, + "client_state": client_state, + "audio_url": audio_url + } + result = send_telnyx_command("actions/playback_start", play_params, TELNYX_API_KEY) + logger.info(f"Play command result: {result}") + return True + + elif tts_payload: + logger.info(f"Speaking text: {tts_payload}") + + # Create complex state for DTMF functionality + import base64 + complex_state = base64.b64encode("MAIN_MEDIA_PLAYED|tts|{}".format(tts_payload).encode('utf-8')).decode('ascii') + client_state = f"{CLIENT_STATE_PREFIX}_{complex_state}" + + speak_params = { + "payload": tts_payload, + "voice": DEFAULT_TTS_VOICE, + "language": DEFAULT_TTS_LANGUAGE, + "call_control_id": call_control_id, + "client_state": client_state + } + result = send_telnyx_command("actions/speak", speak_params, TELNYX_API_KEY) + logger.info(f"Speak command result: {result}") + return True + else: + logger.warning("No audio URL or TTS payload found in call. Hanging up.") + hangup_params = { + "call_control_id": call_control_id, + "client_state": create_client_state("nohdr_hup", call_control_id, CLIENT_STATE_PREFIX) + } + send_telnyx_command("actions/hangup", hangup_params, TELNYX_API_KEY) + return True + + elif event_type in ['call.speak.ended', 'call.playback.ended']: + status = payload.get('status') + ended_event_type = event_type.split('.')[-2] # 'speak' or 'playback' + logger.info(f"Call {ended_event_type} ended: Status={status}") + + # Check what state we're in to determine next action + if state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: + # Main message finished, offer DTMF options + logger.info("Main message finished. Offering repeat options.") + + # Extract media info for potential replay + if len(state_parts) >= 3: + _, media_type, media_value = state_parts[:3] + + # Create state for waiting for DTMF + import base64 + waiting_state = base64.b64encode("WAITING_DTMF|{}|{}".format(media_type, media_value).encode('utf-8')).decode('ascii') + client_state = f"{CLIENT_STATE_PREFIX}_{waiting_state}" + + options_prompt = "Press 0 to repeat the message or press pound to hang up." + + # Get DTMF timeout from config or use default + dtmf_timeout = getattr(sys.modules[__name__], 'DTMF_TIMEOUT_SECONDS', 10) + + gather_params = { + "payload": options_prompt, + "voice": DEFAULT_TTS_VOICE, + "language": DEFAULT_TTS_LANGUAGE, + "valid_digits": "0#", + "max_digits": 1, + "timeout_millis": dtmf_timeout * 1000, + "terminating_digit": "#", + "call_control_id": call_control_id, + "client_state": client_state + } + + result = send_telnyx_command("actions/gather_using_speak", gather_params, TELNYX_API_KEY) + logger.info(f"Gather command result: {result}") + return True + else: + logger.warning("No media info available for replay options. Hanging up.") + + # Default behavior: hang up after media ends (existing behavior) + logger.info(f"Media playback finished. Hanging up.") + hangup_params = { + "call_control_id": call_control_id, + "client_state": create_client_state(f"{ended_event_type}_hup", call_control_id, CLIENT_STATE_PREFIX) + } + send_telnyx_command("actions/hangup", hangup_params, TELNYX_API_KEY) + return True + + elif event_type == 'call.dtmf.received': + digit = payload.get('digit') + logger.info(f"DTMF received: Digit='{digit}'") + + # Immediate hangup on '#' - don't wait for gather to end + if digit == '#': + logger.info("'#' received. Hanging up immediately.") + hangup_params = { + "call_control_id": call_control_id, + "client_state": create_client_state("dtmf_hup", call_control_id, CLIENT_STATE_PREFIX) + } + send_telnyx_command("actions/hangup", hangup_params, TELNYX_API_KEY) + + return True + + elif event_type == 'call.gather.ended': + digits = payload.get('digits', '') + gather_status = payload.get('status', '') + logger.info(f"Gather ended: Digits='{digits}', Status='{gather_status}'") + + # Handle DTMF input + if state_name == 'WAITING_DTMF' and len(state_parts) >= 3: + _, media_type, media_value = state_parts[:3] + + if digits == "0": + logger.info("'0' pressed. Replaying main message.") + + # Create state for replaying + import base64 + replay_state = base64.b64encode("REPLAYING_MEDIA|{}|{}".format(media_type, media_value).encode('utf-8')).decode('ascii') + client_state = f"{CLIENT_STATE_PREFIX}_{replay_state}" + + if media_type == "audio": + play_params = { + "audio_url": media_value, + "call_control_id": call_control_id, + "client_state": client_state + } + result = send_telnyx_command("actions/playback_start", play_params, TELNYX_API_KEY) + elif media_type == "tts": + speak_params = { + "payload": media_value, + "voice": DEFAULT_TTS_VOICE, + "language": DEFAULT_TTS_LANGUAGE, + "call_control_id": call_control_id, + "client_state": client_state + } + result = send_telnyx_command("actions/speak", speak_params, TELNYX_API_KEY) + + logger.info(f"Replay command result: {result}") + else: + # No repeat requested or timeout - hang up + logger.info("Gather ended without repeat request. Hanging up.") + hangup_params = { + "call_control_id": call_control_id, + "client_state": create_client_state("gather_hup", call_control_id, CLIENT_STATE_PREFIX) + } + send_telnyx_command("actions/hangup", hangup_params, TELNYX_API_KEY) + else: + logger.warning(f"Gather ended with unhandled state '{state_name}'") + + return True + + elif event_type == 'call.hangup': + logger.info(f"Call hung up: Cause={payload.get('cause')}") + return True + + else: + logger.info(f"Other voice event: {event_type}") + return True + + else: + logger.warning(f"Unknown record type: {record_type}") + return False + + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook: {e}") + return False + +# Assume these are defined globally or accessible (e.g., from app_args or .env) +# logger = logging.getLogger(...) +# ENABLE_AUDIO_PLAYBACK = True / False +# CLIENT_STATE_PREFIX = "app_state" +# DEFAULT_TTS_VOICE = "female" +# DEFAULT_TTS_LANGUAGE = "en-US" +# TELNYX_API_KEY = "YOUR_API_KEY" +# DTMF_GATHER_TIMEOUT_SECONDS = 15 # Wait 15 seconds for DTMF input + +# Placeholder for your DB function +# def StoreToDB(data): +# app_logger.debug(f"Placeholder: Storing to DB: {json.dumps(data)[:100]}") # Use app_logger + +# (Your existing find_custom_header, create_client_state, send_telnyx_command should be here) +# Make sure send_telnyx_command uses app_logger + +def handle_telnyx_webhook2(webhook_data, remote_addr, request_id): # Renamed logger to app_logger + """Process Telnyx webhook events with IVR logic.""" + logger.info(f"Processing Telnyx webhook from {remote_addr}, Request-ID: {request_id}") + + try: + data = webhook_data.get('data', {}) + event_type = data.get('event_type') + record_type = data.get('record_type') + payload = data.get('payload', {}) + + logger.info(f"Event: {event_type}, Record Type: {record_type}") + + if not event_type or not record_type: + logger.error("Missing event_type or record_type in webhook data") + return False # Indicate failure to process + + call_control_id = payload.get('call_control_id') + call_session_id = payload.get('call_session_id') + # Attempt to decode client_state if present + b64_client_state_rcvd = data.get("payload",{}).get("client_state") + plain_client_state_rcvd = "" + if b64_client_state_rcvd: + try: + plain_client_state_rcvd = base64.b64decode(b64_client_state_rcvd).decode('utf-8') + logger.info(f" Decoded Client State Received: '{plain_client_state_rcvd}'") + except Exception as e: + logger.warning(f" Could not decode client_state: {b64_client_state_rcvd}, Error: {e}") + plain_client_state_rcvd = "undecodable_state" + + + # Store all events to DB if needed + StoreToDB(webhook_data) # Pass the full webhook_data + + # Voice Event Handling + if record_type == 'event': + logger.info(f"Processing voice event: {event_type}, CCID: {call_control_id}") + + # --- Initial Call Setup --- + if event_type == 'call.initiated': + logger.info(f" Call initiated: From: {payload.get('from')}, To: {payload.get('to')}") + # No action needed here, wait for call.answered + + elif event_type == 'call.answered': + logger.info(f" Call answered: From: {payload.get('from')}, To: {payload.get('to')}") + custom_headers = payload.get('custom_headers', []) + logger.debug(f" Custom headers: {json.dumps(custom_headers)}") + + audio_url = find_custom_header(custom_headers, 'X-Audio-Url') + tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') + logger.info(f" X-Audio-Url: {audio_url}, X-TTS-Payload: {tts_payload}") + + # This state means the main message is about to be played. + # After it ends, we'll play the options prompt. + next_client_state = create_client_state("main_media_played", call_control_id, app_args.client_state_prefix) # Use app_args + + action_taken = False + if app_args.enable_audio_playback and audio_url: # Use app_args + logger.info(f" -> Playing main audio: {audio_url}") + play_params = {"call_control_id": call_control_id, "client_state": next_client_state, "audio_url": audio_url} + send_telnyx_command("actions/playback_start", play_params, app_args.api_key) # Use app_args + action_taken = True + elif tts_payload: + logger.info(f" -> Speaking main TTS: {tts_payload}") + speak_params = {"payload": tts_payload, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": call_control_id, "client_state": next_client_state} # Use app_args + send_telnyx_command("actions/speak", speak_params, app_args.api_key) # Use app_args + action_taken = True + + if not action_taken: + logger.warning(" -> No audio URL or TTS payload for main message. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("no_main_media_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + # --- Handling End of Main Media Playback --- + elif event_type in ['call.speak.ended', 'call.playback.ended']: + status = payload.get('status') + ended_event_type_root = event_type.split('.')[1] # speak or playback + + logger.info(f" Call {ended_event_type_root} ended: Status={status}, Current Decoded State='{plain_client_state_rcvd}'") + + # Check if the main media just finished playing + if plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_main_media_played"): + logger.info(" -> Main media finished. Playing DTMF options prompt.") + options_prompt_tts = "press 0 to repeat the message or press pound to hang up." + # This state means the options prompt is playing, and we're waiting for DTMF. + # gather_using_speak will trigger call.gather.ended + next_client_state = create_client_state("waiting_dtmf", call_control_id, app_args.client_state_prefix) + + gather_params = { + "call_control_id": call_control_id, + "client_state": next_client_state, + "payload": options_prompt_tts, + "voice": app_args.default_tts_voice, + "language": app_args.default_tts_language, + "valid_digits": "0#", # Only accept 0 or # + "max_digits": 1, # Expect only one digit + "timeout_millis": app_args.dtmf_timeout_seconds * 1000, # N seconds timeout + "terminating_digits": "#" # # will also terminate gather immediately + } + send_telnyx_command("actions/gather_using_speak", gather_params, app_args.api_key) + + elif plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_replaying_main_media"): + logger.info(" -> Replayed main media finished. Playing DTMF options prompt again.") + # Same logic as above for playing options prompt + options_prompt_tts = "press 0 to repeat the message or press pound to hang up." + next_client_state = create_client_state("waiting_dtmf", call_control_id, app_args.client_state_prefix) + gather_params = { + "call_control_id": call_control_id, "client_state": next_client_state, + "payload": options_prompt_tts, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, + "valid_digits": "0#", "max_digits": 1, "timeout_millis": app_args.dtmf_timeout_seconds * 1000, "terminating_digits": "#" + } + send_telnyx_command("actions/gather_using_speak", gather_params, app_args.api_key) + + else: + logger.warning(f" -> {ended_event_type_root} ended, but client_state ('{plain_client_state_rcvd}') doesn't match expected flow for options. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state(f"{ended_event_type_root}_unexpected_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + # --- Handling DTMF Input Result --- + elif event_type == 'call.gather.ended': + digits_received = payload.get('digits') + gather_status = payload.get('status') # e.g., 'completed_by_terminating_digit', 'timeout', 'call_hangup' + logger.info(f" Call Gather Ended: Digits='{digits_received}', Status='{gather_status}', Current Decoded State='{plain_client_state_rcvd}'") + + if plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_waiting_dtmf"): + if digits_received == "0": + logger.info(" -> DTMF '0' received. Replaying main message.") + # Replay the original message. We need to fetch it again from custom headers. + # This assumes the call.gather.ended payload still contains the original custom_headers. + # If not, we might need to store the original TTS/Audio URL in the client_state. + # For simplicity, let's assume custom_headers are still available or we re-evaluate. + # A more robust way would be to store the main message type/content in the client_state + # when transitioning from main_media_played. + + # Let's try to get custom_headers from the current payload. + # Telnyx usually includes original call details in subsequent events. + custom_headers = payload.get('custom_headers', []) # This might not be reliable for original headers + + # A BETTER APPROACH: Store original media info in client_state or retrieve from DB + # For this example, we'll try to re-evaluate based on what might be in custom_headers + # of the *call.gather.ended* event, which is NOT guaranteed to be the original ones. + # This part needs careful testing or a strategy to pass original media info. + + # Simplified: Assume we need to re-fetch original custom headers if they are not in this payload. + # For now, let's just log and assume we'd need a mechanism to get original X-TTS-Payload/X-Audio-Url + logger.warning(" -> Replay logic needs access to original X-TTS-Payload/X-Audio-Url. This example will try to use current custom_headers if any, or a default.") + + original_audio_url = find_custom_header(custom_headers, 'X-Audio-Url') # May not be original + original_tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') # May not be original + + next_client_state = create_client_state("replaying_main_media", call_control_id, app_args.client_state_prefix) + action_taken = False + if app_args.enable_audio_playback and original_audio_url: + logger.info(f" -> Replaying audio: {original_audio_url}") + play_params = {"call_control_id": call_control_id, "client_state": next_client_state, "audio_url": original_audio_url} + send_telnyx_command("actions/playback_start", play_params, app_args.api_key) + action_taken = True + elif original_tts_payload: + logger.info(f" -> Replaying TTS: {original_tts_payload}") + speak_params = {"payload": original_tts_payload, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": call_control_id, "client_state": next_client_state} + send_telnyx_command("actions/speak", speak_params, app_args.api_key) + action_taken = True + + if not action_taken: + logger.error(" -> Could not find original media to replay. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("replay_fail_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + elif digits_received == "#" or (gather_status == 'completed_by_terminating_digit' and payload.get('terminating_digit') == '#'): + logger.info(" -> DTMF '#' received or terminating digit. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_pound_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + elif gather_status == 'timeout': + logger.info(" -> DTMF gather timed out. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_timeout_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + else: + logger.warning(f" -> Gather ended with unhandled digits '{digits_received}' or status '{gather_status}'. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_unhandled_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + else: + logger.warning(f" -> Gather ended, but client_state ('{plain_client_state_rcvd}') doesn't match waiting_dtmf. Ignoring.") + + + elif event_type == 'call.hangup': + app_logger.info(f" Call Hangup Event: Cause='{payload.get('cause')}', SIPCause='{payload.get('sip_hangup_cause')}', Source='{payload.get('hangup_source')}'") + # Call is already over, no command to send. + + # Log other voice events not explicitly handled above for visibility + elif event_type not in ['call.initiated', 'call.answered', 'call.speak.ended', 'call.playback.ended', 'call.gather.ended', 'call.hangup', 'call.speak.started', 'call.playback.started']: + logger.info(f" Other Voice Event: Type='{event_type}'. Payload: {json.dumps(payload, indent=2)}") + + + # --- SMS Event Handling (Placeholder from your snippet) --- + elif record_type == 'message': + logger.info(f"Processing SMS event: {event_type}") + # Your existing SMS handling code would go here... + # For now, just acknowledge + logger.info(" -> SMS ACK (204)") + return Response(status=204) # Ensure SMS events are also ACKed + + else: + logger.warning(f"Unknown record type: {record_type}") + # Acknowledge to prevent retries from Telnyx + logger.info(" -> Unknown Record Type ACK (204)") + return Response(status=204) + + # If we reached here for a voice event and didn't send a command through send_telnyx_command, + # it means we are just acknowledging the event. + logger.info(" -> Voice Event Processed (no immediate command sent or command sent async). ACK (204) to Telnyx.") + return Response(status=204) # ALWAYS ACK THE WEBHOOK + + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook: {e}") + # Still try to ACK Telnyx if possible, but log the error. + # Depending on the error, Telnyx might retry if it doesn't get a 2xx. + return "Internal Server Error", 500 + +def handle_telnyx_webhook3(webhook_data, remote_addr, request_id): + """ + Processes Telnyx webhook events with full IVR logic for repeating messages. + This function should be added to your well-api.py. + """ + logger.info(f"Processing webhook in handle_telnyx_webhook3 from {remote_addr}, Request-ID: {request_id}") + + # --- ADAPT THIS SECTION to your app's config management --- + # This example assumes config values are accessible as global constants or from a dict. + # Replace these with your actual config access method (e.g., self.config['...']) + config = { + 'api_key': TELNYX_API_KEY, + 'dtmf_timeout_seconds': 10, + 'initial_silence_ms': 500, + 'replay_silence_ms': 100, + 'default_tts_voice': 'female', + 'default_tts_language': 'en-US', + 'client_state_prefix': 'well_api_state', + 'inbound_greeting': 'Thank you for calling. We will be with you shortly.' + } + # --- END ADAPTATION SECTION --- + + try: + StoreToDB(webhook_data) # Call your DB storage function first + + data, payload = webhook_data.get('data', {}), webhook_data.get('data', {}).get('payload', {}) + event_type, record_type, ccid = data.get('event_type'), data.get('record_type'), payload.get('call_control_id') + logger.info(f"EVENT '{event_type}' ({record_type})" + (f", CCID: {ccid}" if ccid else "")) + + if record_type != 'event': + logger.info(f" -> Non-voice event ('{record_type}') received. Ignoring in this handler.") + return True + + b64_client_state = payload.get("client_state") + decoded_parts = decode_state(b64_client_state) + state_name = decoded_parts[0] if decoded_parts else None + if state_name: logger.info(f" State Name Received: '{state_name}'") + + current_api_key = config['api_key'] + + # --- State Machine Logic --- + if event_type == 'call.answered': + if payload.get('direction') == 'incoming': + logger.info(" -> Inbound call detected. Playing generic greeting and hanging up.") + next_state = encode_state(['INBOUND_GREETING_HUP']) + speak_params = {"payload": config['inbound_greeting'], "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", speak_params, current_api_key) + else: # Outgoing call + audio_url = find_custom_header(payload.get('custom_headers'), 'X-Audio-Url') + tts_payload = find_custom_header(payload.get('custom_headers'), 'X-TTS-Payload') + media_type = "audio" if audio_url else "tts" if tts_payload else "none" + media_value = audio_url or tts_payload + if media_value: + logger.info(f" -> Outbound call. Playing {config['initial_silence_ms']}ms silence buffer.") + next_state = encode_state(['INIT_PLAY_MAIN', media_type, media_value]) + send_telnyx_command("actions/play_silence", {"milliseconds": str(config['initial_silence_ms']), "call_control_id": ccid, "client_state": next_state}, current_api_key) + else: + logger.warning(" -> Outbound call, but no audio/tts payload. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.playback.ended': + if state_name == 'INIT_PLAY_MAIN': # Silence ended + logger.info(" -> Silence buffer ended. Playing main message.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['MAIN_MEDIA_PLAYED', media_type, media_value]) + if media_type == "audio": + send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key) + elif media_type == "tts": + params = {"payload": media_value, "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", params, current_api_key) + elif state_name == 'REPLAY_SILENCE': # Replay silence ended + logger.info(" -> Replay silence ended. Replaying main message.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['REPLAYING_MEDIA', media_type, media_value]) + if media_type == "audio": + send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key) + elif media_type == "tts": + params = {"payload": media_value, "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", params, current_api_key) + elif state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: # Actual audio file ended + logger.info(f" -> Main audio playback finished. Playing options menu.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['WAITING_DTMF', media_type, media_value]) + options_prompt = "press 0 to repeat the message or press pound to hang up." + gather_params = { + "payload": options_prompt, "voice": config['default_tts_voice'], "language": config['default_tts_language'], + "valid_digits": "0#", "max_digits": 1, "timeout_millis": config['dtmf_timeout_seconds'] * 1000, "terminating_digit": "#", + "call_control_id": ccid, "client_state": next_state + } + send_telnyx_command("actions/gather_using_speak", gather_params, current_api_key) + else: + logger.warning(f" -> Playback ended with unhandled state '{state_name}'. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.speak.ended': + if state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: + logger.info(f" -> Main message TTS finished. Playing options menu.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['WAITING_DTMF', media_type, media_value]) + options_prompt = "press 0 to repeat the message or press pound to hang up." + gather_params = { + "payload": options_prompt, "voice": config['default_tts_voice'], "language": config['default_tts_language'], + "valid_digits": "0#", "max_digits": 1, "timeout_millis": config['dtmf_timeout_seconds'] * 1000, "terminating_digit": "#", + "call_control_id": ccid, "client_state": next_state + } + send_telnyx_command("actions/gather_using_speak", gather_params, current_api_key) + elif state_name == 'INBOUND_GREETING_HUP': + logger.info(" -> Inbound greeting finished. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + else: + logger.warning(f" -> Speak ended with unhandled state '{state_name}'. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.dtmf.received': + digit = payload.get('digit') + logger.info(f" DTMF Received: Digit='{digit}'") + if digit == '#': + logger.info(" -> '#' received. Terminating call immediately.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.gather.ended': + logger.info(f" -> Gather ended. Digits received: '{payload.get('digits')}', Status: '{payload.get('status')}'") + if state_name == 'WAITING_DTMF': + digits = payload.get('digits') + _, media_type, media_value = decoded_parts + if digits == "0": + logger.info(f" -> '0' pressed. Playing {config['replay_silence_ms']}ms silence before replay.") + next_state = encode_state(['REPLAY_SILENCE', media_type, media_value]) + send_telnyx_command("actions/play_silence", {"milliseconds": str(config['replay_silence_ms']), "call_control_id": ccid, "client_state": next_state}, current_api_key) + else: + logger.info(" -> Gather ended with non-repeat condition. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + else: + logger.warning(f" -> Gather ended with unhandled state '{state_name}'.") + + elif event_type == 'call.hangup': + logger.info(f" Call Hangup Event: Cause='{payload.get('cause')}'") + else: + logger.info(f" -> Unhandled Voice Event: '{event_type}' with state '{state_name}'.") + + return True # Return app-specific success + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook3: {e}") + return False + + +def FilterDevicesByDeviceId(devices_list, device_id_str): + """ + Filter devices list to include only the specified device_id. + + Parameters: + devices_list: tuple of (device_details_list, device_ids_list) + device_id_str: string representation of device_id to filter by + + Returns: + tuple: filtered (device_details_list, device_ids_list) + """ + try: + target_device_id = int(device_id_str) + except ValueError: + return ([], []) + + device_details_list, device_ids_list = devices_list + + filtered_details = [] + filtered_ids = [] + + for i, device_details in enumerate(device_details_list): + device_id = device_details[1] # device_id is second element (index 1) + if device_id == target_device_id: + filtered_details.append(device_details) + filtered_ids.append(device_ids_list[i]) + + return (filtered_details, filtered_ids) + + +def FilterSensorsBySensorType(sensor_type): + """ + Filter s_table to include only the specified sensor type. + + Parameters: + sensor_type: string name of sensor type (e.g., 'temperature', 'radar', 'voc0', etc.) + + Returns: + list: filtered s_table containing only the mapped sensor name + """ + # Map user-friendly sensor names to their s_table equivalents + sensor_mapping = { + 'temperature': 'avg_temperature', + 'humidity': 'avg_humidity', + 'pressure': 'pressure_amplitude', + 'light': 'max_light', + 'radar': 'radar', + 'voc0': 'sensor_min_s0', + 'voc1': 'sensor_min_s1', + 'voc2': 'sensor_min_s2', + 'voc3': 'sensor_min_s3', + 'voc4': 'sensor_min_s4', + 'voc5': 'sensor_min_s5', + 'voc6': 'sensor_min_s6', + 'voc7': 'sensor_min_s7', + 'voc8': 'sensor_min_s8', + 'voc9': 'sensor_min_s9' + } + + # Get the actual sensor name used in s_table + mapped_sensor = sensor_mapping.get(sensor_type, sensor_type) + + # Return the mapped sensor name if it's valid, otherwise empty list + if mapped_sensor in sensor_mapping.values() or mapped_sensor == sensor_type: + return [mapped_sensor] + + return [] + + +def set_character(some_string, bit_nr, new_char): + """ + Replace a character in a string at position bit_nr from the right. + + Parameters: + some_string (str): The input string + bit_nr (int): Position from right (0 = rightmost, 1 = second from right, etc.) + new_char (str): The replacement character + + Returns: + str: The modified string + """ + if bit_nr < 0 or bit_nr >= len(some_string): + return some_string # Invalid position + + # Convert string to list for easier manipulation + chars = list(some_string) + + # Replace character at position bit_nr from right + chars[len(chars) - 1 - bit_nr] = new_char + + # Convert back to string + return ''.join(chars) + +def GetBit(alarm_armed_settings, bit_nr): + if bit_nr <0 or bit_nr >= len(alarm_armed_settings): + return False + return alarm_armed_settings[-(bit_nr+1)] == "1" + +def CreateSensorsMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by, filtered_s_table): + """ + Create a sensor map with filtered devices and sensors. + Based on CreateMapFast but with filtering support. + + Parameters: + map_file: output file path + devices_list: filtered devices list + selected_date: date string + bw: black and white flag + time_zone_s: timezone string + radar_part: radar part specification + group_by: grouping strategy + filtered_s_table: filtered sensor table + + Returns: + tuple: (success_boolean, vocs_scaled_array) + """ + global Id2MACDict + + st = time.time() + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + + try: + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + + for details in devices_list[0]: + well_id = details[0] + ids_list.append(details[1]) + if well_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return False, [] + + if larger_than200 > 0: + sensors_c = len(filtered_s_table) + else: # old sensors not supported + return False, [] + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + image_file = map_file + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + temp_offset = -10 + + # Use filtered sensor table for queries + if sensors_c > 1: + sql = get_deployment_query_filtered( + devices_list_str, + time_from_str, + time_to_str, + ids_list, + radar_part, + temp_offset, + filtered_s_table + ) + else: + sql = get_deployment_single_query( + devices_list_str, + time_from_str, + time_to_str, + ids_list, + radar_part, + temp_offset, + filtered_s_table[0] + ) + + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + day_data = cur.fetchall() + if day_data == None: + return False, [] + + stretch_by = 10 + minutes = 1440 + stripes = devices_c * sensors_c + arr_source_template = np.full((stripes, minutes + 4), -0.001, dtype=float) + arr_stretched_template = np.zeros((int(stripes * stretch_by), minutes, 3), dtype=np.uint8) + + # Use filtered sensor table + arr_source = fast_fill_array_from_timescale_filtered( + day_data, + time_from_str, + devices_list[1], + arr_source_template, + filtered_s_table, + time_zone_s + ) + + arr_source = AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_table, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) + SaveImageInBlob(image_file, arr_stretched) + return True, vocs_scaled + + except Exception as e: + AddToLog(traceback.format_exc()) + return False, [] + + +def get_deployment_query_filtered(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, filtered_s_table): + """ + Generate a filtered TimeScaleDB query for specific sensors only. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name + temp_offset (float): Temperature offset + filtered_s_table (list): List of sensor names to include + + Returns: + str: Generated SQL query + """ + # Generate the CASE statement for ordering + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # Build sensor-specific SELECT clauses + sensor_selects = [] + sensor_aggregates = [] + radar_needed = False + + for sensor in filtered_s_table: + if sensor == "temperature": + sensor_selects.append(f"sr.avg_temperature+ {temp_offset} as avg_temperature") + sensor_aggregates.append("AVG(temperature) AS avg_temperature") + elif sensor == "humidity": + sensor_selects.append("sr.avg_humidity") + sensor_aggregates.append("AVG(humidity) AS avg_humidity") + elif sensor == "pressure": + sensor_selects.append("sr.pressure_amplitude") + sensor_aggregates.append("AVG(pressure) AS pressure_amplitude") + elif sensor == "light": + sensor_selects.append("sr.max_light") + sensor_aggregates.append("MAX(light) AS max_light") + elif sensor == "radar": + sensor_selects.append("rr.radar") + radar_needed = True + elif sensor.startswith("voc"): + # Extract sensor number (e.g., sensor_min_s0 -> s0) + sensor_num = sensor.replace("voc", "") + sensor_selects.append(f"sr.{sensor}") + sensor_aggregates.append(f"MIN(s{sensor_num}) AS {sensor}") + + # Build the query + if radar_needed and sensor_aggregates: + # Need both sensor readings and radar readings + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + {', '.join(sensor_selects)} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {', '.join(sensor_aggregates)} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + elif radar_needed: + # Only radar needed + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + else: + # Only sensor readings needed + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {', '.join(sensor_aggregates)} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + + return sql + + + +def fast_fill_array_from_timescale_filtered(day_data, time_from_str, devices_list, arr_source, filtered_s_table, timezone_str="Europe/Berlin"): + """ + Optimized array filling for filtered sensors. + + Parameters: + day_data: query results + time_from_str: start time string + devices_list: list of device IDs + arr_source: array to fill + filtered_s_table: list of sensor names to process + timezone_str: timezone string + + Returns: + numpy array: filled array + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Pre-process data into a more efficient structure + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Build column mapping based on filtered sensors + columns = {} + col_idx = 2 # Start after time and device_id + + for sensor in filtered_s_table: + columns[sensor] = col_idx + col_idx += 1 + + # Process each device's data + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(filtered_s_table) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1] - 4) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each filtered sensor + for sensor_idx, sensor_name in enumerate(filtered_s_table): + if sensor_name in columns: + row_idx = base_idx + sensor_idx + values = records_array[:, columns[sensor_name]] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + + +def AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_table, percentile): + """ + Vectorized version of AddLimits for filtered sensors. + + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + filtered_s_table: list of sensor names + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + + # Create arrays of sensor legal values for filtered sensors + min_vals = [] + max_vals = [] + windows = [] + + for sensor_name in filtered_s_table: + if sensor_name in sensor_legal_values: + min_vals.append(sensor_legal_values[sensor_name][0]) + max_vals.append(sensor_legal_values[sensor_name][1]) + windows.append(sensor_legal_values[sensor_name][2]) + else: + # Default values if sensor not found + min_vals.append(0) + max_vals.append(1000) + windows.append(1) + + # Repeat for each device + min_vals = np.tile(min_vals, devices_c) + max_vals = np.tile(max_vals, devices_c) + windows = np.tile(windows, devices_c) + + # Process rows that need cleaning (window > 2) + clean_mask = windows > 2 + if np.any(clean_mask): + for window in np.unique(windows[clean_mask]): + rows_to_clean = np.where(clean_mask & (windows == window))[0] + + for row_idx in rows_to_clean: + arr_source[row_idx, :1440] = clean_data_vectorized( + arr_source[row_idx, :1440], + window, + percentile + ) + + # Set min/max values for all rows + arr_source[:, 1440] = min_vals + arr_source[:, 1441] = max_vals + + return arr_source + +def GetNextWellId(min_well_id): + + conn = get_db_connection() + + sql = """ + SELECT COALESCE(MAX(well_id), 0) AS max_well_id + FROM public.devices + """ + + try: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + if result == None: + return min_well_id + else: + max_wel_id = result[0] + if max_wel_id != None and min_well_id != None: + if max_wel_id + 1 > min_well_id: + return max_wel_id + 1 + else: + return min_well_id + else: + if max_wel_id != None: + return max_wel_id + 1 + + except Exception as e: + return min_well_id + + +def GetAlarmAllDetails(deployment_id): + + + device_alarms_json_map = {} + devices = GetVisibleDevices(deployment_id) + deployment_alarms_json, dummy = GetAlarmSimple(deployment_id, 0) + + for device in devices: + device_id = device[0] + dummy, device_alarm_json = GetAlarmSimple(0, device_id) + device_alarms_json_map[device_id] = device_alarm_json + return deployment_alarms_json, device_alarms_json_map + +def GetCalibMaps(device_ids_list): + temp_calib = {} + humid_calib = {} + with get_db_connection() as conn: + with conn.cursor() as cur: + #list all devices that user has access to + sql = f"SELECT device_id, temperature_calib, humidity_calib FROM public.devices WHERE device_id in ({str(device_ids_list)[1:-1]})" + print(sql) + cur.execute(sql) + calib_records = cur.fetchall()#cur.fetchone() + for record in calib_records: + temp_calib[record[0]] = record[1] + humid_calib[record[0]] = record[2] + + return temp_calib, humid_calib +#==================================== ADD FUNCTIONS BEFORE ============================================ + +# Main API class +class WellApi: + def on_get_healthz(self, req, resp): + """Health check endpoint""" + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_TEXT + resp.text = "OK" + + def on_get(self, req, resp, path=""): + """Handle GET requests""" + global s_table_temp + + logger.debug(f"GET request to path: {path}") + logger.debug(f"Sent variables: {req.params}") + logger.debug(f"All headers: {dict(req.headers)}") + if path == "" or path == "/": + # Serve the main portal page + blob_data = read_file("well_portal.html") + if blob_data: + resp.content_type = "text/html" + resp.text = blob_data + else: + # Fall back to JSON response if file not found + resp.media = {"message": "Hello from OpenFaaS Serverless Web Server!", "method": "GET"} + return + elif path == "favicon.ico": + favicon_path = "favicon.ico" + if os.path.isfile(favicon_path): + resp.content_type = 'image/x-icon' + resp.data = read_file(favicon_path, type_="BIN") + resp.status = HTTP_200 + else: + resp.status = HTTP_404 + return + elif path == "health": + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"status": "healthy"}) + return + + # Authentication and authorization + token = req.params.get('token') + user_name = req.params.get('user_name') + ps = req.params.get('ps') + + if ps != "" and ps != None: + #was token sent in ps field? This allows for token and ps be populated by token or ps + user_info = verify_token(ps) + if user_info["username"] == user_name: + token = ps + else: + #is this valid password? + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + + + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + get_function_name = req.params.get('name') + logger.debug(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] - {__name__}.GET_API->{get_function_name}") + privileges = GetPriviledgesOnly(user_name) + + if (token and user_name) or (token and user_name): + user_info = verify_token(token) + if user_info is None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + get_function_name = req.params.get('name') + logger.debug(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] - {__name__}.GET_API->{get_function_name}") + + if get_function_name == "deployment_add": + user_id = req.params.get('user_id') + blob_data = read_file("edit_deployment.html") + caretaker = {'deployment_id': 0, 'beneficiary_id': user_id, 'caretaker_id': user_id, 'owner_id': user_id, 'installer_id': user_id, 'user_id': 0, 'role_ids': '2', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "devices_list": + st = time.time() + user_name = req.params.get('user_name') + privileges = GetPriviledgesOnly(user_name) + first_s = req.params.get('first') + last_s = req.params.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + blob_data = read_file("my_devices.html") + + devices = GetVisibleDevices(privileges) + users = GetUsersFromDeployments(privileges) + blob_data = UpdateDevicesTable(blob_data, devices, users) + blob_data = UpdateDeploymentsSelector(blob_data, users) + resp.content_type = "text/html" + resp.text = blob_data + #print(blob_data) + return + + elif get_function_name == "deployment_edit": + deployment_id = req.params.get('deployment_id') + blob_data = read_file("edit_deployment.html") + + deployment = DeploymentDetails(deployment_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, deployment, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "caretaker_add": + + blob_data = read_file("edit_caretaker.html") + caretaker = {'user_id': 0, 'role_ids': '2', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "caretaker_edit": + user_id = req.params.get('user_id') + blob_data = read_file("edit_caretaker.html") + + caretaker = UserDetails(user_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "device_add": + + blob_data = read_file("edit_device.html") + device = {'device_id': 0, 'device_mac': '', 'well_id': '', 'description': '', 'location': '', 'close_to': '', 'radar_threshold': '["s3_max",12]', 'temperature_calib': '0.0,1.0,0.0', 'humidity_calib': '0.0,1.0,0.0'} + blob_data = FillFields(blob_data, device, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "device_edit": + mac = req.params.get('mac') + + blob_data = read_file("edit_device.html") + + device_det = DeviceDetails(mac) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, device_det, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + + elif get_function_name == "beneficiary_edit": + user_id = req.params.get('user_id') + blob_data = read_file("edit_beneficiary.html") + + beneficiary = UserDetails(user_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, beneficiary, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "beneficiary_add": + + blob_data = read_file("edit_beneficiary.html") + beneficiary = {'user_id': 0, 'role_ids': '1', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, beneficiary, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "get_image_file": + #image represents day in local time + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + group_by = req.params.get("group_by") + timee = StringToEpoch(ddate, time_zone_s) + force_recreate = req.params.get("re_create") == "true" + radar_part = req.params.get("radar_part") + map_type = int(req.params.get("map_type")) + + bw = req.params.get("bw") == "true" + unique_identifier = req.params.get("unique_identifier") + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{group_by}_{radar_part}_{map_type}_{bw}_dayly_image.png" + + #print(check_file_exists(filename)) + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + #ddate is in Local Time + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + #time that describes new devices in deployment_history is in UTC therefore timee is in UTC + + st = time.time() + vocs_scaled = {} + #file_date is in Local time, so we are comparing that and current Local (to install) Date + if force_recreate: + st = time.time() + vocs_scaled = {} + devices_list = GetProximityList(deployment_id, timee) + s_table_temp = s_table + stored, vocs_scaled = CreateMapFast(filename, devices_list, ddate, bw, time_zone_s, radar_part, group_by) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + if stored != True: + AddToLog("Map not created") + #logger.warning("Map not created") + resp.media = package_response("Map not created", HTTP_401) + return + else: + AddToLog("Map created") + #lets send over MQTT vocs_scaled + json_data = numpy_to_json(vocs_scaled, devices_list) + MQSendL("/"+unique_identifier, json_data) + #print(time.time() - st) + + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + sys.stdout.flush() + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_sensors_map": + # Get filtering parameters + device_id_str = req.params.get('device_id') + sensor = req.params.get('sensor') + + # Get standard image parameters + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_", "-") + group_by = ""#req.params.get("group_by") + timee = StringToEpoch(ddate, time_zone_s) + force_recreate = "true" + radar_part = req.params.get("radar_part") + bw = req.params.get("bw") == "true" + unique_identifier = req.params.get("unique_identifier") + + # Create filename with filtering parameters + filter_suffix = "" + if device_id_str: + filter_suffix += f"_dev{device_id_str}" + if sensor: + filter_suffix += f"_sens{sensor}" + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{radar_part}_{bw}{filter_suffix}_sensors_map.png" + + # Check if file exists and needs recreation + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + # Convert date to UTC epoch for device queries + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 5 # add so date boundary is avoided + + st = time.time() + vocs_scaled = {} + + if force_recreate: + st = time.time() + vocs_scaled = {} + + # Get initial device list + devices_list = GetProximityList(deployment_id, timee) + + # Apply device filtering if specified + if device_id_str: + filtered_devices = FilterDevicesByDeviceId(devices_list, device_id_str) + else: + filtered_devices = devices_list + + # Apply sensor filtering if specified + if sensor: + filtered_s_table = [sensor]#FilterSensorsBySensorType(sensor) + else: + filtered_s_table = s_table + + # Validate we have devices and sensors to process + if not filtered_devices[0] or not filtered_s_table: + AddToLog("No devices or sensors match the specified filters") + resp.media = package_response("No devices or sensors match the specified filters", HTTP_400) + return + + # Create the filtered map + stored, vocs_scaled = CreateSensorsMapFast( + filename, + filtered_devices, + ddate, + bw, + time_zone_s, + radar_part, + group_by, + filtered_s_table + ) + + if stored != True: + AddToLog("Sensors map not created") + resp.media = package_response("Sensors map not created", HTTP_401) + return + else: + AddToLog("Sensors map created") + # Send over MQTT vocs_scaled + json_data = numpy_to_json(vocs_scaled, filtered_devices) + MQSendL("/" + unique_identifier, json_data) + + # Read and send image from blob + image_bytes, content_type = GetBlob(filename) + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + sys.stdout.flush() + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_full_location_map": + + raw = req.params.get("raw") == "true" + + if raw: + #function=request_deployment_map_new + #token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvYnN0ZXIiLCJleHAiOjE3MzgxNzYzNTZ9.5wzC2dVQhKlMygHPZfombTINbltNq8vxdilLIugNTtA& + #user_name=robster& + #date=2025-01-27& + #deployment_id=21& + #map_type=2 + chart_type = 8 + else: + chart_type = int(req.params.get("map_type")) + + #image represents day in local time + logger.debug("get_full_location_map") + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + + to_date = ddate + + try: + to_date = req.params.get("to_date") + to_date = to_date.replace("_","-") + except: + pass + + if to_date != ddate: + chart_type = int(req.params.get("map_type")) + + force_recreate = req.params.get("re_create") == "true" + force_recreate_orig = force_recreate + scale_global = req.params.get("scale_global") == "true" + fast = req.params.get("fast") == "true" + bw = req.params.get("bw") == "true" + motion = req.params.get("motion") == "true" + timee = StringToEpoch(ddate, time_zone_s) + + filter_minutes = int(req.params.get("filter")) + + if "flavor" in req.params: #this is to be used only when creating + flavor = int(req.params.get("flavor")) + else: + flavor = 0 + + if bw: + bw_s = "BW" + else: + bw_s = "CLR" + + if fast: + fast_s = "FAST" + else: + fast_s = "SLOW" + + if motion: + motion_s = "M" + else: + motion_s = "S" + + if scale_global: + scl_s = "scl" + else: + scl_s = "nscl" + + + if chart_type == 5 or chart_type == 7: + #now_date = req.params.get("now_date") + #now_date = now_date.replace("_","-") + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{to_date}_{filter_minutes}_history_image.png" + else: + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{bw_s}_{motion_s}_{scl_s}_{chart_type}_Flocation_image.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + #file_exists1, time_modified_utc1 = check_file_exists(filename+".bin") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - time_modified_local + #if time_passed.seconds > 300: #recreate if older than 5 minutes + # force_recreate = True + + else: + force_recreate = True + + if force_recreate: + ddate = ddate.replace("_","-") + #filter_minutes = 5 + #filename = os.path.join(scriptDir+"/daily_maps/"+deployment, proximity_string+"_"+deployment+"_"+ddate+"_dayly_image.png") + + filename = filename.replace('\\','/') + if chart_type == 4: #"collapsed": + GenerateFullLocationMap(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes) + elif chart_type == 5: #"history": + GeneratePresenceHistory(filename, force_recreate, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + elif chart_type == 7: #"history full chart": + filename = GeneratePresenceHistoryChart(filename, force_recreate_orig, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + elif chart_type == 8: #"set for mobile" + GenerateFullLocationMapLabelsOut(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast,time_zone_s, filter_minutes) + else: + GenerateFullLocationMap(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast,time_zone_s, filter_minutes) + + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_presence_map": + #image represents day in local time + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + force_recreate = req.params.get("re_create") == "true" + scale_global = req.params.get("scale_global") == "true" + fast = req.params.get("fast") == "true" + bw = req.params.get("bw") == "true" + motion = req.params.get("motion") == "true" + timee = StringToEpoch(ddate, time_zone_s) + chart_type = int(req.params.get("map_type")) + filter_minutes = int(req.params.get("filter")) + + if bw: + bw_s = "BW" + else: + bw_s = "CLR" + + if fast: + fast_s = "FAST" + else: + fast_s = "SLOW" + + if motion: + motion_s = "M" + else: + motion_s = "S" + + if scale_global: + scl_s = "scl" + else: + scl_s = "nscl" + + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{bw_s}_{motion_s}_{scl_s}_{chart_type}_Flocation_image.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + ddate = ddate.replace("_","-") + days = 7 + + filename = filename.replace('\\','/') + if chart_type == 6: #"AI Locations": + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreatePresenceMap(filename, devices_list, ddate, 1, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + + return + elif get_function_name == "download": + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + date_from = req.params.get("date_from") + date_to = req.params.get("date_to") + date_from = date_from.replace("_","-") + date_to = date_to.replace("_","-") + consolidated_by = req.params.get("consolidated_by") + force_recreate = req.params.get("re_create") == "true" + radar_part = req.params.get("radar_part") + zip_filename = f"/{deployment_id}/{deployment_id}_{date_from}_{date_to}_{consolidated_by}_data.zip" + + #print(check_file_exists(filename)) + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(zip_filename, bucket_name="data-downloads") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = CSVFileToDate(zip_filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + #ddate is in Local Time + dates = DatesSpan(date_from, date_to) + to_zip = [] + for ddate in dates: + force_recreate_csv = force_recreate + csv_dayly_filename = f"/{deployment_id}/{deployment_id}_{ddate}_{consolidated_by}_data.csv" + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + if not force_recreate_csv: + #time that describes new devices in deployment_history is in UTC therefore timee is in UTC + file_exists, time_modified_utc = check_file_exists(csv_dayly_filename, bucket_name="data-downloads") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = CSVFileToDate(csv_dayly_filename) + if time_modified_date <= file_date: + force_recreate_csv = True + else: + force_recreate_csv = True + st = time.time() + vocs_scaled = {} + #file_date is in Local time, so we are comparing that and current Local (to install) Date + if force_recreate_csv: + st = time.time() + vocs_scaled = {} + + devices_list = GetProximityList(deployment_id, timee) + temp_offset = -10 + file_stored = CreateDailyCSV(csv_dayly_filename, devices_list, ddate, vocs_scaled, time_zone_s, radar_part, consolidated_by, temp_offset) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + to_zip.append(file_stored) + else: + to_zip.append(csv_dayly_filename) + + if to_zip: + success = zip_blobs( + blob_paths=to_zip, + zip_blob_name=zip_filename, + bucket_name="data-downloads", + minio_client=miniIO_blob_client + ) + + if success: + print("Files successfully zipped") + else: + print("Error occurred while zipping files") + + #pack CSV files from BLOB into ZIP + #lets read and send image from blob + zip_bytes, content_type = GetBlob(zip_filename, bucket_name="data-downloads") + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if zip_bytes is None: + raise falcon.HTTPNotFound( + title='File not found', + description=f'File {zip_filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = zip_bytes + resp.status = falcon.HTTP_200 + return + resp.media = package_response("Use POST method for this endpoint", HTTP_400) + + # Default response for unmatched paths + #resp.media = package_response(f"Path: /{path}", HTTP_200) + + def on_post(self, req, resp, path=""): + #ToDo make sure that any read/write data functions are authorized for this user_name + global threshold_cache, device_lookup_cache + + """Handle POST requests""" + logger.debug(f"on_post called with path: {path}") + logger.debug(f"Request method: {req.method}") + logger.debug(f"Request path: {req.path}") + logger.debug(f"Request query string: {req.query_string}") + logger.debug(f"Request headers: {req.headers}") + logger.debug(f"Request content type: {req.content_type}") + + + # First, check if this is a Telnyx webhook request + is_telnyx_webhook = ( + req.content_type and 'application/json' in req.content_type and + req.headers.get('USER-AGENT') == 'telnyx-webhooks' + ) + + if is_telnyx_webhook: + logger.info("Processing Telnyx webhook request") + try: + # Read the raw request body + raw_body = req.stream.read().decode('utf-8') + logger.debug(f"Raw webhook request body: {raw_body}") + + if not raw_body: + logger.error("Empty request body received from Telnyx") + resp.status = falcon.HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Empty request body"}) + return + + # Parse JSON + webhook_data = json.loads(raw_body) + logger.debug(f"Parsed webhook data: {json.dumps(webhook_data)}") + + # Get remote address and request ID + remote_addr = req.headers.get('X-REAL-IP') or req.headers.get('X-FORWARDED-FOR') or 'unknown' + request_id = req.headers.get("X-Request-Id") or req.headers.get("Telnyx-Request-Id") or req.headers.get("X-CALL-ID") or "N/A" + + # Process the webhook + handle_telnyx_webhook(webhook_data, remote_addr, request_id) + + # Set response status - always acknowledge webhooks with 204 No Content + resp.status = falcon.HTTP_204 + return + + except json.JSONDecodeError as e: + logger.error(f"Failed to decode JSON from webhook request body: {e}") + resp.status = falcon.HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Invalid JSON payload"}) + return + except Exception as e: + logger.exception(f"Error processing webhook: {e}") + resp.status = falcon.HTTP_500 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Internal Server Error"}) + return + + # If we get here, it's not a Telnyx webhook, so process as normal + try: + # For non-webhook requests, get form data + form_data = get_form_data(req) + logger.debug(f"Form data: {form_data}") + + except Exception as e: + logger.exception(f"Error in on_post: {e}") + resp.status = falcon.HTTP_500 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Internal Server Error"}) + + # Get form data using our helper function - but don't read stream again + #form_data = get_form_data(req) + logger.debug(f"Form data: {form_data}") + + + try: + + # Get basic parameters + function = form_data.get('function') + user_name = form_data.get('user_name') + logger.debug(f"Function: {function}, User: {user_name}") + + if function != "credentials" and function != "new_user_form" and function != "set_deployment": + token = form_data.get('token') + ps = form_data.get('ps') + + if ps != "" and ps != None: + #was token sent in ps field? This allows for token and ps be populated by token or ps + user_info = verify_token(ps) + if user_info != None: + if user_info["username"] == user_name: + token = ps + else: + #is this valid password? + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + + #with get_db_connection() as db_conn: + privileges = GetPriviledgesOnly(user_name) + + # Handle credentials function - most common case + if function == "credentials": + + clientId = form_data.get('clientId') + nonce = form_data.get('nonce') + ps = form_data.get('ps') + + if not user_name: + resp.media = package_response("Required field 'user_name' is missing", HTTP_400) + return + + if not clientId: + resp.media = package_response("Required field 'clientId' is missing", HTTP_400) + return + + if not nonce: + resp.media = package_response("Required field 'nonce' is missing", HTTP_400) + return + + if not ps: + resp.media = package_response("Required field 'ps' is missing", HTTP_400) + return + + + + if False: + pass + else: + #lets check for real + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + access_token = 0 + privileges = 0 + else: + access_token = generate_token(user_name) + + if privileges == "-1": + max_role = -1 + else: + max_role = GetMaxRole(user_name) + if "2" in max_role: + max_role = 2 + else: + max_role = 1 + + token_payload = {'access_token': access_token, 'privileges': privileges, 'user_id': user_id, 'max_role': max_role} + resp.media = package_response(token_payload) + resp.status = falcon.HTTP_200 + return + + # Handle token-protected functions + elif function == "messages_age": + + macs = form_data.get('macs') + + with get_db_connection() as conn: + + #print (sqlr) + with conn.cursor() as cur: + + devices = MACsStrToDevIds(cur, macs) + + devices_string = ",".join(f"{device_id}" for mac, device_id in devices) + + + sqlr = f""" + SELECT + device_id, + GREATEST( + radar_last_time, + sensor_last_time + ) AS latest_time + FROM + (SELECT unnest(ARRAY[{devices_string}]) AS device_id) d + LEFT JOIN LATERAL ( + SELECT time AS radar_last_time + FROM radar_readings + WHERE device_id = d.device_id + ORDER BY time DESC + LIMIT 1 + ) r ON true + LEFT JOIN LATERAL ( + SELECT time AS sensor_last_time + FROM sensor_readings + WHERE device_id = d.device_id + ORDER BY time DESC + LIMIT 1 + ) s ON true;""" + logger.debug(f"sqlr= {sqlr}") + cur.execute(sqlr) + times_list = cur.fetchall() + result = {} + for i in range(len(times_list)): + if times_list[i][1] is not None: + result[devices[i][0]] = times_list[i][1].timestamp() + else: + result[devices[i][0]] = 0 + + dataa = {} + dataa['Command'] = "REPORT" + dataa['body'] = result + dataa['time'] = time.time() + #json_data = json.dumps(dataa) + payload = {'ok': True, 'response': dataa} + resp.media = package_response(payload) + logger.warning(f"Responded: {str(payload)}") + resp.status = falcon.HTTP_200 + return + + elif function == "voice_ask": + + question = form_data.get('question') + deployment_id = form_data.get('deployment_id') + + if ('language_from' in form_data): + language_from = form_data.get('language_from').strip() + else: + language_from = "English" + + if ('language_to' in form_data): + language_to = form_data.get('language_to').strip() + else: + language_to = "English" + + + result, language = AskGPT(question, language_from, language_to) + + if result[0] == "#": + result = RunCommand(result, {}, deployment_id) + + dataa = {} + dataa['Command'] = "REPORT" + dataa['body'] = result + dataa['name'] = "" + dataa['reflected'] = "" + dataa['language'] = language + dataa['time'] = time.time() + #json_data = json.dumps(dataa) + payload = {'ok': True, 'response': dataa} + resp.media = package_response(payload) + logger.warning(f"Responded: {str(payload)}") + resp.status = falcon.HTTP_200 + return + + + elif function == "calibrate_thresholds": + #this will use current date to calibrate radar presence thresholds. + #make sure that data is well defined (has clear absence/presence signature) for all rooms for chosen day + #Format of radar_threshold field = [gates_to_use_Presence_list, p_threshold] + #We need to automate this functionality!!! + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = form_data.get("date") + ddate = ddate.replace("_","-") + selected_date = ddate + + + + stdev_range = int(form_data.get("stdev_range")) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 + devices_list, device_ids = GetProximityList(deployment_id, timee) + + selected_date = FindCalibrationDate(device_ids, ddate) + + devices_c = len(devices_list[0]) + + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s, stdev_range) + fields = ["radar_s_min", "radar_s_max", "radar_m_max", "radar_stdev"] + cnt = 0 + ids_list = [] + for details in devices_list: + ids_list.append(details[1]) + devices_list_str = ",".join(map(str, ids_list)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + + minutes = 1440 + + with get_db_connection() as conn: + with conn.cursor() as cur: + for device_index in range(devices_c): + well_id = devices_list[device_index][0] + device_id = devices_list[device_index][1] + location = devices_list[device_index][2] + + sql = get_device_radar_s28_only_query(time_from_str, time_to_str, device_id) + print(sql) + + #sql1 = get_deployment_radar_only_colapsed_query(str(device_id), time_from_str, time_to_str, [device_id]) + #print(sql1) + st = time.time() + cur.execute(sql) + my_data = cur.fetchall() + + timestamps, stationary, motion = process_raw_data(my_data) + print(type(stationary)) + # Find threshold above which 20% of points lie + AveragePercentSpendsThere = AveragePercentPerLocation[Consolidataed_locations[location]] + threshold_high, threshold_low = FindThreshold(stationary, AveragePercentSpendsThere) + file_save = f"threshold_graph_{location}.png" + title = f"{well_id}_{location}" + + threshold2, x_percent, y_percent = ShowThresholdGraph(stationary, file_save, threshold_low, threshold_high, title, AveragePercentSpendsThere, location) + + print(f"Maximum curvature point found at:") + print(f"Threshold value: {threshold2:.3f}") + print(f"X: {x_percent:.1f}% of range") + print(f"Y: {y_percent:.1f}% of points above") + + ShowArray(stationary, threshold2, filename=f"stationary_{devices_list[device_index][0]}.png", title=f"stationary_{devices_list[device_index][0]}_{devices_list[device_index][2]}", style='line') + + + ##threshold + ##presence_mask, baseline, threshold = detect_presence(timestamps, stationary, motion) + + ### Save visualization to file + ##visualize_detection(timestamps, stationary, motion, presence_mask, + ## baseline, threshold) + + #cur.execute(sql1) + #my_data1 = cur.fetchall()#cur.fetchone() + #print(time.time() - st) + #if my_data == None or my_data1 == None: + #logger.warning(f"No data found for device_id {device_id}") + #else: + #print(type(my_data)) + ##minute, + ##device_id, + ##s_min as radar_s_min, + ##s_max as radar_s_max, + ##m_max as radar_m_max + + #values = [tup[1] for tup in my_data] #10 sec (RAW) data + + #hist, bins = np.histogram(values, bins=1000, range=(0, 100)) + #TR, BR = FindZeroIntersection(hist, bins, f'raw_{device_id}_histogram.png', device_id) + #if True:#device_id == 560: + #plot(values, filename=f"radar_{device_id}_s28.png", title=f"Radar s28 {device_id}", style='line') + #plot(hist, filename=f"radar_{device_id}_s28_hist.png", title=f"Radar s28 {device_id} histogram", style='line') + + ##life = [tup[3] - tup[2] + tup[4] for tup in my_data1] + #life, average = calculate_life_and_average(my_data1, stdev_range) #5 min data + #lhist, lbins = np.histogram(life, bins=1000) + #TLIFE, BLIFE = FindZeroIntersection(lhist, lbins, f'life_{device_id}_histogram.png', device_id) + + #StoreThresholds2DB(device_id, TR, BR, TLIFE, BLIFE) + ##for now not needed... + ##ahist, abins = np.histogram(average, bins=1000) + ##dummy1, dummy = FindZeroIntersection(ahist, abins) + #if True:#device_id == 560: + #plot(average, filename=f"average_{device_id}.png", title=f"Average {device_id}", style='line') + #plot(life, filename=f"life_{device_id}.png", title=f"Life {device_id}", style='line') + #plot(lhist, filename=f"life_{device_id}_hist.png", title=f"life {device_id} histogram", style='line') + ##plot(ahist, filename=f"average_{device_id}_hist.png", title=f"average {device_id} histogram", style='line') + + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + my_data = [] + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + fields_n = len(fields) + stripes = devices_c * fields_n #radar_min and radar_max + print(my_data) + base_minute = ConvertToBase(time_from_str, time_zone_s) + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + #remember: base_minute is offset (smaller) by numbr of minutes in stdev_range + st = time.time() + wave_m = np.zeros((stripes, 1440+2*stdev_range, 1), dtype=np.float32) + + for record in my_data: + #(minute,device_id,s28_min,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the min/max values + x = int((minute - base_minute).total_seconds()/60) + + device_idx = device_to_index[device_id] + #value[0] are mins, value[1] are maxes + #when trying to illustrate presence, use s28_max, when absence (night leaving bed) use s28s_min + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + wave_m[y, x] = value + + print(time.time()-st) + + #we need to reliably determine presence and LIFE (motion) in every 5 minutes of data... + #presence is determined by average value being significntly different from last known base + #last known base is determined by average value during extended periods ( >= H hours) of low stdev (<) while it is determined that: + #person is moving elsewhere, and only 1 person is determined to be in monitored area. + + #lets calculate stdevs + for device_index in range(devices_c): + y = device_index * fields_n + row = wave_m[y] + stdevs = np.zeros((1440+2*stdev_range, 1), dtype=np.float32) + stdevs, amplitude = CalcStdevs(row, stdev_range, stdevs) + wave_m[y+3] = stdevs + plot(stdevs, filename=f"radar{device_index}_stdevs.png", title=f"Radar Stedevs {device_index}", style='line') + + minutes = 1440 + + + device_index = 0 + y = 0 + for device in devices_list: + wave = wave_m[y][stdev_range: stdev_range + minutes] + plot(wave, + filename="radar_wave_min.png", + title="Radar Signal Min", + style='line') + # Create histogram with 1000 bins + hist, bins = np.histogram(wave, bins=1000, range=(0, 100)) + + #bin_centers = (bins[:-1] + bins[1:]) / 2 + hist_line = hist # These are your y values + + # Plot with proper axis labels + plot(hist_line, + filename="radar_histogram_min.png", + title="Radar Signal Histogram Min (1000 bins)", + style='line') + + wave = wave_m[y+1] + plot(wave, + filename="radar_wave_max.png", + title="Radar Signal", + style='line') + # Create histogram with 1000 bins + hist, bins = np.histogram(wave, bins=1000, range=(0, 100)) + + #bin_centers = (bins[:-1] + bins[1:]) / 2 + hist_line = hist # These are your y values + + # Plot with proper axis labels + plot(hist_line, + filename="radar_histogram_max.png", + title="Radar Signal Histogram Max(1000 bins)", + style='line') + + print(wave) + device_to_index += 1 + + #lets see this map + stretch_by = 5 + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + st = time.time() + for yy in range(stripes): + rgb_row = [] + row = wave_m[yy] + for x in range(minutes): + value = 1280 * row[x] / 100 + rgb_row.append(BestColor(value)) + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + filename = f"{deployment_id}/{deployment_id}_{ddate}_min_max_radar.png" + SaveImageInBlob(filename, arr_stretched, []) + + + return + elif function == "get_time_deltas": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + sensor = form_data.get('sensor') + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + radar_part = "" + sensor_data = {} + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + device_id = form_data.get('device_id') + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + + all_slices = {} + + cleaned_values = {} + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + st = time.time() + cleaned_values = [ + (line_part[i][0], (line_part[i][0] - line_part[i-1][0]).total_seconds() * 1000) + for i in range(1, len(line_part)) + ] + print(time.time()-st) + + if True: + # Create CSV content as a string + csv_content = "Record_Index,Timestamp,Value,Time_Diff_Seconds,Time_Diff_Milliseconds\n" + + for i in range(len(line_part)): + timestamp, value = line_part[i] + + if i == 0: + # First record has no previous record to compare + time_diff_seconds = 0 + time_diff_ms = 0 + else: + # Calculate time difference from previous record + prev_timestamp = line_part[i-1][0] + time_diff = timestamp - prev_timestamp + time_diff_seconds = time_diff.total_seconds() + time_diff_ms = time_diff_seconds * 1000 + + # Format the row + row = f"{i},{timestamp.isoformat()},{value},{round(time_diff_seconds, 6)},{round(time_diff_ms, 3)}\n" + csv_content += row + + # Write to file + with open(f'time_differences_{sensor}_{device_id}.csv', 'w', encoding='utf-8') as f: + f.write(csv_content) + + print(f"CSV file 'time_differences_{sensor}_{device_id}.csv' created successfully!") + + line_part_t = [(x[0].timestamp(), x[1]) for x in cleaned_values] + + sensor_data[sensor] = line_part_t + dataa = {} + all_slices = {} + all_slices[device_id] = sensor_data + dataa['Function'] = "time_deltas" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['device_id'] = device_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "get_sensor_deltas": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + sensor = form_data.get('sensor') + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + radar_part = "" + sensor_data = {} + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + device_id = form_data.get('device_id') + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + + all_slices = {} + + cleaned_values = {} + line_part = ReadSensorDeltas(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + st = time.time() + cleaned_values =line_part + #[ + #(line_part[i][0], (line_part[i][1] - line_part[i-1][1]) / (line_part[i][0] - line_part[i-1][0]).total_seconds()) + #for i in range(1, len(line_part)) + #if (line_part[i][0] - line_part[i-1][0]).total_seconds() > 0 + #and abs((line_part[i][1] - line_part[i-1][1]) / (line_part[i][0] - line_part[i-1][0]).total_seconds()) <= 100 + #] + #print(time.time()-st) + + + + line_part_t = [(x[0].timestamp(), x[1]) for x in cleaned_values] + + sensor_data[sensor] = line_part_t + dataa = {} + all_slices = {} + all_slices[device_id] = sensor_data + dataa['Function'] = "time_deltas" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['device_id'] = device_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "request_single_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + devices_list = form_data.get('devices_list') + radar_details = {} + #devices_list = '[267,560,"?",null,"64B70888F6F0"]' + #devices_list = '[[267,560,"?",null,"64B70888F6F0"],[268,561,"?",null,"64B70888F6F1"]]' + sensor_list_loc = [form_data.get('sensor_list')] + is_nested, device_details = check_and_parse(devices_list) + if not is_nested: + device_ids_list = [device_details[1]] + well_ids_list = [device_details[0]] + else: + device_ids_list = list(map(lambda x: x[1], device_details)) + well_ids_list =list(map(lambda x: x[0], device_details)) + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + #we need to + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + + days = (epoch_to_utc - epoch_from_utc) / (60 * 1440) + + well_id = well_ids_list[0] + all_slices = {} + radar_part = "" + if len(device_details) > 4: + device_id2_mac = {device_details[1]: device_details[4]} + #device_id2_mac = {device_details[1]: device_details[3]} + #epoch_to = '1730592010' #smal sample to test + #radar_part = form_data.get('radar_part') we need to find what radar part is configured in device settings + if len(device_details) > 5: + radar_part_all = device_details[5] + + if type(radar_part_all) == str: + radar_part_all = ["s3_max",int(radar_part_all)] + elif type(radar_part_all) == int: + radar_part_all = ["s3_max",radar_part_all] + elif type(radar_part_all) == list: + pass + + + + else: + radar_part_all = ["s3_max",12] + + + if len(radar_part_all) > 1: + radar_part = radar_part_all[0] + #we need only column name and not min or max here + if "_" in radar_part: + radar_parts = radar_part.split("_") + radar_part = radar_parts[0] + radar_details[device_details[1]] = radar_part_all + + #devices = GetVisibleDevices(deployment_id) + temp_calib, humid_calib = GetCalibMaps(device_ids_list) + + for device_id in device_ids_list: + + sensor_data = {} + for sensor in sensor_list_loc: + st = time.time() + if days < 3: + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + elif days < 14: + bucket_size = "1m" + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + else: + bucket_size = "10m" + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + #Lets apply calibration: + if sensor == "temperature": + temperature_calib = temperature_offset #float(temp_calib[device_id].split(",")[2]) + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + if sensor == "humidity": + line_part = [(timestamp, value + humidity_offset) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + + if False: + # Create CSV content as a string + csv_content = "Record_Index,Timestamp,Value,Time_Diff_Seconds,Time_Diff_Milliseconds\n" + + for i in range(len(line_part)): + timestamp, value = line_part[i] + + if i == 0: + # First record has no previous record to compare + time_diff_seconds = 0 + time_diff_ms = 0 + else: + # Calculate time difference from previous record + prev_timestamp = line_part[i-1][0] + time_diff = timestamp - prev_timestamp + time_diff_seconds = time_diff.total_seconds() + time_diff_ms = time_diff_seconds * 1000 + + # Format the row + row = f"{i},{timestamp.isoformat()},{value},{round(time_diff_seconds, 6)},{round(time_diff_ms, 3)}\n" + csv_content += row + + # Write to file + with open('time_differences.csv', 'w', encoding='utf-8') as f: + f.write(csv_content) + + print("CSV file 'time_differences.csv' created successfully!") + + #print("@1", time.time() - st) + #first = 3300 + #last = 3400 + #line_part = line_part[first:last] + line_part_t = [] + #st = time.time() + #line_part_t = [tuple(x[:2]) for x in line_part] + #print(time.time() - st) + #st = time.time() + #line_part_t = list({(dt.timestamp(), value) for dt, value in line_part}) + #print(time.time() - st) + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + #print("@2", time.time() - st) + + #Lets add point in minute 0 and minute 1439 + + #st = time.time() + #cleaned_values = clean_data_fast(line_part_t, window=5, threshold=2.0) + #print("@3", time.time() - st) + + sensor_data[sensor] = cleaned_values + + + if len(device_details) > 4: + all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + else: + all_slices[device_id] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + dataa = {} + dataa['Function'] = "single_slicedata" + dataa['devices_list'] = devices_list + dataa['all_slices'] = all_slices + dataa['radar_details'] = radar_details + dataa['time_zone_st'] = time_zone_s + dataa['well_id'] = well_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + elif function == "get_sensor_bucketed_data_by_room_sensor": + # Inputs: + # user_name and token + # deployment_id - from which report gets deployment set (all rooms and devices) to get timezone + # date - one day in a format YYYY-MM-DD + # sensor - temperature/radar/etc.. see full list + # (tells what sensor data to be retrieved) + # "voc" for all smell use s4 (lower reading is higher smell, max=0 find min for 100%) + # "radar" returns s28 + # radar_part - optional and applies only to radar (tells which segment of radar to be retrieved) + # bucket_size - ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + # location - room name (has to be unique) + # data_type - ML + # Output: son structure with the following info + # chart_data with rooms : [list] + deployment_id = form_data.get('deployment_id') + selected_date = form_data.get('date') + sensor = form_data.get('sensor') # one sensor + radar_part = form_data.get('radar_part') + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + bucket_size = "no" if (result := form_data.get('bucket_size')) in (None, "") else (result.strip() if result.strip() in buckets else "no") + #bucket_size = res2 if (res := form_data.get('bucket_size')) is not None and (res2 := str(res).strip()) and res2 in {'no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'} else 'no' + location = form_data.get('location') + data_type = form_data.get('data_type') + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) + + # obtain devices_list for deployment_id + selected_date = selected_date.replace("_","-") + devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + sensor_data = {} + units = "°C" + if "America" in time_zone_s: + units = "°F" + # see https://www.w3schools.com/cssref/css_colors.php + sensor_props = {"temperature": ["red", units], + "humidity": ["blue", "%"], + "voc": ["orange", "PPM"], + "co2": ["orange", "PPM"], + "pressure": ["magenta", "Bar"], + "radar": ["cyan", "%"], + "light": ["yellow", "Lux"]} + + current_time_la = datetime.datetime.now(pytz.timezone(time_zone_s)) + formatted_time = current_time_la.strftime('%Y-%m-%dT%H:%M:%S') #"2025-02-06T20:09:00" + + result_dictionary = { + "last_report_at": formatted_time, + "color": sensor_props[sensor][0] if sensor in s_table else "grey", + "units": sensor_props[sensor][1] if sensor in s_table else "?" + } + #sensor_mapping = {"co2": "s4", "voc": "s9"} + #sensor = sensor_mapping.get(sensor, sensor) + temp_calib, humid_calib = GetCalibMaps(device_ids) + #print(device_ids) + #print(temp_calib) + #print(humid_calib) + #print("++++++++++++++++++") + chart_data = [] + # example data in each element of devices_list is (266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + for well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to in devices_list: + loc_and_desc = location_name + if description != None and description != "": + loc_and_desc = loc_and_desc + " " + description + + if loc_and_desc == location: + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + if sensor == "temperature": + if "," in temp_calib[device_id]: + temperature_calib = float(temp_calib[device_id].split(",")[2]) + else: + temperature_calib = -10 + + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + + if sensor == "humidity": + line_part = [(timestamp, value + humidity_offset) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + line_part_t = [] + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + compressed_readings = convert_timestamps_lc(cleaned_values, time_zone_s) + if sensor == "temperature": + if units == "°F":#"America" in time_zone_s: + compressed_readings = CelsiusToFahrenheitList(compressed_readings) + + sensor_data[sensor] = compressed_readings + chart_data.append({'name': location_name, 'data': compressed_readings}) + result_dictionary['chart_data'] = chart_data + payload = result_dictionary + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "get_sensor_data_by_deployment_id": + # Inputs: + # user_name and token + # deployment_id - from which report gets deployment set (all rooms and devices) + # date - one day in a format YYYY-MM-DD + # sensor - temperature/radar/etc.. see full list (tells what sensor data to be retrieved) + # radar_part - optional and applies only to radar (tells which segment of radar to be retrieved) + # bucket_size - ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + # data_type - ML + # Output: son structure with the following info + # chart_data with rooms : [list] + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + sensor = form_data.get('sensor') # one sensor + radar_part = form_data.get('radar_part') + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + bucket_size = "no" if (result := form_data.get('bucket_size')) in (None, "") else (result.strip() if result.strip() in buckets else "no") + #bucket_size = res2 if (res := form_data.get('bucket_size')) is not None and (res2 := str(res).strip()) and res2 in {'no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'} else 'no' + data_type = form_data.get('data_type') + + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + # obtain devices_list for deployment_id + selected_date = selected_date.replace("_","-") + #timee = LocalDateToUTCEpoch(selected_date, time_zone_s)+5 + devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + sensor_data = {} + # see https://www.w3schools.com/cssref/css_colors.php + sensor_props = {"temperature": ["red", "°C"], + "humidity": ["blue", "%"], + "voc": ["orange", "PPM"], + "co2": ["orange", "PPM"], + "pressure": ["magenta", "Bar"], + "radar": ["cyan", "%"], + "light": ["yellow", "Lux"]} + result_dictionary = { + "last_report_at": "2025-02-06T20:09:00", + "color": sensor_props[sensor][0] if sensor in s_table else "grey", + "units": sensor_props[sensor][1] if sensor in s_table else "?" + } + #sensor_mapping = {"co2": "s4", "voc": "s9"} + #sensor = sensor_mapping.get(sensor, sensor) + + chart_data = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + if sensor == "temperature": + temperature_calib = float(temp_calib[device_id].split(",")[2]) + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + line_part_t = [] + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + compressed_readings = convert_timestamps_lc(cleaned_values, time_zone_s) + + #compressed_readings = [(time.strftime("%H:%M", time.gmtime(lst[0][0])), float(sum(t for _, t in lst)/len(lst))) + #for _, lst in ((k, list(g)) + #for k, g in itertools.groupby(cleaned_values, key=lambda x: time.gmtime(x[0]).tm_hour))] + sensor_data[sensor] = compressed_readings + chart_data.append({'name': location_name, + 'data': compressed_readings}) + result_dictionary['chart_data'] = chart_data + #all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + #is_neste, device_details = check_and_parse(devices_list) + #if not is_nested: + #device_ids_list = [device_details[1]] + #well_ids_list = [device_details[0]] + #else: + #device_ids_list = list(map(lambda x: x[1], device_details)) + #well_ids_list =list(map(lambda x: x[0], device_details)) + #well_id = well_ids_list[0] + #all_slices = {} + #device_id2_mac = {device_details[1]: device_details[4]} + #for device_id in device_ids_list: + #device_id2_mac + #sensor_data = {} + #for sensor in sensor_list_loc: + #st = time.time() + #line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + #window = sensor_legal_values[sensor][2] + #line_part_t = [] + #line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + #st = time.time() + #cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + #cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + #sensor_data[sensor] = cleaned_values + #all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + #dataa = {} + #dataa['Function'] = "single_slicedata" + #dataa['devices_list'] = devices_list + #dataa['all_slices'] = all_slices + #dataa['time_zone_st'] = time_zone_s + #dataa['well_id'] = well_id + #resp.media = package_response(dataa) + #resp.status = falcon.HTTP_200 + result_dictionary2 = { + "alert_text": "No alert", + "alert_color": "bg-green-100 text-green-700", + "last_report_at": "ISO TIMESTAMP", + "chart_data": [ + { + "rooms": [ + { "name": "Bathroom", + "data": [ + {"title": "12AM","value": 20}, + {"title": "01AM","value": 20}, + {"title": "02AM","value": 26}, + {"title": "03AM","value": 16}, + {"title": "04AM","value": 27}, + {"title": "05AM","value": 23}, + {"title": "06AM","value": 26}, + {"title": "07AM","value": 17}, + {"title": "08AM","value": 18}, + {"title": "09AM","value": 21}, + {"title": "10AM","value": 28}, + {"title": "11AM","value": 24}, + {"title": "12PM","value": 18}, + {"title": "01PM","value": 27}, + {"title": "02PM","value": 27}, + {"title": "03PM","value": 19}, + {"title": "04PM","value": 0}, + {"title": "05PM","value": 0}, + {"title": "06PM","value": 0}, + {"title": "07PM","value": 0}, + {"title": "08PM","value": 0}, + {"title": "09PM","value": 0}, + {"title": "10PM","value": 0}, + {"title": "11PM","value": 0} + ] + }, + { "name": "Kitchen", + "data": [ + {"title": "00AM","value": 19}, + {"title": "01AM","value": 10}, + {"title": "02AM","value": 8}, + {"title": "03AM","value": 14}, + {"title": "04AM","value": 20}, + {"title": "05AM","value": 8}, + {"title": "06AM","value": 7}, + {"title": "07AM","value": 17}, + {"title": "08AM","value": 3}, + {"title": "09AM","value": 19}, + {"title": "10AM","value": 4}, + {"title": "11AM","value": 6}, + {"title": "12PM","value": 4}, + {"title": "01PM","value": 14}, + {"title": "02PM","value": 17}, + {"title": "03PM","value": 20}, + {"title": "04PM","value": 19}, + {"title": "05PM","value": 15}, + {"title": "06PM","value": 5}, + {"title": "07PM","value": 19}, + {"title": "08PM","value": 3}, + {"title": "09PM","value": 30}, + {"title": "10PM","value": 1}, + {"title": "11PM","value": 12 } + ] + }, + { "name": "Living Room", + "data": [ + {"title": "00AM","value": 25}, + {"title": "01AM","value": 24}, + {"title": "02AM","value": 19}, + {"title": "03AM","value": 20}, + {"title": "04AM","value": 22}, + {"title": "05AM","value": 20}, + {"title": "06AM","value": 11}, + {"title": "07AM","value": 5}, + {"title": "08AM","value": 16}, + {"title": "09AM","value": 22}, + {"title": "10AM","value": 23}, + {"title": "11AM","value": 14}, + {"title": "12PM","value": 0}, + {"title": "01PM","value": 7}, + {"title": "02PM","value": 25}, + {"title": "03PM","value": 29}, + {"title": "04PM","value": 23}, + {"title": "05PM","value": 27}, + {"title": "06PM","value": 27}, + {"title": "07PM","value": 20}, + {"title": "08PM","value": 2}, + {"title": "09PM","value": 24}, + {"title": "10PM","value": 21}, + {"title": "11PM","value": 14 } + ] + } + ] + } + ] + } + payload = result_dictionary + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + #AddToLog(payload) + #return + elif function == "request_device_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + epoch_from_utc = form_data.get('epoch_from') + epoch_to_utc = form_data.get('epoch_to') + device_id = form_data.get('device_id') + well_id = form_data.get('well_id') + MAC = form_data.get('MAC') + sensor_list_loc = form_data.get('sensors_list') + sensor_list = sensor_list_loc.split(",") + device_ids_list = [device_id] + well_ids_list = [well_id] + maps_dates, positions_list, timezone_s = GetDeploymentDatesBoth(deployment_id) + + data_type = "RL" + #epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + + #epoch_to = '1730592010' #smal sample to test + radar_part = form_data.get('radar_part') + well_id = well_ids_list[0] + all_slices = {} + #device_id2_mac = {device_details[1]: device_details[4]} + for device_id in device_ids_list: + #device_id2_mac + sensor_data = {} + for sensor in sensor_list: + st = time.time() + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + window = sensor_legal_values[sensor][2] + #print("@1", time.time() - st) + #first = 3300 + #last = 3400 + #line_part = line_part[first:last] + line_part_t = [] + #st = time.time() + #line_part_t = [tuple(x[:2]) for x in line_part] + #print(time.time() - st) + #st = time.time() + #line_part_t = list({(dt.timestamp(), value) for dt, value in line_part}) + #print(time.time() - st) + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + #cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + #print("@2", time.time() - st) + + #Lets add point in minute 0 and minute 1439 + + #st = time.time() + #cleaned_values = clean_data_fast(line_part_t, window=5, threshold=2.0) + #print("@3", time.time() - st) + cleaned_values = ScaleToCommon(cleaned_values_t, sensor) + sensor_data[sensor] = cleaned_values + all_slices[device_id] = sensor_data + + dataa = {} + dataa['Function'] = "device_slicedata" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['proximity'] = positions_list + dataa['well_id'] = well_id + dataa['MAC'] = MAC + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + #return + elif function == "request_single_radar_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + devices_list = form_data.get('devices_list') + ctrl_key_state = form_data.get('ctrl_key_state') + alt_key_state = form_data.get('alt_key_state') + #devices_list = '[267,560,"?",null,"64B70888F6F0"]' + #devices_list = '[[267,560,"?",null,"64B70888F6F0"],[268,561,"?",null,"64B70888F6F1"]]' + sensor_index_list = [form_data.get('sensor_index_list')] + is_nested, device_details = check_and_parse(devices_list) + if not is_nested: + device_ids_list = [device_details[1]] + well_ids_list = [device_details[0]] + else: + device_ids_list = list(map(lambda x: x[1], device_details)) + well_ids_list =list(map(lambda x: x[0], device_details)) + + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + + #epoch_to = '1730592010' #smal sample to test + radar_part = form_data.get('radar_part') + well_id = well_ids_list[0] + all_slices = {} + device_id2_mac = {device_details[1]: device_details[4]} + for device_id in device_ids_list: + device_id2_mac + sensor_data = {} + for sensor_index in sensor_index_list: + st = time.time() + sensor = ["m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m08_max", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s28_max", "s28_min"][int(sensor_index)] + + line_part = ReadRadarDetail(device_id, sensor, epoch_from_utc, epoch_to_utc, alt_key_state) + window = sensor_legal_values["radar"][2] + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + if len(sensor) < 4: + sensor_data[sensor+"_max"] = cleaned_values + else: + sensor_data[sensor] = cleaned_values + all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + dataa = {} + dataa['Function'] = "single_slicedata" + dataa['devices_list'] = devices_list + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['well_id'] = well_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + elif function == "get_deployment": + blob_data = read_file("deployment.html") + deployment_id = form_data.get('deployment_id') + #lets update "Deployments" select + users = GetUsersFromDeployments(privileges) + blob_data = UpdateDeploymentsSelector(blob_data, users, False, deployment_id) + + resp.content_type = "text/html" + resp.text = blob_data + return + elif function == "get_deployment_j": + deployment_id = form_data.get('deployment_id') + time_zone_st = GetTimeZoneOfDeployment(deployment_id) + date = form_data.get('date') + if date == None: + + # Get today's date + local_timezone = pytz.timezone(time_zone_st) # Replace with your local timezone + date = datetime.datetime.now(local_timezone).strftime('%Y-%m-%d') + + #epoch_from_utc = int(datetime.datetime.strptime(date, "%Y-%m-%d").timestamp()) + #devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + + dataa = {} + dataa['Function'] = "deployment_details" + if privileges == "-1": + deployment = DeploymentDetails(deployment_id) + dataa['deployment_details'] = deployment + else: + privileges = privileges.split(",") + if deployment_id in privileges: + deployment = DeploymentDetails(deployment_id) + dataa['deployment_details'] = deployment + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + elif function == "set_floor_layout": + deployment_id = form_data.get('deployment_id') + layout = form_data.get('layout') + + if privileges == "-1" or deployment_id in privileges: + ok = StoreFloorPlan(deployment_id, layout) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + else: + payload = {'ok': 0, 'error': "not allowed"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + return + elif function == "get_floor_layout": + deployment_id = form_data.get('deployment_id') + + dataa = {} + dataa['Function'] = "deployment_details" + if privileges == "-1": + layout = GetFloorPlan(deployment_id) + dataa['layout'] = layout + else: + privileges = privileges.split(",") + if deployment_id in privileges: + layout = GetFloorPlan(deployment_id) + dataa['layout'] = layout + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + elif function == "get_beneficiary": + user_id = form_data.get('user_id') + all_beneficiaries = ListBeneficiaries(privileges, user_id) + beneficiaries_list = [] + for beneficiary_temp in all_beneficiaries: + beneficiaries_list.append(str(beneficiary_temp[0])) + + dataa = {} + dataa['Function'] = "beneficiary_details" + if user_id in beneficiaries_list: + beneficiary = UserDetails(user_id) + #lets remove fields not relevant for beneficiary + try: + del beneficiary['time_edit'] + except: + pass + + try: + del beneficiary['user_edit'] + except: + pass + + try: + del beneficiary['access_to_deployments'] + except: + pass + dataa['beneficiary_details'] = beneficiary + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + elif function == "get_caretaker": + + user_name = form_data.get('user_name') + + + all_caretakers = ListCaretakers(privileges, user_name) + if len(all_caretakers) > 1: + user_id = form_data.get('user_id') + else: + user_id = str(all_caretakers[0][0]) + + caretakers_list = [] + for caretakers_temp in all_caretakers: + caretakers_list.append(str(caretakers_temp[0])) + + dataa = {} + dataa['Function'] = "caretaker_details" + if user_id in caretakers_list: + caretaker = UserDetails(user_id) + #lets remove fields not relevant for beneficiary + try: + del caretaker['time_edit'] + except: + pass + + try: + del caretaker['user_edit'] + except: + pass + + dataa['caretaker_details'] = caretaker + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "get_device": + device_id = form_data.get('device_id') + device_mac = form_data.get('mac') + min_well_id = form_data.get('min_well_id') + if min_well_id != None: + min_well_id = int(min_well_id) + + if device_mac != None: + device_det = GetDeviceDetailsSingleFromMac(device_mac) + print(device_det) + dataa = {} + dataa['Function'] = "device_details" + dataa['device_details'] = device_det + if device_det == {}: + dataa['next_well_id'] = GetNextWellId(min_well_id) + else: + devices = GetVisibleDevices(privileges) + dataa = {} + dataa['Function'] = "device_details" + dataa['device_details'] = {} + if privileges == "-1": + #device_det = GetDeviceDetails(device_id) + device_det = GetDeviceDetailsSingle(device_id) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + dataa['device_details'] = device_det + else: + devices_list = [] + for device_id_temp in devices: + devices_list.append(str(device_id_temp[0])) + + if device_id in devices_list: + device_det = GetDeviceDetailsSingle(device_id) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + + + dataa['device_details'] = device_det + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + + + elif function == "set_deployment": + + user_name = form_data.get('user_name') + token = form_data.get('token') + password = form_data.get('password') + + + if user_name == "" or user_name == None: #new user + + user_name + resp.media = package_response("Log-Out", HTTP_401) + return + + if password != "" and password != None: + privileges, user_id = ValidUser(user_name, password) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + + + if token != "" and token != None: + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + + deployment = form_data.get('deployment') + beneficiary_name = form_data.get('beneficiary_name') + beneficiary_email = form_data.get('beneficiary_email') + beneficiary_address = form_data.get('beneficiary_address') + caretaker_username = form_data.get('caretaker_username') + caretaker_email = form_data.get('caretaker_email') + num_people = int(form_data.get('num_people')) + pets = int(form_data.get('pets')) + year_born = int(form_data.get('year_born')) + gender = form_data.get('gender') + race = form_data.get('race') + gps_lat = form_data.get('gps_lat') + gps_lng = form_data.get('gps_lng') + devices = form_data.get('devices') + devices_list = json.loads(devices) + wifis = form_data.get('wifis') + wifis_list = json.loads(wifis) + + print(deployment) + print(beneficiary_name) + print(beneficiary_email) + print(beneficiary_address) + print(caretaker_username) + print(caretaker_email) + print(num_people) + print(pets) + print(year_born) + print(gender) + print(race) + print(gps_lat) + print(gps_lng) + print(devices_list) + print(wifis_list) + + #lets check if devices listed are not part of existing deployment + success, result = DevicesNotUsed(devices, user_name) + + if success: + if result["deployed"]: + error_string = f"These devices are already deployed: {result['deployed']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if result["not_found"]: + error_string = f"These devices are not available: {result['not_found']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if not result["deployed"] and not result["not_found"]: + print("All devices are available for deployment") + ok, error_string = StoreDisclaimer2DB(form_data) + + if ok == 1: + #Lets check if this user already has account or not + if not AccountExists(user_name): + #lets create new account for this caretaker + if password == None or password == "": + password = CreatePassword(8) + + #lets suplement form_data with parts needed for existing StoreCaretaker2DB function + editing_user_id = "0" #specify if editing existing user, otherwise "0" + user_id = "0" #user that is adding this record. New user so "0" + form_data['role_ids'] = "1,2" + form_data['access_to_deployments'] = "45" + #form_data['email'] = "" #this one matches + form_data['new_user_name'] = form_data['user_name'] + form_data['first_name'] = form_data['firstName'] + form_data['last_name'] = form_data['lastName'] + form_data['address_street'] = "" + form_data['address_city'] = "" + form_data['address_zip'] = "" + form_data['address_state'] = "" + form_data['address_country'] = "" + form_data['phone_number'] = form_data['phone'] + form_data['picture'] = "/" + form_data['key'] = password + + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + + if ok == 1: + + #we need to call cd ~/mqtt-auth-service/acl_manager.py + + SendWelcomeEmail(form_data['email'], form_data['first_name'], form_data['last_name'], devices, form_data['phone_number'], form_data['new_user_name'], form_data['key']) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + privileges, user_id = ValidUser(user_name, password) + if user_id == "0": #bad password + error_string = f"Password does not match user {user_name}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + else: + error_string = f"Error: {result}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + + elif function == "purge_phantom_records": + last_valid_id = int(form_data.get('last_valid_device_id')) + + try: + conn = get_db_connection() + with conn.cursor() as cur: + # 1. Check for non-null radar_threshold records + cur.execute(""" + SELECT COUNT(*) + FROM public.devices + WHERE device_id > %s AND radar_threshold IS NOT NULL + """, (last_valid_id,)) + + non_null_count = cur.fetchone()[0] + + if non_null_count > 0: + resp.media = { + "status": "aborted", + "reason": f"Found {non_null_count} records with device_id > {last_valid_id} that have radar_threshold NOT NULL" + } + return + + # 2. Delete phantom records + cur.execute(""" + DELETE FROM public.devices + WHERE device_id > %s AND radar_threshold IS NULL + """, (last_valid_id,)) + + deleted_count = cur.rowcount + + if deleted_count > 0: + # 3. Reset sequence to the ACTUAL maximum device_id in the table + cur.execute("SELECT COALESCE(MAX(device_id), 0) FROM public.devices") + actual_max_id = cur.fetchone()[0] + + # Reset sequence to actual max + cur.execute("SELECT setval('devices_device_id_seq', %s, true)", (actual_max_id,)) + + # Get next ID to verify + cur.execute("SELECT nextval('devices_device_id_seq')") + next_id = cur.fetchone()[0] + + conn.commit() + + resp.media = { + "status": "success", + "deleted_count": deleted_count, + "actual_max_device_id": actual_max_id, + "sequence_reset_to": actual_max_id, + "next_device_id": next_id + } + else: + resp.media = { + "status": "no_action", + "message": "No phantom records found to delete" + } + + except Exception as e: + conn.rollback() + resp.media = {"error": str(e)} + resp.status = falcon.HTTP_500 + + elif function == "request_deployment_map_new": + st = time.time() + print(f"$0 ----{time.time() - st}") + deployment_id = form_data.get('deployment_id') + map_type = form_data.get('map_type') + print(f"$1 ----{time.time() - st}") + maps_dates, positions_list, timezone_s = GetDeploymentDatesBoth(deployment_id) + print(f"$2 ----{time.time() - st}") + datee = form_data.get('date') + if maps_dates != []: + + if datee == "2022-4-2": #that one is default in HTML so disregard + datee = maps_dates[0] + + locations_desc_map = {} + for details in positions_list: + well_id = details[0] + location = details[2] + if details[3] != None and details[3] != "": + location = location +" "+ details[3] + + if details[6] != None and details[6] != "": + location = location +" "+ details[6] + + MAC = details[4] + locations_desc_map[well_id] = location + print(f"$3 ----{time.time() - st}") + + dataa = {} + dataa['Function'] = "deployments_maps_report" + dataa['proximity'] = positions_list + maps_dates.sort(reverse = True) + dataa['maps_dates'] = maps_dates + dataa['device_count'] = len(positions_list) + dataa['time_zone'] = timezone_s + dataa['map_type'] = map_type + + #MACs_list = GetMACsListSimple(positions_list) + #MACs_map = {} + + #for details in positions_list: + # id = details[0] + # MAC = details[3] + # MACs_map[id] = MAC + #for i in range(len(MACs_list)): + # MACs_map[devices_list[i]] = MACs_list[i][0] + + id = positions_list[0][0] + #dataa['MACs_map'] = MACs_map + dataa['locations_desc_map'] = locations_desc_map + #proximity_list = proximity.split(",") + print(f"$4 ----{time.time() - st}") + + if id < 200: + checkmarks_string = 'T>\n' + checkmarks_string = checkmarks_string + 'H>\n' + checkmarks_string = checkmarks_string + 'P>\n' + checkmarks_string = checkmarks_string + 'C>\n' + checkmarks_string = checkmarks_string + 'V>\n' + checkmarks_string = checkmarks_string + 'L>\n' + checkmarks_string = checkmarks_string + 'R>
    ' + else: #>200 = ["Temperature", "Humidity", "Pressure", "Light", "Radar", "VOC"] + + checkmarks_string = 'T>\n' + checkmarks_string = checkmarks_string + 'H>\n' + checkmarks_string = checkmarks_string + 'P>\n' + checkmarks_string = checkmarks_string + 'L>\n' + checkmarks_string = checkmarks_string + 'R>\n' + + checkmarks_string = checkmarks_string + 'S0>\n' + checkmarks_string = checkmarks_string + 'S1>\n' + checkmarks_string = checkmarks_string + 'S2>\n' + checkmarks_string = checkmarks_string + 'S3>\n' + checkmarks_string = checkmarks_string + 'S4>\n' + checkmarks_string = checkmarks_string + 'S5>\n' + checkmarks_string = checkmarks_string + 'S6>\n' + checkmarks_string = checkmarks_string + 'S7>\n' + checkmarks_string = checkmarks_string + 'S8>\n' + checkmarks_string = checkmarks_string + 'S9>
    ' + + checked_or_not = " checked" + + for index in range(len(positions_list)): + details = positions_list[index] + device_id = details[0] + location = details[2] + if details[3] != None and details[3] != "": + location = location + " " + details[3] + + if details[6] != None and details[6] != "": + location = location + " " + details[6] + + checkmarks_string = checkmarks_string + str(device_id) + '>\n' + checked_or_not = '' + + print(f"$5 ----{time.time() - st}") + + dataa['checkmarks'] = checkmarks_string + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "request_proximity": + deployment = form_data.get('deployment_id') + timee = form_data.get('time') + #timee = StringToEpoch(datee) + #print(deployment, timee) + well_ids, device_ids = GetProximityList(deployment, timee) + #print(proximity) + dataa = {} + dataa['Function'] = "proximity_report" + if len(well_ids) > 0: + dataa['proximity'] = well_ids + else: + dataa['proximity'] = [] + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + elif function == "request_devices": + deployment_id = form_data.get('deployment_id') + group_id = form_data.get('group_id') + location = form_data.get('location') + if location == "0": + location = "All" + is_fresh = form_data.get('is_fresh') + matching_devices = GetMatchingDevices(privileges, group_id, deployment_id, location) + dataa = {} + dataa['Function'] = "devices_report" + if len(matching_devices) > 0: + dataa['devices'] = matching_devices + else: + dataa['devices'] = [] + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "get_deployment_details": + deployment_id = form_data.get('deployment_id') + group_id = form_data.get('group_id') + location = form_data.get('location') + if location == "0": + location = "All" + is_fresh = form_data.get('is_fresh') + matching_devices = GetMatchingDevicesComplete(privileges, group_id, deployment_id, location) + deployment = DeploymentDetails(deployment_id) + dataa = {} + dataa['Function'] = "devices_report" + if len(matching_devices) > 0: + dataa['devices'] = matching_devices + else: + dataa['devices'] = [] + + if len(deployment) > 0: + dataa['details'] = deployment + else: + dataa['details'] = {} + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "device_form": + import uuid + request_id = str(uuid.uuid4())[:8] + logger.debug(f"[{request_id}] device_form ENTRY") + + device_id = None + if 'editing_device_id' in form_data: + device_id = int(form_data.get('editing_device_id')) + logger.debug(f"[{request_id}] Found editing_device_id: {device_id}") + else: + logger.debug(f"[{request_id}] No editing_device_id found, device_id = {device_id}") + + ok = 0 + logger.debug(f"[{request_id}] privileges = {privileges}") + + if privileges == "-1": + logger.debug(f"[{request_id}] CALLING StoreDevice2DB with device_id: {device_id}") + ok = StoreDevice2DB(form_data, device_id) + logger.debug(f"[{request_id}] StoreDevice2DB returned: {ok}") + payload = {'ok': 1} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + logger.debug(f"[{request_id}] Non-admin path...") + if device_id != None: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id: + logger.debug(f"[{request_id}] CALLING StoreDevice2DB in loop with device_id: {device_id}") + ok = StoreDevice2DB(form_data, device_id) + logger.debug(f"[{request_id}] StoreDevice2DB in loop returned: {ok}") + break + else: + logger.debug(f"[{request_id}] device_id is None, returning error") + payload = {'ok': 0} + resp.media = package_response(payload) + resp.status = falcon.HTTP_500 + return + + logger.debug(f"[{request_id}] Final ok value: {ok}") + elif function == "device_set_group": + group_id = int(form_data.get('group_id')) + + MAC = form_data.get('mac') + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = StoreGroupToDevice(device_id_or_mac, group_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreGroupToDevice(device_id_or_mac, group_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_set_well_id": + well_id = int(form_data.get('well_id')) + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + + ok = "" + if privileges == "-1": + ok = StoreWellIdToDevice(device_id_or_mac, well_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreWellIdToDevice(device_id_or_mac, well_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_get_live": + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = GetDeviceLive(device_id_or_mac, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = GetDeviceLive(device_id_or_mac, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_set_network_id": + network_id = int(form_data.get('network_id')) + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = StoreNetworkIdToDevice(device_id_or_mac, network_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreNetworkIdToDevice(device_id_or_mac, network_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_reboot": + if 'mac' in form_data: + MAC = form_data.get('mac').upper() + device_id_or_mac = MAC + else: + device_id = int(form_data.get('device_id')) + device_id_or_mac = device_id + + ok = "" + if privileges == "-1": + ok = DeviceReboot(device_id_or_mac, user_name) + else: + devices = GetVisibleDevices(privileges) + #for this to work, device_id needs to be specified, not MAC! + for device in devices: + if device[0] == device_id_or_mac: + ok = DeviceReboot(device_id_or_mac, user_name) + break + + print(f"OK = {ok}") + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_delete": + + #check if admin! + + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "alarm_on_off": + deployment_id = form_data.get('deployment_id') + alarm_on = int(form_data.get('alarm_on')) + + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + # Lets prepare data to do same as store_alarms function + #read alarm_deployment_settings and all alarm_device_settings from db, and armm all bits that are enabled + deployment_alarms_json, device_alarms_json_map = GetAlarmAllDetails(deployment_id) + deployment_alarms = json.loads(deployment_alarms_json) + enabled = deployment_alarms["enabled"] + if alarm_on == 0: + if GetBit(enabled, 2): + enabled = set_character(enabled, 2, "0") + deployment_alarms["enabled"] = enabled + deployment_alarms_json = json.dumps(deployment_alarms) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + ok = StoreAlarms2DBSimple(deployment_id, 0, deployment_alarms_json, "") + else: + if not GetBit(enabled, 2): + enabled = set_character(enabled, 2, "1") + deployment_alarms["enabled"] = enabled + deployment_alarms_json = json.dumps(deployment_alarms) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + ok = StoreAlarms2DBSimple(deployment_id, 0, deployment_alarms_json, "") + + + if False: #no need to do it since every new_alarms call reads alarm_deployment_settings_ always + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + record_json = json.dumps(record) + redis_conn.lpush('new_alarms', record_json) + + if alarm_on != 0: + for device_id in device_alarms_json_map: + device_alarms_json = device_alarms_json_map[device_id] + device_alarms = json.loads(device_alarms_json) + enabled_alarms = device_alarms["enabled_alarms"] + armed_states = device_alarms["armed_states"] + + if GetBit(enabled_alarms, 8): + armed_states = set_character(armed_states, 8, "1") + if GetBit(enabled_alarms, 9): + armed_states = set_character(armed_states, 9, "1") + if GetBit(enabled_alarms, 10): + armed_states = set_character(armed_states, 10, "1") + + device_alarms["armed_states"] = armed_states + device_alarms_json = json.dumps(device_alarms) + redis_conn.set(f'alarm_device_settings_{device_id}', device_alarms_json) + ok = StoreAlarms2DBSimple(0, device_id, "", device_alarms_json) + + #of course it is needed, how will well-alerts know that new data is stored to db? + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('new_alarms', record_json) + + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_alarm_state": + deployment_id = form_data.get('deployment_id') + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + # Lets prepare data to do same as store_alarms function + #read alarm_deployment_settings and all alarm_device_settings from db, and armm all bits that are enabled + deployment_alarms_json, device_alarms_json_map = GetAlarmAllDetails(deployment_id) + deployment_alarms = json.loads(deployment_alarms_json) + enabled = deployment_alarms["enabled"] + if not GetBit(enabled, 2): + alarm_state = 2 #off + else: + #if any device was trigerred, show 0, otherwise 1 + alarm_state = 1 + for device_id in device_alarms_json_map: + device_alarms_json = device_alarms_json_map[device_id] + device_alarms = json.loads(device_alarms_json) + enabled_alarms = device_alarms["enabled_alarms"] + armed_states = device_alarms["armed_states"] + + if GetBit(enabled_alarms, 8): + if not GetBit(armed_states, 8): #if 0 + alarm_state = 0 + break + if GetBit(enabled_alarms, 9): + if not GetBit(armed_states, 9): + alarm_state = 0 + break + if GetBit(enabled_alarms, 10): + if not GetBit(armed_states, 10): + alarm_state = 0 + break + + + payload = {'ok': 1, 'alarm_state':alarm_state} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "submit_mobile_message": + message = form_data.get('message') + mqtt_id = form_data.get("mqtt_id") + privileges, user_id = GetPriviledgesAndUserId(user_name) + if "function" in message: + current_utc_time = datetime.datetime.now(timezone.utc) + message_map = json.loads(message) + func = message_map["function"] + + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + ok = 1 + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = int(current_utc_time.timestamp() *1000) + + + sql = f""" + INSERT INTO public.mobile_clients_messages + (time, mqtt_id, message, function) + VALUES + ({current_epoch_time}, '{CleanObject(mqtt_id)}','{CleanObject(message)}','{CleanObject(func)}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + + except Exception as e: + logger.error(f"Error inserting to mobile_clients_messages: {str(e)}") + ok = 0 + + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = int(current_utc_time.timestamp() *1000) + + + sql1 = f""" + INSERT INTO public.mobile_clients + (mqtt_id, user_name, user_id, last_message, last_message_time) + VALUES + ('{CleanObject(mqtt_id)}', '{CleanObject(user_name)}', {user_id}, '{CleanObject(message)}', {current_epoch_time}) + ON CONFLICT (mqtt_id) + DO UPDATE SET + user_name = EXCLUDED.user_name, + user_id = EXCLUDED.user_id, + last_message = EXCLUDED.last_message, + last_message_time = EXCLUDED.last_message_time; + """ + logger.debug(f"sql= {sql1}") + # Execute update query + cur.execute(sql1) + + # Commit the changes to the database + conn.commit() + + except Exception as e: + logger.error(f"Error inserting to mobile_clients: {str(e)}") + ok = 0 + + cur.close() + conn.close() + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "get_raw_data": + #container = GetReference("/MAC") + #MAC = req_dict["MAC"][0] + #sensor = req_dict["sensor"][0] + #if "part" in req_dict: + #part = req_dict["part"][0] + #else: + #part = "" + #from_time = req_dict["from_time"][0] + #to_time = req_dict["to_time"][0] + #timezone_str = req_dict["tzone"][0] + #AddToLog("get_raw_data:" + str(MAC) +","+ str(sensor) + "," + str(from_time) + "," + str(to_time) + "," + part+ "," + timezone_str) + ##raw_data = GetRawSensorData(container, MAC, sensor, from_time, to_time, timezone_str) + raw_data = []#GetRawSensorDataFromBlobStorage(MAC, sensor, part, from_time, to_time, timezone_str) + data_payload = {'raw_data': raw_data} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_presence_data": + + deployment_id = form_data.get('deployment_id') + device_id_in_s = form_data.get('device_id') + device_id_in = None + refresh = True#form_data.get('refresh') == "1" + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + filter = int(form_data.get('filter')) + ddate = form_data.get('date') + ddate = ddate.replace("_","-") + to_date = form_data.get('to_date') + + if to_date == None: + to_date = ddate + else: + to_date = to_date.replace("_","-") + + ddate, to_date = ensure_date_order(ddate, to_date) + + + date_obj = datetime.datetime.strptime(ddate, "%Y-%m-%d") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + # Convert back to string + prev_date = previous_day.strftime("%Y-%m-%d") + + data_type = form_data.get('data_type') #all, raw, presence, z-graph + if data_type == None or data_type == "": + data_type = "presence" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + if device_id_in_s != None: #lets remove other devices, since asking for one + device_id_in = int(device_id_in_s) + device_ids = [id for id in device_ids if id == device_id_in] + devices_list = [device for device in devices_list if device[1] == device_id_in] + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + + + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + + + + # Calculate the difference in days + + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from_z.utctimetuple()) + + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + + + #epoch_time = calendar.timegm(time_from.utctimetuple()) + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + + if device_id_in == None or details[1] == device_id_in: + if "," in details[5]: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + #threshold_lst = ["s3_max",12] + else: + threshold_lst = ["s3",int(details[5])] + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #print(sql) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + cur.execute(zsql) + myz_data = cur.fetchall() + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + presence_map['longpresence'] = {} + presence_map['raw'] = {} + + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour - 7, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + #) + start_time = parsed_time.astimezone(pytz.UTC) + + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3",12] + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + + + print(f"start_time: {start_time}") + print(f"epoch_time being sent: {epoch_time}") + print(f"epoch_time as date: {datetime.datetime.fromtimestamp(epoch_time, tz=pytz.UTC)}") + + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + presence_map = optimized_radar_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + #save_list_to_csv_method1(presence_map['longpresence'][475], "longpresence_initial_data.csv") + + + + overlaps_str = GetOverlapps(deployment_id) + overlaps_lst = [] + if overlaps_str != None: + if ":" in overlaps_str: + overlaps_lst = json.loads(overlaps_str) + temporary_map_day_plus = ClearOverlaps(temporary_map_day_plus, overlaps_lst) + + for device_id in ids_list: + device_id_str = str(device_id) + + if filter > 1: + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s, refresh) + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + + #if data_type != "presence": + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + presence_map["z_graph"][id2well_id[device_id]] = z_graph + + + if data_type == "all" or data_type == "multiple": + #lets create "multiple" series + seen_at_lst, seen_where_list_uf = DetectMultiple(temporary_map_day_plus, overlaps_lst) + #here seen_at is straight decas + #seen_at = [1 if x >= 2 else 0 for x in seen_at] + pers_in_deka = [] + dekas_in_day = 6 * 1440 + for i in range(dekas_in_day, len(seen_where_list_uf)): + n_pers = seen_where_list_uf[i] + pers_in_deka.append(100*len(n_pers)) + + seen_at = filter_out_short_highs_iterative(seen_at_lst, filter) #this converts decas into compressed format! + seen_at_lst = Decompress(seen_at) + pers_in_deka = filter_out_short_same_groups_iterative(pers_in_deka, filter) + persons_decompressed = Decompress(pers_in_deka) + persons = Compress(persons_decompressed) + + multiple_list = CreateZGraphAI(seen_at_lst) + presence_map["multiple"] = multiple_list + presence_map["persons"] = persons + + presence_map["presence"] = CompressList(presence_map["longpresence"]) + + if data_type == "z-graph": + if "raw" in presence_map: + del presence_map["raw"] + + #if "presence" in presence_map: + # del presence_map["presence"] + + if "longpresence" in presence_map: + del presence_map["longpresence"] + + if data_type == "multiple": + if "raw" in presence_map: + del presence_map["raw"] + #if "presence" in presence_map: + # del presence_map["presence"] + + if "z_graph" in presence_map: + del presence_map["z_graph"] + + #if "presence" in presence_map: + if "longpresence" in presence_map: + del presence_map["longpresence"] + + data_payload = presence_map + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_zgraph_data": + + deployment_id = form_data.get('deployment_id') + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + device_id = int(form_data.get('device_id')) + + devices = GetVisibleDevices(privileges) + + if not any(item[0] == device_id for item in devices): + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + filter = int(form_data.get('filter')) + ddate = form_data.get('date') + ddate = ddate.replace("_","-") + to_date = form_data.get('to_date') + + if to_date == None: + to_date = ddate + else: + to_date = to_date.replace("_","-") + + ddate, to_date = ensure_date_order(ddate, to_date) + data_type = "z-graph" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from_z.utctimetuple()) + + #time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #epoch_time = calendar.timegm(time_from.utctimetuple()) + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + # Calculate the difference in days + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + #threshold_lst = ["s3_max",12] + + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zsql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + cur.execute(zsql) + myz_data = cur.fetchall() + + #if my_data != None: + + #device_id_2_threshold = {} + #device_id_2_location = {0: "Outside"} + #row_nr_2_device_id = {} + #cnt = 0 + #row_nr_2_device_id[0] = 0 + + ##presence_map['longpresence'] and temporary_map_day_plus are similar, except one is used for Z-graph, and another for multiple persons detection + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'] = {} + #presence_map['longpresence'] = {} + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'] = {} + + #for details in devices_list: + ##(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + #well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "raw" or data_type == "all": + #zeros_list = [0] * 6 * 1440 * days_difference + #presence_map['raw'][well_id] = zeros_list + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #zeros_list = [0] * 6 * 1440 * days_difference + #presence_map['presence'][well_id] = zeros_list + + + ##presence_map[][well_id] = zeros_list + #cnt += 1 + #row_nr_2_device_id[cnt] = well_id + + #if radar_threshold_group_st == None: + #radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + #if len(radar_threshold_group_st) > 8: + #radar_threshold_group = json.loads(radar_threshold_group_st) + #else: + #radar_threshold_group = ["s3",12] + + #device_id_2_location[well_id] = location_name + #device_id_2_threshold[well_id] = radar_threshold_group + + #start_time_ = my_data[0][0] + #parsed_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour - 7, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + #) + + #presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + ##last_device_id = 0 + ##for radar_read in my_data: #(datetime.datetime(2025, 4, 28, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200))), 559, 6.512857142857143, 6.91, 9.28) + ##local_time = radar_read[0] + ##deca = int((local_time - start_time).total_seconds() / 10) + ##device_id = radar_read[1] + ##if device_id != last_device_id: + ##last_device_id = device_id + ##if data_type == "raw" or data_type == "all": + ##days_decas = len(presence_map['raw'][id2well_id[device_id]]) + ##else: + ##days_decas = len(presence_map['presence'][id2well_id[device_id]]) + ##well_id = id2well_id[device_id] + ##radar_threshold_group_st = device_id_2_threshold[device_id] + ##threshold_sig, threshold = radar_threshold_group_st + ##threshold_sig = threshold_sig.split("_")[0] + + ##radar_val = radar_read[2+device_field_indexes[threshold_sig]] + ##if data_type == "presence" or data_type == "z-graph" or data_type == "all" or data_type == "multiple": + ##if radar_val > threshold: + ##if deca < days_decas: + ##presence_map['presence'][id2well_id[device_id]][deca] = 1 + + ##if data_type == "raw" or data_type == "all": + ##if deca < days_decas: + ##presence_map['raw'][id2well_id[device_id]][deca] = radar_val + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zeros_list = [0] * 6 * 1440 * (days_difference_long) #+1 is for previous day + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * (days_difference_long) + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * (days_difference_long) #just place holder + + + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + start_time = datetime.datetime( + parsed_time.year, + parsed_time.month, + parsed_time.day, + parsed_time.hour - 7, # Adjust for UTC-7 + parsed_time.minute, + parsed_time.second, + tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + ) + + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + + if data_type == "all" or data_type == "z-graph" or data_type == "presence" or data_type == "multiple": + overlaps_str = GetOverlapps(deployment_id) + overlaps_lst = [] + if overlaps_str != None: + if ":" in overlaps_str: + overlaps_lst = json.loads(overlaps_str) + temporary_map_day_plus = ClearOverlaps(temporary_map_day_plus, overlaps_lst) + + #if data_type == "all" or data_type == "z-graph" or data_type == "presence" or data_type == "multiple": + for device_id in ids_list: + device_id_str = str(device_id) + #if data_type == "presence" or data_type == "all" or data_type == "z-graph": + if filter > 1: + #presence_list = filter_short_groups_numpy(presence_map["presence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + #presence_list = filter_short_groups_c_wc(presence_map["presence"][id2well_id[device_id]], filter, device_id_str, ddate, to_date, time_zone_s) + #presence_listt = filter_short_groupss(presence_map["presence"][id2well_id[device_id]], filter) + #if presence_list != presence_listt: + # print("stop") + #if data_type != "presence": + #longpresence_list = filter_short_groups_numpy(presence_map["longpresence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s) + #longpresence_listt = filter_short_groupss(presence_map["longpresence"][id2well_id[device_id]], filter) + #if longpresence_list != longpresence_listt: + # print("stop") + # store_to_file(presence_map["longpresence"][id2well_id[device_id]], "test_list") + #presence_map["presence"][id2well_id[device_id]] = presence_list + #if data_type != "presence": + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + + #if data_type != "presence": + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + if filter > 1: #straight decas + presence_list1 = filter_short_high_groups_iterative_analog(temporary_map_day_plus[id2well_id[device_id]], filter) + else: + presence_list1 = temporary_map_day_plus[id2well_id[device_id]] + + temporary_map_day_plus[id2well_id[device_id]] = presence_list1 + + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + for device_id in ids_list: + #print(device_id_2_threshold[id2well_id[device_id]]) + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + presence_map["z_graph"][id2well_id[device_id]] = z_graph + + + if data_type == "all" or data_type == "multiple": + #lets create "multiple" series + seen_at_lst, seen_where_list_uf = DetectMultiple(temporary_map_day_plus, overlaps_lst) + #here seen_at is straight decas + #seen_at = [1 if x >= 2 else 0 for x in seen_at] + pers_in_deka = [] + dekas_in_day = 6 * 1440 + for i in range(dekas_in_day, len(seen_where_list_uf)): + n_pers = seen_where_list_uf[i] + pers_in_deka.append(100*len(n_pers)) + + seen_at = filter_out_short_highs_iterative(seen_at_lst, filter) #this converts decas into compressed format! + seen_at_lst = Decompress(seen_at) + pers_in_deka = filter_out_short_same_groups_iterative(pers_in_deka, filter) + persons_decompressed = Decompress(pers_in_deka) + persons = Compress(persons_decompressed) + + multiple_list = CreateZGraphAI(seen_at_lst) + presence_map["multiple"] = multiple_list + presence_map["persons"] = persons + + presence_map["presence"] = presence_map["longpresence"] + if data_type == "z-graph": + if "raw" in presence_map: + del presence_map["raw"] + if "presence" in presence_map: + del presence_map["presence"] + if "longpresence" in presence_map: + del presence_map["longpresence"] + + if data_type == "multiple": + if "raw" in presence_map: + del presence_map["raw"] + if "presence" in presence_map: + del presence_map["presence"] + if "longpresence" in presence_map: + del presence_map["longpresence"] + if "z_graph" in presence_map: + del presence_map["z_graph"] + + if "presence" in presence_map: + presence_map["presence"] = CompressList(presence_map["presence"]) + + data_payload = presence_map + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_candle_data": + container = GetReference("/MAC") + MAC = req_dict["MAC"][0] + sensor = req_dict["sensor"][0] + from_time = req_dict["from_time"][0] + to_time = req_dict["to_time"][0] + part = req_dict["part"][0] + tzone = req_dict["tzone"][0] + AddToLog(str(req_dict)) + candle_data = GetCandleSensorData(container, MAC, sensor, from_time, to_time, part, tzone) + data_payload = {'candle_data': candle_data} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "deployment_form": + editing_deployment_id = form_data.get('editing_deployment_id') + + ok = StoreDeployment2DB(form_data, editing_deployment_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "deployment_delete": + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "deployments_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + user_id = form_data.get('user_id') + first = 0 + last = 1000000 + + try: + if first_s != None: + first = int(first_s) + except ValueError: + pass + + try: + if last_s != None: + last = int(last_s) + except ValueError: + pass + + #user_id = form_data.get('user_id') + if user_id == "" or user_id == None: + #user_id = GetUserId(user_name) + privileges, user_id = GetPriviledgesAndUserId(user_name) + else: + privileges = GetPriviledgesOnly(user_name) + + all_deployments = ListDeployments(privileges, user_id) + + cnt = 0 + + for deployment in all_deployments: + cnt += 1 + if cnt >= first: + if deployment['beneficiary_id'] in user_id_2_user.keys(): + caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": user_id_2_user[deployment['beneficiary_id']][3], "first_name": user_id_2_user[deployment['beneficiary_id']][5], "last_name": user_id_2_user[deployment['beneficiary_id']][6]} + else: + caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": deployment['beneficiary_id'][3], "first_name": deployment['beneficiary_id'][5], "last_name": deployment['beneficiary_id'][6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "device_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + #user_id = form_data.get('user_id') + + devices = GetVisibleDevices(privileges) + + payload = {'result_list': devices} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_devices_locations": + well_ids = form_data.get('well_ids') + details_list = WellId2Details(well_ids) + #print(details_list) + + to_report = [] + + for details in details_list: + if details[3] == -1: + report_record = str(details[0]) + " ?" + else: + report_record = str(details[0]) + " " + location_names[details[3]] + if details[4] != "" and details[4] != "initial": + report_record = report_record + " " +details[4] + if details[3] == -1: + to_report.append((details[0], "?", details[4], report_record)) + else: + to_report.append((details[0], location_names[details[3]], details[4], report_record)) + + #print(to_report) + payload = {'deployments': to_report} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "find_deployments": + #For all devices, find deployments that they are part of + #For all those deployments, return: + #deployment_id First_name Last_name (of beneficiary) + #list of (well_id, location_descriptions) all devices in each deployment + + + well_ids = form_data.get('well_ids') + #well_ids_lst = well_ids.split(",") + details_list = WellId2Details(well_ids) + to_report = [] + + privileges_lst = [] + if "," in privileges: + privileges_lst = privileges.split(",") + + if len(details_list) > 0: + macs_list_clean = [] + devices_details = {} + + for entry in details_list: + macs_list_clean.append(entry[2]) + macs_formatted = "', '".join(macs_list_clean) + + with get_db_connection() as conn: + with conn.cursor() as cur: + sql = f"SELECT deployment_id, beneficiary_id, devices FROM public.deployment_details WHERE devices::text ~* ANY(ARRAY['{macs_formatted}']);" + print(sql) + cur.execute(sql) + result = cur.fetchall() + + if result != None and result != []: + users_list_clean = [] + for entry in result: #for every deployment + macs_list_clean = [] + deployment_id = str(entry[0]) + if privileges == '-1': + deployment = DeploymentDetails(deployment_id) + address_str = ExtractAddress(deployment) + deployment = PurgeDeployment(deployment) + deployment["address"] = address_str + + users_list_clean.append(str(entry[1])) + users_formatted = ", ".join(users_list_clean) + + mac_devices_in_deployment = ast.literal_eval(entry[2]) + for mac in mac_devices_in_deployment: + macs_list_clean.append(mac) + + device_ids, device_list = MACsToWellIds(cur, macs_list_clean) + + devices_details[deployment_id] = deployment + + elif deployment_id in privileges_lst: + deployment = DeploymentDetails(deployment_id) + address_str = ExtractAddress(deployment) + deployment = PurgeDeployment(deployment) + deployment["address"] = address_str + users_list_clean.append(str(entry[1])) + users_formatted = ", ".join(users_list_clean) + + mac_devices_in_deployment = ast.literal_eval(entry[2]) + for mac in mac_devices_in_deployment: + macs_list_clean.append(mac) + + device_ids, device_list = MACsToWellIds(cur, macs_list_clean) + devices_details[deployment_id] = deployment + + device_list_to_report = [] + + for device in device_list: + #we need well_id, location and decription only + device_list_to_report.append((device[0], device[2], device[3])) + sql = f"SELECT first_name, last_name, email FROM public.person_details WHERE user_id IN ({users_formatted});" + print(sql) + cur.execute(sql) + result1 = cur.fetchall() + counter = 0 + + for entry in result: + deployment_id = str(entry[0]) + deployment = devices_details[deployment_id] + first_name, last_name, email = result1[counter] + deployment["beneficiary_first_name"] = first_name + deployment["beneficiary_last_name"] = last_name + deployment["beneficiary_email"] = last_name + devices_details[deployment_id] = deployment + to_report.append((entry, device_list_to_report, devices_details[deployment_id])) + + + print(to_report) + payload = {'deployments': to_report} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "device_list_by_deployment": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + deployment_id = form_data.get('deployment_id') + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + + if privileges == "-1": + devices = GetVisibleDevices(deployment_id) + else: + privileges = privileges.split(",") + if deployment_id in privileges: + devices = GetVisibleDevices(deployment_id) + + payload = {'result_list': devices} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "device_list_4_gui": + result_list = [] + deploymentData = [] + deviceData = [] + macs_list = [] + user_id = GetUserId(user_name) + all_deployments = ListDeployments(privileges, user_id) + #{'deployment_id': 21, 'beneficiary_id': 25, 'caretaker_id': 1, 'owner_id': 1, 'installer_id': 1, 'address_street': '661 Encore Way', 'address_city': 'San Jose', 'address_zip': '95134', 'address_state': 'CA', 'address_country': 'USA', 'devices': '["64B70888FAB0","64B70888F860","64B70888F6F0","64B708896BDC","64B708897428","64B70888FA84","64B70889062C"]', 'wifis': '', 'persons': 1, 'gender': 1, 'race': 1, 'born': 1940, 'pets': 0, 'time_zone': 'America/Los_Angeles'} + MAC2Deployment = {} + + for deployment in all_deployments: + beneficiary_id = deployment['beneficiary_id'] + user = GetNameFromUserId(beneficiary_id) + name = f"{user[1]} {user[2]}" + deploymentData.append({'deployment_id': str(deployment['deployment_id']), 'name': name}) + devices = deployment['devices'] + if devices != None and devices != None != "": + + devices_list = ToList(devices) + for device in devices_list: + macs_list.append(device) + MAC2Deployment[device] = deployment['deployment_id'] + #deviceData.append({'well_id': device[0], 'mac': device[1]}) + + deployment_id_list = [] + deviceData = [] + + #row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + + with get_db_connection() as conn: + with conn.cursor() as cur: + device_ids, device_list = MACsToWellIds(cur, macs_list) + + + for device in device_list: + if MAC2Deployment[device[4]] != "": + deviceData.append({'well_id': device[0], 'mac': device[4], 'room_name': device[2], 'deployment_id': MAC2Deployment[device[4]]}) + + #deploymentData = [{'deployment_id': '21', 'name': 'Robert Zmrzli House'}, {'deployment_id': '36', 'name': 'Fred Zmrzli Apartment'}] + #deviceData = [{ 'well_id': '300', 'mac': '64B70888F6F0', 'room_name': 'Living Room', 'deployment_id': '21' }, { 'well_id': '301', 'mac': '64B70888F6F1', 'room_name': 'Bathroom Main', 'deployment_id': '36' }] + + payload = { + 'status': "success", 'deploymentData': deploymentData, 'deviceData': deviceData + } + + logger.debug(f"device_list_4_gui------ {payload} ------------------------------------------") + + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "caretaker_form": + editing_user_id = form_data.get('editing_user_id') + email = form_data.get('email') + user_id = form_data.get('user_id') + if "@" not in email: + resp.media = package_response("Missing or illegal 'email' parameter", HTTP_400) + return + + print(privileges) + if privileges == "-1": + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif "-1" in privileges: + payload = {'ok': 0, 'error': "Not allowed!"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + + elif function == "caretaker_delete": + if privileges == "-1": + ok = DeleteRecordFromDB(form_data) + else: + ok = 0 + AddToLog(ok) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "caretakers_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + if privileges == "-1": + all_caretakers = ListCaretakers(privileges, user_name) + + cnt = 0 + + for caretaker in all_caretakers: + cnt += 1 + if cnt >= first: + caretaker_min_object = {"user_id": caretaker[0], "email": caretaker[3], "first_name": caretaker[5], "last_name": caretaker[6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + elif "-1" in privileges: + all_caretakers = ListCaretakers(privileges, user_name) + + cnt = 0 + + for caretaker in all_caretakers: + cnt += 1 + if cnt >= first: + caretaker_min_object = {"user_id": caretaker[0], "email": caretaker[3], "first_name": caretaker[5], "last_name": caretaker[6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "new_user_form": + devices = form_data.get('devices') + user_name = form_data.get('user_name') + password = form_data.get('password') + #lets check if devices listed are not part of existing deployment + success, result = DevicesNotUsed(devices, user_name) + + if success: + if result["deployed"]: + error_string = f"These devices are already deployed: {result['deployed']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if result["not_found"]: + error_string = f"These devices are not available: {result['not_found']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if not result["deployed"] and not result["not_found"]: + print("All devices are available for deployment") + ok, error_string = StoreDisclaimer2DB(form_data) + + if ok == 1: + #Lets check if this user already has account or not + if not AccountExists(user_name): + #lets create new account for this caretaker + if password == None or password == "": + password = CreatePassword(8) + + #lets suplement form_data with parts needed for existing StoreCaretaker2DB function + editing_user_id = "0" #specify if editing existing user, otherwise "0" + user_id = "0" #user that is adding this record. New user so "0" + form_data['role_ids'] = "1,2" + form_data['access_to_deployments'] = "45" + #form_data['email'] = "" #this one matches + form_data['new_user_name'] = form_data['user_name'] + form_data['first_name'] = form_data['firstName'] + form_data['last_name'] = form_data['lastName'] + form_data['address_street'] = "" + form_data['address_city'] = "" + form_data['address_zip'] = "" + form_data['address_state'] = "" + form_data['address_country'] = "" + form_data['phone_number'] = form_data['phone'] + form_data['picture'] = "/" + form_data['key'] = password + + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + + if ok == 1: + + #we need to call cd ~/mqtt-auth-service/acl_manager.py + + SendWelcomeEmail(form_data['email'], form_data['first_name'], form_data['last_name'], devices, form_data['phone_number'], form_data['new_user_name'], form_data['key']) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + privileges, user_id = ValidUser(user_name, password) + if user_id == "0": #bad password + error_string = f"Password does not match user {user_name}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + else: + error_string = f"Error: {result}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiary_form": + editing_user_id = form_data.get('editing_user_id') + email = form_data.get('email') + user_id = GetUserId(user_name) + if "@" in email: + ok, error_string = StoreBeneficiary2DB(form_data, editing_user_id, user_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiary_delete": + + + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiaries_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + user_id = form_data.get('user_id') + all_beneficiaries = ListBeneficiaries(privileges, user_id) + + cnt = 0 + + for beneficiary in all_beneficiaries: + cnt += 1 + if cnt >= first: + beneficiary_min_object = {"user_id": beneficiary[0], "email": beneficiary[3], "first_name": beneficiary[5], "last_name": beneficiary[6]} + result_list.append(beneficiary_min_object) + if cnt > last: + break + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "activities_report_details": + deployment_id = form_data.get('deployment_id') + + timezone_str = GetTimeZoneOfDeployment(deployment_id) + filterr = form_data.get('filter') + if filterr == None: + filterr = 6 + else: + filterr = int(filterr) + + refresh = form_data.get('refresh') == "1" + ddate = current_date_at_tz(timezone_str) + timee = LocalDateToUTCEpoch(ddate, timezone_str)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Here we need to add per day: (all based on Z-graph data!) + #Bathroom visits number + #Bathroom time spent + #Sleep weakes number (As breaks in Z-graph indicates in 10PM to 9AM period) + #Sleep length (For now add all times seen in bedroom) + #Kitchen visits number + #Kitchen time spent + #Most frequented room visits number + #Most frequented room time spent + + #Lets find device_id of bathroom sensor + + + bathroom_device_id, location_ba, bathroom_well_id = FindDeviceByRole(deployment_id, ["Bathroom Main", "Bathroom", "Bathroom Guest"]) + bedroom_device_id, location_be, bedroom_well_id = FindDeviceByRole(deployment_id, ["Bedroom Master", "Bedroom", "Bedroom Guest"]) + kitchen_device_id, location_ke, kitchen_well_id = FindDeviceByRole(deployment_id, ["Kitchen"]) + most_present_device_id, location_ot, most_present_well_id = FindDeviceByRole(deployment_id, []) #this will find most_present (as defined in other filed of device record) + + if isinstance(location_ot, int): + other_location = location_names[location_ot] + else: + other_location = location_ot + + #weekly + week_dates = get_week_days_and_dates(7, timezone_str) + month_dates = get_week_days_and_dates(30, timezone_str) + six_months_dates = get_week_days_and_dates(180, timezone_str) + + other_color = Loc2Color[other_location][0] + rgb_string = f"rgb({other_color[0]}, {other_color[1]}, {other_color[2]})" + + rooms_reports = [("Bathroom", "blue", bathroom_device_id, bathroom_well_id), ("Bedroom", "green", bedroom_device_id, bedroom_well_id), ("Kitchen", "red", kitchen_device_id, kitchen_well_id), (other_location, rgb_string, most_present_device_id, most_present_well_id)] + + six_months_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in six_months_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + if hours > 18: + print("Too long 6m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + six_months_report.append(room) + + weekly_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in week_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + data_record = { "title": day_activity[1], "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + weekly_report.append(room) + + monthly_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in month_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + #if datee == "2025-05-20" and device_id == 572: + # print(hours) + if hours > 18: + print("Too long m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + monthly_report.append(room) + + + + + result_dictionary = { + "alert_text": "No alert", + "alert_color": "bg-green-100 text-green-700", + "chart_data": [ + { + "name": "Weekly", + "rooms": [ + { + "name": "Bathroom", + "color": "blue", + "data": [ + { "title": "Monday", "events": 186, "hours": 80.56 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Bedroom", + "color": "green", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Kitchen", + "color": "red", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Other", + "color": "yellow", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + } + ] + }, + { + "name": "Monthly", + "rooms": [ + { + "name": "Bathroom", + "color": "purple", + "data": [ + { "title": "01", "events": 67, "hours": 45 }, + { "title": "02", "events": 97, "hours": 67 }, + { "title": "03", "events": 87, "hours": 23 }, + { "title": "04", "events": 42, "hours": 12 }, + { "title": "05", "events": 64, "hours": 48 }, + { "title": "06", "events": 53, "hours": 34 }, + { "title": "07", "events": 75, "hours": 23 }, + { "title": "08", "events": 45, "hours": 56 }, + { "title": "09", "events": 85, "hours": 47 }, + { "title": "10", "events": 34, "hours": 29 }, + { "title": "11", "events": 49, "hours": 30 }, + { "title": "12", "events": 62, "hours": 33 }, + { "title": "13", "events": 75, "hours": 44 }, + { "title": "14", "events": 88, "hours": 57 }, + { "title": "15", "events": 94, "hours": 65 }, + { "title": "16", "events": 45, "hours": 21 }, + { "title": "17", "events": 76, "hours": 54 }, + { "title": "18", "events": 85, "hours": 62 }, + { "title": "19", "events": 43, "hours": 28 }, + { "title": "20", "events": 59, "hours": 34 }, + { "title": "21", "events": 78, "hours": 56 }, + { "title": "22", "events": 64, "hours": 39 }, + { "title": "23", "events": 93, "hours": 72 }, + { "title": "24", "events": 52, "hours": 28 }, + { "title": "25", "events": 71, "hours": 48 }, + { "title": "26", "events": 85, "hours": 63 } + ] + }, + { + "name": "Bedroom", + "color": "#3b82f6", + "data": [ + { "title": "01", "events": 61, "hours": 42 }, + { "title": "02", "events": 72, "hours": 36 }, + { "title": "03", "events": 94, "hours": 49 }, + { "title": "04", "events": 67, "hours": 59 }, + { "title": "05", "events": 54, "hours": 20 }, + { "title": "06", "events": 77, "hours": 64 }, + { "title": "07", "events": 81, "hours": 70 }, + { "title": "08", "events": 53, "hours": 25 }, + { "title": "09", "events": 79, "hours": 42 }, + { "title": "10", "events": 84, "hours": 65 }, + { "title": "11", "events": 62, "hours": 54 }, + { "title": "12", "events": 45, "hours": 23 }, + { "title": "13", "events": 88, "hours": 71 }, + { "title": "14", "events": 74, "hours": 44 }, + { "title": "15", "events": 91, "hours": 59 }, + { "title": "16", "events": 46, "hours": 31 }, + { "title": "17", "events": 73, "hours": 40 }, + { "title": "18", "events": 85, "hours": 63 }, + { "title": "19", "events": 78, "hours": 66 }, + { "title": "20", "events": 66, "hours": 42 }, + { "title": "21", "events": 95, "hours": 78 }, + { "title": "22", "events": 57, "hours": 39 }, + { "title": "23", "events": 72, "hours": 48 }, + { "title": "24", "events": 48, "hours": 21 }, + { "title": "25", "events": 89, "hours": 61 }, + { "title": "26", "events": 77, "hours": 44 } + ] + }, + { + "name": "Kitchen", + "color": "orange", + "data": [ + { "title": "01", "events": 94, "hours": 59 }, + { "title": "02", "events": 62, "hours": 48 }, + { "title": "03", "events": 76, "hours": 38 }, + { "title": "04", "events": 81, "hours": 62 }, + { "title": "05", "events": 64, "hours": 27 }, + { "title": "06", "events": 53, "hours": 31 }, + { "title": "07", "events": 92, "hours": 65 }, + { "title": "08", "events": 85, "hours": 42 }, + { "title": "09", "events": 74, "hours": 35 }, + { "title": "10", "events": 67, "hours": 55 }, + { "title": "11", "events": 49, "hours": 23 }, + { "title": "12", "events": 88, "hours": 75 }, + { "title": "13", "events": 93, "hours": 66 }, + { "title": "14", "events": 76, "hours": 34 }, + { "title": "15", "events": 59, "hours": 39 }, + { "title": "16", "events": 72, "hours": 51 }, + { "title": "17", "events": 83, "hours": 44 }, + { "title": "18", "events": 74, "hours": 33 }, + { "title": "19", "events": 69, "hours": 28 }, + { "title": "20", "events": 85, "hours": 56 }, + { "title": "21", "events": 53, "hours": 22 }, + { "title": "22", "events": 92, "hours": 70 }, + { "title": "23", "events": 71, "hours": 41 }, + { "title": "24", "events": 67, "hours": 25 }, + { "title": "25", "events": 86, "hours": 74 }, + { "title": "26", "events": 94, "hours": 68 } + ] + }, + { + "name": "Other", + "color": "hotpink", + "data": [ + { "title": "01", "events": 57, "hours": 27 }, + { "title": "02", "events": 74, "hours": 33 }, + { "title": "03", "events": 84, "hours": 53 }, + { "title": "04", "events": 95, "hours": 68 }, + { "title": "05", "events": 71, "hours": 48 }, + { "title": "06", "events": 92, "hours": 76 }, + { "title": "07", "events": 85, "hours": 62 }, + { "title": "08", "events": 49, "hours": 25 }, + { "title": "09", "events": 66, "hours": 38 }, + { "title": "10", "events": 63, "hours": 31 }, + { "title": "11", "events": 75, "hours": 47 }, + { "title": "12", "events": 94, "hours": 72 }, + { "title": "13", "events": 79, "hours": 49 }, + { "title": "14", "events": 72, "hours": 45 }, + { "title": "15", "events": 88, "hours": 61 }, + { "title": "16", "events": 83, "hours": 52 }, + { "title": "17", "events": 92, "hours": 76 }, + { "title": "18", "events": 73, "hours": 40 }, + { "title": "19", "events": 65, "hours": 28 }, + { "title": "20", "events": 76, "hours": 63 }, + { "title": "21", "events": 58, "hours": 30 }, + { "title": "22", "events": 84, "hours": 67 }, + { "title": "23", "events": 72, "hours": 41 }, + { "title": "24", "events": 79, "hours": 46 }, + { "title": "25", "events": 63, "hours": 29 }, + { "title": "26", "events": 68, "hours": 39 } + ] + } + ] + }, + { + "name": "6 Months", + "rooms": [ + { + "name": "Bathroom", + "color": "purple", + "data": [ + { "title": "October", "events": 62, "hours": 23 }, + { "title": "November", "events": 76, "hours": 42 }, + { "title": "December", "events": 85, "hours": 54 }, + { "title": "January", "events": 94, "hours": 67 }, + { "title": "February", "events": 63, "hours": 35 }, + { "title": "March", "events": 81, "hours": 46 } + ] + }, + { + "name": "Bedroom", + "color": "#3b82f6", + "data": [ + { "title": "October", "events": 64, "hours": 35 }, + { "title": "November", "events": 88, "hours": 71 }, + { "title": "December", "events": 79, "hours": 54 }, + { "title": "January", "events": 72, "hours": 49 }, + { "title": "February", "events": 53, "hours": 32 }, + { "title": "March", "events": 93, "hours": 67 } + ] + }, + { + "name": "Kitchen", + "color": "orange", + "data": [ + { "title": "October", "events": 92, "hours": 65 }, + { "title": "November", "events": 85, "hours": 62 }, + { "title": "December", "events": 74, "hours": 49 }, + { "title": "January", "events": 63, "hours": 33 }, + { "title": "February", "events": 78, "hours": 56 }, + { "title": "March", "events": 69, "hours": 41 } + ] + }, + { + "name": "Other", + "color": "hotpink", + "data": [ + { "title": "October", "events": 88, "hours": 54 }, + { "title": "November", "events": 72, "hours": 39 }, + { "title": "December", "events": 84, "hours": 63 }, + { "title": "January", "events": 76, "hours": 46 }, + { "title": "February", "events": 93, "hours": 72 }, + { "title": "March", "events": 68, "hours": 29 } + ] + } + ] + } + ] + } + + result_dictionary["chart_data"][0]["rooms"] = weekly_report + result_dictionary["chart_data"][1]["rooms"] = monthly_report + result_dictionary["chart_data"][2]["rooms"] = six_months_report + + + + payload = result_dictionary #{'result_dictionary': result_dictionary} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + + elif function == "dashboard_list": + # works in UTC only + + logger.error(f"------------------------------- dashboard_list ------------------------------------------") + + caretaker = user_name + #date_s = form_data.get('date') + time_s = form_data.get('time') + date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + filterr = form_data.get('filter') + if filterr == None: + filterr = 5 + + privileges = GetPriviledgesOnly(caretaker) + + deployments_list = GetUsersFromDeployments(privileges) + + #all_beneficiaries = ListBeneficiariesOfCaretaker(caretaker) #GetPriviledgesOnly + #AddToLog(all_beneficiaries) + + result_list = [] + + for deployment_id, first_name, last_name in deployments_list: + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + if details != {}: + + details["units"] = "°C" + if "America" in details["time_zone"]: + details["temperature"] = CelsiusToFahrenheit(details["temperature"]) + details["units"] = "°F" + devices_list, device_ids = GetProximityList(deployment_id, date_s) + # convert dates back to UTC + #details['bathroom_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bathroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['kitchen_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bathroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['bedroom_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bedroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['last_detected_time'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['last_detected_time'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + location_list = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if description == None or description == "": + location_list.append(location_name) + else: + location_list.append(location_name + " " + description) + details["deployment_id"] = deployment_id + details["location_list"] = location_list + result_list.append(details) + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + + elif function == "dashboard_single": + caretaker = user_name + #date_s = form_data.get('date') + date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + deployment_id = form_data.get('deployment_id') + filterr = form_data.get('filter') + if filterr == None: + filterr = 5 + + + #all_beneficiaries = ListBeneficiariesOfCaretaker(caretaker) #GetPriviledgesOnly + #AddToLog(all_beneficiaries) + + result_list = [] + + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr, False) + details["units"] = "°C" + if "America" in details["time_zone"]: + details["temperature"] = CelsiusToFahrenheit(details["temperature"]) + details["bedroom_temperature"] = CelsiusToFahrenheit(details["bedroom_temperature"]) + details["units"] = "°F" + devices_list, device_ids = GetProximityList(deployment_id, date_s) + location_list = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if description == None or description == "": + location_list.append(location_name) + else: + location_list.append(location_name + " " + description) + details["deployment_id"] = deployment_id + details["location_list"] = location_list + settings = {"wellness_score": False, "last_seen": False, "sleep_report": True, "activity_report": True, "temperature": True, "humidity": True, "air_pressure": True, "light": True, "air_quality": True, "radar": True, "other_activities": False} + details["settings"] = settings + result_list.append(details) + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + elif function == "request_node_red": + logger.error(f"------------------------------- {function} ------------------------------------------") + #this will: + # 1.prepare folder and settings.js + # 2.start instance on node-red and return it's return port + #caretaker = user_name + #date_s = form_data.get('date') + time_s = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + #deployment_id = form_data.get('deployment_id') + #redis_conn.set('node_red_requests', str([radar_threshold_signal, radar_threshold_value])) + # Hashes (dictionaries) + logger.error(f"Storing to node_red_requests {user_name}") + redis_conn.hset('node_red_requests', mapping={ + 'user_name': user_name, + 'token': token, + 'time': time_s, + 'requests': 1 + }) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "get_node_red_port": + #this will: + # 1.prepare folder and settings.js + # 2.start instance on node-red and return it's return port + hash_data = GetRedisMap(f'node_red_status_{user_name}') + port = 0 + if hash_data != {}: + port = hash_data['port'] + #date_s = form_data.get('date') + #date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + #deployment_id = form_data.get('deployment_id') + payload = {'port': port} + logger.debug(f"get_node_red_port: {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "activity_detected": + #this will: + # 1.store to REDIS time of last activity + time_s = form_data.get('time') + + hash_data = GetRedisMap(f'node_red_status_{user_name}') + port = 0 + if hash_data != {}: + port = hash_data['port'] + + redis_conn.hset(f'node_red_status_{user_name}', mapping={ + 'port': port, + 'last_activity': time_s + }) + + payload = {'ok': 1} + logger.debug(f"activity_detected: {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "store_flow": + #this will: + # 1.store flow into DB + time_s = form_data.get('time') + flow_json = form_data.get('flow') + logger.debug(f"store_flow: {flow_json}") + StoreFlow2DB(user_name, time_s, flow_json) + payload = {'ok': 1} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "store_alarms": + #this will: + # 1.store flow into DB + deployment_id = form_data.get('deployment_id') + device_id = form_data.get('device_id') + deployment_alarms_json = form_data.get('deployment_alarms') + device_alarms_json = form_data.get('device_alarms') + + logger.debug(f"store_alarms: {deployment_alarms_json}") + + + if privileges == "-1" or deployment_id in privileges: + ok = StoreAlarms2DB(deployment_id, device_id, deployment_alarms_json, device_alarms_json) + + redis_conn.set('alarm_device_settings_'+device_id, device_alarms_json) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('new_alarms', record_json) + + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + else: + payload = {'ok': 0, 'error': "not allowed"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + return + + elif function == "send_walarm": + # Extract data from form + deployment_id = form_data.get('deployment_id') + device_id = form_data.get('device_id') + location = form_data.get('location') + method = form_data.get('method') + feature = form_data.get('feature') + currentAlertTableMode = form_data.get('currentAlertTableMode') + time_s = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + content = form_data.get('content') + enabledCellContent = form_data.get('enabledCellContent') + currentUnits = form_data.get('currentUnits') + test_only = form_data.get('test_only') + action = form_data.get('action') + + logger.debug(f"send_requests: {user_name}") + + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'location': location, + 'method': method, + 'feature': feature, + 'currentAlertTableMode': currentAlertTableMode, + 'time': time_s, + 'content': content, + 'currentUnits': currentUnits, + 'test_only': test_only, + 'action': action, + 'enabledCellContent': enabledCellContent + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('send_requests', record_json) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "node-red_deployed": + #this will: + # 1.store flow into DB + time_s = form_data.get('time') + logger.debug(f"node-red_deployed: {user_name}") + redis_conn.hset('node-red_deployed', mapping={ + 'user_name': user_name, + 'token': token, + 'time': time_s, + 'requests': 1 + }) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + debug_string = "Error: function not recognized!" + AddToLog(debug_string) + payload = {'ok': 0, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + except Exception as e: + print(traceback.format_exc()) + resp.media = package_response(f"Error: {str(e)} {traceback.format_exc()}", HTTP_500) + + + def on_put(self, req, resp, path=""): + """Handle PUT requests""" + if path == "users": + logger.info("PUT request to users endpoint") + try: + # Parse the request body + request_data = json.loads(req.stream.read().decode('utf-8')) + + # TODO: Implement user update logic + + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"id": request_data.get("id"), "message": "User updated"}) + except json.JSONDecodeError: + resp.status = HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Invalid JSON"}) + else: + resp.media = package_response(f"PUT to /{path} not implemented", HTTP_400) + + def on_delete(self, req, resp, path=""): + """Handle DELETE requests""" + if path == "users": + logger.info("DELETE request to users endpoint") + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"message": "User deleted"}) + else: + resp.media = package_response(f"DELETE to /{path} not implemented", HTTP_400) + + +def SendWelcomeEmail(email, first_name, last_name, devices, phone_number, user_name, password): + logger.error(f"Requesting welcome email to {email}") + redis_conn.hset('messaging_requests', mapping={ + 'email': email, + 'user_name': user_name, + 'first_name': first_name, + 'last_name': last_name, + 'devices': devices, + 'phone_number': phone_number, + 'password': password, + 'requests': 1 + }) + + + +def DevicesNotUsed(devices, user_name): + """ + Check if devices exist in DB and if they are already deployed (excluding user's own deployments). + + Args: + devices (str): Comma-separated string of well_id or device_mac values + user_name (str): Username to check access permissions + + Returns: + tuple: (success_flag, result) + - If success: (1, {"deployed": [well_ids], "not_found": [device_identifiers]}) + - If error: (0, error_string) + """ + + if not devices: + return 1, {"deployed": [], "not_found": []} + + # Clean and split the devices string + device_list = [CleanObject(device.strip()) for device in devices.split(',') if device.strip()] + + if not device_list: + return 1, {"deployed": [], "not_found": []} + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + # First, get user's accessible deployments + user_deployments = [] + is_superuser = False + + if user_name: + user_sql = f""" + SELECT access_to_deployments + FROM public.person_details + WHERE user_name = '{CleanObject(user_name)}' + """ + + logger.debug(f"user_sql= {user_sql}") + cur.execute(user_sql) + user_result = cur.fetchone() + + if user_result and user_result[0]: + access_deployments = user_result[0].strip() + + if access_deployments == "-1": + # Superuser has access to all deployments + is_superuser = True + logger.debug("User is superuser - has access to all deployments") + else: + # Parse comma-separated deployment IDs + user_deployments = [int(dep_id.strip()) for dep_id in access_deployments.split(',') if dep_id.strip().isdigit()] + logger.debug(f"User has access to deployments: {user_deployments}") + + deployed_well_ids = [] + not_found_devices = [] + + for device in device_list: + # Check if this device is a well_id or device_mac by string length + if len(device) == 12: + # Exactly 12 characters = device_mac + device_mac = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE device_mac = '{device_mac}' + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + device_well_id = device_result[0] + + # Query to check if this device_mac is deployed (excluding user's accessible deployments) + if is_superuser: + # Superuser has access to all deployments, so no devices are "deployed elsewhere" + sql = "SELECT NULL WHERE FALSE" # Returns no results + elif user_deployments: + deployment_exclusion = f"AND dd.deployment_id NOT IN ({','.join(map(str, user_deployments))})" + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' {deployment_exclusion} + """ + else: + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' + """ + + else: + # Any other length = well_id + well_id = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE well_id = {well_id} + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + # Query to check if any device with this well_id is deployed (excluding user's accessible deployments) + if is_superuser: + # Superuser has access to all deployments, so no devices are "deployed elsewhere" + sql = "SELECT NULL WHERE FALSE" # Returns no results + elif user_deployments: + deployment_exclusion = f"AND dd.deployment_id NOT IN ({','.join(map(str, user_deployments))})" + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} {deployment_exclusion} + """ + else: + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + results = cur.fetchall() + + # Add any found well_id's to our list + for row in results: + if row[0] is not None and row[0] not in deployed_well_ids: + deployed_well_ids.append(row[0]) + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog(f"DevicesNotUsed check completed. Found {len(deployed_well_ids)} deployed devices, {len(not_found_devices)} not found.") + return 1, {"deployed": deployed_well_ids, "not_found": not_found_devices} + + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + if cur: + cur.close() + if conn: + conn.close() + return 0, error_string + +def CreatePassword(chat_len): + """ + Generate a reasonable password of specified length using common words + with character substitutions (leet speak style). + + Args: + chat_len (int): Desired password length + + Returns: + str: Generated password + """ + + # Seed random with current time for better randomness + random.seed(int(time.time() * 1000000) % 2**32) + + # Common words to use as base + words = [ + "password", "secure", "strong", "safe", "guard", "shield", "lock", + "key", "code", "access", "login", "enter", "open", "door", + "house", "home", "family", "friend", "happy", "sunny", "bright", + "quick", "fast", "smart", "clever", "power", "energy", "magic", + "super", "ultra", "mega", "cool", "awesome", "great", "best", + "gold", "silver", "diamond", "star", "moon", "sun", "fire", + "water", "earth", "wind", "storm", "thunder", "lightning", + "mountain", "ocean", "forest", "river", "bridge", "castle", + "knight", "dragon", "wizard", "hero", "champion", "winner" + ] + + # Character substitution mapping (leet speak) + substitutions = { + 'a': '@', 'A': '@', + 'e': '3', 'E': '3', + 'i': '1', 'I': '1', + 'o': '0', 'O': '0', + 's': '$', 'S': '$', + 't': '7', 'T': '7', + 'b': '8', 'B': '8', + 'g': '9', 'G': '9', + 'l': '!', 'L': '!', + 'z': '2', 'Z': '2' + } + + password = "" + + while len(password) < chat_len: + # Pick a random word + word = random.choice(words) + + # Apply random substitutions (not all characters, to keep it readable) + modified_word = "" + for char in word: + # 40% chance to substitute if substitution exists + if char in substitutions and random.random() < 0.4: + modified_word += substitutions[char] + else: + modified_word += char + + # Randomly capitalize some letters for variety + if random.random() < 0.3: + modified_word = modified_word.capitalize() + + # Add the word to password + if len(password) + len(modified_word) <= chat_len: + password += modified_word + else: + # If word is too long, take only what we need + remaining = chat_len - len(password) + password += modified_word[:remaining] + break + + # Add a random number or symbol between words (if space allows) + if len(password) < chat_len and random.random() < 0.5: + separators = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '#', '*', '+', '='] + separator = random.choice(separators) + if len(password) + 1 <= chat_len: + password += separator + + return password + +def DevicesNotUsedNoOwnershipCheck(devices): + """ + Check if devices exist in DB and if they are already deployed. + + Args: + devices (str): Comma-separated string of well_id or device_mac values + + Returns: + tuple: (success_flag, result) + - If success: (1, {"deployed": [well_ids], "not_found": [device_identifiers]}) + - If error: (0, error_string) + """ + + if not devices: + return 1, {"deployed": [], "not_found": []} + + # Clean and split the devices string + device_list = [CleanObject(device.strip()) for device in devices.split(',') if device.strip()] + + if not device_list: + return 1, {"deployed": [], "not_found": []} + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + deployed_well_ids = [] + not_found_devices = [] + + for device in device_list: + # Check if this device is a well_id or device_mac by string length + if len(device) == 12: + # Exactly 12 characters = device_mac + device_mac = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE device_mac = '{device_mac}' + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + #device_well_id = device_result[0] + + # Query to check if this device_mac is deployed + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' + """ + + else: + # Any other length = well_id + well_id = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE well_id = {well_id} + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + # Query to check if any device with this well_id is deployed + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + results = cur.fetchall() + + # Add any found well_id's to our list + for row in results: + if row[0] is not None and row[0] not in deployed_well_ids: + deployed_well_ids.append(row[0]) + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog(f"DevicesNotUsed check completed. Found {len(deployed_well_ids)} deployed devices, {len(not_found_devices)} not found.") + return 1, {"deployed": deployed_well_ids, "not_found": not_found_devices} + + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + if cur: + cur.close() + if conn: + conn.close() + return 0, error_string + + +def ExtractAddress(deployment_map): + address_map = {} + ''' +{ + "city": "mountain view", + "country": "usa", + "house_number": "760", + "postcode": "95070", + "road": "hope st.", + "state": "ca" +} + ''' + print(deployment_map) #{'deployment_id': 24, 'time_edit': 1753129300.0, 'user_edit': 32, 'persons': 2, 'gender': 1, 'race': 1, 'born': 1972, 'pets': 0, + # 'address_street': '', 'address_city': '', 'address_zip': '95070', 'address_state': '', 'address_country': '', 'wifis': '{"CBX_F": "69696969", "CBX": "69696969"}', 'lat': 37.267117, 'lng': -121.99548, 'gps_age': 0, 'note': 'me', 'overlapps': None}' + address_map["city"] = deployment_map["address_city"] + address_map["country"] = deployment_map["address_country"] + address_map["road"] = deployment_map["address_street"] + address_map["postcode"] = deployment_map["address_zip"] + address_map["state"] = deployment_map["address_state"] + + #address_map["city"] = "San Francisco" + #address_map["country"] = "USA" + #address_map["road"] = "230 Hope str." + #address_map["postcode"] = "95070" + #address_map["state"] = "CA" + + address_str = JoinAddress(address_map) + return(address_str["formatted_address"].strip()) + +# Initialize data files + +def PurgeDeployment(deployment): + deployment.pop('floor_plan', None) + deployment.pop('context', None) + deployment.pop('alarm_details', None) + deployment.pop('devices', None) + deployment.pop('floor_plan', None) + + deployment.pop('time_zone_s', None) + deployment.pop('beneficiary_id', None) + deployment.pop('caretaker_id', None) + deployment.pop('owner_id', None) + deployment.pop('installer_id', None) + #print(deployment) + deployment.pop('address_street', None) + deployment.pop('address_city', None) + deployment.pop('address_zip', None) + deployment.pop('address_state', None) + deployment.pop('address_country', None) + + deployment.pop('gps_age', None) + deployment.pop('note', None) + deployment.pop('overlapps', None) + deployment.pop('time_edit', None) + deployment.pop('user_edit', None) + + + return deployment + +def save_list_to_csv_method1(data_list, filename): + """Save list data to CSV with one item per row using csv module""" + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile) + # Optional: Add header + writer.writerow(['Value']) + # Write each item in a separate row + for item in data_list: + writer.writerow([item]) + print(f"Data saved to {filename} using csv module") + + +logger.error(f"------------------------------- STARTED ------------------------------------------") +try: + searches_text = read_file("searches.json") + searches_dict = json.loads(searches_text) if searches_text else {} + + dialogs_data = read_file("dialog.json") + dialog_dict = json.loads(dialogs_data) if dialogs_data else {"utterances": {}, "intents": {}} + + intent_map = dialog_dict.get("utterances", {}) + utterances = {} + for key in intent_map: + logger.debug(key) + list_of_utterances = intent_map[key] + for utterance in list_of_utterances: + utterances[utterance] = key + + intents = dialog_dict.get("intents", {}) +except Exception as e: + logger.error(f"Error initializing data files: {str(e)}") + searches_dict = {} + utterances = {} + intents = {} + +# Create Falcon application with middleware +middlewares = [CORSMiddleware(), RequestParser(), StripPathMiddleware()] +try: + # For newer Falcon versions + app = falcon.App(middleware=middlewares) +except: + # For older Falcon versions + app = falcon.API(middleware=middlewares) + +#logger.error(f"@1") +# Add routes for well-api +well_api_instance = WellApi() + +# New routes for well_api with multiple access paths +app.add_route('/function/well-api', well_api_instance) +app.add_route('/function/well-api/{path}', well_api_instance) +app.add_route('/api/well_api', well_api_instance) +app.add_route('/api/well_api/{path}', well_api_instance) +app.add_route('/healthz', well_api_instance, suffix='healthz') + +# Add routes for the standard API paths +app.add_route('/health', well_api_instance) +app.add_route('/users', well_api_instance) +app.add_route('/items', well_api_instance) + +# Keep the original routes for backward compatibility +app.add_route('/', well_api_instance) +app.add_route('/{path}', well_api_instance) + +MQTTSERVERL = "eluxnetworks.net" +MQTT_PortL = 443 + +MyName = "well-api" + +clientL = mqtt.Client(client_id=MyName+str(time.time()), transport="websockets") +clientL.tls_set(cert_reqs=ssl.CERT_NONE) # For self-signed certs, use proper CA in production +clientL.ws_set_options(path="/mqtt") # Important! Same path as in your JS code +clientL.username_pw_set("well_user","We3l1_best!") + +clientL.on_connect = on_connectL +clientL.on_message = on_messageL + +#clientL.connect(MQTTSERVERL, MQTT_PortL, 60) +#lientL.loop_start() +#logger.error(f"@2") + +# This code runs when executed directly (for development/debugging) +if __name__ == "__main__": + from wsgiref.simple_server import make_server + redis_conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB) + # Use port 8000 for local debugging + port = int(os.environ.get('PORT', 8000)) + #port = int(os.environ.get('PORT', 1998)) + + # Create a WSGI server + with make_server('', port, app) as httpd: + print(f'Serving on port {port}...') + + # Serve until process is killed + httpd.serve_forever() +else: + redis_conn = redis.Redis(host=redis_host, port=6379, db=0) diff --git a/well-api_20250825.py b/well-api_20250825.py new file mode 100644 index 0000000..3aa940f --- /dev/null +++ b/well-api_20250825.py @@ -0,0 +1,21270 @@ +#!/usr/bin/env python3 +#Vesion 2.0.1 +import os +import sys +import ast +from ast import literal_eval +import falcon +from falcon import HTTP_200, HTTP_400, HTTP_401, HTTP_500 +import json +import logging +from dotenv import load_dotenv +import calendar +import io +import datetime +from datetime import timedelta, timezone +import jwt +import psycopg2 +import html +import re +import fnmatch +import traceback +import time +import pytz +from PIL import Image, ImageDraw, ImageFont +import paho.mqtt.client as mqtt +import ssl +import hashlib +import itertools +from collections import defaultdict, deque +import warnings +from io import BytesIO +import zipfile +from minio import Minio +from minio.error import S3Error +import numpy as np +import cv2 +from sklearn.mixture import GaussianMixture +import openai +from openai import OpenAI +from typing import List, Tuple +import redis +import base64 +import requests +import uuid +import csv +import random + +base_url = "http://192.168.68.70:5050" + +# Try to import the module +try: + from filter_short_groups import filter_short_groups_c + print("Successfully imported filter_short_groups_c") +except ImportError as e: + print(f"Error importing module: {e}") + exit(1) + + +device_lookup_cache = {} +threshold_cache = {} +humidity_offset = 34 +temperature_offset = -10 +st = 0 +if True: + + #from scipy import interpolate + from scipy.optimize import curve_fit + from scipy import stats + import pandas as pd + #from scipy.signal import savgol_filter + +EnablePlot = False #True + +if EnablePlot: + import matplotlib + matplotlib.use('Agg') # Set the backend before importing pyplot + import matplotlib.pyplot as plt + from matplotlib.colors import LinearSegmentedColormap + import matplotlib.dates as mdates + +# Configure logging +logging.basicConfig( + level=logging.DEBUG,# .ERROR, + format='%(asctime)s [%(levelname)s] %(message)s' +) +logger = logging.getLogger(__name__) + +location_names = {-1:"All",0:"?",5:"Office",6:"Hallway",7:"Garage",8:"Outside",9:"Conference Room",10:"Room",34:"Kitchen", + 56:"Bedroom",78:"Living Room",102:"Bathroom",103:"Dining Room",104:"Bathroom Main",105:"Bathroom Guest", + 106:"Bedroom Master", 107:"Bedroom Guest", 108:"Conference Room", 109:"Basement", 110:"Attic", 200:"Other"} + +#Loc2Color = {"?":(0,0,0),"Office":(255,255,0),"Hallway":(128,128,128),"Garage":(128,0,0),"Outside":(0,0,0),"Conference Room":(0,0,128), + #"Room":(64,64,64),"Kitchen":(255,0,0),"Bedroom":(16,255,16),"Living Room":(160,32,240),"Bathroom":(0,0,255), + #"Dining Room":(255,128,0),"Bathroom Main":(16,16,255), "Bedroom Master":(0,255,0),"Bathroom Guest":(32,32,255), + #"Bedroom Guest":(32,255,32), "Basement":(64,64,64), "Attic":(255,165,0), "Other":(192,192,192)} + +Loc2Color = {"Bedroom":((16,255,16),0),"Bedroom Master":((0,255,0),0),"Bedroom Guest":((32,255,32),0),"Bathroom":((0,0,255),1), + "Bathroom Main":((16,16,255),1),"Bathroom Guest":((32,32,255),1),"Kitchen":((255,0,0),2),"Dining Room":((255,128,0),3),"Dining":((255,128,0),3), + "Office":((255,255,0),4),"Conference Room":((0,0,128),5),"Conference":((0,0,128),5),"Room":((64,64,64),6),"Living Room":((160,32,240),7),"Living":((160,32,240),7),"Hallway":((128,128,128),8), + "Garage":((128,0,0),9),"Basement":((64,64,64), 10),"Attic":((255,165,0), 11),"Other":((192,192,192),12),"?":((0,0,0),13),"Outside":((0,0,0),14)} + + +s_table = ["temperature", "humidity", "pressure", "light", "radar", "voc0", "voc1", "voc2", "voc3", "voc4", "voc5", "voc6", "voc7", "voc8", "voc9"] # derived +smells_table = ["s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9"] # derived + +s_table_temp = [] + +Consolidataed_locations = {"?":"Room","Office":"Office","Hallway":"Hallway","Garage":"Garage","Outside":"Outside","Conference Room":"Office", + "Room":"Room","Kitchen":"Kitchen","Bedroom":"Bedroom","Living Room":"Living Room","Bathroom Guest":"Bathroom", + "Dining Room":"Dining Room","Bathroom":"Bathroom", "Bathroom Main":"Bathroom","Bedroom Master":"Bedroom", + "Bedroom Guest":"Bedroom", "Basement":"Basement", "Attic":"Attic", "Other":"Room"} + +AveragePercentPerLocation = {"Bedroom":[29, 37.5], "Bathroom":[2, 4], "Office":[10, 40],"Hallway":[0.1, 0.2],"Garage":[2, 3],"Outside":[5, 10], + "Room":[5, 10],"Kitchen":[5, 12.5], "Living Room":[5, 10], + "Dining Room":[5, 10], "Basement":[0, 0.2], "Attic":[0, 0.2]} + +location_indexes = {} + +for i in location_names: + location_indexes[location_names[i]] = i + + + +# HTTP Status codes +HTTP_200 = falcon.HTTP_200 +HTTP_201 = falcon.HTTP_201 +HTTP_400 = falcon.HTTP_400 +HTTP_401 = falcon.HTTP_401 +HTTP_404 = falcon.HTTP_404 +HTTP_500 = falcon.HTTP_500 + +load_dotenv() + +DB_NAME = os.getenv('DB_NAME') +DB_USER = os.getenv('DB_USER') +DB_PASSWORD = os.getenv('DB_PASSWORD') +DB_HOST = os.getenv('DB_HOST') +DB_PORT = os.getenv('DB_PORT') +MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') +MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY') +MINIO_HOST = os.getenv('MINIO_HOST') +MINIO_PORT = os.getenv('MINIO_PORT') +DAILY_MAPS_BUCKET_NAME = os.getenv('DAILY_MAPS_BUCKET_NAME') +JWT_SECRET = os.getenv('JWT_SECRET') +MASTER_ADMIN = os.getenv('MASTER_ADMIN') +MASTER_PS = os.getenv('MASTER_PS') + +OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') +model_engine = os.getenv('OPENAI_API_MODEL_ENGINE') + +# Redis Configuration +REDIS_HOST = os.getenv('REDIS_HOST', 'localhost') +REDIS_PORT = int(os.getenv('REDIS_PORT')) +REDIS_DB = int(os.getenv('REDIS_DB', 0)) +REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', None) + +ENABLE_AUDIO_PLAYBACK = True +OPT_IN_KEYWORD = "WELLNUOJOIN" +DEFAULT_TTS_VOICE = "female" +DEFAULT_TTS_LANGUAGE = "en-US" +CLIENT_STATE_PREFIX = "app_state" +TELNYX_API_KEY = os.getenv('TELNYX_API_KEY') +TELNYX_API_BASE_URL = os.getenv("TELNYX_API_BASE_URL") + +logger.debug(f"REDIS_PORT: {REDIS_PORT}") +logger.debug(f"TELNYX_API_KEY: {TELNYX_API_KEY}") +logger.debug(f"TELNYX_API_BASE_URL: {TELNYX_API_BASE_URL}") + +redis_host = os.getenv('REDIS_HOST', '192.168.68.70') +redis_host = '192.168.68.70' + +use_pdb = True +debug = False +debug_string = "" +logger.debug(f"Environment variables: {os.environ}") +filesDir = "/home/app/well_web_storage" #os.path.dirname(os.path.realpath(__file__)) +min_io_address = MINIO_HOST + ":" + MINIO_PORT + +miniIO_blob_client = Minio(min_io_address, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False) + +user_id_2_user = {} +smell_min = 1 +no_smell = 102400000 +smell_max = no_smell - 1 +sensor_legal_values = {"radar": (0,1000, 1), "co2": (smell_min, smell_max, 31), "humidity": (1,99, 31), "light": (0, 4095, 1), + "pressure": (0, 10000, 5), "temperature": (1, 60, 31), "voc": (smell_min, smell_max, 31), "voc0": (smell_min, smell_max, 31), + "voc1": (smell_min, smell_max, 31), "voc2": (smell_min, smell_max, 31), "voc3": (smell_min, smell_max, 31), "voc4": (smell_min, smell_max, 31), + "voc5": (smell_min, smell_max, 31), "voc6": (smell_min, smell_max, 31), "voc7": (smell_min, smell_max, 31), "voc8": (smell_min, smell_max, 31), "voc9": (smell_min, smell_max, 31), + "s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), + "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} + +smell_legal_values = {"s0": (smell_min, smell_max, 31), "s1": (smell_min, smell_max, 31), "s2": (smell_min, smell_max, 31), "s3": (smell_min, smell_max, 31), "s4": (smell_min, smell_max, 31), + "s5": (smell_min, smell_max, 31), "s6": (smell_min, smell_max, 31), "s7": (smell_min, smell_max, 31), "s8": (smell_min, smell_max, 31), "s9": (smell_min, smell_max, 31)} + + +def format_address_component(component, component_type): + """ + Apply proper capitalization rules based on component type + """ + if not component: + return "" + + component = component.strip() + + if component_type == 'street_number': + # Street numbers should remain as-is + return component + + elif component_type in ['street_name', 'city', 'country']: + # Title case with special handling for common patterns + return title_case_address(component) + + elif component_type == 'state': + # States should be uppercase if abbreviation, title case if full name + if len(component) == 2: + return component.upper() + else: + return title_case_address(component) + + elif component_type == 'zip_code': + # ZIP codes remain as-is + return component + + elif component_type in ['apt', 'unit', 'suite']: + # Apartment/unit numbers remain as-is + return component + + else: + # Default to title case + return title_case_address(component) + + +def title_case_address(text): + """ + Apply proper title case with address-specific rules + """ + # Words that should remain lowercase (unless at start) + lowercase_words = { + 'and', 'at', 'by', 'for', 'in', 'of', 'on', 'to', 'up', 'via', 'with' + } + + # Words that should be uppercase + uppercase_words = { + 'ne', 'nw', 'se', 'sw', 'n', 's', 'e', 'w', # Directions + 'st', 'nd', 'rd', 'th', # Ordinal suffixes + 'po', 'llc', 'inc', 'corp' # Business suffixes + } + + # Street type abbreviations that should be title case + street_types = { + 'st': 'St', 'ave': 'Ave', 'rd': 'Rd', 'dr': 'Dr', 'ln': 'Ln', + 'ct': 'Ct', 'pl': 'Pl', 'blvd': 'Blvd', 'pkwy': 'Pkwy', + 'hwy': 'Hwy', 'way': 'Way', 'circle': 'Circle', 'court': 'Court', + 'drive': 'Drive', 'lane': 'Lane', 'place': 'Place', 'road': 'Road', + 'street': 'Street', 'avenue': 'Avenue', 'boulevard': 'Boulevard' + } + + words = text.lower().split() + result = [] + + for i, word in enumerate(words): + # Remove punctuation for comparison + clean_word = word.rstrip('.,;:') + punctuation = word[len(clean_word):] + + if clean_word in uppercase_words: + result.append(clean_word.upper() + punctuation) + elif clean_word in street_types: + result.append(street_types[clean_word] + punctuation) + elif i > 0 and clean_word in lowercase_words: + result.append(clean_word + punctuation) + else: + # Handle special cases like "McDonald", "O'Connor" + formatted = format_special_cases(clean_word) + result.append(formatted + punctuation) + + return ' '.join(result) + + +def format_special_cases(word): + """ + Handle special capitalization cases + """ + # Handle names with apostrophes (O'Connor, D'Angelo) + if "'" in word: + parts = word.split("'") + return "'".join(part.capitalize() for part in parts) + + # Handle hyphenated words + if "-" in word: + parts = word.split("-") + return "-".join(part.capitalize() for part in parts) + + # Handle Mc/Mac prefixes + if word.startswith('mc') and len(word) > 2: + return 'Mc' + word[2:].capitalize() + elif word.startswith('mac') and len(word) > 3: + return 'Mac' + word[3:].capitalize() + + # Default capitalization + return word.capitalize() + + +def recreate_address(parsed_address): + """ + Recreate a properly formatted address string from parsed components + + Args: + parsed_address (dict): Dictionary containing parsed address components + OR list of tuples: [('value', 'type'), ...] + + Expected keys/types: + - street_number/house_number + - street_name/road + - apt/unit/suite (optional) + - city + - state + - zip_code/postcode + - country + + Returns: + str: Properly formatted address string + """ + + # Handle both dict and list of tuples formats + if isinstance(parsed_address, list): + # Convert list of tuples to dict + addr_dict = {} + for value, addr_type in parsed_address: + # Map the parser's field names to our expected names + if addr_type == 'house_number': + # Only use house_number if street_number doesn't already exist + if 'street_number' not in addr_dict: + addr_dict['street_number'] = value + elif addr_type == 'street_number': + # street_number takes priority over house_number + addr_dict['street_number'] = value + elif addr_type == 'road': + addr_dict['street_name'] = value + elif addr_type == 'postcode': + addr_dict['zip_code'] = value + else: + addr_dict[addr_type] = value + parsed_address = addr_dict + + # Format each component + street_number = format_address_component( + parsed_address.get('street_number', ''), 'street_number' + ) + + street_name = format_address_component( + parsed_address.get('street_name', ''), 'street_name' + ) + + # Handle apartment/unit/suite + apt_unit = '' + for key in ['apt', 'apartment', 'unit', 'suite', 'ste']: + if parsed_address.get(key): + apt_value = format_address_component(parsed_address[key], 'apt') + if key.lower() in ['apt', 'apartment']: + apt_unit = f"Apt {apt_value}" + elif key.lower() == 'unit': + apt_unit = f"Unit {apt_value}" + elif key.lower() in ['suite', 'ste']: + apt_unit = f"Suite {apt_value}" + break + + city = format_address_component( + parsed_address.get('city', ''), 'city' + ) + + state = format_address_component( + parsed_address.get('state', ''), 'state' + ) + + zip_code = format_address_component( + parsed_address.get('zip_code', ''), 'zip_code' + ) + + country = format_address_component( + parsed_address.get('country', ''), 'country' + ) + + # Build the address string - detect format based on available components + if country and not state: + # International format (like Croatian): Street Number, PostalCode, City, Country + address_parts = [] + + # Street address line + street_parts = [street_number, street_name] + street_line = ' '.join(filter(None, street_parts)) + + if apt_unit: + street_line += f", {apt_unit}" + + if street_line: + address_parts.append(street_line) + + # Add postal code, city, country as separate parts + if zip_code: + address_parts.append(zip_code) + if city: + address_parts.append(city) + if country: + address_parts.append(country) + + return ', '.join(address_parts) + + else: + # US format: Street\nCity, State ZIP + address_parts = [] + + # Street address line + street_parts = [street_number, street_name] + street_line = ' '.join(filter(None, street_parts)) + + if apt_unit: + street_line += f", {apt_unit}" + + if street_line: + address_parts.append(street_line) + + # City, State ZIP line + city_state_zip = [] + if city: + city_state_zip.append(city) + + if state and zip_code: + city_state_zip.append(f"{state} {zip_code}") + elif state: + city_state_zip.append(state) + elif zip_code: + city_state_zip.append(zip_code) + + if city_state_zip: + address_parts.append(', '.join(city_state_zip)) + + return '\n'.join(address_parts) + + +def GetRedisInt(key_name): + try: + result = int(redis_conn.get(key_name).decode('utf-8')) + except: + result = None + return result + +def GetRedisFloat(key_name): + try: + result = float(redis_conn.get(key_name).decode('utf-8')) + except: + result = None + + return result + +def GetRedisString(key_name): + try: + result = redis_conn.get(key_name).decode('utf-8') + except: + result = None + return result + +def GetRedisMap(key_name): + try: + result_bytes = redis_conn.hgetall(key_name) + result = {k.decode('utf-8'): v.decode('utf-8') for k, v in result_bytes.items()} + except: + result = {} + return result + + + +def read_file(file_name, source = "LOCAL", type_ = "TEXT", bucket_name="daily-maps"): + + blob_data = "" + if source == "MINIO": + blob_data = ReadObjectMinIO(bucket_name, file_name) + elif source == "LOCAL": + login_file = os.path.join(filesDir, file_name) + login_file = login_file.replace("\\","/") + logger.debug(f"Full file path: {login_file}") + logger.debug(f"File exists: {os.path.exists(login_file)}") + #print(login_file) + if type_ == "TEXT": + with open(login_file, encoding="utf8") as f: + blob_data = f.read() + else: + with open(login_file, 'rb') as f: + blob_data = f.read() + + elif source == "AZURE": + try: + blob_data = ""#container_client.download_blob(file_name).readall() + except Exception as err: + logger.error("Not reading Azure blob "+str(err)) + blob_data = "" + return blob_data + else: + pass + return blob_data + + +def match_with_wildcard(string, pattern): + return fnmatch.fnmatchcase(string, pattern) + +def extract_differing_part(string, pattern): + regex_pattern = re.escape(pattern).replace(r'\*', r'(.+)') + match = re.match(regex_pattern, string) + if match: + return match.group(1) + else: + return None + +def get_db_connection(): + + #when new_table created: + #GRANT ALL PRIVILEGES ON TABLE public.new_table TO well_app; + #GRANT ALL PRIVILEGES ON TABLE public.new_table TO postgres; + return psycopg2.connect(dbname=DB_NAME, user=DB_USER, password=DB_PASSWORD, host=DB_HOST, port=DB_PORT) + + +def generate_token(username): + expiration = datetime.datetime.now(timezone.utc) + timedelta(hours=24) + token = jwt.encode({"username": username, "exp": expiration}, JWT_SECRET, algorithm="HS256") + return token + + +def verify_token(token): + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) + return payload + except jwt.ExpiredSignatureError: + return None + except jwt.InvalidTokenError: + return None + +def SmartSplit(data_string): + """ + Splits a comma-separated string into a list, properly handling nested structures + and converting values to appropriate Python types using only the ast library. + """ + if not data_string: + return [] + + # Remove trailing comma if present + data_string = data_string.rstrip(',') + + items = [] + current_item = "" + bracket_count = 0 + in_quotes = False + quote_char = None + + i = 0 + while i < len(data_string): + char = data_string[i] + + # Handle quotes + if char in ('"', "'") and (i == 0 or data_string[i-1] != '\\'): + if not in_quotes: + in_quotes = True + quote_char = char + elif char == quote_char: + in_quotes = False + quote_char = None + + # Track brackets only when not in quotes + if not in_quotes: + if char in '[{(': + bracket_count += 1 + elif char in ']}': + bracket_count -= 1 + + # Split on comma only when not inside brackets/quotes + if char == ',' and bracket_count == 0 and not in_quotes: + items.append(current_item.strip()) + current_item = "" + else: + current_item += char + + i += 1 + + # Add the last item + if current_item.strip(): + items.append(current_item.strip()) + + # Convert each item using ast.literal_eval when possible + result = [] + for item in items: + if item == '': + result.append(None) + else: + try: + # Try to evaluate as Python literal + converted = ast.literal_eval(item) + result.append(converted) + except (ValueError, SyntaxError): + # If it fails, keep as string + result.append(item) + + return result + + +def SaveObjectInBlob(file_name, obj): + """ + Saves a Python object to MinIO blob storage using JSON serialization + + Args: + file_name (str): Name of the file to save in blob storage + obj: Python object to serialize and save + """ + try: + # Convert object to JSON string + json_str = json.dumps(obj) + # Convert string to bytes + json_bytes = json_str.encode('utf-8') + + # Save to MinIO + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(json_bytes), + len(json_bytes) + ) + return True + except Exception as e: + logger.error(f"Error saving object to blob: {traceback.format_exc()}") + return False + +def SaveGenericObjectInBlob(bucket_name, file_name, obj): + """ + Saves a Python object to MinIO blob storage using JSON serialization + + Args: + file_name (str): Name of the file to save in blob storage + obj: Python object to serialize and save + """ + try: + # Convert object to JSON string + json_str = json.dumps(obj) + # Convert string to bytes + json_bytes = json_str.encode('utf-8') + + # Save to MinIO + miniIO_blob_client.put_object( + bucket_name, + file_name, + io.BytesIO(json_bytes), + len(json_bytes) + ) + return True + except Exception as e: + logger.error(f"Error saving object to blob: {traceback.format_exc()}") + return False + + + +def ReadObjectMinIO(bucket_name, file_name, filter_date=None): + """ + Read object from MinIO with optional date filtering. + + Args: + bucket_name (str): Name of the MinIO bucket + file_name (str): Name of the file/object + filter_date (str, optional): Date string in format "YYYY-MM-DD". + If provided, returns empty string if object + was modified before or on this date. + + Returns: + str: Object content as string, empty string if filtered out, or None on error + """ + try: + # If date filtering is requested, check object's last modified date first + if filter_date: + try: + # Get object metadata to check last modified date + stat = miniIO_blob_client.stat_object(bucket_name, file_name) + last_modified = stat.last_modified + + # Parse filter date (assuming format YYYY-MM-DD) + target_date = datetime.datetime.strptime(filter_date, "%Y-%m-%d").date() + + # If object was modified before or on target date, return empty string + if last_modified.date() <= target_date: + return None + + except S3Error as e: + logger.error(f"Error getting metadata for {file_name}: {e}") + return None + except ValueError as e: + logger.error(f"Invalid date format '{filter_date}': {e}") + return None + + # Retrieve the object data + response = miniIO_blob_client.get_object(bucket_name, file_name) + # Read the data from response + data_bytes = response.read() + # Convert bytes to string + data_string = data_bytes.decode('utf-8') + # Don't forget to close the response + response.close() + response.release_conn() + return data_string + + except S3Error as e: + logger.error(f"An error occurred while reading {file_name}: {e}") + return None + except: + logger.error(f"An error occurred while decoding {file_name}") + return None + +#def ReadObjectMinIO(bucket_name, file_name): + #try: + ## Retrieve the object data + #response = miniIO_blob_client.get_object(bucket_name, file_name) + + ## Read the data from response + #data_bytes = response.read() + + ## Convert bytes to string and then load into a dictionary + #data_string = data_bytes.decode('utf-8') + + ## Don't forget to close the response + #response.close() + #response.release_conn() + + #return data_string + + #except S3Error as e: + #logger.error(f"An error occurred: {e}") + #return None + + +def package_response_C(payload, status_code=HTTP_200): + """Package response in a standard format""" + if status_code == HTTP_200: + return {"status": "success", "data": payload} + else: + return {"status": "error", "message": payload, "code": status_code} + +def package_response(content, status=falcon.HTTP_200): + """ + Format the HTTP response. + + :param content: The content to be returned in the response. + :param status: HTTP status code (default is 200 OK). + :return: A dictionary containing the formatted response. + """ + if isinstance(content, str): + # If content is a string, try to parse it as JSON + try: + response = json.loads(content) + except json.JSONDecodeError: + # If it's not valid JSON, use it as message + response = {"message": content} + elif isinstance(content, dict): + # If content is a dictionary, serialize it with datetime handling + try: + # First serialize to JSON string with datetime handling + json_str = json.dumps(content, default=datetime_handler) + # Then parse back to dict + response = json.loads(json_str) + except TypeError as e: + response = {"message": f"Serialization error: {str(e)}"} + else: + # For any other type, convert to string and use as message + response = {"message": str(content)} + + # Add status code to the response + response["status"] = status + + # Handle specific status codes + if status == falcon.HTTP_400: + response["error"] = "Bad Request" + elif status == falcon.HTTP_401: + response["error"] = "Unauthorized" + elif status == falcon.HTTP_500: + response["error"] = "Internal Server Error" + + return response + +def GetPriviledges(conn, user_name, password): + sql = "SELECT key, access_to_deployments, user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None and result != []: + if result[0][0] == password: + return result[0][1], result[0][2] + else: + return "0", "0" + else: + return "0", "0" + +def GetPriviledgesOnly(user): + with get_db_connection() as conn: + if isinstance(user, (int)) or user.isdigit(): + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_id = " + user + else: + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_name = '" + user + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetUserId(user_name): + with get_db_connection() as conn: + sql = "SELECT user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetNameFromUserId(user_id): + with get_db_connection() as conn: + sql = f"SELECT user_name, first_name, last_name FROM public.person_details WHERE user_id = {user_id}" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0] + else: + return None + + + +def ListDeployments(priviledges, user_id): + + global user_id_2_user + + conn = get_db_connection() + + if priviledges == "-1": + sql = "SELECT * FROM public.deployments ORDER BY deployment_id ASC;" + else: + sql = f"SELECT * FROM public.deployments WHERE deployment_id IN ({priviledges}) OR user_edit = {user_id} ORDER BY deployment_id ASC;" + + try: + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + complete_result = [] + else: + deployment_ids = [] + deployment_records_dict = {} + for record in result: + deployment_id = record[0] + deployment_ids.append(deployment_id) + deployment_records_dict[deployment_id] = record + + sql = f"SELECT * FROM public.deployment_details WHERE deployment_id IN ({','.join(map(str, deployment_ids))}) ORDER BY deployment_id ASC;" + cur.execute(sql) + details_result = cur.fetchall() + + beneficiary_ids = [] + + for record_details in details_result: + if record_details[1] != None and record_details[1] not in beneficiary_ids: + beneficiary_ids.append(record_details[1]) + + sql = f"SELECT * FROM public.person_details WHERE user_id IN ({','.join(map(str, beneficiary_ids))});" + + + cur.execute(sql) + user_id_2_user = {} + users = cur.fetchall()#cur.fetchone() + for usr_record in users: + user_id_2_user[usr_record[0]] = usr_record + + complete_result = [] + if details_result != None: + for record_details in details_result: + deployment_record = deployment_records_dict[record_details[0]] + complete_record = {'deployment_id': record_details[0], 'beneficiary_id': record_details[1], 'caretaker_id': record_details[2], + 'owner_id': record_details[3], 'installer_id': record_details[4], + 'address_street': record_details[6], 'address_city': record_details[7], 'address_zip': record_details[8], + 'address_state': record_details[9], 'address_country': record_details[10], + 'devices': record_details[5], 'wifis': record_details[11], 'persons': deployment_record[4], 'gender': deployment_record[5], + 'race': deployment_record[6], 'born': deployment_record[7], 'pets': deployment_record[8], 'time_zone': deployment_record[3] + } + complete_result.append(complete_record) + except: + logger.debug(f"Error: {traceback.format_exc()}") + return complete_result + +def ListCaretakers(privileges, user_name): + + conn = get_db_connection() + if privileges == "-1": + sql = "SELECT * FROM public.person_details WHERE role_ids LIKE '%2%' ORDER BY last_name;" #2 is caretaker + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + else: + #we need to check if + sql = f"SELECT * FROM public.person_details WHERE user_name = '{user_name}';" #2 is caretaker + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + + pass + return result + +def ListBeneficiaries(privilidges, user_info): + + conn = get_db_connection() + with conn.cursor() as cur: + + if (privilidges == "-1"): + sql = "SELECT * FROM public.person_details WHERE role_ids LIKE '%1%' ORDER BY last_name;" #1 is beneficiary + else: + #we need to find beneficiaries from list of deployments + sql = f"SELECT beneficiary_id FROM public.deployment_details WHERE deployment_id IN ({privilidges}) ORDER BY deployment_id ASC;" + cur.execute(sql) + result1 = cur.fetchall()#cur.fetchone() + if result1 == None: + result = [] + return result + beneficiaries = ",".join(str(x[0]) for x in result1) + sql = f"SELECT * FROM public.person_details WHERE user_id IN ({beneficiaries}) OR user_edit = {user_info} AND role_ids LIKE '%1%' ORDER BY last_name;" #1 is beneficiary + logger.debug(f"sql= {sql}") + + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result == None: + result = [] + + return result + +def UserDetails(user_id): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'person_details';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.person_details WHERE user_id = "+user_id + + caretaker_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + caretaker_record[field[0]] = result[cnt] + cnt += 1 + + return caretaker_record + +def DeviceDetails(mac): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_mac = '" + mac + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + + return device_record + +def GetDeviceDetailsSingle(device_id): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_id = '" + device_id + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + + return device_record + +def GetDeviceDetailsSingleFromMac(device_mac): + + conn = get_db_connection() + + sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';" + with conn.cursor() as cur: + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.devices WHERE device_mac = '" + device_mac + "'" + + device_record = {} + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + device_record[field[0]] = result[cnt] + cnt += 1 + else: + #device is not in DB so first lets find it in + pass + return device_record + +def DeploymentDetails(deployment_id): + + deployment_record = {} + + conn = get_db_connection() + + with conn.cursor() as cur: + + sql = "SELECT * FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'deployments';" + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.deployments WHERE deployment_id = '" + deployment_id + "'" + + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + deployment_record[field[3]] = result[cnt] + cnt += 1 + + sql = "SELECT * FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'deployment_details';" + cur.execute(sql) + columns_names = cur.fetchall() + + sql = "SELECT * FROM public.deployment_details WHERE deployment_id = '" + deployment_id + "'" + + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + cnt = 0 + for field in columns_names: + deployment_record[field[3]] = result[cnt] + cnt += 1 + + + return deployment_record + +def ValidUser(user_name, password): + + + if use_pdb: + with get_db_connection() as db_conn: + priviledges, user_id= GetPriviledges(db_conn, user_name, password) + return priviledges, user_id + + else: + pass + #container = GetReference("/MAC") + #try: + ## We can do an efficient point read lookup on partition key and id + ##response = container.read_item(item="64B708896BD8_temperature_2024-01-01_00", partition_key="64B708896BD8") #OK + ##items = query_items(container, '64B708896BD8') #Too slow + ##AddToLog("1!") + #privileges = GetCaretakers(container, email, password) + #return privileges + + #except Exception as err: + #AddToLog("Error !1 "+str(err)) + + +def GetMaxRole(user_name): + + with get_db_connection() as db_conn: + + sql = "SELECT role_ids FROM public.person_details WHERE user_name = '" + user_name + "'" + + with db_conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None and result != []: + return str(result[0]) + return "" + + +def SelectOption(html_code, select_id, selected_item): + """ + Modifies HTML code to set the selected attribute for a specific option in a select element. + + Args: + html_code (str): Original HTML code + select_id (str): ID of the select element to modify + selected_item (str or int): Value of the option to be selected + + Returns: + str: Modified HTML code with the selected attribute added + """ + # Convert selected_item to string for comparison + selected_item = str(selected_item) + + # Find the select element with the given ID + select_pattern = rf']*id=[\'"]?{select_id}[\'"]?[^>]*>(.*?)' + select_match = re.search(select_pattern, html_code, re.IGNORECASE | re.DOTALL) + + if not select_match: + return html_code # Return unchanged if select element not found + + select_content = select_match.group(0) + select_content_orig = select_content + # Remove any existing selected attributes + select_content = re.sub(r'\s+selected(?=[>\s])', '', select_content, flags=re.IGNORECASE) + + # Add selected attribute to the matching option + def replace_option(match): + value = re.search(r'value=[\'"]?([^\'">\s]+)', match.group(0)) + if value and value.group(1) == selected_item: + # Add selected attribute before the closing > + return match.group(0).rstrip('>') + ' selected>' + return match.group(0) + + modified_select = re.sub( + r']*>', + replace_option, + select_content + ) + + # Replace the original select element with the modified one + return html_code.replace(select_content_orig, modified_select) + +def FillFields(blob_data, record, form_type): + """ + Fill in the input fields in the HTML blob_data with values from the caretaker dictionary. + + :param blob_data: str - The initial HTML string containing empty or placeholder input fields. + :param caretaker: dict - The dictionary containing values to populate the fields. + :return: str - The HTML string with the input fields filled with the appropriate values. + """ + # Ensure blob_data is a string + #blob_data = str(blob_data) + + # Populate the fields + for field in record: + logger.debug(f"field= {field}") + if field == "user_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_user_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "deployment_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_deployment_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "device_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_device_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "user_name": + if record[field] != None: + escaped_string = html.escape(record[field]) + pattern = rf'(]+id="new_user_name"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="new_user_name"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + elif field == "location": + if record[field] != None: + blob_data = SelectOption(blob_data, 'location', record[field]) + + elif field == "gender": + if record[field] != None: + blob_data = SelectOption(blob_data, 'gender', record[field]) + + elif field == "race": + if record[field] != None: + blob_data = SelectOption(blob_data, 'race', record[field]) + + elif field == "time_zone_s": + if record[field] != None: + blob_data = SelectOption(blob_data, 'time_zone_s', record[field]) + + elif field == "time_edit" or field == "user_edit": + pass + else: + if record[field] != None: + escaped_string = html.escape(str(record[field])) + pattern = rf'(]+id="{field}"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="{field}"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + return blob_data + +def StoreThresholds2DB(device_id, TR, BR, TLIFE, BLIFE): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + + if device_id == None or device_id == 0: + return 1 + + try: + + + sql = f""" + UPDATE public.devices + SET + radar_threshold = '[{TR},{BR},{TLIFE},{BLIFE}]' + WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql}") + # Execute update query + print(sql) + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + logger.debug("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + + +def StoreBeneficiary2DB(parameters, editing_user_id, user_id): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + if editing_user_id == None or editing_user_id == "": + editing_user_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_user_id != "0": + sql = f""" + UPDATE public.person_details + SET + email = '{CleanObject(parameters.get('email'))}', + user_name = '{CleanObject(parameters.get('new_user_name'))}', + first_name = '{CleanObject(parameters.get('first_name'))}', + last_name = '{CleanObject(parameters.get('last_name'))}', + address_street = '{CleanObject(parameters.get('address_street'))}', + address_city = '{CleanObject(parameters.get('address_city'))}', + address_zip = '{CleanObject(parameters.get('address_zip'))}', + address_state = '{CleanObject(parameters.get('address_state'))}', + address_country = '{CleanObject(parameters.get('address_country'))}', + time_edit = {current_epoch_time}, + user_edit = {user_id}, + role_ids = '{CleanObject(parameters.get('role_ids'))}', + phone_number = '{CleanObject(parameters.get('phone_number'))}', + picture = '{CleanObject(parameters.get('picture'))}', + key = '{CleanObject(parameters.get('key'))}' + WHERE user_id = {editing_user_id}; -- replace 34 with the actual person_id you want to update + """ + + else: + sql = f""" + INSERT INTO public.person_details + (role_ids, email, user_name, first_name, last_name, address_street, address_city, address_zip, address_state, address_country, time_edit, user_edit, phone_number, picture, key) + VALUES + ('{CleanObject(parameters.get('role_ids'))}', '{CleanObject(parameters.get('email'))}', '{CleanObject(parameters.get('new_user_name'))}', + '{CleanObject(parameters.get('first_name'))}', '{CleanObject(parameters.get('last_name'))}', '{CleanObject(parameters.get('address_street'))}', + '{CleanObject(parameters.get('address_city'))}', '{CleanObject(parameters.get('address_zip'))}', '{CleanObject(parameters.get('address_state'))}', + '{CleanObject(parameters.get('address_country'))}', {current_epoch_time}, {user_id}, '{CleanObject(parameters.get('phone_number'))}', + '{CleanObject(parameters.get('picture'))}', '{CleanObject(parameters.get('key'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1, error_string + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + return 0, error_string + +def is_valid_email_strict(email): + """ + More comprehensive email validation with additional checks. + + Args: + email (str): Email address to validate + + Returns: + bool: True if email is valid, False otherwise + """ + if not isinstance(email, str) or len(email) > 254: + return False + + # Check for basic structure + if email.count('@') != 1: + return False + + local, domain = email.split('@') + + # Check local part (before @) + if not local or len(local) > 64: + return False + + # Check domain part (after @) + if not domain or len(domain) > 253: + return False + + # Comprehensive regex pattern + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + + # Additional checks + if email.startswith('.') or email.endswith('.'): + return False + + if '..' in email: + return False + + return bool(re.match(pattern, email)) + +def StoreDisclaimer2DB(parameters): + + email = CleanObject(parameters.get('email')) + if not is_valid_email_strict(email): + error_string = "email has wrong format" + #AddToLog(error_string) + return 0, error_string + + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + + sql = f""" + INSERT INTO public.disclaimers + (email, user_name, first_name, last_name, devices, date, policy_version) + VALUES + ('{CleanObject(parameters.get('email'))}', '{CleanObject(parameters.get('user_name'))}', + '{CleanObject(parameters.get('firstName'))}', '{CleanObject(parameters.get('lastName'))}', '{CleanObject(parameters.get('devices'))}', + '{CleanObject(parameters.get('agreementDate'))}', '{CleanObject(parameters.get('privacyPolicyVersion'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1, error_string + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + return 0, error_string + + +def AccountExists(user_name): + conn = get_db_connection() + + sql = "SELECT * FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + if len(result) > 0: + return True + else: + return False + return False + +def DeleteRecordFromDB(form_data): + + caretaker = form_data['user_name'] + privileges = GetPriviledgesOnly(caretaker) + + if privileges != "-1": + AddToLog("Forbidden!") + return 0 + + conn = get_db_connection() + cur = conn.cursor() + function = form_data.get('function') + if function == "deployment_delete": + user_id = form_data['user_id'] + editing_deployment_id = form_data['editing_deployment_id'] + priviledges = form_data['priviledges'] + if editing_deployment_id == None or editing_deployment_id == "" or editing_deployment_id == "0": + AddToLog("deployment_id is not defined") + cur.close() + conn.close() + return 0 + try: + if user_id == "-1": + sql = f""" + DELETE FROM public.deployments WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + sql = f""" + DELETE FROM public.deployment_details WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + else: + #lets check if user_edit = user_id + sql = f"SELECT user_edit FROM public.deployments WHERE deployment_id = '{editing_deployment_id}'" + cur.execute(sql) + result = cur.fetchone() + if priviledges != "-1": + if result[0] != int(user_id): + cur.close() + conn.close() + return 0 + + sql = f""" + DELETE FROM public.deployments WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + sql = f""" + DELETE FROM public.deployment_details WHERE deployment_id = {editing_deployment_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + + elif function == "device_delete": + user_id = form_data['user_id'] + editing_device_id = form_data['editing_device_id'] + priviledges = form_data['priviledges'] + if editing_device_id == None or editing_device_id == "" or editing_device_id == "0": + AddToLog("editing_device_id is not defined") + cur.close() + conn.close() + return 0 + try: + if user_id == "-1": + sql = f""" + DELETE FROM public.deployments WHERE device_id = {editing_device_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + #else: + ##lets check if user_edit = user_id + #sql = f"SELECT user_edit FROM public.deployments WHERE deployment_id = '{editing_deployment_id}'" + #cur.execute(sql) + #result = cur.fetchone() + #if priviledges != "-1": + #if result[0] != int(user_id): + #cur.close() + #conn.close() + #return 0 + + #sql = f""" + #DELETE FROM public.deployments WHERE device_id = {editing_device_id} + #""" + #logger.debug(f"sql= {sql}") + ## Execute update query + #cur.execute(sql) + + #conn.commit() + + ## Close the cursor and connection + #cur.close() + #conn.close() + + #AddToLog("Deleted!") + #return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + else: + + #user_id = form_data['user_id'] + editing_user_id = form_data['delete_user_id'] + if editing_user_id == None or editing_user_id == "" or editing_user_id == "0": + AddToLog("user_id is not defined") + cur.close() + conn.close() + return 0 + try: + if privileges == "-1": #user_id == "-1": + sql = f""" + DELETE FROM public.person_details WHERE user_id = {editing_user_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + else: + #lets check if user_edit = user_id + sql = f"SELECT user_edit FROM public.person_details WHERE user_id = '{editing_user_id}'" + cur.execute(sql) + result = cur.fetchone() + if result[0] != int(user_id): + cur.close() + conn.close() + return 0 + + sql = f""" + DELETE FROM public.person_details WHERE user_id = {editing_user_id} + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Deleted!") + return 1 + + except Exception as err: + AddToLog(traceback.format_exc()) + cur.close() + conn.close() + return 0 + +def StoreCaretaker2DB(parameters, editing_user_id, user_id): + + #print('\nCreating create_caretaker\n') + # Create a caretaker object. This object has nested properties and various types including numbers, DateTimes and strings. + # This can be saved as JSON as is without converting into rows/columns. + conn = get_db_connection() + cur = conn.cursor() + + if editing_user_id == None or editing_user_id == "": + editing_user_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_user_id != "0": + sql = f""" + UPDATE public.person_details + SET + role_ids = '{parameters.get('role_ids')}', + access_to_deployments = '{parameters.get('access_to_deployments')}', + email = '{parameters.get('email')}', + user_name = '{parameters.get('new_user_name')}', + first_name = '{parameters.get('first_name')}', + last_name = '{parameters.get('last_name')}', + address_street = '{parameters.get('address_street')}', + address_city = '{parameters.get('address_city')}', + address_zip = '{parameters.get('address_zip')}', + address_state = '{parameters.get('address_state')}', + address_country = '{parameters.get('address_country')}', + time_edit = {current_epoch_time}, + user_edit = {user_id}, + phone_number = '{parameters.get('phone_number')}', + picture = '{parameters.get('picture')}', + key = '{parameters.get('key')}' + WHERE user_id = {editing_user_id}; -- replace 34 with the actual person_id you want to update + """ + + else: + sql = f""" + INSERT INTO public.person_details + (role_ids, access_to_deployments, email, user_name, first_name, last_name, address_street, address_city, address_zip, address_state, address_country, time_edit, user_edit, phone_number, picture, key) + VALUES + ('{parameters.get('role_ids')}', '{parameters.get('access_to_deployments')}', '{parameters.get('email')}', '{parameters.get('new_user_name')}', '{parameters.get('first_name')}', + '{parameters.get('last_name')}', '{parameters.get('address_street')}', '{parameters.get('address_city')}', '{parameters.get('address_zip')}', '{parameters.get('address_state')}', + '{parameters.get('address_country')}', {current_epoch_time}, {user_id}, '{parameters.get('phone_number')}', '{parameters.get('picture')}', '{parameters.get('key')}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + return ok + +def StoreFlow2DB(user_name, time_s, flow_json): + + conn = get_db_connection() + cur = conn.cursor() + + query = f""" + INSERT INTO public.node_reds (user_name, last_activity, flow) + VALUES ('{user_name}', {time_s}, '{flow_json}') + ON CONFLICT (user_name) + DO UPDATE SET + last_activity = EXCLUDED.last_activity, + flow = EXCLUDED.flow + """ + logger.debug(f"sql= {query}") + + try: + #cur.execute(query, (user_name, time_s, flow_json)) + cur.execute(query) + conn.commit() + logger.debug(f"OK!") + return True + except Exception as e: + conn.rollback() + print(f"Error storing flow: {e}") + logger.debug(f"Error storing flow: {e}") + return False + finally: + cur.close() + conn.close() + logger.debug(f"Closing!") + +def StoreAlarms2DB(deployment_id, device_id, deployment_alarms_json, device_alarms_json): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + + try: + sql = f""" + UPDATE public.deployments SET alarm_details = '{CleanObject(deployment_alarms_json)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + + sql1 = f""" + UPDATE public.devices SET alert_details = '{CleanObject(device_alarms_json)}' WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def StoreAlarms2DBSimple(deployment_id, device_id, deployment_alarms_json, device_alarms_json): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + + try: + if deployment_id != 0: + sql = f""" + UPDATE public.deployments SET alarm_details = '{CleanObject(deployment_alarms_json)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + + if device_id != 0: + + sql1 = f""" + UPDATE public.devices SET alert_details = '{CleanObject(device_alarms_json)}' WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def GetAlarmSimple(deployment_id, device_id): + + conn = get_db_connection() + cur = conn.cursor() + + # Extract the overlapping list + deployment_alarms_json = "" + device_alarms_json = "" + + try: + if deployment_id != 0: + sql = f""" + SELECT alarm_details FROM public.deployments WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + deployment_alarms_json = cur.fetchone()[0] + except Exception as err: + pass + + + try: + if device_id != 0: + + sql1 = f""" + SELECT alert_details FROM public.devices WHERE device_id = {device_id}; + """ + + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + device_alarms_json = cur.fetchone()[0] + + + cur.close() + conn.close() + + return deployment_alarms_json, device_alarms_json + except Exception as err: + pass + return deployment_alarms_json, device_alarms_json + + +def CleanObject(object_in, typee = "s"): + + if typee == "n": + res = 0 + if object_in == None or object_in == "": + return 0 + if isinstance(object_in, str): + try: + res = object_in.replace("'", '"') + except: + pass + return res + else: + res = "" + if object_in == None: + return "" + if isinstance(object_in, str): + try: + res = object_in.replace("'", '"') + except: + pass + return res + return object_in + +def StoreDeployment2DB(parameters, editing_deployment_id): + conn = get_db_connection() + cur = conn.cursor() + + if editing_deployment_id == None or editing_deployment_id == "": + editing_deployment_id = "0" + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = current_utc_time.timestamp() + + if editing_deployment_id != "0": + sql = f""" + UPDATE public.deployments + SET + persons = {CleanObject(parameters.get('persons'), "n")}, + gender = {CleanObject(parameters.get('gender'), "n")}, + race = {CleanObject(parameters.get('race'), "n")}, + born = {CleanObject(parameters.get('born'), "n")}, + pets = {CleanObject(parameters.get('pets'), "n")}, + time_zone_s = '{CleanObject(parameters.get('time_zone_s'))}', + user_edit = {CleanObject(parameters.get('user_id'), "n")}, + time_edit = {current_epoch_time} + WHERE deployment_id = {CleanObject(editing_deployment_id, "n")}; + """ + + else: + sql = f""" + INSERT INTO public.deployments + (persons, gender, race, born, pets, time_zone_s, user_edit, time_edit) + VALUES + ({CleanObject(parameters.get('persons'), "n")}, {CleanObject(parameters.get('gender'), "n")}, {CleanObject(parameters.get('race'), "n")}, + {CleanObject(parameters.get('born'), "n")}, {CleanObject(parameters.get('pets'), "n")}, '{CleanObject(parameters.get('time_zone_s'))}', + {CleanObject(parameters.get('user_id'), "n")}, {current_epoch_time}) + RETURNING deployment_id; + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + if editing_deployment_id == "0": + new_deployment_id = cur.fetchone()[0] + # Commit the changes to the database + conn.commit() + + if editing_deployment_id != "0": + sql = f""" + UPDATE public.deployment_details + SET + beneficiary_id = {CleanObject(parameters.get('beneficiary_id'), "n")}, + caretaker_id = {CleanObject(parameters.get('caretaker_id'), "n")}, + owner_id = {CleanObject(parameters.get('owner_id'), "n")}, + installer_id = {CleanObject(parameters.get('installer_id'), "n")}, + address_street = '{CleanObject(parameters.get('address_street'))}', + address_city = '{CleanObject(parameters.get('address_city'))}', + address_zip = '{CleanObject(parameters.get('address_zip'))}', + address_state = '{CleanObject(parameters.get('address_state'))}', + address_country = '{CleanObject(parameters.get('address_country'))}', + + wifis = '{CleanObject(parameters.get('wifis'))}', + devices = '{CleanObject(parameters.get('devices'))}', + lat = {CleanObject(parameters.get('lat'), "n")}, + lng = {CleanObject(parameters.get('lng'), "n")}, + gps_age = {CleanObject(parameters.get('gps_age'), "n")} + + WHERE deployment_id = {editing_deployment_id}; + """ + + else: + sql = f""" + INSERT INTO public.deployment_details + (deployment_id, beneficiary_id, caretaker_id, owner_id, installer_id, address_street, address_city, address_zip, address_state, address_country) + VALUES + ({new_deployment_id}, {CleanObject(parameters.get('beneficiary_id'), "n")}, {CleanObject(parameters.get('caretaker_id'), "n")}, {CleanObject(parameters.get('owner_id'), "n")}, {CleanObject(parameters.get('installer_id'), "n")}, + '{CleanObject(parameters.get('address_street'))}', '{CleanObject(parameters.get('address_city'))}', '{CleanObject(parameters.get('address_zip'))}', '{CleanObject(parameters.get('address_state'))}', + '{CleanObject(parameters.get('address_country'))}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + + + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + AddToLog(traceback.format_exc()) + return 0 + return ok + +def StoreDevice2DB(parameters, editing_device_id): + import uuid + import time + call_id = str(uuid.uuid4())[:8] + + logger.debug(f"[{call_id}] StoreDevice2DB ENTRY - editing_device_id: {editing_device_id}") + logger.debug(f"[{call_id}] Parameters: {parameters}") + print(f"[{call_id}] StoreDevice2DB ENTRY - editing_device_id: {editing_device_id}") + + # Database connection + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + logger.debug(f"[{call_id}] Converted editing_device_id to: {editing_device_id}") + + try: + current_utc_time = datetime.datetime.now(timezone.utc) + + if editing_device_id != "0": + # Update specific device by device_id + logger.debug(f"[{call_id}] UPDATE mode - editing device_id: {editing_device_id}") + sql = f""" + UPDATE public.devices + SET + device_mac = '{CleanObject(parameters.get('device_mac'))}', + well_id = '{CleanObject(parameters.get('well_id'))}', + description = '{CleanObject(parameters.get('description'))}', + location = '{CleanObject(parameters.get('location'))}', + close_to = '{CleanObject(parameters.get('close_to'))}', + group_id = {CleanObject(parameters.get('group_id'))}, + radar_threshold = '{CleanObject(parameters.get('radar_threshold'))}', + temperature_calib = '{CleanObject(parameters.get('temperature_calib'))}', + humidity_calib = '{CleanObject(parameters.get('humidity_calib'))}' + WHERE device_id = {editing_device_id}; + """ + else: + # Use PostgreSQL's UPSERT with ON CONFLICT to handle duplicates safely + logger.debug(f"[{call_id}] INSERT/UPSERT mode") + well_id = CleanObject(parameters.get('well_id')) + device_mac = CleanObject(parameters.get('device_mac')) + + # Debug CleanObject results + logger.debug(f"[{call_id}] CleanObject results:") + logger.debug(f"[{call_id}] device_mac: '{device_mac}'") + logger.debug(f"[{call_id}] well_id: '{well_id}'") + logger.debug(f"[{call_id}] description: '{CleanObject(parameters.get('description'))}'") + logger.debug(f"[{call_id}] location: '{CleanObject(parameters.get('location'))}'") + logger.debug(f"[{call_id}] close_to: '{CleanObject(parameters.get('close_to'))}'") + logger.debug(f"[{call_id}] radar_threshold: '{CleanObject(parameters.get('radar_threshold'))}'") + logger.debug(f"[{call_id}] temperature_calib: '{CleanObject(parameters.get('temperature_calib'))}'") + logger.debug(f"[{call_id}] humidity_calib: '{CleanObject(parameters.get('humidity_calib'))}'") + logger.debug(f"[{call_id}] group_id: '{CleanObject(parameters.get('group_id'))}'") + + # Validate required fields + if not well_id or not device_mac: + logger.error(f"[{call_id}] Missing required fields: well_id={well_id}, device_mac={device_mac}") + raise ValueError("well_id and device_mac are required") + + # Use UPSERT with composite key (well_id, device_mac) + sql = f""" + INSERT INTO public.devices + (device_mac, well_id, description, location, close_to, radar_threshold, temperature_calib, humidity_calib, group_id) + VALUES + ('{device_mac}', '{well_id}', '{CleanObject(parameters.get('description'))}', + '{CleanObject(parameters.get('location'))}', '{CleanObject(parameters.get('close_to'))}', '{CleanObject(parameters.get('radar_threshold'))}', + '{CleanObject(parameters.get('temperature_calib'))}', '{CleanObject(parameters.get('humidity_calib'))}', {CleanObject(parameters.get('group_id'))}) + ON CONFLICT (well_id, device_mac) + DO UPDATE SET + description = EXCLUDED.description, + location = EXCLUDED.location, + close_to = EXCLUDED.close_to, + radar_threshold = EXCLUDED.radar_threshold, + temperature_calib = EXCLUDED.temperature_calib, + humidity_calib = EXCLUDED.humidity_calib, + group_id = EXCLUDED.group_id; + """ + + logger.debug(f"[{call_id}] Using UPSERT with composite key (well_id, device_mac)") + + logger.debug(f"[{call_id}] SQL: {sql}") + print(f"[{call_id}] SQL: {sql}") + + + logger.debug(f"[{call_id}] SQL: {sql}") + print(f"[{call_id}] SQL: {sql}") + + # === ADD THIS NEW DEBUGGING BLOCK === + final_well_id = CleanObject(parameters.get('well_id')) + final_device_mac = CleanObject(parameters.get('device_mac')) + logger.debug(f"[{call_id}] FINAL KEY VALUES FOR SQL:") + logger.debug(f"[{call_id}] well_id: {repr(final_well_id)} (Type: {type(final_well_id)})") + logger.debug(f"[{call_id}] device_mac: {repr(final_device_mac)} (Type: {type(final_device_mac)})") + + # Execute update/insert query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + cur.close() + conn.close() + + logger.debug(f"[{call_id}] StoreDevice2DB SUCCESS - returning 1") + print(f"[{call_id}] StoreDevice2DB SUCCESS - returning 1") + AddToLog("Written/updated!") + return 1 + + except Exception as err: + logger.error(f"[{call_id}] Error in StoreDevice2DB: {traceback.format_exc()}") + AddToLog(traceback.format_exc()) + # Make sure to close connections in case of error + try: + if cur: + cur.close() + if conn: + conn.close() + except: + pass + logger.debug(f"[{call_id}] StoreDevice2DB ERROR - returning 0") + print(f"[{call_id}] StoreDevice2DB ERROR - returning 0") + return 0 + +def StoreGroupToDevice(editing_device_id_or_mac, group_id, user_name): + + max_wait = 30 + + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_group", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'group_id': group_id + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + + record = { + 'function': "set_group", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'group_id': group_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + logger.error(f"{(st + max_wait)-time.time()}: {queue_length}") + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.error(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.1) + + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + + try: + + #current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + #current_epoch_time = current_utc_time.timestamp() + + if editing_device_id != "0" and editing_device_id != None: + sql = f"UPDATE public.devices SET group_id = {CleanObject(group_id)} WHERE device_id = {editing_device_id}" + else: + cur.close() + conn.close() + return 0 + + logger.debug(f"sql= {sql}") + + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + + AddToLog("Written/updated!") + return str(record) + except Exception as err: + AddToLog(traceback.format_exc()) + return "" + +def StoreWellIdToDevice(editing_device_id_or_mac, well_id, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_well_id", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'well_id': well_id + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + + record = { + 'function': "set_well_id", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'well_id': well_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + logger.error(f"{(st + max_wait)-time.time()}: {queue_length}") + + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + conn = get_db_connection() + cur = conn.cursor() + + if editing_device_id == None or editing_device_id == "": + editing_device_id = "0" + + try: + + #current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + #current_epoch_time = current_utc_time.timestamp() + + if editing_device_id != "0": + sql = f"UPDATE public.devices SET well_id = {CleanObject(well_id)} WHERE device_id = {editing_device_id}" + else: + cur.close() + conn.close() + return 0 + + logger.debug(f"sql= {sql}") + + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + AddToLog("Written/updated!") + return str(record) + except Exception as err: + AddToLog(f"{traceback.format_exc()} {err}") + return "" + + +def GetDeviceLive(editing_device_id_or_mac, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "get_device_live", + 'user_name': user_name, + 'mac': editing_device_id_or_mac + } + MAC = editing_device_id_or_mac + editing_device_id = MACsToDeviceId(MAC) + else: + record = { + 'function': "get_device_live", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + editing_device_id = editing_device_id_or_mac + + if editing_device_id != 0: + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + redis_conn.delete(f'from_alerter_{MAC}') + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + + return str(record) + else: + return("Error: Device not found!") + + +def StoreNetworkIdToDevice(editing_device_id_or_mac, network_id, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "set_network_id", + 'user_name': user_name, + 'mac': editing_device_id_or_mac, + 'network_id': network_id + } + MAC = editing_device_id_or_mac + else: + record = { + 'function': "set_network_id", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac, + 'network_id': network_id + } + MAC = DeviceId2MAC(editing_device_id_or_mac) + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + logger.info(f"Cleared REDIS: from_alerter_{MAC}") + redis_conn.delete(f'from_alerter_{MAC}') + logger.info(f"Pushing to REDIS: send_requests {record_json}") + redis_conn.lpush('send_requests', record_json) + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{MAC}') + if queue_length > 0: + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{MAC}') + logger.info(f"Received in REDIS: from_alerter_{MAC} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + return str(record) + + +def DeviceReboot(editing_device_id_or_mac, user_name): + + max_wait = 10 + #First lets try to send message to device over MQTT + # Create record dictionary + if isinstance(editing_device_id_or_mac, str): + record = { + 'function': "reboot", + 'user_name': user_name, + 'mac': editing_device_id_or_mac + } + mac = editing_device_id_or_mac + else: + record = { + 'function': "reboot", + 'user_name': user_name, + 'device_id': editing_device_id_or_mac + } + mac = DeviceId2MAC(editing_device_id_or_mac) + + try: + + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.delete(f'from_alerter_{mac}') + logger.info(f"Pushing to REDIS: send_requests {record_json}") + redis_conn.lpush('send_requests', record_json) + + + #lets wait for response up to 4 seconds + st = time.time() + record = {} + while time.time() < st + max_wait: + + queue_length = redis_conn.llen(f'from_alerter_{mac}') + if queue_length > 0: + + # Process each item + for i in range(queue_length): + item_json = redis_conn.rpop(f'from_alerter_{mac}') + logger.info(f"Received in REDIS: from_alerter_{mac} {item_json}") + if item_json is None: + break + try: + record = json.loads(item_json) + print(record) + break + except: + pass + if item_json is not None: + break + time.sleep(0.01) + + AddToLog(str(record)) + return str(record) + except Exception as err: + AddToLog(traceback.format_exc()) + return "" + +def ShowAge(seconds): + minutes = int(seconds // 60) + hours = int(minutes // 60) + days = int(hours // 24) + + if seconds >= 0: + hours = int(hours - (days * 24)) + minutes = int(minutes - (days * 24 * 60) - (hours * 60)) + seconds = int(seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60)) + + if days > 0: + report = f"{int(days)} d {int(hours)} h {int(minutes)} m {int(seconds)} s" + elif hours > 0: + report = f"{int(hours)} h {int(minutes)} m {int(seconds)} s" + elif minutes > 0: + report = f"{int(minutes)} m {int(seconds)} s" + else: + report = f"{int(seconds)} s" + + return report + else: + return "0 s" + +def UpdateDevicesTable(html_string, devices, users): + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + table_rows_string = "" + for device in devices: + #result = next(item for item in users if item[0] == device[6]) + + deployment_name = "?" + for item in users: + if item[0] == device[6]: + result = item + deployment_name = result[1] + if result[2] != None: + deployment_name = deployment_name + " " + result[2] + break + + + + + mac = device[2] + if mac == "64B7088903B4": + print("stop") + mac_row_string = f' {mac}\n' + age = time.time() - device[3] + + if (age < 300): + row_sting = f' \n {device[0]}\n' + else: + row_sting = f' \n {device[0]}\n' + + row_ending = f' \n \n' + for col_cnt in range(1, len(device)): + column_value = device[col_cnt] + #print(column_value) + if col_cnt == 2: + col_string_template = mac_row_string + elif col_cnt == 3: + if column_value > 0: + col_string_template = f' {ShowAge(age)}\n' + else: + col_string_template = f' No data\n' + elif col_cnt == 4: + col_string_template = f' {column_value}\n' + elif col_cnt == 5: + if column_value == None: + col_string_template = f' \n' + else: + col_string_template = f' {column_value}\n' + elif col_cnt == 6: + col_string_template = f' {column_value}\n'; + else: + if column_value == None: + column_value = "" + col_string_template = f' {column_value}\n' + row_sting = row_sting + col_string_template + row_sting = row_sting + row_ending + table_rows_string = table_rows_string + row_sting + #print(table_rows_string) + + html_string = html_string.replace("###ROWS###",table_rows_string) + return html_string + +def UpdateDeploymentsSelector(html_string, deployments, include_all=True, selected="1"): + # + if include_all: + selector_string = f' \n' + else: + selector_string = '' + + for deployment in deployments: + first_name = "" + last_name = "" + if deployment[1] != None: + first_name = deployment[1] + if deployment[2] != None: + last_name = deployment[2] + if deployment[0] == int(selected): + choice_string = f' \n' + else: + choice_string = f' \n' + selector_string = selector_string + choice_string + #print(selector_string) + + html_string = html_string.replace("###INSTALLS###",selector_string) + return html_string + +def GetDeviceDetails(cur, deployment_ids, location_id): + + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + macs = [mac for _, mac in deployment_ids] + #macs = list(deployment_ids.keys()) + macs_string_nq = ",".join(macs) + macs_string = "'" + "','".join(macs) + "'" + + if location_id == -1: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) + ORDER BY om.position; + """ + else: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) AND location = {location_id} + ORDER BY om.position; + """ + + cur.execute(sql) + print(sql) + devices_ids_records = cur.fetchall() + all_details = [] + + + devices_ids_list = [x[0] for x in devices_ids_records] + device_ids_string = ",".join(map(str, devices_ids_list)) + #sql = f"SELECT device_id, MAX(time) as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) GROUP BY device_id" #to slow + sql = f"SELECT DISTINCT ON (device_id) device_id, time as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) AND time > now() - INTERVAL '1 day' ORDER BY device_id, time DESC" + cur.execute(sql) + print(sql) + devices_times = cur.fetchall()#cur.fetchone() + found_device_details = {} + for device_record in devices_times: + device_id, last_message_time = device_record + found_device_details[device_id] = last_message_time + cnt = 0 + for device_table_record in devices_ids_records: + if len(devices_times) > 0: + device_id = device_table_record[0] + if device_id in found_device_details: + last_message_time = found_device_details[device_id] + last_message_epoch = int(last_message_time.timestamp()) + else: + try: + last_message_time = int(device_table_record[14]) + except: + last_message_time = 0 + last_message_epoch = last_message_time + else: + last_message_time = 0 + last_message_epoch = 0 + + #print(last_message_epoch) + #print(type(last_message_epoch)) + device_id = device_table_record[0] + mac = device_table_record[1] + well_id = device_table_record[2] + description = device_table_record[3] + if description == None: + description = "" + if device_table_record[5] != None: + if device_table_record[5] != "": + description = description + " Close to " + device_table_record[5] + location_id = device_table_record[4] + if location_id == None: + location_id = 0 + try: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + except: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + cnt += 1 + all_details.append(row_data) + + return all_details + +def GetDeviceDetailsComplete(cur, deployment_ids, location_id): + + #ID, Well id, MAC, Last_Message, Location, Description, Deployment + macs = [mac for _, mac in deployment_ids] + #macs = list(deployment_ids.keys()) + macs_string_nq = ",".join(macs) + macs_string = "'" + "','".join(macs) + "'" + + if location_id == -1: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) + ORDER BY om.position; + """ + else: + sql = f""" + WITH ordered_macs AS ( + SELECT unnest(string_to_array('{macs_string_nq}', ',')) as mac, + generate_series(1, array_length(string_to_array('{macs_string_nq}', ','), 1)) as position + ) + SELECT d.* + FROM public.devices d + JOIN ordered_macs om ON d.device_mac = om.mac::text + WHERE device_mac IN ({macs_string}) AND location = {location_id} + ORDER BY om.position; + """ + + cur.execute(sql) + print(sql) + devices_ids_records = cur.fetchall() + all_details = [] + + + devices_ids_list = [x[0] for x in devices_ids_records] + device_ids_string = ",".join(map(str, devices_ids_list)) + #sql = f"SELECT device_id, MAX(time) as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) GROUP BY device_id" #to slow + sql = f"SELECT DISTINCT ON (device_id) device_id, time as last_reading_time FROM sensor_readings WHERE device_id IN ({device_ids_string}) AND time > now() - INTERVAL '1 day' ORDER BY device_id, time DESC" + cur.execute(sql) + print(sql) + devices_times = cur.fetchall()#cur.fetchone() + found_device_details = {} + for device_record in devices_times: + device_id, last_message_time = device_record + found_device_details[device_id] = last_message_time + cnt = 0 + for device_table_record in devices_ids_records: + if len(devices_times) > 0: + + if device_id in found_device_details: + last_message_time = found_device_details[device_id] + last_message_epoch = int(last_message_time.timestamp()) + else: + try: + last_message_time = int(device_table_record[14]) + except: + last_message_time = 0 + last_message_epoch = last_message_time + else: + last_message_time = 0 + last_message_epoch = 0 + + #print(last_message_epoch) + #print(type(last_message_epoch)) + device_id = device_table_record[0] + mac = device_table_record[1] + well_id = device_table_record[2] + description = device_table_record[3] + alarm_details = device_table_record[16] + if description == None: + description = "" + if device_table_record[5] != None: + if device_table_record[5] != "": + description = description + " Close to " + device_table_record[5] + location_id = device_table_record[4] + if location_id == None: + location_id = 0 + #try: + # row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0], alarm_details] + #except: + row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0], alarm_details] + cnt += 1 + all_details.append(row_data) + + return all_details + +def GetVisibleDevices(deployments): + + devices_details = [] + stt = time.time() + with get_db_connection() as conn: + with conn.cursor() as cur: + #list all devices that user has access to + if deployments == "-1": + sql = "SELECT device_mac FROM public.devices ORDER BY device_id ASC"# SELECT deployment_id, devices FROM public.deployment_details" + macs_group = [] + deployment_ids = [] + print(sql) + cur.execute(sql) + macs_records = cur.fetchall()#cur.fetchone() + for record in macs_records: + deployment_ids.append((0, record[0])) + devices_details = GetDeviceDetails(cur, deployment_ids, -1) + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + print(sql) + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if "[" not in dev_group: + if "," not in dev_group: + dev_group = '["' + dev_group + '"]' + else: + dev_group = dev_group.replace(" ", "") + dev_group = dev_group.replace(",", '","') + dev_group = '["' + dev_group + '"]' + + macs_group = literal_eval(dev_group) + + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + else: + print(f"Deployment {deployment_id} has dev_group empty") + devices_details = GetDeviceDetails(cur, deployment_ids, -1) + #devices_details.append(devices_detail) + + return devices_details + +def GetVisibleDevicesPerLocation(deployments, location): + + devices_details = [] + + with get_db_connection() as conn: + #list all devices that user has access to + if deployments == "-1" or deployments == "0": + sql = "SELECT deployment_id, devices FROM public.deployment_details" + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + with conn.cursor() as cur: + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if dev_group[0] == "[": + macs_group = literal_eval(dev_group) + else: + macs_group = dev_group.split(',') + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + + devices_details = GetDeviceDetails(cur, deployment_ids, location_indexes[location]) + #devices_details.append(devices_detail) + + return devices_details + +def GetVisibleDevicesPerLocationComplete(deployments, location): + + devices_details = [] + + with get_db_connection() as conn: + #list all devices that user has access to + if deployments == "-1" or deployments == "0": + sql = "SELECT deployment_id, devices FROM public.deployment_details" + else: + sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + + with conn.cursor() as cur: + cur.execute(sql) + devices_groups = cur.fetchall()#cur.fetchone() + deployment_ids = [] + for deployment_id, dev_group in devices_groups: + if dev_group != None: + if len(dev_group) > 10: + if dev_group[0] == "[": + macs_group = literal_eval(dev_group) + else: + macs_group = dev_group.split(',') + for mac in macs_group: + deployment_ids.append((deployment_id, mac)) + + devices_details = GetDeviceDetailsComplete(cur, deployment_ids, location_indexes[location]) + #devices_details.append(devices_detail) + + return devices_details + +def GetUsersFromDeployments(deployments): + #list all devices that user has access to + deployments_dets = [] + with get_db_connection() as conn: + try: + if deployments == "-1": + sql = f""" + SELECT dd.deployment_id, pd.first_name, pd.last_name + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + ORDER BY dd.deployment_id; + """ + else: + sql = f""" + SELECT dd.deployment_id, pd.first_name, pd.last_name + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + WHERE dd.deployment_id IN ({deployments}) + ORDER BY dd.deployment_id; + """ + with conn.cursor() as cur: + cur.execute(sql) + deployments_dets = cur.fetchall()#cur.fetchone() + except Exception as err: + logger.error("GetUsersFromDeployments "+str(err) +" "+sql) + + return deployments_dets + +def GetPreviousDate(current_date): + date_obj = datetime.datetime.strptime(current_date, "%Y-%m-%d") + # Subtract one day + previous_date = date_obj - timedelta(days=1) + # Convert back to string format + previous_date_str = previous_date.strftime("%Y-%m-%d") + return(previous_date_str) + +def CovertToIsoTime(date_s, n_minute): + + hours = n_minute // 60 # Integer division + minutes = n_minute % 60 + + base_date = datetime.datetime.strptime(date_s, "%Y-%m-%d") + final_datetime = base_date + timedelta(hours=hours, minutes=minutes) + iso_timestamp = final_datetime.isoformat() + return iso_timestamp + + +def sleep_length(presence_list, short_absence_threshold=15): + """ + Calculate the total sleep duration and wake time based on presence data. + + This function correctly interprets the presence_list to determine sleep duration by: + 1. Properly aggregating the total sleep time from all significant in-bed periods + 2. Considering short absences as part of the same sleep session + 3. Determining the wake time when the main sleep session ended + + Args: + presence_list (list): List of tuples indicating bed presence/absence + short_absence_threshold (int, optional): Maximum duration in decas to consider + an absence "short" and still count as sleep. + Default is 15 (2.5 minutes) + + Returns: + tuple: (sleep_duration_minutes, wake_time_minutes) + sleep_duration_minutes: Total sleep duration in minutes + wake_time_minutes: Minute in the day when person was determined to be + done sleeping (minutes since midnight) + """ + # Extract in-bed periods and out-of-bed periods + in_bed_periods = [] + out_bed_periods = [] + + # First process the raw data into periods + for i in range(len(presence_list)): + deca_index, deca_count = presence_list[i] + + # Skip separator tuples where deca_count is 0 + if deca_count == 0: + continue + + if deca_count > 0: # In bed + # Special case for the midnight (first) tuple + if i == 0 and deca_index == 0: + # This is time in bed before midnight + start_deca = -deca_count # Negative because it's before midnight + end_deca = 0 # Midnight + else: + start_deca = deca_index + end_deca = deca_index + deca_count + + in_bed_periods.append({ + 'start': start_deca, + 'end': end_deca, + 'duration': deca_count + }) + else: # Out of bed + out_bed_periods.append({ + 'start': deca_index, + 'end': deca_index + abs(deca_count), + 'duration': abs(deca_count) + }) + + # Sort periods to ensure chronological order + in_bed_periods.sort(key=lambda p: p['start']) + out_bed_periods.sort(key=lambda p: p['start']) + + # Merge in-bed periods that are separated by short absences + merged_periods = [] + current_period = None + + for period in in_bed_periods: + # If we're at the start or after a long break, begin a new period + if current_period is None: + current_period = period.copy() + else: + # Check if this period starts shortly after the previous one ends + gap = period['start'] - current_period['end'] + + # If the gap is negative, the periods overlap (data error), treat as continuous + if gap < 0: + gap = 0 + + # If the gap is short enough, merge the periods + if gap <= short_absence_threshold: + # Extend the current period + current_period['end'] = period['end'] + current_period['duration'] += period['duration'] + gap # Include gap + else: + # Gap too long, add the completed period and start a new one + merged_periods.append(current_period) + current_period = period.copy() + + # Add the last period if there is one + if current_period is not None: + merged_periods.append(current_period) + + # Find significant sleep periods (at least 30 minutes) - REMOVED night-time restriction + significant_sleep_threshold = 180 # 30 minutes (180 decas) + significant_periods = [p for p in merged_periods if p['duration'] >= significant_sleep_threshold] + + if significant_periods: + # Find the LATEST significant sleep period (for multi-day data) + main_sleep_period = max(significant_periods, key=lambda p: p['start']) + + # Calculate total sleep duration + sleep_duration_minutes = round(main_sleep_period['duration'] / 6) # Convert to minutes + + # Wake time is when this period ended + # Handle multi-day scenarios by taking modulo 24 hours + wake_time_deca = main_sleep_period['end'] + wake_time_minutes = round(wake_time_deca / 6) + + # If wake time is beyond 24 hours, wrap it to the current day + if wake_time_minutes >= 1440: # 1440 = 24 hours in minutes + wake_time_minutes = wake_time_minutes % 1440 + + return (sleep_duration_minutes, wake_time_minutes) + + # No significant sleep periods found + return (0, 0) + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + + +# Example usage: +# sleep_minutes = sleep_length(presence_list) # Use default threshold +# sleep_minutes = sleep_length(presence_list, short_absence_threshold=30) # Allow longer absences (5 minutes) + + + +# Example usage: +# presence_list = [ +# [0, 554], [3303, 3857], [3303, 0], [3387, -84], [3387, 0], [3388, 1], [3388, 0], +# [3668, -280], [3668, 0], [3669, 1], [3669, 0], [3699, -30], [3699, 0], [3700, 1], +# [3700, 0], [3863, -163], [3863, 0], [3864, 1], [3864, 0], [4418, -554], [4418, 0], +# [4419, 1], [4419, 0], [4547, -128], [4547, 0], [4548, 1], [4548, 0], [4603, -55], +# [4603, 0], [4604, 1], [4604, 0], [4965, -361], [4965, 0], [4966, 1], [4966, 0], +# [4984, -18], [4984, 0], [4985, 1], [4985, 0], [8639, -3654] +# ] +# print(f"Sleep duration: {sleep_length(presence_list)} minutes") + +def filter_short_groups_c_wc_old(presence_list, filter_size, device_id_str, from_date, to_date, time_zone_s, refresh = False): + + #days = presence_list + #for from_date, to_date + + tz = pytz.timezone(time_zone_s) + # Get current time in that timezone + current_time = datetime.datetime.now(tz) + # Return just the date part as string + now_date_str = current_time.strftime("%Y-%m-%d") + + start_date = datetime.datetime.strptime(from_date, "%Y-%m-%d") + end_date = datetime.datetime.strptime(to_date, "%Y-%m-%d") + + last_offset = 0 + #if to_date == now_date_str: + # last_offset = 1 + + # Loop through each date (including end_date) + current_date = start_date + dates_list = [] + days_difference = 1 + (end_date - start_date).days + whole_result = [0] * 6 * 1440 * (days_difference) + + is_long = False + if len(presence_list)/(6 * 1440) > (days_difference): #long version + is_long = True + + while current_date <= end_date: + current_date_str = current_date.strftime("%Y-%m-%d") + print(current_date_str) + dates_list.append(current_date_str) + current_date += timedelta(days=1) + + for day in range(1, days_difference-last_offset+1): + print(day) + end_index = (1 + day) * 6 * 1440 + if end_index > len(presence_list): + end_index = len(presence_list) + + if is_long: + start_index = end_index - 2 * 6 * 1440 + else: + start_index = end_index - 6 * 1440 + + current_date_str = dates_list[day-1] + filename_day_presence = f"/{device_id_str}/{device_id_str}_{current_date_str}_{filter_size}_presence.bin" + filtered_day_str = None + if refresh == False: + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence) + + if filtered_day_str == None: + filtered_day = filter_short_groups_c(presence_list[start_index:end_index], filter_size, device_id_str, from_date) + SaveGenericObjectInBlob("filtered-presence", filename_day_presence, filtered_day) + else: + filtered_day = json.loads(filtered_day_str) + whole_result[start_index:end_index] = filtered_day + + if current_date_str != to_date: + end_index = len(presence_list) + start_index = end_index - 2 * 6 * 1440 + filtered_day = filter_short_groups_c(presence_list[start_index:end_index], filter_size, device_id_str, from_date) + whole_result[start_index:end_index] = filtered_day + + return whole_result + +def filter_short_groups_c_wc(presence_list, filter_size, device_id_str, from_date, to_date, time_zone_s, refresh=False): + """ + Filter out short groups across multiple days. + For each target day, processes [previous_day + target_day] and extracts only the target_day result. + """ + # Setup timezone and current time + tz = pytz.timezone(time_zone_s) + current_time = datetime.datetime.now(tz) + now_date_str = current_time.strftime("%Y-%m-%d") + + start_date = datetime.datetime.strptime(from_date, "%Y-%m-%d") + end_date = datetime.datetime.strptime(to_date, "%Y-%m-%d") + + # Build dates list + current_date = start_date + dates_list = [] + while current_date <= end_date: + current_date_str = current_date.strftime("%Y-%m-%d") + print(current_date_str) + dates_list.append(current_date_str) + current_date += timedelta(days=1) + + days_difference = len(dates_list) + + # Handle current day limitation + samples_per_day = 6 * 1440 + total_samples = samples_per_day * days_difference + + # If today is the last day, limit the data + effective_total_samples = total_samples + if to_date == now_date_str: + current_minute_of_day = current_time.hour * 60 + current_time.minute + current_sample_of_day = min(current_minute_of_day * 6, samples_per_day) + effective_total_samples = (days_difference - 1) * samples_per_day + current_sample_of_day + print(f"Today detected: limiting to {current_sample_of_day} samples for last day") + + # Initialize result - use effective total samples + whole_result = [0] * effective_total_samples + + # Process each day (0-indexed to avoid confusion) + for day_idx in range(days_difference): + current_date_str = dates_list[day_idx] + print(f"Processing day {day_idx + 1}: {current_date_str}") + + # Calculate result array indices for this day + result_start_idx = day_idx * samples_per_day + result_end_idx = (day_idx + 1) * samples_per_day + + # For the last day, if it's today, limit the end index + if day_idx == days_difference - 1 and to_date == now_date_str: + result_end_idx = result_start_idx + current_sample_of_day + + # Skip if this day's range is beyond our result array + if result_start_idx >= len(whole_result): + break + + # Ensure we don't exceed result array bounds + result_end_idx = min(result_end_idx, len(whole_result)) + + # Calculate input data range - FIXED LOGIC + if day_idx == 0: + # First day: no previous day available in our data, process only current day + input_start_idx = 0 + input_end_idx = min(len(presence_list), samples_per_day) + has_previous_day = False + else: + # Other days: use previous day + current day + input_start_idx = (day_idx - 1) * samples_per_day + input_end_idx = min(len(presence_list), (day_idx + 1) * samples_per_day) + has_previous_day = True + + # Skip if no input data available + if input_start_idx >= input_end_idx or input_start_idx >= len(presence_list): + print(f"No input data available for {current_date_str}") + continue + + # Try to load cached data + filename_day_presence = f"/{device_id_str}/{device_id_str}_{current_date_str}_{filter_size}_presence.bin" + filtered_day_str = None + + if not refresh: + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence, current_date_str) + + if filtered_day_str is not None and filtered_day_str != "": + has_larger = bool(re.search(r'\b(?:[2-9]|\d{2,})\.\d+\b', filtered_day_str)) + if has_larger: + filtered_day_str = None + + if filtered_day_str is None or filtered_day_str == "": + # Filter the input data + input_data = presence_list[input_start_idx:input_end_idx] + print(f"Input range: {input_start_idx}:{input_end_idx}, length: {len(input_data)}, has_previous_day: {has_previous_day}") + + filtered_data = filter_short_groups_c(input_data, filter_size, device_id_str, from_date) + + # Calculate how much data we need for this day + needed_samples = result_end_idx - result_start_idx + + # Determine extraction offset - FIXED LOGIC + if has_previous_day and len(filtered_data) >= 2 * samples_per_day: + # We processed [previous_day + current_day], take the second day (current_day) + day_data_start = samples_per_day # Skip the first day (previous day context) + print(f"Extracting day 1 from 2-day filtered result: [{day_data_start}:{day_data_start + needed_samples}]") + elif has_previous_day and len(filtered_data) >= samples_per_day: + # We have previous day context but less than 2 full days + # Take from the portion that corresponds to current day + available_current_day_samples = len(filtered_data) - samples_per_day + day_data_start = samples_per_day + needed_samples = min(needed_samples, available_current_day_samples) + print(f"Extracting partial day 1: [{day_data_start}:{day_data_start + needed_samples}]") + else: + # First day or single day processing, take from beginning + day_data_start = 0 + print(f"Extracting day 0 (first/single day): [{day_data_start}:{day_data_start + needed_samples}]") + + day_data_end = day_data_start + needed_samples + + # Extract the day's portion, ensuring we don't exceed bounds + if day_data_start < len(filtered_data): + filtered_day = filtered_data[day_data_start:min(day_data_end, len(filtered_data))] + else: + filtered_day = [] + + print(f"Filtered data length: {len(filtered_data)}") + print(f"Extracted day data: start={day_data_start}, end={day_data_end}, length={len(filtered_day)}") + + # Cache the result + SaveGenericObjectInBlob("filtered-presence", filename_day_presence, filtered_day) + else: + filtered_day = json.loads(filtered_day_str) + + # Copy to result array + copy_length = min(len(filtered_day), result_end_idx - result_start_idx) + if copy_length > 0: + whole_result[result_start_idx:result_start_idx + copy_length] = filtered_day[:copy_length] + + print(f"Completed {current_date_str}: copied {copy_length} samples") + + return whole_result + + +def GetLastDurationMinutes(deployment_id, selected_devices, filter, ddate): + + global threshold_cache, device_lookup_cache + + max_sleep = 0 + max_device_id = 0 + max_woke_up = 0 + presence_list = [] + to_date = ddate + + + date_obj = datetime.datetime.strptime(ddate, "%Y-%m-%d") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + # Convert back to string + prev_date = previous_day.strftime("%Y-%m-%d") + + data_type = "z-graph" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + + + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Lets filter bedrooms only + just_selected_devices = [] + for device_details in devices_list: + if device_details[1] in selected_devices: + just_selected_devices.append(device_details) + + devices_list = just_selected_devices + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + #time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + #epoch_time = calendar.timegm(time_from_z.utctimetuple()) + time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from.utctimetuple()) + + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + # Calculate the difference in days + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + + if data_type == "all" or data_type == "z-graph" or data_type == "multiple": + + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + threshold_str = details[5] + if "," in threshold_str: + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + else: + if is_number(threshold_str): + threshold_lst = ["s3",float(threshold_str)] + + print(threshold_lst) + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + if len(devices_list) > 0: + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #print(sql) + if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zsql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + cur.execute(zsql) + myz_data = cur.fetchall() + + #if my_data != None: + + #device_id_2_threshold = {} + #device_id_2_location = {0: "Outside"} + #row_nr_2_device_id = {} + #cnt = 0 + #row_nr_2_device_id[0] = 0 + + ##presence_map['longpresence'] and temporary_map_day_plus are similar, except one is used for Z-graph, and another for multiple persons detection + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'] = {} + #presence_map['longpresence'] = {} + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'] = {} + + #for details in devices_list: + ##(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + #well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'][well_id] = [0] * 6 * 1440 * days_difference + + + ##presence_map[][well_id] = zeros_list + #cnt += 1 + #row_nr_2_device_id[cnt] = well_id + + #if radar_threshold_group_st == None: + #radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + #if len(radar_threshold_group_st) > 8: + #radar_threshold_group = json.loads(radar_threshold_group_st) + #else: + #radar_threshold_group = ["s3",12] + + #device_id_2_location[well_id] = location_name + #device_id_2_threshold[well_id] = radar_threshold_group + #if len(my_data) > 1: + + #start_time_ = my_data[0][0] + #parsed_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + #) + + #presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + presence_map['longpresence'] = {} + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + + print(deployment_id) + print(time_from_z_str) + print(devices_list) + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + start_time = datetime.datetime( + parsed_time.year, + parsed_time.month, + parsed_time.day, + parsed_time.hour, # Adjust for UTC-7 + parsed_time.minute, + parsed_time.second, + tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + ) + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + presence_map['longpresence'][well_id] = temporary_map_day_plus[well_id] + + for device_id in ids_list: + device_id_str = str(device_id) + if data_type == "presence" or data_type == "all" or data_type == "z-graph": + if filter > 1: + #presence_list = filter_short_groups_numpy(presence_map["presence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + #presence_list = filter_short_groups_c_wc(presence_map["presence"][id2well_id[device_id]], filter, device_id_str, ddate, to_date, time_zone_s) + #presence_map["presence"][id2well_id[device_id]] = presence_list + #longpresence_list = filter_short_groups_numpy(presence_map["longpresence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s, True) + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + + max_sleep = 0 + max_device_id = 0 + max_woke_up = 0 + for device_id in ids_list: + #print(device_id_2_threshold[id2well_id[device_id]]) + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + sleep_minutes, woke_up = sleep_length(z_graph) + if sleep_minutes > max_sleep: + max_sleep = sleep_minutes + max_device_id = device_id + max_woke_up = woke_up + presence_map = {} + return max_sleep, max_device_id, max_woke_up, z_graph + +def is_number(s): + try: + float(s) + return True + except ValueError: + return False + +def GetTempOffset(device_id): + result = 0 + sql = f""" + SELECT temperature_calib + FROM public.devices + WHERE device_id = {device_id}; + """ + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + try: + if isinstance(result, tuple): + if "," in result[0]: + string_nr = result[0].split(',')[-1].strip() + if is_number(string_nr): + return float(string_nr) + else: + return temperature_offset + else: + if is_number(result[0]): + return float(result[0]) + else: + return temperature_offset + else: + return temperature_offset + except: + return temperature_offset + + +def GetTemperature(bedroom_device_id): + result = 0 + sql = f""" + SELECT * + FROM public.sensor_readings + WHERE device_id = {bedroom_device_id} and temperature > 0 + ORDER BY "time" DESC + LIMIT 1; + """ + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + + if result == None: + return 0 + else: + return result[2] + +def GetSensorsDetailsFromDeployment(deployment_id, ddate, filter_minutes, fast=False): + #list all devices that user has access to + deployments_dets = [] + with get_db_connection() as conn: + try: + sql = f""" + SELECT pd.user_id, pd.first_name, pd.last_name, pd.address_street, pd.picture + FROM deployment_details dd + JOIN person_details pd ON dd.beneficiary_id = pd.user_id + WHERE dd.deployment_id ={deployment_id}; + """ + with conn.cursor() as cur: + cur.execute(sql) + deployments_dets = cur.fetchone() + except Exception as err: + logger.error("GetSensorsDetailsFromDeployment "+str(err) +" "+sql) + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Which sensor is in: Bathroom, Kitchen, Bedroom ? + bathrooms = [] + kitchens = [] + bedrooms = [] + dev_id_to_location = {0: "Outside/?"} + for device in devices_list: + dev_id_to_location[device[1]] = device[2] + if Consolidataed_locations[device[2]] == "Bathroom": + bathrooms.append(device[1]) + elif Consolidataed_locations[device[2]] == "Kitchen": + kitchens.append(device[1]) + elif Consolidataed_locations[device[2]] == "Bedroom": + bedrooms.append(device[1]) + + #we need to determine where user is seen last, and user sensor data from there... + locations_file = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + logger.debug(f"locations_file1 ={locations_file}") + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + force_recreate = False + file_exists1, file_modified_utc1 = check_file_exists(locations_file+".bin") + if file_exists1: + file_modified_local = file_modified_utc1.astimezone(pytz.timezone(time_zone_s)) + file_modified_date_local = file_modified_local.date() #local date + file_modified_date_utc = file_modified_utc1.date() + file_date_utc = MapFileToDate(locations_file) #locations_file is UTC + #if file_modified_date_local < file_date_utc: + if file_modified_utc1.date() < file_date_utc: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - file_modified_local + if time_passed.seconds > 30: #recreate if older than 5 minutes + force_recreate = True + else: + force_recreate = True + + + logger.debug(f"force_recreate={str(force_recreate)}") + + if force_recreate: + CreateLocationsStripe(locations_file, time_zone_s) + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + + last_present_device = 0 + last_present = 0 + last_bathroom = 0 + last_kitchen = 0 + last_bedroom = 0 + last_bathroom_date = ddate + last_kitchen_date = ddate + last_bedroom_date = ddate + before_last_present_device = 0 + last_present_duration = 0 + + if locations_list_s is not None: + locations_list = json.loads(locations_list_s) + + if len(locations_list) > 1: + if locations_list[-1][0] > 0: + last_present_device = locations_list[-1][0] + last_present = locations_list[-1][1] + locations_list[-1][2] + before_last_present_device, last_present_duration = FindPreviousLocation(locations_list, last_present_device, -1) + #before_last_present_device = locations_list[-3][0] + #last_present_duration = locations_list[-1][2] + + else: + last_present_device = locations_list[-2][0] + last_present = locations_list[-2][1] + locations_list[-2][2] + before_last_present_device, last_present_duration = FindPreviousLocation(locations_list, last_present_device, -2) + #before_last_present_device = locations_list[-4][0] + #last_present_duration = locations_list[-2][2] + + elif len(locations_list) == 1: + last_present_device = locations_list[0][0] + + #Lets find last bathroom presence time + + if len(locations_list) > 0 and len(bathrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bathrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bathroom: + last_bathroom = loc_time[1] + loc_time[2] + last_bathroom_date = ddate + + + #Lets find last kitchen presence time + + if len(locations_list) > 0 and len(kitchens) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in kitchens: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_kitchen: + last_kitchen = loc_time[1] + loc_time[2] + last_kitchen_date = ddate + + + #Lets find last bedroom presence time + + if len(locations_list) > 0 and len(bedrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bedrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bedroom: + last_bedroom = loc_time[1] + loc_time[2] + last_bedroom_date = ddate + + + if last_bathroom == 0 or last_kitchen == 0 or last_bedroom == 0: + ddate = GetPreviousDate(ddate) + + locations_file = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + logger.debug(f"locations_file2 ={locations_file}") + + force_recreate = False + file_exists1, file_modified_utc1 = check_file_exists(locations_file+".bin") + logger.debug(f"file_exists1={str(file_exists1)}") + logger.debug(f"file_modified_utc1={str(file_modified_utc1)}") + #file_exists1, file_modified_utc1 + if file_exists1: + file_modified_local = file_modified_utc1.astimezone(pytz.timezone(time_zone_s)) + file_modified_date_local = file_modified_local.date() + file_date_utc = MapFileToDate(locations_file) + logger.debug(f"file_modified_utc1={str(file_modified_utc1.date())} file_date_utc={str(file_date_utc)}") + if file_modified_utc1.date() < file_date_utc: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - file_modified_local + logger.debug(f"current_time={current_time} file_modified_local={file_modified_local} time_passed={time_passed}") + if time_passed.seconds > 30: #recreate if older than 5 minutes + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + CreateLocationsStripe(locations_file, time_zone_s) + locations_list_s = ReadObjectMinIO("daily-maps", locations_file+".bin") + + + + logger.debug(f"locations_list_s={locations_list_s}") + if (locations_list_s is not None): + + locations_list = json.loads(locations_list_s) + + if last_present_device == 0: + if len(locations_list) > 1: + if locations_list[-1][0] > 0: + last_present_device = locations_list[-1][0] + else: + last_present_device = locations_list[-2][0] + + elif len(locations_list) == 1: + last_present_device = locations_list[0][0] + + if last_bathroom == 0: + if len(locations_list) > 0 and len(bathrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bathrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bathroom: + last_bathroom = loc_time[1] + loc_time[2] + last_bathroom_date = ddate + + if last_kitchen == 0: + if len(locations_list) > 0 and len(kitchens) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in kitchens: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_kitchen: + last_kitchen = loc_time[1] + loc_time[2] + last_kitchen_date = ddate + + if last_bedroom == 0: + if len(locations_list) > 0 and len(bedrooms) > 0: + for loc_time in reversed(locations_list): + for device_id_temp in bedrooms: + if device_id_temp == loc_time[0]: + if (loc_time[1] + loc_time[2]) > last_bedroom: + last_bedroom = loc_time[1] + loc_time[2] + last_bedroom_date = ddate + + + + last_bathroom_time = "2023-01-01T00:00:00" + if last_bathroom > 0: + last_bathroom_time = CovertToIsoTime(last_bathroom_date, last_bathroom) + + last_kitchen_time = "2023-01-01T00:00:00" + if last_kitchen > 0: + last_kitchen_time = CovertToIsoTime(last_kitchen_date, last_kitchen) + + last_bedroom_time = "2023-01-01T00:00:00" + if last_bedroom > 0: + last_bedroom_time = CovertToIsoTime(last_bedroom_date, last_bedroom) + + last_present_time = "2023-01-01T00:00:00" + if last_present > 0: + last_present_time = CovertToIsoTime(ddate, last_present) + + # debug for 48h bug + if last_bathroom_time == "2023-01-01T00:00:00" or last_kitchen_time == "2023-01-01T00:00:00" or last_bedroom_time == "2023-01-01T00:00:00": + #last_bathroom_time = "48h" if last_bathroom_time == "2023-01-01T00:00:00" else f"{last_bathroom-last_bathroom_time}" + #last_kitchen_time = "48h" if last_kitchen_time == "2023-01-01T00:00:00" else f"{last_kitchen-last_kitchen_time}" + #last_bedroom_time = "48h" if last_bedroom_time == "2023-01-01T00:00:00" else f"{last_bedroom-last_bedroom_time}" + + logger.debug(f"48h-> deployment_id={str(deployment_id)}, ddate={str(ddate)}") + logger.debug(f"48h-> force_recreate={force_recreate}") + logger.debug(f"48h-> last_bathroom_time={last_bathroom_time}|last_kitchen_time={last_kitchen_time}|last_bedroom_time={last_bedroom_time}") + logger.debug(f"48h-> devices_list={str(devices_list)}") + logger.debug(f"48h-> bathrooms={str(bathrooms)}") + logger.debug(f"48h-> kitchens={str(kitchens)}") + logger.debug(f"48h-> bedrooms={str(bedrooms)}") + logger.debug(f"48h-> locations_list_s={str(locations_list_s)}") + + + # wellness_score_percent + wellness_score_percent = 90 + + sleep_filter_minutes = 5 + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + + sleep_bathroom_visit_count = 0 + # bedroom_co2 + bedroom_co2 = 500 + device_detail = None + bedroom_temperature = 0 + sleep_hours = 0 + if fast == False: + + if len(bedrooms) > 0: + sleep_minutes, bedroom_device_id, woke_up, presence_list = GetLastDurationMinutes(deployment_id, bedrooms, sleep_filter_minutes, ddate) + sleep_hours = sleep_minutes/ 60 + # bedroom_temperature + temp_offset = GetTempOffset(bedroom_device_id) + + bedroom_temperature = GetTemperature(bedroom_device_id) + temp_offset + + # sleep_bathroom_visit_count + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + if sleep_minutes < woke_up: # went to sleep after midnight + date_sleep = ddate + to_sleep = woke_up - sleep_minutes + else:# went to sleep before midnight + to_sleep = 1440 + woke_up - sleep_minutes + + # Convert string to datetime object + previous_day = date_obj - timedelta(days=1) + date_sleep = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + + # shower_detected_time + shower_detected_time = last_bathroom_time + + # breakfast_detected_time + breakfast_detected_time = 0 + + # living_room_detected_time + living_room_time_spent = 0 + + # outside_hours + outside_hours = 0 + + #lets find last time seen at Bathroom, Kitchen, Bedroom pd.first_name, pd.last_name, pd.address_street, pd.picture + + picture_url = deployments_dets[4] + + temp_offset = GetTempOffset(last_present_device) + temperature = GetTemperature(last_present_device) + temp_offset + + + report = {} + + try: + + report = {"user_id":deployments_dets[0], + "name":deployments_dets[1] + " " + deployments_dets[2], + "address":deployments_dets[3], + "time_zone":time_zone_s, + "picture":picture_url, + "bathroom_at": last_bathroom_time, + "kitchen_at": last_kitchen_time, + "bedroom_at": last_bedroom_time, + "temperature": temperature, + "smell": "clean", + "bathroom_delayed": [6, 12], + "kitchen_delayed": [6, 12], + "bedroom_delayed": [13, 16], + "last_location": dev_id_to_location[last_present_device], + "last_detected_time": last_present_time, + "before_last_location": dev_id_to_location[before_last_present_device], + "last_present_duration": last_present_duration, + "wellness_score_percent": wellness_score_percent, + "wellness_descriptor_color": "bg-green-100 text-green-700", + "bedroom_temperature": round(bedroom_temperature, 2), + "sleep_bathroom_visit_count": sleep_bathroom_visit_count, + "bedroom_co2": bedroom_co2, + "shower_detected_time": shower_detected_time, + "breakfast_detected_time": breakfast_detected_time, + "living_room_time_spent": round(living_room_time_spent, 2), + "outside_hours": round(outside_hours, 2), + "wellness_descriptor": "Great!", + "last_seen_alert": "Alert = None", + "last_seen_alert_colors": "bg-green-100 text-green-700", #https://tailwindcss.com/docs/colors + "most_time_spent_in": "Bedroom", + "sleep_hours": round(sleep_hours, 2) + } + except Exception as e: + print(traceback.format_exc()) + return report + +def FindPreviousLocation(locations_list, last_present_device, start_index): + """ + Find the previous device_id (not last_present_device and not 0) counting backwards from start_index. + Also sum the minutes for all entries except the one being reported. + + Args: + locations_list: List of [device_id, start_time, minutes] entries + last_present_device: Device ID to exclude from search + start_index: Index to start searching backwards from + + Returns: + tuple: (device_id, total_minutes) or (None, total_minutes) if no valid device found + """ + total_minutes = 0 + found_device = None + end_index = -len(locations_list) - 1 + print(locations_list[-4:]) + # Count backwards from start_index + for i in range(start_index, end_index, -1): + device_id = locations_list[i][0] + minutes = locations_list[i][2] + + # If we haven't found our target device yet, check if this one qualifies + if found_device is None and device_id != last_present_device and device_id != 0: + found_device = device_id + break + else: + # Add minutes for all entries except the one we're reporting + total_minutes += minutes + + return found_device, total_minutes + + +def ToList(input_data): + # If input is already a list + if isinstance(input_data, list): + return [str(x).strip() for x in input_data] + + # If input is string + if isinstance(input_data, str): + # Remove outer brackets if present + cleaned = input_data.strip('()') + cleaned = cleaned.strip('[]') + # Remove extra quotes + cleaned = cleaned.replace('"', '').replace("'", '') + # Split by comma and clean each element + return [x.strip() for x in cleaned.split(',')] + + raise ValueError(f"Unsupported input type: {type(input_data)}") + +def MACsToWellIds(cur, macs_list): + + device_ids = [] + device_list = [] + + macs_string = ",".join(f"'{mac}'" for mac in macs_list) + if macs_string != "'None'": + sqlr = f"SELECT well_id, device_mac, device_id, location, description, radar_threshold, close_to FROM public.devices WHERE device_mac IN ({macs_string})" + + print (sqlr) + macs_map = {} + cur.execute(sqlr) + proximitys_list = cur.fetchall() + for well_id, mac, device_id, location, description, radar_threshold, close_to in proximitys_list: + if location == None: + location = -1 + if radar_threshold == None: + radar_threshold = "50" + if close_to == None: + close_to = "" + + macs_map[mac] = (well_id, device_id, location_names[location], description, mac, radar_threshold, close_to) + + + for mac in macs_list: + if mac != "": + print(mac) + device_ids.append(macs_map[mac][1]) + device_list.append(macs_map[mac]) + + return device_ids, device_list + +def MACsToDeviceId(MAC): + + sqlr = f"SELECT device_id FROM public.devices WHERE device_mac = '{MAC}'" + device_id = 0 + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchone() + if result != None: + device_id = result[0] + + + return device_id + +def DeviceId2MAC(device_id): + + try: + + with get_db_connection() as conn: + + sqlr = f"SELECT device_mac FROM public.devices WHERE device_id ='{device_id}'" + with conn.cursor() as cur: + + cur.execute(sqlr) + mac = cur.fetchone()[0] + return mac + except: + return "" + + +def WellId2Details(well_ids): + + try: + + with get_db_connection() as conn: + #sql = f"SELECT deployment_id, devices FROM public.deployment_details WHERE deployment_id IN ({deployments})" + #device_ids_string = ",".join(map(str, devices_ids_list)) + sqlr = f"SELECT well_id, device_id, device_mac, location, description FROM public.devices WHERE well_id IN ({well_ids})" + print(sqlr) + with conn.cursor() as cur: + + cur.execute(sqlr) + details = cur.fetchall() + return details + except: + return "" + + + +def MACsStrToDevIds(cur, macs): + + device_ids = [] + #we need to repcakage string to contain ' + macs_list = ToList(macs) + macs_string = ",".join(f"'{mac}'" for mac in macs_list) + if macs_string != "'None'": + + sqlr = f"SELECT device_mac, device_id FROM public.devices WHERE device_mac IN ({macs_string})" + print (sqlr) + macs_map = {} + cur.execute(sqlr) + proximitys_list = cur.fetchall() + for mac, device_id in proximitys_list: + device_ids.append((mac, device_id)) + + return device_ids + + + +def ReadCleanStringDB(cur, sql): + cur.execute(sql) + temp_string = cur.fetchone() + if temp_string == None: + return "" + else: + return str(temp_string[0]).strip() + +# obtain device_list, device_ids for deployment_id on time as epoch_from_file_s (usually today) +# it tries first overridden/newly-installed (FROM public.deployment_history) +# then if none found there searches FROM public.deployment_details +def GetProximityList(deployment_id, epoch_from_file_s): + + #both are valid: + #64B70888FA84,64B70888F6F0,64B70888F860,64B70889062C,64B70888FAB0,64B708896BDC,64B708897428 + #['64B70888FA84', '64B70888F6F0', '64B70888F860', '64B70889062C', '64B70888FAB0', '64B708896BDC', '64B708897428'] + + #result_list = [] + #well_ids = [] + with get_db_connection() as conn: + + sqlr = f""" + SELECT * FROM ( + SELECT proximity + FROM public.deployment_history + WHERE deployment_id = {deployment_id} + AND time <= {epoch_from_file_s} + ORDER BY time DESC + LIMIT 1 + ) AS latest_deployment + """ + #print (sqlr) + with conn.cursor() as cur: + devices_string = ReadCleanStringDB(cur, sqlr) + + if devices_string == "": + sqlr = f"SELECT devices from public.deployment_details WHERE deployment_id ={deployment_id}" + #print (sqlr) + devices_string = ReadCleanStringDB(cur, sqlr) + + + if devices_string == "": + return [], [] + + macs_list = ToList(devices_string) + device_ids, device_list = MACsToWellIds(cur, macs_list) + return device_list, device_ids + +def FilterList(to_filter: str, allowed: str) -> str: + # Convert comma-separated strings to sets + filter_set = set(to_filter.split(',')) + allowed_set = set(allowed.split(',')) + + # Find intersection and sort the result + filtered = sorted(filter_set.intersection(allowed_set), key=int) + + # Join back to comma-separated string + return ','.join(filtered) + + +def GetMatchingDevices(privileges, group, deployment, location): + + global LocationsMap + + results=[] + if privileges != "-1": + if deployment == "" or deployment == "0": + deployment = privileges + + privileges_list = privileges.split(',') + if deployment != "0": + if "," in deployment: + deployment = FilterList(deployment, privileges) + else: + if deployment not in privileges_list: + return results + else: + if deployment == "0": + deployment = "-1" + + devices = GetVisibleDevicesPerLocation(deployment, location) + return devices + +def GetMatchingDevicesComplete(privileges, group, deployment, location): + + global LocationsMap + + results=[] + if privileges != "-1": + if deployment == "" or deployment == "0": + deployment = privileges + + privileges_list = privileges.split(',') + if deployment != "0": + if "," in deployment: + deployment = FilterList(deployment, privileges) + else: + if deployment not in privileges_list: + return results + else: + if deployment == "0": + deployment = "-1" + + devices = GetVisibleDevicesPerLocationComplete(deployment, location) + return devices + +def getOldestDeploymentHistoryFromBeneficiary(deployment_id): + #this will return oldest entry as well as last proximity (devices) + st = time.time() + print(f"*0 ----{time.time() - st}") + results=[] + well_ids_last = [] #this needs to be list of tuples (well_id, Location_st, Description) + oldest_time = None + try: + print(f"*0a ----{time.time() - st}") + with get_db_connection() as conn: + sqlr = f""" + SELECT * FROM ( + SELECT time, proximity + FROM public.deployment_history + WHERE deployment_id = {deployment_id} + ORDER BY time ASC + ) AS latest_deployment + """ + print (sqlr) + print(f"*1 ----{time.time() - st}") + with conn.cursor() as cur: + cur.execute(sqlr) + print(f"*2 ----{time.time() - st}") + results = cur.fetchall() + print(f"*3 ----{time.time() - st}") + #lets find which of historical sets has data in DB + if results == None or results == []: #look in deployment_details + sqlr = f"SELECT devices from public.deployment_details WHERE deployment_id ={deployment_id}" + #print (sqlr) + print(f"*4 ----{time.time() - st}") + devices_string = ReadCleanStringDB(cur, sqlr) + print(f"*5 ----{time.time() - st}") + macs_list = ToList(devices_string) + print(f"*6 ----{time.time() - st}") + device_ids_last, device_alls_last = MACsToWellIds(cur, macs_list) + sql_query = """ + SELECT device_id, first_seen_at + FROM device_first_seen + WHERE device_id = ANY(%s) + GROUP BY device_id; + """ + print(f"*7 ----{time.time() - st}") + try: + cur.execute(sql_query, (device_ids_last,)) + results1 = cur.fetchall() + if results1 == []: + try: + # Find the oldest timestamp for each device from sensor_readings + oldest_query = """ + SELECT device_id, MIN(time) as first_seen_at + FROM sensor_readings + WHERE device_id = ANY(%s) + GROUP BY device_id + """ + + cur.execute(oldest_query, (device_ids_last,)) + oldest_results = cur.fetchall() + + if oldest_results: + # Insert the oldest records into device_first_seen + insert_query = """ + INSERT INTO device_first_seen (device_id, first_seen_at) + VALUES (%s, %s) + ON CONFLICT (device_id) DO NOTHING + """ + + # Insert each device's first seen timestamp + for device_id, first_seen_at in oldest_results: + cur.execute(insert_query, (device_id, first_seen_at)) + + # Commit the inserts + conn.commit() + + # Set oldest_time to the earliest timestamp found + oldest_time = min(result[1] for result in oldest_results) + + AddToLog(f"Inserted {len(oldest_results)} device first seen records") + else: + # No sensor readings found for these devices + AddToLog("No sensor readings found for the specified devices") + oldest_time = None + + except Exception as e: + AddToLog(f"Error determining oldest records: {traceback.format_exc()}") + AddToLog(str(e)) + oldest_time = None + # Rollback in case of error + conn.rollback() + else: + oldest_time = results1[0][1] + except Exception as e: + AddToLog(traceback.format_exc()) + AddToLog(str(e)) + print(f"*8 ----{time.time() - st}") + else: + history_entry = results[-1] + macs_list = ToList(history_entry[1]) + print(f"*9 ----{time.time() - st}") + device_ids_last, device_alls_last = MACsToWellIds(cur, macs_list) + + + for history_entry in results: + macs_list = ToList(history_entry[1]) + print(f"*10 ----{time.time() - st}") + device_ids, device_alls = MACsToWellIds(cur, macs_list) + #print(f"*11 ----{time.time() - st}") + #sql_query = """ + #SELECT time as oldest_record_time + #FROM sensor_readings + #WHERE device_id = ANY(%s) + #ORDER BY time ASC + #LIMIT 1; + #""" + print(f"*12 ----{time.time() - st}") + print("Getting oldest record time for devices:", device_ids_last) + + #print(sql_query, device_ids_last) + #try: + #cur.execute(sql_query, (device_ids_last,)) + #results1 = cur.fetchall() + #oldest_time = results1[0][0] + #if oldest_time != None: + #break + + + #except Exception as e: + #print(str(e)) + + try: + oldest_time = get_oldest_record_time_optimized(cur, device_ids_last) + if oldest_time is not None: + break + except Exception as e: + print(str(e)) + + print(f"*13 ----{time.time() - st}") + + except Exception as e: + print(f"*0b ----{time.time() - st}") + AddToLog(traceback.format_exc()) + + print(f"*14 ----{time.time() - st}") + return oldest_time, device_alls_last + +def get_oldest_record_time_optimized(cur, device_ids): + """ + Get the oldest record time for devices, using device_first_seen cache + and only querying sensor_readings for devices not in cache. + """ + if not device_ids: + return None + + # First, check which devices already have cached first_seen times + device_ids_tuple = tuple(device_ids) + cache_query = """ + SELECT device_id, first_seen_at + FROM device_first_seen + WHERE device_id = ANY(%s) + """ + + cur.execute(cache_query, (device_ids,)) + cached_results = cur.fetchall() + cached_device_times = {device_id: first_seen for device_id, first_seen in cached_results} + + # Find devices that are NOT in cache + uncached_device_ids = [device_id for device_id in device_ids if device_id not in cached_device_times] + + # For uncached devices, query sensor_readings and populate cache + for device_id in uncached_device_ids: + try: + # Query oldest record for this specific device + oldest_query = """ + SELECT time as oldest_record_time + FROM sensor_readings + WHERE device_id = %s + ORDER BY time ASC + LIMIT 1; + """ + + cur.execute(oldest_query, (device_id,)) + result = cur.fetchone() + + if result and result[0] is not None: + oldest_time = result[0] + + # Insert into cache table (use ON CONFLICT in case of race conditions) + insert_cache_query = """ + INSERT INTO device_first_seen (device_id, first_seen_at) + VALUES (%s, %s) + ON CONFLICT (device_id) DO NOTHING; + """ + cur.execute(insert_cache_query, (device_id, oldest_time)) + + # Add to our local cache dict + cached_device_times[device_id] = oldest_time + + except Exception as e: + print(f"Error processing device_id {device_id}: {str(e)}") + continue + + # Return the earliest time among all devices that have data + valid_times = [time for time in cached_device_times.values() if time is not None] + return min(valid_times) if valid_times else None + + +def getLastEditedBeneficiary(beneficiary): + + #lets generate token here to elliminate issues with outdated token... + token = generate_token(beneficiary) + url = 'https://well-api.azurewebsites.net/api/well_api' + params = { + "name": "beneficiary_detail", + "beneficiary": beneficiary, + "token": token + } + #{"id": "user_beneficiary_bernhard@wellnuo.com", "MAC": "BENEFICIARY", "email": "bernhard@wellnuo.com", "edit_date": "Fri Aug 16 06:45:01 2024", "c_password": "bern1", "first_name": "Bernhard", "last_name": "Knigge", "address": "776 Dubanski Dr.", "address_city": "San Jose", "address_state": "CA", "address_zip": "95123", "address_country": "United States", "phone_number": "4087055709", "persons": "2", "gender": "M", "race": "W", "born": "1972", "pets": "1", "creds": "", "devs": "[[203, 'Living Room', '', '64B708890B14'], [251, 'Bathroom', '', '64B7088909E8'], [252, 'Bedroom', '', '64B708890734'], [204, 'Bathroom', 'Guest', '64B708890288'], [201, 'Kitchen', 'toaster', '64B708890584'], [202, 'Kitchen', 'stove', '64B7088906D8'], [205, 'Office', '', '64B708897018']]", "tzone": "America/Los_Angeles", "ttl": -1, "_rid": "R60hANIG-K+qTQIAAAAAAg==", "_self": "dbs/R60hAA==/colls/R60hANIG-K8=/docs/R60hANIG-K+qTQIAAAAAAg==/", "_etag": "\"3500a0ae-0000-0800-0000-66bef56d0000\"", "_attachments": "attachments/", "_ts": 1723790701} + response = requests.get(url, params=params) + if response.status_code == 200: + + text = response.text + #print(text) + if text == "Log-Out": + return text + if text[0] == "{": + data = json.loads(response.text) + date_string = data["edit_date"] + parsed_date = datetime.datetime.strptime(date_string, '%c') + # Convert the datetime object to a timestamp (epoch time) + epoch_str = str(time.mktime(parsed_date.timetuple())) + devices = data["devs"] + return(epoch_str, devices) + else: + return text,"" + else: + logger.debug((f"Failed to retrieve the data, status code: {response.status_code}")) + + return "","" + +def GetDeploymentNameFromId(Id): + + con = sqlite3.connect(main_db) + con.text_factory = str + cur = con.cursor() + results=[] + SQL = "SELECT name FROM deployments WHERE id =" + Id + df = cur.execute(SQL) + results = cur.fetchall() + if len(results) > 0: + return results[0][0] + else: + return "" + +def GetTimeZoneOfDeployment(deployment_id): + time_zone_st = 'America/Los_Angeles' + with get_db_connection() as conn: + with conn.cursor() as cur: + sqlr = f"SELECT time_zone_s from public.deployments WHERE deployment_id ={deployment_id}" + time_zone_st = ReadCleanStringDB(cur, sqlr) + return time_zone_st + +def StringToEpoch(date_string, time_zone_s): + """ + Convert a date string to epoch timestamp for start of day (midnight) in specified timezone + + Args: + date_string (str): Date in 'YYYY-MM-DD' format + time_zone_s (str): Timezone string (e.g. 'America/Los_Angeles') + + Returns: + float: Epoch timestamp in seconds + """ + # Parse the date string + date_format = '%Y-%m-%d' + naive_date = datetime.datetime.strptime(date_string, date_format) + + # Get the timezone + timezone = pytz.timezone(time_zone_s) + + # Localize the date to midnight in the specified timezone + local_date = timezone.localize(naive_date) + + # Convert to epoch timestamp + epoch_time = local_date.timestamp() + + return epoch_time + +def LocalDateToUTCEpoch(local_date_str, time_zone_s): + """ + Convert a date string to epoch timestamp for start of day (midnight) in UTC + + Args: + local_date_str (str): Date in 'YYYY-MM-DD' format + time_zone_s (str): Timezone string (e.g. 'America/Los_Angeles') + + Returns: + float: Epoch UTC timestamp in seconds + """ + timezone = pytz.timezone(time_zone_s) + # Parse the date string + date_format = '%Y-%m-%d' + local_datetime = datetime.datetime.strptime(local_date_str, date_format) + local_datetime = timezone.localize(local_datetime) + + utc_datetime = local_datetime.astimezone(pytz.UTC) + epoch_time = int(utc_datetime.timestamp()) + + return epoch_time + +def GetDeploymentDatesBoth(deployment_in): + + #when looking at the date, date is defined in TZ where device is! + #Lets take oldest data from first member of deployment + st = time.time() + date_list = [] + print(f"&0 ----{time.time() - st}") + time_zone_st = GetTimeZoneOfDeployment(deployment_in) + print(f"&1 ----{time.time() - st}") + oldest_date_dt_utc, devices_all = getOldestDeploymentHistoryFromBeneficiary(deployment_in) + print(f"&2 ----{time.time() - st}") + if oldest_date_dt_utc != None: + #get date in local time zone from UTC datetime + + #oldest_date_dt + # Get today's date + local_timezone = pytz.timezone(time_zone_st) # Replace with your local timezone + oldest_date_dt_local = oldest_date_dt_utc.astimezone(local_timezone) + today_date = datetime.datetime.now(local_timezone) + + # Generate a list of date strings from oldest_date to today in inverted order + date_list = [(today_date - timedelta(days=x)).strftime('%Y-%m-%d') for x in range((today_date - oldest_date_dt_local).days + 1)] + print(f"&3 ----{time.time() - st}") + return date_list, devices_all, time_zone_st + +def check_file_exists(file_name, bucket_name="daily-maps"): + try: + # Try to get the object's stats - this will raise an exception if the object doesn't exist + stat_result = miniIO_blob_client.stat_object(bucket_name, file_name) + last_modified_utc = stat_result.last_modified + return True, last_modified_utc + except S3Error as e: + if e.code == 'NoSuchKey': + return False, 0 + # Re-raise if it's a different error + raise + +def get_text_dimensions(text, font, font_scale, thickness): + (width, height), baseline = cv2.getTextSize(text, font, font_scale, thickness) + return { + 'width': width, + 'height': height, + 'baseline': baseline, + 'total_height': height + baseline + } + +def save_to_minio(image, filename, bucket_name="daily-maps", content_type="image/png"): + """ + Save a PIL Image directly to MinIO + + Args: + image (PIL.Image): Image to save + filename (str): Filename to use in MinIO + bucket_name (str): MinIO bucket name + content_type (str): Content type of the file + + Returns: + bool: True if successful, False otherwise + """ + logger = logging.getLogger(__name__) + try: + # Convert PIL image to bytes + img_byte_arr = io.BytesIO() + image.save(img_byte_arr, format='PNG') + img_byte_arr.seek(0) # Move to start of the BytesIO buffer + + # Upload to MinIO + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + filename, + img_byte_arr, + length=len(img_byte_arr.getvalue()), + content_type=content_type + ) + return True + + except Exception as e: + logger.error(f"Error saving to MinIO: {traceback.format_exc()}") + return False + + +def SaveImageInBlob(file_name, arr_stretched, labels = []): + + #labels=[(caption,(x,y),font,scale,color,thickness,line_type)] + try: + + image_with_text = arr_stretched.copy() + + for label in labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + # Encode the image to a memory buffer using imencode + success, encoded_image = cv2.imencode('.png', image_with_text) + AddToLog(f"success={success}") + if not success: + raise Exception("Could not encode image!") + + #AddToLog(f"DAILY_MAPS_BUCKET_NAME={DAILY_MAPS_BUCKET_NAME}") + + image_bytes = encoded_image.tobytes() + AddToLog(f"len(image_bytes)={len(image_bytes)}") + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(image_bytes), + len(image_bytes)) + return True + except Exception as e: + AddToLog(f"{traceback.format_exc()}") + logger.error(f"{traceback.format_exc()}") + return False + +def SaveImageInBlobLabelsOut(file_name, arr_stretched, labels, title_labels): + + #labels=[(caption,(x,y),font,scale,color,thickness,line_type)] + try: + + image_with_text = arr_stretched.copy() + + for label in labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + for label in title_labels: + cv2.putText( + image_with_text, # Image + label[0], # Text to write + label[1], # Position (x, y) + label[2], # Font type + label[3], # Font scale + label[4], # Color (BGR) + label[5], # Thickness + label[6] # Line type + ) + + + # Encode the image to a memory buffer using imencode + success, encoded_image = cv2.imencode('.png', image_with_text) + AddToLog(f"success={success}") + if not success: + raise Exception("Could not encode image!") + + #AddToLog(f"DAILY_MAPS_BUCKET_NAME={DAILY_MAPS_BUCKET_NAME}") + + image_bytes = encoded_image.tobytes() + AddToLog(f"len(image_bytes)={len(image_bytes)}") + miniIO_blob_client.put_object( + DAILY_MAPS_BUCKET_NAME, + file_name, + io.BytesIO(image_bytes), + len(image_bytes)) + return True + except Exception as e: + AddToLog(f"{traceback.format_exc()}") + logger.error(f"{traceback.format_exc()}") + return False + +def GetLocalTimeForDate(selected_date, time_zone_s, minutes_padding = 0): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + if minutes_padding > 0: + local_start = local_start - timedelta(minutes=minutes_padding) + local_next = local_next + timedelta(minutes=minutes_padding) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Format as strings + time_from_str = utc_start.strftime("%Y-%m-%d %H:%M:%S") + time_to_str = utc_next.strftime("%Y-%m-%d %H:%M:%S") + + return time_from_str + "+0000", time_to_str + "+0000" + +def GetLocalTimeForDateSimple(selected_date, time_zone_s, minutes_padding = 0): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + if minutes_padding > 0: + local_start = local_start - timedelta(minutes=minutes_padding) + local_next = local_next + timedelta(minutes=minutes_padding) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + return utc_start, utc_next + +def GetLocalTimeEpochsForDate(selected_date, time_zone_s): + """ + Get start and end of day epochs for a given date in a specific timezone. + + Args: + selected_date (str): Date in "YYYY-MM-DD" format + time_zone_s (str): Timezone string (e.g., "America/New_York") + + Returns: + tuple: (start_epoch, end_epoch) - Unix timestamps for start and end of day + """ + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(local_date) + + # Get the next day + local_next = local_start + timedelta(days=1) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Convert to epochs (Unix timestamps) + start_epoch = int(utc_start.timestamp()) + end_epoch = int(utc_next.timestamp()) + + return start_epoch, end_epoch + +def UTC2Local(utc_time, time_zone_s): + # Parse the selected date + local_tz = pytz.timezone(time_zone_s) + + # Convert selected_date string to datetime object (start of day in local time) + #local_date = datetime.datetime.strptime(selected_date, "%Y-%m-%d") + local_start = local_tz.localize(selected_date) + + # Convert to UTC + utc_start = local_start.astimezone(pytz.UTC) + utc_next = local_next.astimezone(pytz.UTC) + + # Format as strings + time_from_str = utc_start.strftime("%Y-%m-%d %H:%M:%S") + time_to_str = utc_next.strftime("%Y-%m-%d %H:%M:%S") + + return time_from_str + "+0000", time_to_str + "+0000" + +def get_timezone_aware_datetime(time_str, timezone_str="America/Los_Angeles"): + """ + Convert a naive datetime string to a timezone-aware datetime object. + + Parameters: + time_str: String in format 'YYYY-MM-DD HH:MM:SS' + timezone_str: String representing the timezone (default: "America/Los_Angeles") + + Returns: + datetime: A timezone-aware datetime object + """ + # Parse the naive datetime + naive_dt = datetime.datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S') + + # Get the timezone + tz = pytz.timezone(timezone_str) + + # Localize the datetime (make it timezone-aware) + # localize() is the correct way to do this, as it handles DST transitions properly + aware_dt = tz.localize(naive_dt) + return aware_dt + +def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6 + } + # Add sensor columns dynamically + cols_len = len(columns) + for i in range(10): + columns[f'sensor_min_s{i}'] = i + cols_len #smell * 10 + 5 + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_smell_array_from_timescale(day_data, time_from_str, device_to_index, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Define column mappings (sensor type to position in record) + columns = { + 's0': 2, + 's1': 3, + 's2': 4, + 's3': 5, + 's4': 6, + 's5': 7, + 's6': 8, + 's7': 9, + 's8': 10, + 's9': 11 + } + ## Add sensor columns dynamically + #cols_len = len(columns) + #for i in range(10): + #columns[f'sensor_min_s{i}'] = i + cols_len #smell * 10 + 5 + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_radar_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #start_time = start_time.replace(tzinfo=timezone.utc) + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) + columns = { + 's2': 2, + 's3': 3, + 's4': 4, + 's5': 5, + 's6': 6, + 's7': 7, + 's8': 8 + } + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + #print(times[0], start_time, (times[0] - start_time).total_seconds()) + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def ScaleTemperature(temperature_in_f): + # Define our key temperature points and their corresponding color values + temp_points = [30, 50, 70, 90, 110] + color_values = [768, 640, 384, 128, 0] # Color values in the rainbow scale + + # Clamp the temperature to our range + clamped_temp = max(min(temperature_in_f, 110), 30) + + # Find which segment the temperature falls into + for i in range(len(temp_points) - 1): + if temp_points[i] <= clamped_temp <= temp_points[i + 1]: + # Linear interpolation between the two nearest points + t = (clamped_temp - temp_points[i]) / (temp_points[i + 1] - temp_points[i]) + color_value = int(color_values[i] + t * (color_values[i + 1] - color_values[i])) + return color_value + + # Fallback (should never reach here due to clamping) + return 0 + +def GetTemperatureColor(temperature_in_f): + color_value = ScaleTemperature(temperature_in_f) + return BestColor(color_value) + +def BestColor(in_val): + #this function uses numbers from 0 to 1279 to convert to rainbow from Blue to Red(1024) to Violet 1279 + r,g,b=0,0,0 + in_val = int(in_val) + if(in_val > 1279): + in_val = 1279 + if (in_val < 256): + r = 255 + g = in_val + elif (in_val < 512): + r = 511 - in_val + g = 255 + elif (in_val < 768): + g = 255 + b = in_val-512 + elif (in_val < 1024): + g = 1023 - in_val + b = 255 + else: + r = in_val - 1024 + b = 255 + + #if (r > 255): + # print(in_val) + # print(int(r),int(g),int(b)) + return(int(r),int(g),int(b)) + +def GrayColor(in_val): + #this function uses numbers from 0 to 1279 to convert to rainbow from Blue to Red(1024) to Violet 1279 + r,g,b=0,0,0 + in_val = int(in_val) + if(in_val < 0): + in_val = 0 + if(in_val > 255): + in_val = 255 + + r = in_val + g = r + b = r + return(int(r),int(g),int(b)) + +def fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str): + """ + Fill numpy array with data from TimeScaleDB query results. + + Parameters: + day_data: List of tuples from database query + time_from_str: Starting datetime string in format 'YYYY-MM-DD HH:MM:SS' + devices_list: List of device IDs + arr_source: Pre-initialized numpy array to fill + + Returns: + numpy.ndarray: Filled array + """ + # Parse the start time + #start_time = get_timezone_aware_datetime(time_from_str, timezone_str) + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + # Create mapping of device_ids to their index positions + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define columns and their positions in the result tuple + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Process each record + for record in day_data: + # Get minute and device_id from record + record_time = record[0] # minute column + device_id = record[1] # device_id column + + if record_time and device_id: + # Calculate minute delta + minute_delta = int((record_time - start_time).total_seconds() / 60) + + if 0 <= minute_delta < arr_source.shape[1]: + # Calculate base index for this device + base_idx = device_to_index[device_id] * len(columns) + + # Fill data for each sensor/measurement type + for col_name, col_offset in columns.items(): + value = record[col_offset] + if value is not None: # Skip NULL values + row_idx = base_idx + list(columns.keys()).index(col_name) + arr_source[row_idx, minute_delta] = value + + return arr_source + +def fast_fill_array_from_timescale_bad(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) - KEEP EXACT SAME ORDER as original + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Pre-compute column keys list for consistent indexing + column_keys = list(columns.keys()) + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + # Use pre-computed column_keys list for consistent indexing + row_idx = base_idx + column_keys.index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_array_from_timescale(day_data, time_from_str, devices_list, arr_source, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Define column mappings (sensor type to position in record) - KEEP EXACT SAME ORDER as original + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + # Pre-compute column keys list for consistent indexing + column_keys = list(columns.keys()) + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + # Use pre-computed column_keys list for consistent indexing + row_idx = base_idx + column_keys.index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def fast_fill_array_from_timescale_single(day_data, time_from_str, devices_list, arr_source, sensor, timezone_str="Europe/Berlin"): + """ + Optimized version of array filling from TimeScaleDB data. + Uses vectorized operations for significant speed improvement. + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Pre-process data into a more efficient structure + # Group by device_id to reduce lookup operations + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + if sensor != None: + columns = { + 'avg_temperature': 2, + 'avg_humidity': 2, + 'pressure_amplitude': 2, + 'max_light': 2, + 'radar': 2, + 'sensor_min_s0': 2, + 'sensor_min_s1': 2, + 'sensor_min_s2': 2, + 'sensor_min_s3': 2, + 'sensor_min_s4': 2, + 'sensor_min_s5': 2, + 'sensor_min_s6': 2, + 'sensor_min_s7': 2, + 'sensor_min_s8': 2, + 'sensor_min_s9': 2 + } + else: + + columns = { + 'avg_temperature': 2, + 'avg_humidity': 3, + 'pressure_amplitude': 4, + 'max_light': 5, + 'radar': 6, + 'sensor_min_s0': 7, + 'sensor_min_s1': 8, + 'sensor_min_s2': 9, + 'sensor_min_s3': 10, + 'sensor_min_s4': 11, + 'sensor_min_s5': 12, + 'sensor_min_s6': 13, + 'sensor_min_s7': 14, + 'sensor_min_s8': 15, + 'sensor_min_s9': 16 + } + + column_keys = list(columns.keys()) + + # Process each device's data in bulk + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] #* len(columns) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1]) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each column type in bulk + # Use pre-computed column_keys list for consistent indexing + + + #row_idx = base_idx + 2#column_keys.index(col_name) + #values = records_array[:, column_keys.index(col_name)] + + ## Filter out None values + #valid_values = ~np.equal(values, None) + #if not np.any(valid_values): + #continue + + # Process each column type in bulk + for col_name, col_offset in columns.items(): + row_idx = base_idx + list(columns.keys()).index(col_name) + values = records_array[:, col_offset] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + +def CalcExtremes(arr_source, length, height): + """ + Calculate min and max values for each row within legal bounds. + Optimized version using numpy vectorized operations. + + Parameters: + arr_source: numpy array of shape (height, length+4) containing data and bounds + length: number of data points to process (typically 1440 for minutes in a day) + height: number of rows in the array + + Returns: + numpy array with min/max values stored in columns 1442 and 1443 + """ + # Extract the data portion and bounds + data = arr_source[:, :length] + ignore_below = arr_source[:, 1440:1441] # Keep 2D shape for broadcasting + ignore_above = arr_source[:, 1441:1442] # Keep 2D shape for broadcasting + + # Create masks for valid values + above_min_mask = data >= ignore_below + below_max_mask = data <= ignore_above + valid_mask = above_min_mask & below_max_mask + + # Create a masked array to handle invalid values + masked_data = np.ma.array(data, mask=~valid_mask) + + # Calculate min and max values for each row + row_mins = np.ma.min(masked_data, axis=1).filled(-0.001) + row_maxs = np.ma.max(masked_data, axis=1).filled(-0.001) + + # Store results + arr_source[:, 1442] = row_mins + arr_source[:, 1443] = row_maxs + + return arr_source + +def plot(arr, filename="histogram.png", title="Histogram Plot", figsize=(12, 6), + color='blue', style='histogram', bins=1000): + """ + Plot a 1D numpy array as a line or scatter plot + + Parameters: + arr : 1D numpy array + title : str, plot title + figsize : tuple, figure size in inches + color : str, line/point color + style : str, 'line' or 'scatter' + """ + title = filename + plt.figure(figsize=figsize) + x = np.arange(len(arr)) + + if style == 'line': + x = np.arange(len(arr)) + plt.plot(x, arr, color=color) + elif style == 'scatter': + x = np.arange(len(arr)) + plt.scatter(x, arr, color=color, alpha=0.6) + elif style == 'histogram': + plt.hist(arr.ravel(), bins=bins, range=(0, 100), color=color, alpha=0.8) + plt.yscale('log') # Using log scale for better visualization + plt.xlabel('Signal Value') + plt.ylabel('Frequency') + + plt.title(title) + plt.xlabel('Index') + plt.ylabel('Value') + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig(filename) + plt.close() + print(f"Plot saved to: {filename}") + #plt.show() + +def ShowArray(arr, threshold, filename="histogram.png", title="Histogram Plot", figsize=(12, 6), + color='blue', style='histogram', bins=1000): + """ + Plot a 1D numpy array as a line or scatter plot + + Parameters: + arr : 1D numpy array + title : str, plot title + figsize : tuple, figure size in inches + color : str, line/point color + style : str, 'line' or 'scatter' + """ + title = filename + plt.figure(figsize=figsize) + x = np.arange(len(arr)) + + if style == 'line': + x = np.arange(len(arr)) + plt.plot(x, arr, color=color) + plt.axhline(y=threshold, color='red', linestyle='--', + label=f'Threshold: {threshold:.3f}') + plt.xlabel('Index') + plt.ylabel('Value') + elif style == 'scatter': + x = np.arange(len(arr)) + plt.scatter(x, arr, color=color, alpha=0.6) + elif style == 'histogram': + plt.hist(arr.ravel(), bins=bins, range=(0, 100), color=color, alpha=0.8) + plt.yscale('log') # Using log scale for better visualization + plt.xlabel('Signal Value') + plt.ylabel('Frequency') + + plt.title(title) + plt.xlabel('Index') + plt.ylabel('Value') + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig(filename) + plt.close() + print(f"Plot saved to: {filename}") + #plt.show() + +def AddLimits_optimized(arr_source, devices_c, sensors_c, percentile): + """ + Vectorized version of AddLimits that processes all sensors at once. + + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + + # Create arrays of sensor indices for all rows + sensor_indices = np.arange(total_sensors) % sensors_c + + # Convert sensor_legal_values into arrays for vectorized access + sensor_types = np.array([s_table[i] for i in range(sensors_c)]) + min_vals = np.array([sensor_legal_values[t][0] for t in sensor_types]) + max_vals = np.array([sensor_legal_values[t][1] for t in sensor_types]) + windows = np.array([sensor_legal_values[t][2] for t in sensor_types]) + + # Get values for each row based on sensor type + row_windows = windows[sensor_indices] + row_mins = min_vals[sensor_indices] + row_maxs = max_vals[sensor_indices] + + # Process rows that need cleaning (window > 2) + clean_mask = row_windows > 2 + if np.any(clean_mask): + # Clean each row with its corresponding window size + for window in np.unique(row_windows[clean_mask]): + # Get indices of rows that need this window size + rows_to_clean = np.where(clean_mask & (row_windows == window))[0] + + # Clean each row individually (since clean_data_vectorized expects 1D input) + for row_idx in rows_to_clean: + arr_source[row_idx, :1440] = clean_data_vectorized( + arr_source[row_idx, :1440], + window, + percentile + ) + + # Set min/max values for all rows at once + arr_source[:, 1440] = row_mins + arr_source[:, 1441] = row_maxs + + return arr_source + +def AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile): + """ + Vectorized version of AddLimits that processes all sensors at once. + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + # Create arrays of sensor indices for all rows + sensor_indices = np.arange(total_sensors) % sensors_c + # Convert sensor_legal_values into arrays for vectorized access + sensor_types = np.array([smells_table[i] for i in range(sensors_c)]) + min_vals = np.array([smell_legal_values[t][0] for t in sensor_types]) + max_vals = np.array([smell_legal_values[t][1] for t in sensor_types]) + # Get values for each row based on sensor type + row_mins = min_vals[sensor_indices] + row_maxs = max_vals[sensor_indices] + + # Replace values smaller than smell_min and larger than smell_max with no_smell + # Create a mask for the data points (first 1440 columns) + data_mask_below = arr_source[:, :1440] < smell_min + data_mask_above = arr_source[:, :1440] > smell_max + data_mask_invalid = data_mask_below | data_mask_above + + # Replace invalid values with no_smell + arr_source[:, :1440][data_mask_invalid] = no_smell + + # Set min/max values for all rows at once + arr_source[:, 1440] = row_mins + arr_source[:, 1441] = row_maxs + return arr_source + +def AddLimits(arr_source, devices_c, sensors_c, percentile): + for y in range(devices_c*sensors_c): + sensor_index = y % sensors_c + min_ok, max_ok, window = sensor_legal_values[s_table[sensor_index]] + #if EnablePlot: + #if (y == 33): + #print("stop") + #plot(arr_source[y, :1440], "before_clean_sensor.png") + if window > 2: + arr_source[y, :1440] = clean_data_vectorized(arr_source[y, :1440], window, percentile) + + #if EnablePlot: + #if (y == 33): + #print("stop") + #plot(arr_source[y, :1440], "after_clean_sensor.png") + + arr_source[y][1440] = min_ok + arr_source[y][1441] = max_ok + return arr_source + +def clean_data_vectorized(data, window, percentile): + """ + Vectorized version of clean_data function using pure numpy + First removes zeros, then cleans outliers + + Parameters: + data: numpy array of sensor readings + window: int, size of rolling window + percentile: float, percentile threshold for deviation filtering + """ + # Create a copy to avoid modifying original data + working_data = data.copy() + + # Replace zeros with NaN + zero_mask = working_data == 0 + working_data[zero_mask] = np.nan + + # Create rolling window view of the data + def rolling_window(a, window): + shape = a.shape[:-1] + (a.shape[-1] - window + 1, window) + strides = a.strides + (a.strides[-1],) + return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides) + + # Pad array for edge handling + pad_width = window // 2 + padded = np.pad(working_data, pad_width, mode='edge') + + # Create rolling windows + windows = rolling_window(padded, window) + + # Calculate rolling median (ignoring NaN values) + medians = np.nanmedian(windows, axis=1) + + # Forward/backward fill any NaN in medians + # Forward fill + mask = np.isnan(medians) + idx = np.where(~mask, np.arange(mask.shape[0]), 0) + np.maximum.accumulate(idx, out=idx) + medians[mask] = medians[idx[mask]] + + # Backward fill any remaining NaNs + mask = np.isnan(medians) + idx = np.where(~mask, np.arange(mask.shape[0]), mask.shape[0] - 1) + idx = np.minimum.accumulate(idx[::-1])[::-1] + medians[mask] = medians[idx[mask]] + + # Calculate deviations (ignoring NaN values) + deviations = np.abs(working_data - medians) + + # Calculate threshold (ignoring NaN values) + threshold = np.nanpercentile(deviations, percentile) + + # Create mask and replace outliers with median values + # Points are good if they're not NaN and deviation is within threshold + good_points = (~np.isnan(working_data)) & (deviations <= threshold) + + # Replace all bad points (including zeros and outliers) with median values + result = np.where(good_points, working_data, medians) + + return result + + +def process_chunk(args): + """ + Process a chunk of rows + """ + chunk, sensors_c, sensor_legal_values, s_table, window, percentile = args + result = np.copy(chunk) + + # Process all time series in the chunk at once + result[:, :1440] = np.array([ + clean_data_vectorized(row[:1440], window, percentile) + for row in chunk + ]) + + # Set limits for all rows in chunk using vectorized operations + sensor_indices = np.arange(len(chunk)) % sensors_c + min_values = np.array([sensor_legal_values[s_table[i]][0] for i in sensor_indices]) + max_values = np.array([sensor_legal_values[s_table[i]][1] for i in sensor_indices]) + + result[:, 1440] = min_values + result[:, 1441] = max_values + + return result + + +def FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw): + """ + Optimized version of FillImage function that fills the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + sensors_c: number of sensors per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + group_by: grouping strategy ("sensortype" or other) + bw: boolean flag for black and white output + + Returns: + arr_stretched: Filled array with RGB values + and vocs_scaled aray from 0 to 1280 + """ + stripes = devices_c * sensors_c + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Pre-calculate VOC rows mask + if group_by != "sensortype": + voc_rows = np.arange(stripes) >= 5 * devices_c + else: + voc_rows = (np.arange(stripes) % sensors_c) >= 5 + + # Pre-calculate destination row mapping for sensortype grouping + if group_by == "sensortype": + row_indices = np.arange(stripes) + sensor_indices = row_indices % sensors_c + device_indices = row_indices // sensors_c + dest_rows = sensor_indices * devices_c + device_indices + dest_rows = dest_rows[:, np.newaxis] * stretch_by + np.arange(stretch_by) + else: + row_indices = np.arange(stripes)[:, np.newaxis] * stretch_by + np.arange(stretch_by) + + # Optimize color calculation functions + def best_color_vectorized(vals): + """Vectorized version of BestColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 1279).astype(np.int32) + r = np.zeros_like(vals, dtype=np.int32) + g = np.zeros_like(vals, dtype=np.int32) + b = np.zeros_like(vals, dtype=np.int32) + + # Region 0-255 + mask1 = vals < 256 + r[mask1] = 255 + g[mask1] = vals[mask1] + + # Region 256-511 + mask2 = (vals >= 256) & (vals < 512) + r[mask2] = 511 - vals[mask2] + g[mask2] = 255 + + # Region 512-767 + mask3 = (vals >= 512) & (vals < 768) + g[mask3] = 255 + b[mask3] = vals[mask3] - 512 + + # Region 768-1023 + mask4 = (vals >= 768) & (vals < 1024) + g[mask4] = 1023 - vals[mask4] + b[mask4] = 255 + + # Region 1024-1279 + mask5 = vals >= 1024 + r[mask5] = vals[mask5] - 1024 + b[mask5] = 255 + + return r, g, b + + def gray_color_vectorized(vals): + """Vectorized version of GrayColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 255).astype(np.int32) + return vals, vals, vals + + color_func = gray_color_vectorized if bw else best_color_vectorized + + # Process all rows at once + valid_mask = scaled_day[:, :minutes] != -0.001 + big_min = scaled_day[:, 1442:1443] # Keep 2D shape for broadcasting + big_max = scaled_day[:, 1443:1444] + + # Calculate k factors where max > min + valid_range_mask = big_max > big_min + k = np.zeros_like(big_min) + k[valid_range_mask] = (1280 if not bw else 255) / (big_max[valid_range_mask] - big_min[valid_range_mask]) + + # Calculate normalized values for all rows at once + normalized_vals = np.zeros_like(scaled_day[:, :minutes]) + valid_range_indices = np.where(valid_range_mask)[0] + + normalized_vals[valid_range_indices] = ( + k[valid_range_indices] * + (scaled_day[valid_range_indices, :minutes] - big_min[valid_range_indices]) + ) + + # Invert VOC rows + normalized_vals[voc_rows] = (1279 if not bw else 255) - normalized_vals[voc_rows] + + # Apply valid mask + normalized_vals[~valid_mask] = 0 + + # Convert to RGB + r, g, b = color_func(normalized_vals) + + # Create RGB array + rgb_values = np.stack([r, g, b], axis=-1) + + # Handle special case where max == min + equal_range_mask = ~valid_range_mask + if np.any(equal_range_mask): + rgb_values[equal_range_mask.ravel()] = 128 + + # Fill the stretched array efficiently + if group_by == "sensortype": + arr_stretched[dest_rows] = rgb_values[:, None] + else: + arr_stretched[row_indices] = rgb_values[:, None] + + return arr_stretched, normalized_vals[voc_rows] + + +def FillSmellImage_optimized(scaled_day, arr_stretched, y_offset): + """ + Fill the stretched array with colored sensor data from scaled_day. + + Parameters: + scaled_day: 2D array of shape (70, 1444) containing sensor readings + arr_stretched: 3D array of shape (2685, 1640, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stretch_by = 20 + x_offset = 200 + + def best_color_vectorizedS(vals): + """Vectorized version of BestColor that matches the original implementation exactly""" + vals = np.clip(vals, 0, 1279).astype(np.int32) + r = np.zeros_like(vals, dtype=np.int32) + g = np.zeros_like(vals, dtype=np.int32) + b = np.zeros_like(vals, dtype=np.int32) + + # Region 0-255 + mask1 = vals < 256 + r[mask1] = 255 + g[mask1] = vals[mask1] + + # Region 256-511 + mask2 = (vals >= 256) & (vals < 512) + r[mask2] = 511 - vals[mask2] + g[mask2] = 255 + + # Region 512-767 + mask3 = (vals >= 512) & (vals < 768) + g[mask3] = 255 + b[mask3] = vals[mask3] - 512 + + # Region 768-1023 + mask4 = (vals >= 768) & (vals < 1024) + g[mask4] = 1023 - vals[mask4] + b[mask4] = 255 + + # Region 1024-1279 + mask5 = vals >= 1024 + r[mask5] = vals[mask5] - 1024 + b[mask5] = 255 + + return r, g, b + + # Process each row in scaled_day + for row_idx in range(scaled_day.shape[0]): + # Extract min and max for this row + row_min = scaled_day[row_idx, 1442] + row_max = scaled_day[row_idx, 1443] + + # Get data for this row (first 1440 elements) + row_data = scaled_day[row_idx, :1440] + + # Check if min and max are the same + if row_min == row_max: + # Create gray stripe + stripe = np.ones((stretch_by, 1440, 3), dtype=np.int32) * 128 + else: + # Normalize the data between 0 and 1279 + k = 1280 / (row_max - row_min) + normalized_vals = k * (row_data - row_min) + normalized_vals = np.clip(normalized_vals, 0, 1279) + + # Convert to RGB + r, g, b = best_color_vectorizedS(normalized_vals) + + # Create RGB stripe + stripe = np.zeros((stretch_by, 1440, 3), dtype=np.int32) + + # Fill stripe with the same color pattern for all stretch_by rows + for i in range(stretch_by): + stripe[i, :, 0] = r + stripe[i, :, 1] = g + stripe[i, :, 2] = b + + # Calculate the y position for this stripe + y_pos = y_offset + row_idx * stretch_by + + # Place the stripe into arr_stretched + print(stretch_by, stripe.shape, arr_stretched.shape, y_pos) + arr_stretched[y_pos:y_pos+stretch_by, x_offset:x_offset+1440, :] = stripe + + return arr_stretched + +def FillImage(scaled_day, devices_c, sensors_c, arr_stretched, group_by, bw): + """ + Fill the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + sensors_c: number of sensors per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stripes = devices_c * sensors_c + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Create a boolean mask for VOC sensors + if group_by != "sensortype": + voc_rows = np.array([i for i in range(stripes) if int(i/devices_c) >= 5]) + else: + voc_rows = np.array([i for i in range(stripes) if int(i % sensors_c) >= 5]) + # Vectorize the BestColor function + if not bw: + vectorized_best_color = np.vectorize(BestColor) + else: + vectorized_best_color = np.vectorize(GrayColor) + + # Process each row + for row in range(stripes): + + row_data = scaled_day[row, :minutes] # Get minute data + + #if row == 33: + # print("stop") + # plot(row_data, "row_data.png") + big_min = scaled_day[row, 1442] # min value + big_max = scaled_day[row, 1443] # max value + + # Create mask for valid values + valid_mask = row_data != -0.001 + + # Initialize RGB row with zeros + rgb_row = np.zeros((minutes, 3), dtype=np.uint8) + + if big_max > big_min: + # Scale factor + if not bw: + k = 1280/(big_max-big_min) + else: + k = 255/(big_max-big_min) + # Calculate normalized values + normalized_vals = k * (row_data - big_min) + + # Invert if it's a VOC row + if row in voc_rows: + if not bw: + normalized_vals = 1279 - normalized_vals + else: + normalized_vals = 255 - normalized_vals + + # Apply valid mask + normalized_vals = np.where(valid_mask, normalized_vals, 0) + #if row == 33: + # plot(normalized_vals, "normalized_vals.png") + + # Convert to RGB colors (vectorized) + r, g, b = vectorized_best_color(normalized_vals) + + # Combine into RGB array + rgb_row[valid_mask] = np.stack([r[valid_mask], + g[valid_mask], + b[valid_mask]], axis=1) + else: + # Set to gray where valid + rgb_row[valid_mask] = 128 + + + if group_by == "sensortype": + + # Fill the stretched rows + sensor_index = row % sensors_c + device_index = int(row/sensors_c) + dest_row = sensor_index * devices_c + device_index #0-0, 1- + start_idx = dest_row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + else: + # Fill the stretched rows + start_idx = row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + + return arr_stretched + +def FillRadarImage(scaled_day, devices_c, bands, arr_stretched, group_by, map_type): + """ + Fill the stretched array with colored sensor data. + + Parameters: + scaled_day: 2D array of shape (stripes, minutes+4) containing sensor readings + devices_c: number of devices + bands: number of bands per device + arr_stretched: 3D array of shape (stripes*stretch_by, minutes, 3) to fill with RGB values + + Returns: + arr_stretched: Filled array with RGB values + """ + stripes = devices_c * bands + stretch_by = arr_stretched.shape[0] // stripes + minutes = arr_stretched.shape[1] + + # Create a boolean mask for VOC sensors + if group_by != "sensortype": + voc_rows = np.array([i for i in range(stripes) if int(i/devices_c) >= 5]) + else: + voc_rows = np.array([i for i in range(stripes) if int(i % bands) >= 5]) + # Vectorize the BestColor function + if map_type == 3: + vectorized_best_color = np.vectorize(BestColor) + else: + vectorized_best_color = np.vectorize(GrayColor) + + # Process each row + for row in range(stripes): + + row_data = scaled_day[row, :minutes] # Get minute data + + #if row == 33: + # print("stop") + # plot(row_data, "row_data.png") + big_min = 0 #scaled_day[row, 1442] # min value + big_max = 255 #scaled_day[row, 1443] # max value + + # Create mask for valid values + valid_mask = row_data != -0.001 + + # Initialize RGB row with zeros + rgb_row = np.zeros((minutes, 3), dtype=np.uint8) + + if big_max > big_min: + # Scale factor + if map_type == 3: + k = 1280/(big_max-big_min) + else: + k = 255/(big_max-big_min) + # Calculate normalized values + normalized_vals = k * (row_data - big_min) + + # Invert if it's a VOC row + if row in voc_rows: + if map_type == 3: + normalized_vals = 1279 - normalized_vals + else: + normalized_vals = 255 - normalized_vals + + # Apply valid mask + normalized_vals = np.where(valid_mask, normalized_vals, 0) + #if row == 33: + # plot(normalized_vals, "normalized_vals.png") + + # Convert to RGB colors (vectorized) + r, g, b = vectorized_best_color(normalized_vals) + + # Combine into RGB array + rgb_row[valid_mask] = np.stack([r[valid_mask], + g[valid_mask], + b[valid_mask]], axis=1) + else: + # Set to gray where valid + rgb_row[valid_mask] = 128 + + + if group_by == "sensortype": + + # Fill the stretched rows + band_index = row % bands + device_index = int(row/bands) + dest_row = band_index * devices_c + device_index #0-0, 1- + start_idx = dest_row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + else: + # Fill the stretched rows + start_idx = row * stretch_by + end_idx = start_idx + stretch_by + arr_stretched[start_idx:end_idx] = rgb_row + + return arr_stretched + +def GetFullLocMapDetails(map_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + local_timezone = pytz.timezone('America/Los_Angeles') # Replace with your local timezone + dest_path = os.path.dirname(map_file) + + parts = map_file.split("/") + deployment = parts[-2] + parts1 = parts[-1].split("_") + date_string = parts1[1] + + deployments = GetDeploymentB(deployment, -1) #All + last_locations_file = "" + last_per_minute_file = "" + + today = datetime.today() + deployment_details = deployments[0] + deployment_pair = deployment_details[0] + proximity_lst = deployment_details[1] + + date_object = datetime.strptime(date_string, "%Y-%m-%d") + date_object_midnight = local_timezone.localize(date_object.replace(hour=0, minute=0, second=0, microsecond=0)) + selected_epoch = int(date_object_midnight.timestamp()) + + sel_date = datetime.fromtimestamp(selected_epoch) + devices_list_str = GetDevicesList(deployment_details, sel_date)#.split(',') + devices_list = ast.literal_eval(devices_list_str) + return devices_list, selected_epoch, dest_path + +def median_filter(data, window_size): + filtered_data = [] + print(len(data)) + window = deque(maxlen=window_size) + last_value = -1 + offset = 0 + added_old = 0 + for value in data: + if value != '': + added_old = 0 + last_value = value + window.append(value) + if len(window) == window_size: + # Sort the window and get the median value + sorted_window = sorted(window) + median = sorted_window[window_size // 2] + filtered_data.append(median) + else: + if last_value != -1: + if added_old < window_size: + added_old = added_old + 1 + window.append(last_value) + else: + window.append(-1) + + if len(window) == window_size: + # Sort the window and get the median value + sorted_window = sorted(window) + median = sorted_window[window_size // 2] + filtered_data.append(median) + + else: + offset +=1 + + if len(filtered_data) > 0: + offset += (window_size // 2) + #if starts empty, just leav it such, do not fake backwards from midnight + first_val = -1# filtered_data[0] + last_val = filtered_data[-1] + front_padding = [first_val] * offset + remaining = len(data) - len(filtered_data) - len(front_padding) + back_padding = [last_val] * remaining + out_data = front_padding + filtered_data + back_padding + else: + out_data = data + #add front and back padding + + return out_data + +def FilterGlitches(wave_in, filter_minutes): + + if(filter_minutes > 0): + notfiltered_wave = [i[0] for i in wave_in] + filtered_wave = median_filter(notfiltered_wave, filter_minutes) + for i, value in enumerate(filtered_wave): + wave_in[i][0] = value + + return wave_in + +def setup_timezone_converter(time_zone_st): + """ + Setup timezone converter to be reused + + Parameters: + time_zone_st (str): Timezone string (e.g. 'Europe/Berlin') + + Returns: + pytz.timezone: Timezone object for conversion + """ + return pytz.timezone(time_zone_st) + +def ReadDailyRadar(MAC, current_date): + + #This will return all 1 Minute radar data for each gate in the file + #Will return list (2 items) of lists: Maxes, Mins + + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + start_of_day = ToLocal(calendar.timegm(datetime(current_date.year, current_date.month,current_date.day, 0, 0).timetuple())) + end_of_day = start_of_day + 1440 * 60 + file = os.path.join(scriptDir, "DB/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+".db") + file = file.replace("\\","/") + file1 = os.path.join(scriptDir, "DB/processed_db/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+".db") + file1 = file1.replace("\\","/") + if (not path.exists(file) and not path.exists(file1)): + print(file + " and " + file1 + " are not found") + return [] + result = [] + min_OK = "0" + sqlr = "SELECT * FROM radars WHERE time >= "+str(start_of_day) +" and time < "+str(end_of_day) +" ORDER BY time ASC" + #sqlr = "SELECT Date, high, low from "+sensor.lower()+"s1Min"+" WHERE low >= "+min_OK+" and Date >= "+str(start_of_day) +" and Date < "+str(end_of_day) + print(sqlr) + if os.path.exists(file): + result = QuerrySql(file, sqlr) + elif os.path.exists(file1): + result = QuerrySql(file1, sqlr) + + # M0 ............M8 S2 ........S8 + #day_minutes_data = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]] * (24 * 60 + 2) + day_minutes_data = [[0] * 16 for _ in range(24 * 60)] + #for each gate lets find maximum value per minute + for mgate in range(9): + max_per_min = 0 + for minute_data in result: + seconde = minute_data[0] + date_time_minute = datetime.fromtimestamp(seconde) + minute_m = 60*date_time_minute.hour+date_time_minute.minute + if minute_data[mgate + 6] > day_minutes_data[minute_m][mgate]: + day_minutes_data[minute_m][mgate] = minute_data[mgate + 6] + + for sgate in range(7): + for minute_data in result: + seconde = minute_data[0] + date_time_minute = datetime.fromtimestamp(seconde) + minute_m = 60*date_time_minute.hour+date_time_minute.minute + if minute_data[sgate + 17] > day_minutes_data[minute_m][sgate+9]: + day_minutes_data[minute_m][sgate+9] = minute_data[sgate + 17] + + return day_minutes_data + + +def FromLocalMidnight(epoch_time, local_delta): + + # Convert epoch time to UTC datetime object + print(type(epoch_time)) + print(epoch_time) + local_datetime = datetime.datetime.utcfromtimestamp(epoch_time+local_delta).replace(tzinfo=pytz.UTC) + + # Calculate minute count from midnight + minutes_from_midnight = (local_datetime - local_datetime.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60 + return minutes_from_midnight + +def process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest): + """ + NumPy-based version of wave processing + + Parameters: + my_data: List of tuples containing (time_val, device_id, other radar_fields_of_interest) + time_zone_s: Target timezone string + device_id_2_threshold: Dictionary mapping device_ids to their thresholds + + Returns: + List of [device_id, max_val] pairs for each minute + """ + wave_m = None + tz = pytz.timezone(time_zone_s) + if not my_data: + return [["", -1] for _ in range(1440)] + + vectorized_BestColor = np.vectorize(BestColor) + stripes = len(device_id_2_threshold) + stretch_by = 5 + minutes = 1440 + arr_source = np.zeros((int(stripes), minutes), dtype=np.float32) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + index_map = {word: idx for idx, word in enumerate(radar_fields_of_interest)} + devices_map = {word: idx for idx, word in enumerate(device_id_2_threshold)} + times = [] + start_time = 0 + for data_set in my_data: + time_stamp = data_set[0] + if start_time == 0: + + # Convert timestamp to a datetime object in UTC + local_tz = pytz.timezone(time_zone_s) + local_time = time_stamp.astimezone(local_tz) + # Set the time to the start of the day in the local time zone + start_of_day_local = local_time.replace(hour=0, minute=0, second=0, microsecond=0) + # Convert the start of the day back to UTC + start_time = start_of_day_local.astimezone(pytz.utc) + + diff = time_stamp - start_time + minute = int(diff.total_seconds() / 60) + device_id = data_set[1] + field_name = device_id_2_threshold[device_id][0] + field_index = index_map[field_name] + threshold = device_id_2_threshold[device_id][1] + value = data_set[2+field_index] + if value > threshold: + arr_source[devices_map[device_id]][minute] = value + #np.savetxt('output.csv', arr_source, delimiter=',') + if False: + for yy in range(stripes): + rgb_row = vectorized_BestColor(1280*arr_source[yy]/100) + rgb_reshaped = np.array(rgb_row).reshape(3, minutes).T + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_reshaped + + SaveImageInBlob(image_file, arr_stretched, []) + + + max_values = np.max(arr_source, axis=0) + + # Get indices (0-based) + wave_m = np.argmax(arr_source, axis=0) + + # Add 1 to convert to 1-based indexing + wave_m = wave_m + 1 + + # Set to 0 where the column was all zeros + wave_m[max_values == 0] = 0 + + return wave_m + + + +def ReadDailyCollapsedFastRadar(MAC, time_from_str, time_to_str): + + #This will return all 1 Minute radar data for each gate in the file + #Will return list (2 items) of lists: Maxes, Mins based on s28 (stationary[2] to [8]) + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + result = [] + min_OK = "0" + sqlr = "SELECT radar_max FROM devices WHERE MAC = '"+MAC +"'" + print(sqlr) + DB_to_be_found_in_full = os.path.join(scriptDir, "main.db") + DB_to_be_found_in_full = DB_to_be_found_in_full.replace("\\","/") + result = QuerrySql(DB_to_be_found_in_full, sqlr) + sqlr = "SELECT date, low FROM radars1Min WHERE date >= "+str(start_of_day) +" and date < "+str(end_of_day) + " ORDER BY date" + if len(result)>0: + if result[0][0] == 1: + sqlr = "SELECT date, high FROM radars1Min WHERE date >= "+str(start_of_day) +" and date < "+str(end_of_day) + " ORDER BY date" + + print(sqlr) + if os.path.exists(file): + result = QuerrySql(file, sqlr) + elif os.path.exists(file1): + result = QuerrySql(file1, sqlr) + + return result + +def vectorized_best_color_numpy(values): + """Vectorized version of BestColor using pure NumPy""" + # Ensure values are within range + values = np.clip(values, 0, 1279) + + # Initialize output arrays + r = np.zeros_like(values, dtype=np.uint8) + g = np.zeros_like(values, dtype=np.uint8) + b = np.zeros_like(values, dtype=np.uint8) + + # Create masks for each range + mask_0_255 = values < 256 + mask_256_511 = (values >= 256) & (values < 512) + mask_512_767 = (values >= 512) & (values < 768) + mask_768_1023 = (values >= 768) & (values < 1024) + mask_1024_plus = values >= 1024 + + # Set values for each range using masks + r[mask_0_255] = 255 + g[mask_0_255] = values[mask_0_255] + + r[mask_256_511] = 511 - values[mask_256_511] + g[mask_256_511] = 255 + + g[mask_512_767] = 255 + b[mask_512_767] = values[mask_512_767] - 512 + + g[mask_768_1023] = 1023 - values[mask_768_1023] + b[mask_768_1023] = 255 + + r[mask_1024_plus] = values[mask_1024_plus] - 1024 + b[mask_1024_plus] = 255 + + return np.stack([r, g, b], axis=-1) + +def create_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val, max_val): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] - min_val / (max_val - min_val)) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Calculate color values + color_values = np.zeros_like(values[batch_slice]) + + color_values[:, :] = ((values[batch_slice, :] - min_val) / (max_val - min_val)) * 1279.0 + #color_values[:, :] = (values[batch_slice, :] / 100.0) * 1279.0 # other fields + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Convert to RGB colors + for i in range(end_idx - start_idx): + rgb_values = vectorized_best_color_numpy(color_values[i]) + wave_m[y_coords[i], x_coords[batch_slice][i]] = rgb_values + + return wave_m + +def create_light_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=4095): + """ + Create an optimized heatmap for light data (range 0-4095) + + Parameters: + my_data (list): Data from the database query + bw (bool): Whether to create a black and white (True) or color (False) heatmap + fields (list): List of field names + wave_m (numpy.ndarray): The image array to fill + device_to_index (dict): Mapping from device_id to index + base_minute (datetime): The base minute for time calculations + timezone_st (str): Timezone string + min_val (float): Minimum value for normalization (default: 0) + max_val (float): Maximum value for normalization (default: 4095) + + Returns: + numpy.ndarray: The filled image array + """ + if len(my_data) < 1: + return wave_m + + import numpy as np + import pytz + + # Get the local timezone + local_tz = pytz.timezone(timezone_st) + + # Number of fields (should be 1 for light data) + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float - light data is in column 2 + # Reshape to match expected format (n_samples, n_fields) + values = data_array[:, 2].astype(np.float32).reshape(-1, 1) + + # Process in batches to avoid memory issues + batch_size = 1000 + + if bw: + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Normalize light values (0-4095) to grayscale (0-255) + gray_values = ((values[batch_slice] - min_val) / (max_val - min_val) * 255.0) + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + + # Assign values to the image array + for i in range(end_idx - start_idx): + # Create RGB grayscale (same value for R, G, B) + gray_rgb = np.full(3, gray_values[i, 0], dtype=np.uint8) + wave_m[y_coords[i, 0], x_coords[batch_slice][i]] = gray_rgb + else: + # Color mode + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Normalize light values (0-4095) to color range (0-1279) + color_values = ((values[batch_slice] - min_val) / (max_val - min_val) * 1279.0) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + + # For each value, calculate its RGB color and assign to the image + for i in range(end_idx - start_idx): + # Convert normalized value to RGB using vectorized_best_color_numpy + rgb_value = vectorized_best_color_numpy(np.array([color_values[i, 0]]))[0] + wave_m[y_coords[i, 0], x_coords[batch_slice][i]] = rgb_value + + return wave_m + +def create_temperature_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=4095): + """ + Create an optimized heatmap for temperature data with alarm levels + + Parameters: + my_data (list): Data from the database query with columns for minute, device_id, temperature_avg, alarm_level + bw (bool): Whether to create a black and white (True) or color (False) heatmap + fields (list): List of field names - should be ['temperature', 'temperature_state'] + wave_m (numpy.ndarray): The image array to fill + device_to_index (dict): Mapping from device_id to index + base_minute (datetime): The base minute for time calculations + timezone_st (str): Timezone string + min_val (float): Minimum value for temperature normalization + max_val (float): Maximum value for temperature normalization + + Returns: + numpy.ndarray: The filled image array + """ + if len(my_data) < 1: + return wave_m + + import numpy as np + import pytz + + # Get the local timezone + local_tz = pytz.timezone(timezone_st) + + # Number of fields (should be 2 for temperature data: temperature and alarm state) + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Process in batches to avoid memory issues + batch_size = 1000 + + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + for i in range(end_idx - start_idx): + # Get data for this record + temperature = data_array[batch_slice][i, 2] + if temperature == None: + temperature = min_val + alarm_level = 0 + + # If we have an alarm_level column (index 3), use it + if data_array.shape[1] > 3: + alarm_level = data_array[batch_slice][i, 3] + + # Calculate base y-coordinate for this device + base_y = device_indices[batch_slice][i] * n_fields + + # Temperature row (even row - index 0, 2, 4...) + # Normalize temperature to the color range and create color + if not bw: + # For color mode + normalized_temp = np.clip((temperature - min_val) / (max_val - min_val) * 1279.0, 0, 1279) + temp_rgb = vectorized_best_color_numpy(np.array([normalized_temp]))[0] + else: + # For B&W mode + normalized_temp = np.clip((temperature - min_val) / (max_val - min_val) * 255.0, 0, 255) + gray_value = int(normalized_temp) + temp_rgb = np.array([gray_value, gray_value, gray_value], dtype=np.uint8) + + # Set the temperature color in the even row + wave_m[base_y, x_coords[batch_slice][i]] = temp_rgb + + # Alarm level row (odd row - index 1, 3, 5...) + # Set color based on alarm level (0=green, 1=yellow, 2=red) + if alarm_level == 0: + # Green for normal + alarm_rgb = np.array([0, 255, 0], dtype=np.uint8) + elif alarm_level == 1: + # Yellow for warning + alarm_rgb = np.array([0, 255, 255], dtype=np.uint8) + else: # alarm_level == 2 + # Red for critical + alarm_rgb = np.array([0, 0, 255], dtype=np.uint8) + + # Set the alarm color in the odd row + wave_m[base_y + 1, x_coords[batch_slice][i]] = alarm_rgb + + return wave_m + + +def create_humidity_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val=0, max_val=100): + """ + Create a heatmap with the exact blue-cyan-green-yellow-red-violet spectrum + matching Image 2, with green at position 40 + """ + if len(my_data) < 1: + return wave_m + + # Number of fields + n_fields = len(fields) + + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + x_coords = np.array([(minute - base_minute).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Process in batches to avoid memory issues + batch_size = 1000 + + # Define the color mapping function based on the exact spectrum we want + def get_color(t): + """Get RGB color from humidity 0-100""" + # Define color stops - exact RGB values at each step + # Format: (position, (r, g, b)) + #color_stops = [ + #(0, (0, 0, 255)), # Blue + #(20, (0, 255, 255)), # Cyan + #(40, (0, 255, 0)), # Green (centered at 40) + #(60, (255, 255, 0)), # Yellow + #(80, (255, 0, 0)), # Red + #(100, (255, 0, 255)) # Violet + #] + + color_stops = [ + (0, (0, 0, 255)), # Blue + (16, (0, 255, 255)), # Cyan + (32, (0, 255, 0)), # Green (now centered at 32) + (60, (255, 255, 0)), # Yellow + (80, (255, 0, 0)), # Red + (100, (255, 0, 255)) # Violet + ] + + # Ensure t is within range + t = max(0, min(100, t)) + + # Find the two stops to interpolate between + for i in range(len(color_stops) - 1): + pos1, color1 = color_stops[i] + pos2, color2 = color_stops[i+1] + + if pos1 <= t <= pos2: + # Linear interpolation between the two color stops + ratio = (t - pos1) / (pos2 - pos1) + r = int(color1[0] + ratio * (color2[0] - color1[0])) + g = int(color1[1] + ratio * (color2[1] - color1[1])) + b = int(color1[2] + ratio * (color2[2] - color1[2])) + return r, g, b + + # Should never reach here + return 0, 0, 0 + + humidity = min_val + + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + for i in range(end_idx - start_idx): + # Get data for this record + if data_array[batch_slice][i, 2] != None: + humidity = float(data_array[batch_slice][i, 2]) + + # Map humidity from min_val-max_val to 0-100 for our color function + normalized_temp = 100.0 * (humidity - min_val) / (max_val - min_val) if max_val > min_val else 0 + normalized_temp = max(0, min(100, normalized_temp)) # Clamp to 0-100 + + alarm_level = 0 + # If we have an alarm_level column (index 3), use it + if data_array.shape[1] > 3: + alarm_level = data_array[batch_slice][i, 3] + + # Calculate base y-coordinate for this device + base_y = device_indices[batch_slice][i] * n_fields + + # Temperature row (even row) + if not bw: + # Get RGB color from our direct mapping function + r, g, b = get_color(normalized_temp) + + # OpenCV uses BGR ordering, not RGB + temp_rgb = np.array([b, g, r], dtype=np.uint8) + else: + # For B&W mode + gray_value = int(normalized_temp * 2.55) # 0-100 to 0-255 + gray_value = max(0, min(255, gray_value)) + temp_rgb = np.array([gray_value, gray_value, gray_value], dtype=np.uint8) + + # Set the humidity color in the even row + wave_m[base_y, x_coords[batch_slice][i]] = temp_rgb + + # Alarm level row (odd row) + if alarm_level == 0: + # Green for normal + alarm_rgb = np.array([0, 255, 0], dtype=np.uint8) #thisis B,G,R !!! + elif alarm_level == 1: + # Yellow for warning + alarm_rgb = np.array([0, 255, 255], dtype=np.uint8) + else: # alarm_level == 2 + # Red for critical + alarm_rgb = np.array([0, 0, 255], dtype=np.uint8) + + # Set the alarm color in the odd row + wave_m[base_y + 1, x_coords[batch_slice][i]] = alarm_rgb + + return wave_m + +def create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields, device_to_index, base_minute, timezone_st, smell_component_stretch_by, selected_date, y_offset): + """ + Create a heatmap with the exact blue-cyan-green-yellow-red-violet spectrum + matching Image 2, with green at position 40 + """ + if len(my_data) < 1: + return + + minutes = 1440 + devices_c = len(device_to_index) + sensors_c = len(fields) + stripes = devices_c * sensors_c #2 for upper maxes, lower mins + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) + st = time.time() + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, timezone_st) + st = time.time() + arr_source = fast_fill_smell_array_from_timescale(my_data, time_from_str, device_to_index, arr_source_template, timezone_st) + arr_source = AddSmellLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + + arr_stretched = FillSmellImage_optimized(scaled_day, arr_stretched, y_offset) + + return + + +def create_optimized_heatmap_simple(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st, min_val, max_val): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] - min_val / (max_val - min_val)) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for i in range(0, len(data_array)): + rgb_value = ((values[i] - min_val) / (max_val - min_val)) * 1279.0 + wave_m[i, x_coords[i]] = rgb_value + + return wave_m + + +def create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, timezone_st): + + if len(my_data) < 1: + return [] + + + local_tz = pytz.timezone(timezone_st) + + n_fields = len(fields) + # Convert my_data to numpy array for faster processing + data_array = np.array(my_data) + + # Get unique device IDs and create mapping + + # Convert device IDs to indices using vectorized operation + device_indices = np.vectorize(device_to_index.get)(data_array[:, 1]) + + # Calculate x coordinates (minutes from base) + #minute is in local time zone, and base_minute is UTC + base_minute_local = base_minute #.astimezone(local_tz) + #x_coords = np.array([(minute.replace(tzinfo=datetime.timezone.utc) - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + x_coords = np.array([(minute - base_minute_local).total_seconds()/60 for minute in data_array[:, 0]], dtype=np.int32) + + # Extract values and convert to float + values = data_array[:, 2:].astype(np.float32) + + if bw: + # Process in batches to avoid memory issues + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + + # Calculate gray values + gray_values = (values[batch_slice, :] / 100.0) * 255.0 + + # Clip values to valid range + gray_values = np.clip(gray_values, 0, 255).astype(np.uint8) + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Assign values to the image array + for i in range(end_idx - start_idx): + wave_m[y_coords[i], x_coords[batch_slice][i]] = gray_values[i, :, np.newaxis] + + else: # Color mode + # Process in batches + batch_size = 1000 + for start_idx in range(0, len(data_array), batch_size): + end_idx = min(start_idx + batch_size, len(data_array)) + batch_slice = slice(start_idx, end_idx) + + # Calculate color values + color_values = np.zeros_like(values[batch_slice]) + color_values[:, :] = (values[batch_slice, :] / 100.0) * 1279.0 # other fields + + # Create y coordinates for each record + y_coords = (device_indices[batch_slice] * n_fields).reshape(-1, 1) + np.arange(n_fields) + + # Convert to RGB colors + for i in range(end_idx - start_idx): + rgb_values = vectorized_best_color_numpy(color_values[i]) + wave_m[y_coords[i], x_coords[batch_slice][i]] = rgb_values + + return wave_m + +def visualize_gmm_fit(stationary_signal, output_file='gmm_explanation.png'): + """ + Visualize how GMM separates the stationary signal into components + """ + # Prepare data + X = stationary_signal.reshape(-1, 1) + + # Fit GMM + gmm = GaussianMixture(n_components=2, random_state=42) + gmm.fit(X) + + # Get parameters + means = gmm.means_.flatten() + stds = np.sqrt(gmm.covariances_.flatten()) + weights = gmm.weights_ + + # Create histogram of actual data + plt.figure(figsize=(12, 6)) + + # Plot histogram of actual data + plt.hist(X, bins=50, density=True, alpha=0.6, color='gray', + label='Actual Signal Distribution') + + # Generate points for GMM curves + x = np.linspace(X.min(), X.max(), 200) + + # Plot individual components + for i in range(len(means)): + plt.plot(x, weights[i] * stats.norm.pdf(x, means[i], stds[i]), + label=f'Component {i+1}: mean={means[i]:.2f}, std={stds[i]:.2f}') + + # Plot combined GMM + gmm_curve = np.zeros_like(x) + for i in range(len(means)): + gmm_curve += weights[i] * stats.norm.pdf(x, means[i], stds[i]) + plt.plot(x, gmm_curve, 'r--', linewidth=2, label='Combined GMM') + + # Add vertical lines for threshold + baseline = min(means) + threshold = baseline + 3 * np.sqrt(gmm.covariances_.flatten()[np.argmin(means)]) + plt.axvline(x=baseline, color='g', linestyle='--', label='Baseline') + plt.axvline(x=threshold, color='r', linestyle='--', label='Threshold') + + plt.title('Gaussian Mixture Model Components of Stationary Signal') + plt.xlabel('Signal Value') + plt.ylabel('Density') + plt.legend() + plt.grid(True) + + # Save and close + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.close() + +def process_location_data(location_data): + """ + Convert raw location data into aligned time series. + """ + timestamps = np.array([t[0] for t in location_data]) + stationary = np.array([t[1] for t in location_data]) + motion = np.array([t[2] for t in location_data]) + return timestamps, stationary, motion + +def detect_presence_for_location(stationary_signal, motion_signal, + motion_threshold=5, gmm_components=2): + """ + Simplified presence detection for a single location. + Returns presence mask and parameters. + """ + # Fit GMM to stationary signal + gmm = GaussianMixture(n_components=gmm_components, random_state=42) + X = stationary_signal.reshape(-1, 1) + gmm.fit(X) + + visualize_gmm_fit(stationary_signal, output_file='gmm_explanation.png') + + # Get baseline and threshold + baseline = min(gmm.means_)[0] + components_sorted = sorted(zip(gmm.means_.flatten(), gmm.covariances_.flatten())) + baseline_std = np.sqrt(components_sorted[0][1]) + threshold = baseline + 3 * baseline_std + + # Detect presence + presence_mask = (motion_signal > motion_threshold) | (stationary_signal > threshold) + + # Smooth presence detection (15 seconds window = 1.5 samples at 10sec sampling) + smooth_window = 3 + presence_mask = np.convolve(presence_mask.astype(int), + np.ones(smooth_window)/smooth_window, + mode='same') > 0.5 + + return presence_mask, threshold + +def find_current_location(data_sets, start_time, end_time, motion_threshold=10): + """ + Analyze presence across multiple locations for each minute. + + Parameters: + ----------- + data_sets : dict + Dictionary of location_name: data_tuples pairs + start_time : datetime + Start time for analysis + end_time : datetime + End time for analysis + motion_threshold : float + Threshold for significant motion detection + + Returns: + -------- + dict + Minute by minute analysis of presence and movement + """ + # Process each location's data + location_data = {} + for location, data in data_sets.items(): + timestamps, stationary, motion = process_location_data(data) + presence, threshold = detect_presence_for_location(stationary, motion, motion_threshold) + location_data[location] = { + 'timestamps': timestamps, + 'presence': presence, + 'motion': motion, + 'stationary': stationary, + 'threshold': threshold + } + + # Create minute-by-minute analysis + current_time = start_time + results = [] + + while current_time < end_time: + minute_end = current_time + timedelta(minutes=1) + + # Analysis for current minute + minute_status = { + 'timestamp': current_time, + 'locations': [], + 'moving_locations': [], + 'presence_values': {}, + 'motion_values': {}, + 'status': 'nobody_present' + } + + # First pass: collect all presence and motion values + for location, data in location_data.items(): + # Find indices for current minute + mask = (data['timestamps'] >= current_time) & (data['timestamps'] < minute_end) + if not any(mask): + continue + + presence_in_minute = data['presence'][mask] + motion_in_minute = data['motion'][mask] + stationary_in_minute = data['stationary'][mask] + + if any(presence_in_minute): + minute_status['presence_values'][location] = np.max(stationary_in_minute) + minute_status['motion_values'][location] = np.max(motion_in_minute) + + # If no presence detected anywhere + if not minute_status['presence_values']: + minute_status['status'] = 'nobody_present' + results.append(minute_status) + current_time += timedelta(minutes=1) + continue + + + # Find location with strongest presence + primary_location = max(minute_status['presence_values'].items(), + key=lambda x: x[1])[0] + + # Count locations with significant motion + moving_locations = [loc for loc, motion in minute_status['motion_values'].items() + if motion > motion_threshold] + + plot(motion, filename=f"motion.png", title=f"Motion", style='line') + # Update status based on motion and presence + if len(moving_locations) > 1: + # Multiple locations with significant motion indicates multiple people + minute_status['status'] = 'multiple_people_moving' + minute_status['locations'] = moving_locations + minute_status['moving_locations'] = moving_locations + else: + # Single or no motion - assign to location with strongest presence + minute_status['locations'] = [primary_location] + if moving_locations: + minute_status['status'] = f'single_person_moving_in_{primary_location}' + minute_status['moving_locations'] = moving_locations + else: + minute_status['status'] = f'single_person_stationary_in_{primary_location}' + + results.append(minute_status) + current_time += timedelta(minutes=1) + + return results + +def get_size(obj, seen=None): + # Recursively find size of objects and their contents + if seen is None: + seen = set() + + obj_id = id(obj) + if obj_id in seen: + return 0 + + seen.add(obj_id) + size = sys.getsizeof(obj) + + if isinstance(obj, (list, tuple, set, dict)): + if isinstance(obj, (list, tuple, set)): + size += sum(get_size(i, seen) for i in obj) + else: # dict + size += sum(get_size(k, seen) + get_size(v, seen) for k, v in obj.items()) + + return size + +def CreatePresenceMap(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, + fast, filter_minutes, time_zone_s): + + #global Id2MACDict + data_sets = {} + ids_list = [] + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + time_from, time_to = GetLocalTimeForDateSimple(selected_date, time_zone_s) + + for details in devices_list: + + sql = get_device_radar_only_query(str(details[1]), time_from_str, time_to_str, [details[1]]) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + data_sets[details[2]] = cur.fetchall()#cur.fetchone() + + + + + # Get minute-by-minute analysis + location_analysis = find_current_location(data_sets, time_from, time_to) + + # Example of printing results + for minute in location_analysis: + print(f"Time: {minute['timestamp']}") + print(f"Status: {minute['status']}") + print(f"Present in: {', '.join(minute['locations'])}") + if minute['moving_locations']: + print(f"Movement in: {', '.join(minute['moving_locations'])}") + print("---") + + print(f"Dictionary size: {get_size(data_sets)} bytes") + devices_list_str = ','.join(str(device[1]) for device in devices_list) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + sql = get_device_radar_only_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #thresholds_dict = {} + + #stretch_to_min_max = True + #devices_c = len(devices_list) + + #data_sets = { + #'living_room': my_data1, + #'kitchen': my_data2, + #'bedroom1': my_data3, + #'bedroom2': my_data4, + #'hallway': my_data5, + #'bathroom': my_data6, + #'office': my_data7 + #} + + sensors_c = 1#len(sensors_table) + + + image_file = location_image_file + + minutes = 1440 + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] #Why 'm8_max' and 'm08_max' ?because m08 is m0 + m1 .. to 8! + + fields_n = len(fields) + + stripes = len(devices_list) * fields_n + + #device_counter = 0 + stretch_by = 5 + + #arr_source = np.zeros((stripes, minutes), dtype=float) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + ids_list = [] + + labels = [] + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_scale = 1 + label_font_color = (255, 255, 255) + label_font_thickness = 2 + label_font_line = cv2.LINE_AA + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, 10 + text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + + + # Get start and end times from your data + start_time = min(data_sets['living_room'][0][0], + data_sets['kitchen'][0][0], + # ... add other locations + ) + end_time = max(data_sets['living_room'][-1][0], + data_sets['kitchen'][-1][0], + # ... add other locations + ) + + # Get minute-by-minute analysis + location_analysis = find_current_location(data_sets, start_time, end_time) + + # Example of printing results + for minute in location_analysis: + print(f"Time: {minute['timestamp']}") + print(f"Status: {minute['status']}") + print(f"Present in: {', '.join(minute['locations'])}") + if minute['moving_locations']: + print(f"Movement in: {', '.join(minute['moving_locations'])}") + print("---") +#---------------------------------------------------------------------------------------------------- + + + + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #device_ids = sorted(set(record[1] for record in my_data)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + + base_minute = ConvertToBase(time_from_str, time_zone_s) + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + if False: + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + for record in my_data: + + #(minute,device_id,absent_min,stationary_max,moving_max,both_max,m0_max,m1_max,m2_max,m3_max,m4_max, + # m5_max,m6_max,m7_max,m8_max,m08_max,s2_max,s3_max,s4_max,s5_max,s6_max,s7_max,s8_max,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the max/min values + x = int((minute - base_minute).total_seconds()/60) + device_idx = device_to_index[device_id] + + if bw: + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 255.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = [gray_value, gray_value, gray_value] + else: #color + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * 22 + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 1279.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = BestColor(gray_value) + + print(time.time()-st) + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + + SaveImageInBlob(image_file, arr_stretched, labels) + #arr_source[2*gate, :] = wave_m + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_m[col] + #if sens_val != 0: + #r,g,b=BestColor(km*(sens_val-m_min)) + #if r > 255 or g > 255 or b > 255: + #print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + ##print(y, row, devices_c, sensor_index, location_index, stretch_index) + ##arr_stretched[y, :] = rgb_row + + #if gate > 1: + #ks = 0 + #if(s_max > s_min): + #if bw: + #ks = 255/(s_max - s_min) + #else: + #ks = 1280/(s_max - s_min) + ##wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + #wave_s = np.array([0.0] * 1440) + + #for minute_m in range(1440): + #wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + ##wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + ##DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + #arr_source[2*gate + 1, :] = wave_s + + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_s[col] + #if sens_val != 0: + #if bw: + #r = ks*(sens_val-s_min) + #g = r + #b = r + #else: + #r,g,b=BestColor(ks*(sens_val-s_min)) + ##print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + #y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + + print("stop") + +def ConvertToBase(time_from_str, time_zone_s): + print(time_from_str) + dt = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + return dt + +def GetTimeAndEvents(data): + """ + Calculates non-zero elements and consecutive non-zero groups using itertools. + This is often the most readable and efficient pure Python approach. + """ + # Fast way to count non-zeros since they are all 1.0 + #non_zeros = int(sum(data)) + non_zeros = sum(1 for x in data if x != 0) + # Count groups of non-zero elements + events = sum(1 for key, group in itertools.groupby(data) if key != 0.0) + return non_zeros, events + +def current_date_at_tz(timezone_str): + """ + Returns the current date in the specified timezone in yyyy-mm-dd format. + + Args: + timezone_str (str): Timezone string like "America/Los_Angeles" + + Returns: + str: Current date in yyyy-mm-dd format + """ + # Get the timezone object + tz = pytz.timezone(timezone_str) + + # Get current datetime in the specified timezone + current_dt = datetime.datetime.now(tz) + + # Format as yyyy-mm-dd + return current_dt.strftime('%Y-%m-%d') + + +def GetActivities(device_id, well_id, date_str, filter_size, refresh, timezone_str, radar_threshold_group_st): + #filtered_day has non 0 points that exceeded threshold of radar reads + device_id_str = str(device_id) + + try: + + time_from_str, time_to_str = GetLocalTimeForDate(date_str, timezone_str) + filename_day_presence = f"/{device_id_str}/{device_id_str}_{date_str}_{filter_size}_presence.bin" + filtered_day_str = None + if refresh == False and date_str != current_date_at_tz(timezone_str): + has_larger = False + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence, date_str) + if filtered_day_str != None and filtered_day_str != "": + has_larger = bool(re.search(r'\b(?:[2-9]|\d{2,})\.\d+\b', filtered_day_str)) + if has_larger: + filtered_day_str = None + if filtered_day_str == None: + + radar_fields_of_interest = [] + + try: + threshold_lst = json.loads(radar_threshold_group_st) + except: + threshold_lst = ["s3_max",12] + radar_fields_of_interest = [threshold_lst[0]] + ids_list = [int(device_id)] + devices_list_str = device_id_str + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + sql = get_deployment_radar_10sec_snapped_query_min_max(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = None + my_data = cur.fetchall() + + days_difference_long = 2 + + presence_map = {'longpresence': {}, 'raw': {}} + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference_long + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3",12] + + #device_id_2_location = {device_id: ""} + device_id_2_threshold = {device_id: radar_threshold_group} + device_field_indexes = {radar_threshold_group[0].split("_")[0]: 1} #len(radar_fields_of_interest) + id2well_id = {device_id: well_id} + + if len(my_data) > 1: + + start_time_ = my_data[0][0] + parsed_time_ = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(hours=-7)) + #) + + presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, "presence") + + presence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter_size, device_id_str, date_str, date_str, timezone_str) + filtered_day_str = ReadObjectMinIO("filtered-presence", filename_day_presence) + filtered_day = json.loads(filtered_day_str) + else: + filtered_day = json.loads(filtered_day_str) + + non_zeros, events = GetTimeAndEvents(filtered_day) + + return(non_zeros / 360, events) #decas to hours + except Exception as e: + print(filename_day_presence) + print(filtered_day_str) + print(traceback.format_exc()) + return(0, 0) +def CreateFullLocationMap(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s): + #global Id2MACDict + + thresholds_dict = {} + + stretch_to_min_max = True + devices_c = len(devices_list) + + if devices_c == 0: + return + + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + if scale_global and chart_type != 3 and chart_type != 4: #"digital" and chart_type != "collapsed" + + max_gate={} + for gate in range(9): + max_gate[str(gate)+"_m"] = 0 + max_gate[str(gate)+"_s"] = 0 + + device_counter = 0 + for details in devices_list: + MAC, threshold, location_name, description = details + if threshold == None: + threshold = '["s3_max",12]' + + + #day_minutes_data = [(0,0)] * (24 * 60 + 2) + #day_minutes_data_l = [[0] * 10 for _ in range(24 * 60 + 2)] + + minute_radar_lists = ReadDailyRadar(MAC, current_date) + + for gate in range(9): + for minute_m in range(1440): + if (minute_radar_lists[minute_m][gate] > max_gate[str(gate)+"_m"]): + max_gate[str(gate)+"_m"] = minute_radar_lists[minute_m][gate] + + if gate > 1: + if (minute_radar_lists[minute_m][gate + 7] > max_gate[str(gate)+"_s"]): + max_gate[str(gate)+"_s"] = minute_radar_lists[minute_m][gate + 7] + + + if (chart_type == 2): #"analog" + + + #fields = ['absent_min', 'stationary_max', 'moving_max', 'both_max', + #'m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + #'m6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + #'s4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max'] + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] + + + fields_n = len(fields) + + stripes = len(devices_list) * fields_n + + device_counter = 0 + stretch_by = 5 + + arr_source = np.zeros((stripes, minutes), dtype=float) + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + + labels = [] + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_scale = 1 + label_font_color = (255, 255, 255) + label_font_thickness = 2 + label_font_line = cv2.LINE_AA + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, 10 + text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + + #device_ids = sorted(set(record[1] for record in my_data)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + if False: + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + for record in my_data: + + #(minute,device_id,absent_min,stationary_max,moving_max,both_max,m0_max,m1_max,m2_max,m3_max,m4_max, + # m5_max,m6_max,m7_max,m8_max,m08_max,s2_max,s3_max,s4_max,s5_max,s6_max,s7_max,s8_max,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the max/min values + x = int((minute - base_minute).total_seconds()/60) + device_idx = device_to_index[device_id] + + if bw: + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 255.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = [gray_value, gray_value, gray_value] + else: #color + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * 22 + field_idx + + # Convert value to grayscale (0-100 to 0-255) + gray_value = int((value / 100.0) * 1279.0) + + # Set RGB values (all same for grayscale) + wave_m[y, x] = BestColor(gray_value) + + print(time.time()-st) + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + + SaveImageInBlob(image_file, arr_stretched, labels) + #arr_source[2*gate, :] = wave_m + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_m[col] + #if sens_val != 0: + #r,g,b=BestColor(km*(sens_val-m_min)) + #if r > 255 or g > 255 or b > 255: + #print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + ##print(y, row, devices_c, sensor_index, location_index, stretch_index) + ##arr_stretched[y, :] = rgb_row + + #if gate > 1: + #ks = 0 + #if(s_max > s_min): + #if bw: + #ks = 255/(s_max - s_min) + #else: + #ks = 1280/(s_max - s_min) + ##wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + #wave_s = np.array([0.0] * 1440) + + #for minute_m in range(1440): + #wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + ##wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + ##DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + #arr_source[2*gate + 1, :] = wave_s + + #rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + #for col in range(1440): + #sens_val = wave_s[col] + #if sens_val != 0: + #if bw: + #r = ks*(sens_val-s_min) + #g = r + #b = r + #else: + #r,g,b=BestColor(ks*(sens_val-s_min)) + ##print(r,g,b) + #rgb_row[col] = r,g,b + + #for stretch_index in range(stretch_by): + #y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + #y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + #arr_stretched[y, :] = rgb_row + + print("stop") + elif (chart_type == 3): #"digital" + device_counter = 0 + for details in devices_list: + dev_id = details[0] + MAC, threshold, location_id, description = GetMacThrFromId(dev_id) + if threshold == None: + threshold = 30 + + sensor = "Radar" + location_name = location_names[location_id] + pickle_file = os.path.join(scriptDir, "scratch/"+MAC.upper() +"_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_radarM.pkl") + pickle_file = pickle_file.replace("\\","/") + #day_minutes_data = [(0,0)] * (24 * 60 + 2) + #day_minutes_data_l = [[0] * 10 for _ in range(24 * 60 + 2)] + + minute_radar_lists = ReadDailyRadar(MAC, current_date) + + y = 0 + sensor_index = 0 + + #location_index = 0 + + for gate in range(9): + threshold = 15 + if (gate > 1): + threshold = thresholds_dict[dev_id][gate-2] + + for minute_m in range(1440): + if (minute_radar_lists[minute_m][gate] > threshold): + minute_radar_lists[minute_m][gate] = 100 + else: + minute_radar_lists[minute_m][gate] = 0 + + if gate > 1: + if (minute_radar_lists[minute_m][gate + 7] > threshold): + minute_radar_lists[minute_m][gate + 7] = 100 + else: + minute_radar_lists[minute_m][gate + 7] = 0 + + m_max = 100 + m_min = 0 + s_max = 100 + s_min = 0 + + km = 0 + if(m_max > m_min): + km = 1280/(m_max - m_min) + #wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + wave_m = np.array([0.0] * 1440) + + for minute_m in range(1440): + wave_m[minute_m] = minute_radar_lists[minute_m][gate] + + if gate < 2: + DoDisplay(wave_m, location_name+" "+ description+" " + str(gate)) + #wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + arr_source[2*gate, :] = wave_m + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + for col in range(1440): + sens_val = wave_m[col] + if sens_val != 0: + r,g,b=BestColor(km*(sens_val-m_min)) + #print(r,g,b) + rgb_row[col] = r,g,b + + for stretch_index in range(stretch_by): + y = device_counter * (18*stretch_by) + 2*gate * stretch_by + stretch_index + #print(y, row, devices_c, sensor_index, location_index, stretch_index) + + #arr_stretched[y, :] = rgb_row + + if gate > 1: + ks = 0 + if(s_max > s_min): + if bw: + ks = 255/(s_max - s_min) + else: + ks = 1280/(s_max - s_min) + #wave_m = np.array([km*(item[0]-m_min) for item in minute_radar_lists[:1440][gate]]) + wave_s = np.array([0.0] * 1440) + + for minute_m in range(1440): + wave_s[minute_m] = minute_radar_lists[minute_m][gate+7] + #wave_m = np.array([item[0] for item in minute_radar_lists[:1440][gate]]) + DoDisplay2(wave_m, wave_s, location_name+" "+str(dev_id)+" "+ description+" "+ str(gate)) + arr_source[2*gate + 1, :] = wave_s + + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + + for col in range(1440): + sens_val = wave_s[col] + if sens_val != 0: + if bw: + r = ks*(sens_val-s_min) + g = r + b = r + else: + r,g,b=BestColor(ks*(sens_val-s_min)) + #print(r,g,b) + rgb_row[col] = r,g,b + + for stretch_index in range(stretch_by): + y = device_counter * (18*stretch_by) + (2*(gate) + 1) * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + y = device_counter * (18*stretch_by) + (2*(gate)) * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + device_counter += 1 + print("stop") + + elif (chart_type == 4): #"collapsed" + + stretch_by = 50 + arr_source = np.zeros((1, minutes), dtype=float) + arr_stretched = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + + device_counter = 0 + wave_m = [["", -1] for _ in range(1440)] + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + radar_fields_of_interest = [] + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3_max",12] + + + if isinstance(threshold_lst, int): + threshold_lst = ["s3_max",threshold_lst] + + radar_field = threshold_lst[0] + if radar_field not in radar_fields_of_interest: + radar_fields_of_interest.append(radar_field) + + threshold = threshold_lst[1] + dev_id = details[0] + ids_list.append(details[1]) + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + cnt += 1 + row_nr_2_device_id[cnt] = device_id + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + device_id_2_location[device_id] = location_name + + device_id_2_threshold[device_id] = radar_threshold_group + + target_tz = pytz.timezone(time_zone_s) + st = time.time() + + #each record in my_data has time, device_id and radar_fields_of_interest in it + result_np = None + try: + result_np = process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest) + print(time.time() - st) + except Exception as err: + print(str(err)) + + if False: + for record in my_data: + time_val, device_id, min_val, max_val = record + radar_threshold = device_id_2_threshold[device_id] + local_time = time_val.astimezone(target_tz) + minute_m = int((local_time - local_time.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60) + + if (wave_m[minute_m][0] == ""): + if max_val > radar_threshold: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + else: + if max_val > radar_threshold: + if max_val > wave_m[minute_m][1]: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + + + + print(time.time()-st) + if result_np is not None: + wave_m = result_np + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + #wave_m = FilterGlitches(wave_m, filter_minutes) + r = 0 + g = 0 + b = 0 + + if isinstance(wave_m[0], np.int64): + inital_device_id = row_nr_2_device_id[wave_m[0]] + else: + inital_device_id = 0 + + present_at = [[inital_device_id, 0, 1]] #device_id, minute, duration + + for minute_m in range(1440): + try: + + if isinstance(wave_m[minute_m], np.int64): + device_id = row_nr_2_device_id[wave_m[minute_m]] + else: + device_id = 0 + + if device_id != "" and device_id != -1: + r,g,b = Loc2Color[device_id_2_location[device_id]][0] + rgb_row[minute_m] = b,g,r + + if minute_m > 0: + if present_at[-1][0] != device_id: + present_at.append([device_id, minute_m, 1]) + else: + present_at[-1][2] += 1 + + except Exception as err: + print(str(err)) + + for stretch_index in range(stretch_by): + y = stretch_index + arr_stretched[y, :] = rgb_row + + #print("stop") + #print(r,g,b) + SaveObjectInBlob(image_file+".bin", present_at) + SaveImageInBlob(image_file, arr_stretched) + +def CreateFullLocationMapLabelsOut(location_image_file, devices_list, selected_date, + map_type, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s): + #global Id2MACDict + + thresholds_dict = {} + + stretch_to_min_max = True + devices_c = len(devices_list) + + if devices_c == 0: + return + + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + + if (chart_type == 8): #"all graphs" + + fields = ['m0_max', 'm1_max', 'm2_max', 'm3_max', 'm4_max', 'm5_max', + 'm6_max', 'm7_max', 'm8_max', 'm08_max', 's2_max', 's3_max', + 's4_max', 's5_max', 's6_max', 's7_max', 's8_max', 's28_max', 's28_min'] + + + show_radar = True + show_light = True + show_temperature = True + show_humidity = True + show_smell = True + + labels_width = 200 + title_labels_height = 40 + title_label_width = 100 + + #common + label_font = cv2.FONT_HERSHEY_SIMPLEX + label_font_line = cv2.LINE_AA + + #different + title_label_font_scale = 1 + title_label_font_color = (0, 0, 0)#(128, 255, 255) + title_label_font_thickness = 2 + + label_font_scale = 0.5 + label_font_color = (0, 0, 0)#(0, 255, 255) + label_font_thickness = 1 + + fields_n = len(fields) + + radar_stripes = len(devices_list) * fields_n + radar_stretch_by = 5 + + light_stripes = len(devices_list) + light_stretch_by = 20 + + smell_sensors_stripes = 10 * len(devices_list) + other_sensors_stripes = len(devices_list) + + temp_stripe_width = 15 + alarm_stripe_width = 5 + temperature_stretch_by = temp_stripe_width + alarm_stripe_width # Total height per device + + humidity_stripe_width = 15 + humidity_stretch_by = humidity_stripe_width + alarm_stripe_width + smell_component_stretch_by = 20 + + text_dimensions = get_text_dimensions("TEST", label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + + all_maps_height = 0 + + # radar, light, temperature, humidity, smell*10 + + if show_radar: + all_maps_height = title_labels_height + radar_stripes*radar_stretch_by + + if show_light: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*light_stretch_by + + if show_temperature: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*temperature_stretch_by + + if show_humidity: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*humidity_stretch_by + + if show_smell: + all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*smell_component_stretch_by * 10 + + + if all_maps_height == 0: + return + + vertical_offset = 0 + arr_stretched = np.full((all_maps_height, minutes+labels_width, 3), [255, 174, 70], dtype=np.uint8) + + #Lets add divider lines + x = 190 + if show_radar: + stretch_by = radar_stretch_by + cnt = 0 + for details in devices_list: + y = vertical_offset + title_labels_height + (cnt)*fields_n*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + + section_height = title_labels_height + radar_stripes*radar_stretch_by + vertical_offset = vertical_offset + section_height + if show_light: + stretch_by = light_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + + section_height = title_labels_height + other_sensors_stripes*stretch_by + vertical_offset = vertical_offset + section_height + + if show_temperature: + stretch_by = temperature_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + section_height = title_labels_height + other_sensors_stripes*stretch_by + vertical_offset = vertical_offset + section_height + if show_humidity: + stretch_by = humidity_stretch_by + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*1*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + section_height = title_labels_height + other_sensors_stripes*humidity_stretch_by + vertical_offset = vertical_offset + section_height + if show_smell: + stretch_by = smell_component_stretch_by + + cnt = 0 + + for details in devices_list: + y = vertical_offset + title_labels_height+ (cnt)*10*stretch_by + arr_stretched[y, 190:201, :] = 0 + cnt += 1 + #section_height = title_labels_height + other_sensors_stripes**stretch_by * 10 + #vertical_offset = vertical_offset + section_height + + #all_maps_height = all_maps_height + title_labels_height + other_sensors_stripes*stretch_by * 10 + + + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + labels = [] + title_labels = [] + vertical_offset = 0 + ######################################## RADAR ################################################################## + if show_radar: + title_label_text = "RADAR" + fields_s = fields + stripes = radar_stripes + stretch_by = radar_stretch_by + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), vertical_offset + 10 + title_text_height), label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + cnt = 0 + for details in devices_list: + dev_id = details[0] + ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + labels.append((descriptor, (10, vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by), label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + sql = get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data != None: + + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + st = time.time() + if True: + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + wave_m = create_radar_optimized_heatmap(my_data, bw, fields_s, wave_m, device_to_index, base_minute, time_zone_s) + print(time.time()-st) + + + st = time.time() + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(radar_stretch_by): + y = yy * radar_stretch_by + stretch_index + arr_stretched[title_labels_height+y, 200:] = rgb_row + + print(time.time()-st) + vertical_offset = vertical_offset + title_labels_height + stripes*radar_stretch_by + ######################################## LIGHT ################################################################## + if show_light: + title_label_text = "LIGHT" + fields_s = ['light'] + min_val = 0 + max_val = 4095 + stretch_by = light_stretch_by + stripes = len(devices_list) * len(fields_s) # Calculate number of rows needed + # Calculate the correct vertical offset for light section + + # Draw the light section title at the correct position + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for light section + cnt = 0 + light_ids_list = [] # Create a separate list for light section + for details in devices_list: + dev_id = details[0] + light_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the light section + labels.append((descriptor, (10, vertical_offset + title_labels_height + text_height + (cnt)*len(fields_s)*stretch_by), + label_font, label_font_scale, label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get light data using the existing query function + sql = get_deployment_light_only_query(devices_list_str, time_from_str, time_to_str, light_ids_list) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(light_ids_list)} + + # Calculate base minute + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process light data + st = time.time() + + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + + # Use the light-specific function + wave_m = create_light_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Light heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap vertically + st = time.time() + section_start = vertical_offset + title_labels_height + + for yy in range(stripes): + rgb_row = wave_m[yy] + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + target_y = section_start + y + + # Make sure we're within bounds of the array + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = rgb_row + else: + print(f"Warning: Row {target_y} is out of bounds (max: {arr_stretched.shape[0]-1})") + + vertical_offset = vertical_offset + title_labels_height + stripes*stretch_by + print(f"Light stretching time: {time.time()-st:.4f} seconds") + + ######################################## TEMPERATURE ################################################################## + if show_temperature: + title_label_text = "TEMPERATURE" + fields_s = ['temperature', 'temperature_state'] + + # Define different stripe widths for temperature and alarm + + temp_offset = -10#GetTempOffset(device_id) + min_val = 20 + max_val = 30 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height + text_height + cnt * temperature_stretch_by + #y_pos = vertical_offset + title_labels_height + text_height + (cnt)*len(fields_s)*stretch_by) + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get temperature data + sql = get_deployment_temperature_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, temp_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process temperature data + st = time.time() + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + if False: + # Simulate data for testing + for i in range(min(len(my_data), 500)): + if i >= 100: # Only modify indices 100-500 + t = (i - 100) / 4.0 # Temperature value + + # Set correct alarm levels based on temperature + if CelsiusToFahrenheit(t) <= 50 or CelsiusToFahrenheit(t) >= 90: + alarm_level = 2 # Critical - should be red + elif CelsiusToFahrenheit(t) <= 60 or CelsiusToFahrenheit(t) >= 80: + alarm_level = 1 # Warning - should be yellow + else: + alarm_level = 0 # Normal - should be green + + # Replace the tuple with new values + my_data[i] = (my_data[i][0], my_data[i][1], t, alarm_level) + + # Create the heatmap data + wave_m = create_temperature_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Temperature heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap with different heights for temperature and alarm + st = time.time() + section_start = vertical_offset + title_labels_height + + # Loop through each device + for device_idx in range(len(temp_ids_list)): + # Get the data rows for this device + temp_row = wave_m[device_idx * 2] # Temperature row (even index) + alarm_row = wave_m[device_idx * 2 + 1] # Alarm row (odd index) + + # Calculate the starting y-position for this device + device_y_start = section_start + device_idx * temperature_stretch_by + + # Draw the temperature stripe (15 pixels) + for stretch_index in range(temp_stripe_width): + target_y = device_y_start + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = temp_row + + # Draw the alarm stripe (5 pixels) + for stretch_index in range(alarm_stripe_width): + target_y = device_y_start + temp_stripe_width + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = alarm_row + + print(f"Temperature stretching time: {time.time()-st:.4f} seconds") + + ######################################## HUMIDITY ################################################################## + ''' + Ideal indoor humidity: 30-50% + Too dry: Below 30% - Can cause dry skin, irritated eyes, and respiratory issues + Too humid: Above 60% - Feels warmer than actual temperature, promotes mold growth + ''' + if show_humidity: + title_label_text = "HUMIDITY" + fields_s = ['humidity', 'humidity_state'] + + # Define different stripe widths for humidity and alarm + + humidity_offset = 0 + min_val = 40 + max_val = 90#60 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + if show_temperature: + vertical_offset += title_labels_height + other_sensors_stripes * temperature_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height + text_height + cnt * humidity_stretch_by + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get humidity data + sql = get_deployment_humidity_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, humidity_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Process temperature data + st = time.time() + wave_m = np.zeros((stripes, 1440, 3), dtype=np.uint8) + if False: + # Simulate data for testing + for i in range(min(len(my_data), 500)): + if i >= 100: # Only modify indices 100-500 + h = (i - 100) / 4.0 # Temperature value + + # Set correct alarm levels based on temperature + if h <= 20 or h >= 60: + alarm_level = 2 # Critical - should be red + elif h <= 30 or h >= 50: + alarm_level = 1 # Warning - should be yellow + else: + alarm_level = 0 # Normal - should be green + + # Replace the tuple with new values + my_data[i] = (my_data[i][0], my_data[i][1], h, alarm_level) + + # Create the heatmap data + wave_m = create_humidity_optimized_heatmap(my_data, bw, fields_s, wave_m, + device_to_index, base_minute, time_zone_s, + min_val, max_val) + print(f"Humidity heatmap creation time: {time.time()-st:.4f} seconds") + + # Stretch the heatmap with different heights for humidity and alarm + st = time.time() + section_start = vertical_offset + title_labels_height + + # Loop through each device + for device_idx in range(len(temp_ids_list)): + # Get the data rows for this device + humidity_row = wave_m[device_idx * 2] # Humidity row (even index) + alarm_row = wave_m[device_idx * 2 + 1] # Alarm row (odd index) + + # Calculate the starting y-position for this device + device_y_start = section_start + device_idx * humidity_stretch_by + + # Draw the humidity stripe (15 pixels) + for stretch_index in range(humidity_stripe_width): + target_y = device_y_start + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = humidity_row + + # Draw the alarm stripe (5 pixels) + for stretch_index in range(alarm_stripe_width): + target_y = device_y_start + temp_stripe_width + stretch_index + if target_y < arr_stretched.shape[0]: + arr_stretched[target_y, labels_width:] = alarm_row + + print(f"Temperature stretching time: {time.time()-st:.4f} seconds") + + ######################################## SMELL ################################################################## + if show_smell: + title_label_text = "SMELL" + fields_s = ['S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8', 'S9'] + + # Define different stripe widths for humidity and alarm + + smell_offset = 0 + + # Calculate the correct vertical offset for temperature section + vertical_offset = 0 + if show_radar: + vertical_offset += title_labels_height + radar_stripes * radar_stretch_by + if show_light: + vertical_offset += title_labels_height + other_sensors_stripes * light_stretch_by + if show_temperature: + vertical_offset += title_labels_height + other_sensors_stripes * temperature_stretch_by + if show_humidity: + vertical_offset += title_labels_height + other_sensors_stripes * humidity_stretch_by + + stripes = len(devices_list) * len(fields_s) # Number of rows needed in data array + + # Draw the temperature section title + title_text_dimensions = get_text_dimensions(title_label_text, label_font, title_label_font_scale, label_font_thickness) + title_text_height = title_text_dimensions["height"] + title_label_width = title_text_dimensions["width"] + title_label = (title_label_text, (int(labels_width + minutes * 0.5 - title_label_width / 2), + vertical_offset + 10 + title_text_height), + label_font, title_label_font_scale, title_label_font_color, title_label_font_thickness, label_font_line) + + title_labels.append(title_label) + + # Draw device labels for temperature section + cnt = 0 + temp_ids_list = [] # Create a separate list for temperature section + for details in devices_list: + dev_id = details[0] + temp_ids_list.append(details[1]) + descriptor = details[2] + if details[3] != None and details[3] != "": + descriptor = descriptor + " " + details[3] + if details[6] != None and details[6] != "": + descriptor = descriptor + " " + details[6] + + text_dimensions = get_text_dimensions(descriptor, label_font, label_font_scale, label_font_thickness) + text_height = text_dimensions["height"] + # Position labels in the temperature section + y_pos = vertical_offset + title_labels_height +80+ text_height + cnt * smell_component_stretch_by * 10 + #y_pos = vertical_offset + title_labels_height+40+text_height + (cnt)*fields_n*stretch_by) + labels.append((descriptor, (10, y_pos), label_font, label_font_scale, + label_font_color, label_font_thickness, label_font_line)) + cnt += 1 + + # Get smell data + sql = get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str, temp_ids_list, smell_offset) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall() + + if my_data != None and len(my_data) > 0: + device_to_index = {device: idx for idx, device in enumerate(temp_ids_list)} + base_minute = ConvertToBase(time_from_str, time_zone_s) + + # Create the heatmap data + create_smell_optimized_heatmap(arr_stretched, my_data, bw, fields_s, device_to_index, base_minute, time_zone_s, smell_component_stretch_by, selected_date, vertical_offset + 18 + title_text_height) + + + + SaveImageInBlobLabelsOut(image_file, arr_stretched, labels, title_labels) + + print("stop") + + + + + +def CreateDailyLocationMap(location_image_file, devices_list, selected_date, filter_minutes, time_zone_s, stretch_by): + + devices_c = len(devices_list) + sensors_c = 1#len(sensors_table) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + + image_file = location_image_file + + minutes = 1440 + + + #search_pattern = os.path.join(scriptDir, "scratch/*_"+str(current_date.year)+"_"+str(current_date.month).rjust(2, '0')+"_"+str(current_date.day)+"_*.pkl") + + #allFiles = [os.path.join(dest_path, f) for f in glob.glob(search_pattern)] + #rekreate .pckl files if missing + today_date = datetime.datetime.fromtimestamp(time.time()) + + arr_source = np.zeros((1, minutes), dtype=float) + arr_stretched = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + arr_stretched_sorted = np.zeros((int(stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + + device_counter = 0 + wave_m = [["", -1] for _ in range(1440)] + devices_list_str = ','.join(str(device[1]) for device in devices_list) + ids_list = [] + radar_fields_of_interest = [] + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + if len(threshold_lst) > 2: + threshold_lst = ["s3_max",12] + radar_field = threshold_lst[0] + except: + threshold_lst = ["s3_max",12] + + radar_field = threshold_lst[0] + if radar_field not in radar_fields_of_interest: + radar_fields_of_interest.append(radar_field) + + threshold = threshold_lst[1] + dev_id = details[0] + ids_list.append(details[1]) + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + cnt += 1 + row_nr_2_device_id[cnt] = device_id + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + device_id_2_location[device_id] = location_name + + device_id_2_threshold[device_id] = radar_threshold_group + + target_tz = pytz.timezone(time_zone_s) + st = time.time() + + #each record in my_data has time, device_id and radar_fields_of_interest in it + + try: + result_np = process_wave_data_numpy(image_file, my_data, time_zone_s, device_id_2_threshold, radar_fields_of_interest) + print(time.time() - st) + except Exception as err: + print(str(err)) + + if False: + for record in my_data: + time_val, device_id, min_val, max_val = record + radar_threshold = device_id_2_threshold[device_id] + local_time = time_val.astimezone(target_tz) + minute_m = int((local_time - local_time.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() / 60) + + if (wave_m[minute_m][0] == ""): + if max_val > radar_threshold: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + else: + if max_val > radar_threshold: + if max_val > wave_m[minute_m][1]: + wave_m[minute_m][0] = device_id + wave_m[minute_m][1] = max_val + + + + print(time.time()-st) + wave_m = result_np + rgb_row = np.zeros(( 1440, 3), dtype=np.uint8) + rgbsorted_row = np.zeros(( 1440, 3), dtype=np.uint8) + #wave_m = FilterGlitches(wave_m, filter_minutes) + r = 0 + g = 0 + b = 0 + + presence_minutes = {} + + #we want to generate present_at array + if isinstance(wave_m[0], np.int64): + inital_device_id = row_nr_2_device_id[wave_m[0]] + else: + inital_device_id = 0 + present_at = [[inital_device_id, 0, 1]] #device_id, minute, duration + for minute_m in range(1440): + try: + if isinstance(wave_m[minute_m], np.int64): + device_id = row_nr_2_device_id[wave_m[minute_m]] + else: + device_id = 0 + + if device_id != "" and device_id != -1: + r,g,b = Loc2Color[device_id_2_location[device_id]][0] + rgb_row[minute_m] = b,g,r + + if Loc2Color[device_id_2_location[device_id]][1] in presence_minutes: + presence_minutes[Loc2Color[device_id_2_location[device_id]][1]] = [presence_minutes[Loc2Color[device_id_2_location[device_id]][1]][0] + 1, Loc2Color[device_id_2_location[device_id]][0]] + else: + presence_minutes[Loc2Color[device_id_2_location[device_id]][1]] = [1, Loc2Color[device_id_2_location[device_id]][0]] + + if minute_m > 0: + if present_at[-1][0] != device_id: + present_at.append([device_id, minute_m, 1]) + else: + present_at[-1][2] += 1 + + except Exception as err: + print(str(err)) + + start_minute = 0 + for color_key in sorted(presence_minutes): + print(color_key, presence_minutes[color_key]) + rgbsorted_row[start_minute:start_minute+presence_minutes[color_key][0]] = presence_minutes[color_key][1][::-1] + start_minute += presence_minutes[color_key][0] + + #we need to save present_at list to blob + SaveObjectInBlob(image_file+".bin", present_at) + #present_at_back_s = ReadObjectMinIO("daily-maps", image_file+".bin") + #present_at_back = json.loads(present_at_back_s) + #print(present_at_back) + for stretch_index in range(stretch_by): + y = stretch_index + arr_stretched[y, :] = rgb_row + arr_stretched_sorted[y, :] = rgbsorted_row + #print("stop") + #print(r,g,b) + SaveImageInBlob(image_file, arr_stretched) + SaveImageInBlob(image_file[:-4]+"S.png", arr_stretched_sorted) + + +def GenerateFullLocationMap(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreateFullLocationMap(map_file, devices_list, ddate, 1, recreate_or_not, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + +def GenerateFullLocationMapLabelsOut(map_file, deployment_id, ddate, recreate_or_not, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes = 5): + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreateFullLocationMapLabelsOut(map_file, devices_list, ddate, 1, recreate_or_not, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + +def CreateMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by): + global Id2MACDict, s_table_temp + + st = time.time() + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + try: + + #stretch_to_min_max = True + + #current_date_p = selected_date.replace("-", "_") + #current_date_s = selected_date + + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + for details in devices_list[0]: + dev_id = details[0] + ids_list.append(details[1]) + if dev_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return False, [] + + if larger_than200 > 0: + sensors_c = len(s_table_temp) + else: #old sensors not supported + return False, [] + + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + image_file = map_file + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + temp_offset = -10 + if sensors_c > 1: + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + else: + sql = get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, s_table_temp[0]) + print(sql) + #print(sql) + #st = time.time() + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + day_data = cur.fetchall()#cur.fetchone() + #print(result) + if day_data == None: + return False, [] + + stretch_by = 10 + minutes = 1440 + stripes = devices_c * sensors_c #2 for upper maxes, lower mins + arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float) + arr_stretched_template = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) # 3 for RGB channels + arr_source = fast_fill_array_from_timescale(day_data, time_from_str, devices_list[1], arr_source_template, time_zone_s) + arr_source = AddLimits_optimized(arr_source, devices_c, sensors_c, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) + SaveImageInBlob(image_file, arr_stretched) + return True, vocs_scaled + + except Exception as e: + AddToLog(traceback.format_exc()) + return False, [] + +def get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+ {temp_offset} as avg_temperature, + sr.avg_humidity, + sr.pressure_amplitude, + sr.max_light, + rr.radar, + sr.min_s0 as sensor_min_s0, + sr.min_s1 as sensor_min_s1, + sr.min_s2 as sensor_min_s2, + sr.min_s3 as sensor_min_s3, + sr.min_s4 as sensor_min_s4, + sr.min_s5 as sensor_min_s5, + sr.min_s6 as sensor_min_s6, + sr.min_s7 as sensor_min_s7, + sr.min_s8 as sensor_min_s8, + sr.min_s9 as sensor_min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS pressure_amplitude, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_single_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, sensor_in): + """ + Generate a TimeScaleDB query for a single sensor reading based on device IDs. + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + temp_offset (float): Temperature offset to apply + sensor (str): Single sensor to query from s_table + Returns: + str: Generated SQL query + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + if "_" in sensor_in: + sensor = sensor_in.split("_")[1] + else: + sensor = sensor_in + + # Handle different sensor types + if sensor == "radar": + # Query only radar readings + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS {sensor_in} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "temperature": + # Query temperature with offset + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) + {temp_offset} AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "humidity": + # Query humidity + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(humidity) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "pressure": + # Query pressure + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(pressure) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor == "light": + # Query light + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(light) AS {sensor_in} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + elif sensor.startswith("voc"): + # Query VOC sensors (voc0-voc9) - these correspond to s0-s9 in the original query + voc_num = sensor[3:] # Extract number from "voc0", "voc1", etc. + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(CASE WHEN s{voc_num} > 0 THEN s{voc_num} END) AS {sensor} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + else: + raise ValueError(f"Unknown sensor type: {sensor}. Must be one of: temperature, humidity, pressure, light, radar, voc0-voc9") + + return sql + +def get_deployment_single_query_rz(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, sensor): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name, defaults to 'radar' + + Returns: + str: Generated SQL query + """ + + #table_sens = {"temperature": (f"sr.avg_temperature+ {temp_offset} as avg_temperature", "avg_temperature"), + #"humidity": ("sr.avg_humidity", "avg_humidity"), + #"pressure": ("sr.pressure_amplitude", "pressure_amplitude"), + #"light":("sr.max_light", "max_light"), + #"radar":("rr.radar") + #"voc0": + #"voc1": + #"voc2": + #"voc3": + #"voc4": + #"voc5": + #"voc6": + #"voc7": + #"voc8": + #"voc9": , + #} + # derived + + #if sensor == + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+ {temp_offset} as avg_temperature, + sr.avg_humidity, + sr.pressure_amplitude, + sr.max_light, + rr.radar, + sr.min_s0 as sensor_min_s0, + sr.min_s1 as sensor_min_s1, + sr.min_s2 as sensor_min_s2, + sr.min_s3 as sensor_min_s3, + sr.min_s4 as sensor_min_s4, + sr.min_s5 as sensor_min_s5, + sr.min_s6 as sensor_min_s6, + sr.min_s7 as sensor_min_s7, + sr.min_s8 as sensor_min_s8, + sr.min_s9 as sensor_min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS pressure_amplitude, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + + +def get_deployment_rd_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+{temp_offset} as avg_temperature, + sr.avg_humidity, + sr.avg_pressure, + sr.max_light, + sr.min_s0 as smell_s0, + sr.min_s1 as smell_s1, + sr.min_s2 as smell_s2, + sr.min_s3 as smell_s3, + sr.min_s4 as smell_s4, + sr.min_s5 as smell_s5, + sr.min_s6 as smell_s6, + sr.min_s7 as smell_s7, + sr.min_s8 as smell_s8, + sr.min_s9 as smell_s9, + rr.absent as radar_absent, + rr.moving as radar_moving, + rr.stationary as radar_stationary, + rr.both as radar_both, + rr.m0 as radar_m0, + rr.m1 as radar_m1, + rr.m2 as radar_m2, + rr.m3 as radar_m3, + rr.m4 as radar_m4, + rr.m5 as radar_m5, + rr.m6 as radar_m6, + rr.m7 as radar_m7, + rr.m8 as radar_m8, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS avg_pressure, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(absent) AS absent, + MAX(moving) AS moving, + MAX(stationary) AS stationary, + MAX(\"both\") AS both, + MAX(m0) AS m0, + MAX(m1) AS m1, + MAX(m2) AS m2, + MAX(m3) AS m3, + MAX(m4) AS m4, + MAX(m5) AS m5, + MAX(m6) AS m6, + MAX(m7) AS m7, + MAX(m8) AS m8, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_radar_only_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_device_radar_s28_only_query(time_from_str, time_to_str, device_id): + sql = f""" + SELECT + time, + (s2+s3+s4+s5+s6+s7+s8)/7 AS s28, + (m2+m3+m4+m5+m6+m7+m8)/7 AS m28 + FROM + radar_readings + WHERE + device_id = {device_id} + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + time ASC + """ + return sql + +def get_device_radar_only_query(devices_list_str, time_from_str, time_to_str, device_id): + sql = f""" + SELECT + time, + (s2+s3+s4+s5+s6+s7+s8)/7 AS s28, + (m2+m3+m4+m5+m6+m7+m8)/7 AS m28 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + time ASC + """ + return sql + + +def get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list) list of different unique fields required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + radar_fields_to_get = "" + q_parts = "" + for field in radar_fields_of_interest: + if field == "s28_min": + q_part = "MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min" + elif field == "s28_max": + q_part = "MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max" + elif field == "m08_max": + q_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max" + elif field == "s2_max": + q_part = "MAX(s2) AS s2_max" + elif field == "s3_max": + q_part = "MAX(s3) AS s3_max" + elif field == "s4_max": + q_part = "MAX(s4) AS s4_max" + elif field == "s5_max": + q_part = "MAX(s5) AS s5_max" + elif field == "s6_max": + q_part = "MAX(s6) AS s6_max" + elif field == "s7_max": + q_part = "MAX(s7) AS s7_max" + elif field == "s8_max": + q_part = "MAX(s8) AS s8_max" + elif field == "m0_max": + q_part = "MAX(m0) AS m0_max" + elif field == "m1_max": + q_part = "MAX(m1) AS m1_max" + elif field == "m2_max": + q_part = "MAX(m2) AS m2_max" + elif field == "m3_max": + q_part = "MAX(m3) AS m3_max" + elif field == "m4_max": + q_part = "MAX(m4) AS m4_max" + elif field == "m5_max": + q_part = "MAX(m5) AS m5_max" + elif field == "m6_max": + q_part = "MAX(m6) AS m6_max" + elif field == "m7_max": + q_part = "MAX(m7) AS m7_max" + elif field == "m8_max": + q_part = "MAX(m8) AS m8_max" + else: + q_part = field + + if q_parts == "": + q_parts = q_part + else: + q_parts = q_parts + ", " + q_part + if radar_fields_to_get == "": + radar_fields_to_get = field + else: + radar_fields_to_get = radar_fields_to_get + ", " + field + sql = f""" + SELECT + minute, + device_id, + {radar_fields_to_get} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {q_parts} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_radar_only_colapsed_query_wid(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + radar_fields_to_get = "" + q_parts = "" + for field in radar_fields_of_interest: + if field == "s28_min": + q_part = "MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min" + elif field == "s28_max": + q_part = "MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max" + elif field == "m08_max": + q_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max" + elif field == "s2_max": + q_part = "MAX(s2) AS s2_max" + elif field == "s3_max": + q_part = "MAX(s3) AS s3_max" + elif field == "s4_max": + q_part = "MAX(s4) AS s4_max" + elif field == "s5_max": + q_part = "MAX(s5) AS s5_max" + elif field == "s6_max": + q_part = "MAX(s6) AS s6_max" + elif field == "s7_max": + q_part = "MAX(s7) AS s7_max" + elif field == "s8_max": + q_part = "MAX(s8) AS s8_max" + elif field == "m0_max": + q_part = "MAX(m0) AS m0_max" + elif field == "m1_max": + q_part = "MAX(m1) AS m1_max" + elif field == "m2_max": + q_part = "MAX(m2) AS m2_max" + elif field == "m3_max": + q_part = "MAX(m3) AS m3_max" + elif field == "m4_max": + q_part = "MAX(m4) AS m4_max" + elif field == "m5_max": + q_part = "MAX(m5) AS m5_max" + elif field == "m6_max": + q_part = "MAX(m6) AS m6_max" + elif field == "m7_max": + q_part = "MAX(m7) AS m7_max" + elif field == "m8_max": + q_part = "MAX(m8) AS m8_max" + else: + q_part = field + + if q_parts == "": + q_parts = q_part + else: + q_parts = q_parts + ", " + q_part + if radar_fields_to_get == "": + radar_fields_to_get = field + else: + radar_fields_to_get = radar_fields_to_get + ", " + field + sql = f""" + SELECT + minute, + device_id, + {radar_fields_to_get} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {q_parts} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_radar_only_detailed_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + This is looking for presence, NOT absence... othervise all MAXes need to be converted to MINs + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + m0_max, + m1_max, + m2_max, + m3_max, + m4_max, + m5_max, + m6_max, + m7_max, + m8_max, + m08_max, + s2_max, + s3_max, + s4_max, + s5_max, + s6_max, + s7_max, + s8_max, + s28_max, + s28_min + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(m0) AS m0_max, + MAX(m1) AS m1_max, + MAX(m2) AS m2_max, + MAX(m3) AS m3_max, + MAX(m4) AS m4_max, + MAX(m5) AS m5_max, + MAX(m6) AS m6_max, + MAX(m7) AS m7_max, + MAX(m8) AS m8_max, + MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max, + MAX(s2) AS s2_max, + MAX(s3) AS s3_max, + MAX(s4) AS s4_max, + MAX(s5) AS s5_max, + MAX(s6) AS s6_max, + MAX(s7) AS s7_max, + MAX(s8) AS s8_max, + MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max, + MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_min + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_light_only_query(devices_list_str, time_from_str, time_to_str, ids_list): + #light detailed + """ + Generate a TimeScaleDB query for light readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + light_max + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX(light) AS light_max + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_temperature_only_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + """ + Generate a TimeScaleDB query for temperature readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with temperature and alarm level + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + temperature_avg, + CASE + WHEN (temperature_avg * 9/5 + 32) <= 50 OR (temperature_avg * 9/5 + 32) >= 90 THEN 2 + WHEN (temperature_avg * 9/5 + 32) <= 60 OR (temperature_avg * 9/5 + 32) >= 80 THEN 1 + ELSE 0 + END AS alarm_level + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + AVG(temperature)+{temp_offset} AS temperature_avg + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_humidity_only_query(devices_list_str, time_from_str, time_to_str, ids_list, humidity_offset): + """ + Generate a TimeScaleDB query for humidity readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with humidity and alarm level + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + col_expr = f"2.3592 * AVG(humidity) + 23.5546" #= 2.3592 * J2 + 33.5546 + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + humidity_avg, + CASE + WHEN humidity_avg <= 20 OR humidity_avg >= 60 THEN 2 + WHEN humidity_avg <= 30 OR humidity_avg >= 50 THEN 1 + ELSE 0 + END AS alarm_level + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {col_expr} AS humidity_avg + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + +def get_deployment_smell_only_query(devices_list_str, time_from_str, time_to_str, ids_list, humidity_offset): + """ + Generate a TimeScaleDB query for smell readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + temp_offset (float): Temperature offset to apply + + Returns: + str: Generated SQL query with smell components + """ + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # SQL query with added alarm_level calculation + sql = f""" + SELECT + minute, + device_id, + min_s0, + min_s1, + min_s2, + min_s3, + min_s4, + min_s5, + min_s6, + min_s7, + min_s8, + min_s9 + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql + + +def get_deployment_radar_only_detailed_all_query(devices_list_str, time_from_str, time_to_str, ids_list): + #radar detailed + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + This is looking for presence, NOT absence... othervise all MAXes need to be converted to MINs + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + minute, + device_id, + absent_min, + stationary_max, + moving_max, + both_max, + m0_max, + m1_max, + m2_max, + m3_max, + m4_max, + m5_max, + m6_max, + m7_max, + m8_max, + m08_max, + s2_max, + s3_max, + s4_max, + s5_max, + s6_max, + s7_max, + s8_max, + s28_max + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MIN(absent) AS absent_min, + MAX(stationary) AS stationary_max, + MAX(moving) AS moving_max, + MAX("both") AS both_max, + MAX(m0) AS m0_max, + MAX(m1) AS m1_max, + MAX(m2) AS m2_max, + MAX(m3) AS m3_max, + MAX(m4) AS m4_max, + MAX(m5) AS m5_max, + MAX(m6) AS m6_max, + MAX(m7) AS m7_max, + MAX(m8) AS m8_max, + MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/9) AS m08_max, + MAX(s2) AS s2_max, + MAX(s3) AS s3_max, + MAX(s4) AS s4_max, + MAX(s5) AS s5_max, + MAX(s6) AS s6_max, + MAX(s7) AS s7_max, + MAX(s8) AS s8_max, + MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28_max + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + + ORDER BY + CASE device_id + {case_order} + END, + minute + """ + return sql +def get_deployment_deca_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset): + """ + Generate a TimeScaleDB query for sensor and radar readings based on device IDs. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + sr.avg_temperature+{temp_offset} as avg_temperature, + sr.avg_humidity, + sr.avg_pressure, + sr.max_light, + sr.min_s0 as smell_s0, + sr.min_s1 as smell_s1, + sr.min_s2 as smell_s2, + sr.min_s3 as smell_s3, + sr.min_s4 as smell_s4, + sr.min_s5 as smell_s5, + sr.min_s6 as smell_s6, + sr.min_s7 as smell_s7, + sr.min_s8 as smell_s8, + sr.min_s9 as smell_s9, + rr.absent as radar_absent, + rr.moving as radar_moving, + rr.stationary as radar_stationary, + rr.both as radar_both, + rr.m0 as radar_m0, + rr.m1 as radar_m1, + rr.m2 as radar_m2, + rr.m3 as radar_m3, + rr.m4 as radar_m4, + rr.m5 as radar_m5, + rr.m6 as radar_m6, + rr.m7 as radar_m7, + rr.m8 as radar_m8, + rr.s2 as radar_s2, + rr.s3 as radar_s3, + rr.s4 as radar_s4, + rr.s5 as radar_s5, + rr.s6 as radar_s6, + rr.s7 as radar_s7, + rr.s8 as radar_s8 + FROM ( + SELECT + time_bucket('10 seconds', time) AS minute, + device_id, + AVG(temperature) AS avg_temperature, + AVG(humidity) AS avg_humidity, + AVG(pressure) AS avg_pressure, + MAX(light) AS max_light, + MIN(CASE WHEN s0 > 0 THEN s0 END) AS min_s0, + MIN(CASE WHEN s1 > 0 THEN s1 END) AS min_s1, + MIN(CASE WHEN s2 > 0 THEN s2 END) AS min_s2, + MIN(CASE WHEN s3 > 0 THEN s3 END) AS min_s3, + MIN(CASE WHEN s4 > 0 THEN s4 END) AS min_s4, + MIN(CASE WHEN s5 > 0 THEN s5 END) AS min_s5, + MIN(CASE WHEN s6 > 0 THEN s6 END) AS min_s6, + MIN(CASE WHEN s7 > 0 THEN s7 END) AS min_s7, + MIN(CASE WHEN s8 > 0 THEN s8 END) AS min_s8, + MIN(CASE WHEN s9 > 0 THEN s9 END) AS min_s9 + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('10 seconds', time) AS minute, + device_id, + MAX(absent) AS absent, + MAX(moving) AS moving, + MAX(stationary) AS stationary, + MAX(\"both\") AS both, + MAX(m0) AS m0, + MAX(m1) AS m1, + MAX(m2) AS m2, + MAX(m3) AS m3, + MAX(m4) AS m4, + MAX(m5) AS m5, + MAX(m6) AS m6, + MAX(m7) AS m7, + MAX(m8) AS m8, + MAX(s2) AS s2, + MAX(s3) AS s3, + MAX(s4) AS s4, + MAX(s5) AS s5, + MAX(s6) AS s6, + MAX(s7) AS s7, + MAX(s8) AS s8 + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + return sql + +def get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + """ + Generate a TimeScaleDB query for radar readings based on device IDs with time snapped to 10-second intervals. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list): List of field names required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + # Handle fields processing + select_fields = [] + for field in radar_fields_of_interest: + if field == "s28": + select_fields.append("(s2+s3+s4+s5+s6+s7+s8)/7 AS s28") + else: + select_fields.append(field) + + fields_str = ", ".join(select_fields) + + sql = f""" + SELECT + time_bucket('10 seconds', time) AS ten_seconds, + device_id, + {fields_str} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + ORDER BY + CASE device_id + {case_order} + END, + ten_seconds + """ + return sql + +def get_deployment_radar_10sec_snapped_query_min_max(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest): + """ + Generate a TimeScaleDB query for radar readings based on device IDs with time snapped to 10-second intervals. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_fields_of_interest (list): List of field names required across all devices + + Returns: + str: Generated SQL query + """ + + # Generate the CASE statement for ordering based on the provided ids_list + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + + case_order = "\n ".join(case_statements) + + # Handle fields processing + select_fields = [] + for field in radar_fields_of_interest: + + radar_fields = field.split("_") + field_t = radar_fields[0] + if field_t == "s28": + if radar_fields[1] == "max": + select_fields.append("MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS s28") + else: + select_fields.append("MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS s28") + else: + if radar_fields[1] == "max": + select_fields.append(f"MAX({field_t}) as {field}") + else: + select_fields.append(f"MIN({field_t}) as {field}") + + fields_str = ", ".join(select_fields) + + sql = f""" + SELECT + time_bucket('10 seconds', time) AS ten_seconds, + device_id, + {fields_str} + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + ten_seconds, + device_id + ORDER BY + CASE device_id + {case_order} + END, + ten_seconds + """ + return sql + +def export_query_to_minio_chunked(connection_params, query, minio_client, bucket_name, blob_name=None, chunksize=10000): + """ + Export query results to MinIO as CSV in chunks to handle large datasets + + Parameters: + connection_params (dict): Database connection parameters + query (str): SQL query to execute + minio_client: Initialized MinIO client + bucket_name (str): Name of the MinIO bucket + blob_name (str): Name for the blob in MinIO. If None, generates timestamped name + chunksize (int): Number of rows to process at a time + + Returns: + str: Name of the created blob + """ + try: + # Create direct connection using psycopg2 + conn = psycopg2.connect(**connection_params) + + # Generate blob name if not provided + if blob_name is None: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + blob_name = f'query_results_{timestamp}.csv' + + # Create a buffer to store CSV data + csv_buffer = io.StringIO() + + # Stream the query results in chunks + first_chunk = True + for chunk_df in pd.read_sql_query(query, conn, chunksize=chunksize): + # Write header only for the first chunk + chunk_df.to_csv( + csv_buffer, + index=False, + header=first_chunk, + mode='a' + ) + first_chunk = False + + # Get the CSV data as bytes + csv_buffer.seek(0) + csv_bytes = csv_buffer.getvalue().encode('utf-8') + + # Upload to MinIO + minio_client.put_object( + bucket_name, + blob_name, + io.BytesIO(csv_bytes), + len(csv_bytes) + ) + + print(f"Data exported successfully to MinIO: {bucket_name}/{blob_name}") + return blob_name + + except Exception as e: + print(f"Error exporting data: {str(e)}") + print(f"Traceback: {traceback.format_exc()}") + raise + + finally: + if 'conn' in locals(): + conn.close() + if 'csv_buffer' in locals(): + csv_buffer.close() + +def export_query_to_csv_pandas(connection_params, query, output_path=None): + """ + Export query results to CSV using pandas with psycopg2 connection + + Parameters: + connection_params (dict): Database connection parameters + query (str): SQL query to execute + output_path (str): Path for output CSV file. If None, generates timestamped filename + + Returns: + str: Path to the created CSV file + """ + try: + # Create direct connection using psycopg2 + conn = psycopg2.connect(**connection_params) + + # Generate output path if not provided + if output_path is None: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + output_path = f'query_results_{timestamp}.csv' + + # Read query directly into DataFrame using the psycopg2 connection + df = pd.read_sql_query(query, conn) + + # Export to CSV with all headers + df.to_csv(output_path, index=False) + print(f"Data exported successfully to {output_path}") + return output_path + + except Exception as e: + print(f"Error exporting data: {str(e)}") + raise + + finally: + if 'conn' in locals(): + conn.close() + + +def CreateDailyCSV(csv_file, devices_list, selected_date, vocs_scaled, time_zone_s, radar_part, consolidated_by, temp_offset): + global Id2MACDict + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + try: + + #stretch_to_min_max = True + + #current_date_p = selected_date.replace("-", "_") + #current_date_s = selected_date + + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + for details in devices_list[0]: + dev_id = details[0] + ids_list.append(details[1]) + if dev_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return "" + + if larger_than200 > 0: + sensors_c = len(s_table) + else: #old sensors not supported + return "" + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + if consolidated_by == "by_minute_rc": + sql = get_deployment_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset) + elif consolidated_by == "by_deca_rd": + sql = get_deployment_deca_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset) + elif consolidated_by == "by_minute_rd": + sql = get_deployment_rd_query(devices_list_str, time_from_str, time_to_str, ids_list, temp_offset) + + print(sql) + + connection_params = { + 'host': DB_HOST, + 'database': DB_NAME, + 'user': DB_USER, + 'password': DB_PASSWORD, + 'port': DB_PORT + } + + # Using pandas approach (recommended) + output_file = export_query_to_minio_chunked( + connection_params, + sql, + miniIO_blob_client, + "data-downloads", + csv_file, + chunksize=10000 + ) + return output_file + except Exception as e: + logging.error(str(traceback.format_exc())) + return "" + +def GetBlob(file_name, bucket_name="daily-maps"): + """ + Retrieve image from blob storage + + Args: + file_name (str): Name of the file to retrieve from blob storage + + Returns: + tuple: (image_bytes, content_type) + Returns None, None if image not found or error occurs + """ + logger.debug(f"GetBlob({file_name})") + try: + # Get the object from blob storage + data = miniIO_blob_client.get_object( + bucket_name, + file_name + ) + + # Read the data into bytes + data_bytes = data.read() + #logger.debug(f"len(data_bytes)={len(data_bytes)}") + + if bucket_name == "daily-maps": + return data_bytes, 'image/png' + else: + return data_bytes, 'application/zip' + + except Exception as e: + logger.error(f"Error: {traceback.format_exc()}") + return None, None + + +def MapFileToDate(map_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + parts = map_file.split("/") + parts = parts[-1].split("_") + + if "-" in parts[0]: + date_string = parts[0] + elif "-" in parts[1]: + date_string = parts[1] + + date_object = datetime.datetime.strptime(date_string, "%Y-%m-%d") + date_only = date_object.date() + return date_only + +def CSVFileToDate(csv_file): + #'/Volumes/XTRM-Q/wellnuo/daily_maps/1/1_2023-11-07_dayly_image.png' + parts = csv_file.split("/") + parts = parts[-1].split("_") + + if "-" in parts[0]: + date_string = parts[0] + elif "-" in parts[1]: + date_string = parts[1] + + date_object = datetime.datetime.strptime(date_string, "%Y-%m-%d") + date_only = date_object.date() + return date_only + +def GetMACsListSimple(list_of_lists): + + result = [] + if len(list_of_lists) > 0: + result = [sublist[3] for sublist in list_of_lists] + + return(result) + +def datetime_handler(obj): + """Handle datetime serialization for JSON""" + if isinstance(obj, datetime.datetime): + if obj.tzinfo: + return obj.isoformat() + return obj.strftime('%Y-%m-%d %H:%M:%S.%f') + raise TypeError(f"Object of type {type(obj)} is not JSON serializable") + +def ReadCandles(file, sensor, period, time_from, time_to): + result = [] + if sensor == "voc0": + sqlr = "SELECT * from vocs_0"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc1": + sqlr = "SELECT * from vocs_1"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc2": + sqlr = "SELECT * from vocs_2"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc3": + sqlr = "SELECT * from vocs_3"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc4": + sqlr = "SELECT * from vocs_4"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc5": + sqlr = "SELECT * from vocs_5"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc6": + sqlr = "SELECT * from vocs_6"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc7": + sqlr = "SELECT * from vocs_7"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc8": + sqlr = "SELECT * from vocs_8"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + elif sensor == "voc9": + sqlr = "SELECT * from vocs_9"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + else: + sqlr = "SELECT * from "+sensor+"s"+period+ " WHERE Date >= "+str(time_from) + " AND Date <= "+str(time_to) + logger.debug(f"sqlr = {sqlr}") + + with get_db_connection() as conn: + with conn.cursor() as cur: + devices_string = ReadCleanStringDB(cur, sqlr) + result = QuerrySql(file, sqlr) + return result + +def ReadSensor(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + result = [] + if sensor == "radar": + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor[0] == "s": + sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + if sensor == "temperature": + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype=17 ORDER BY time ASC" + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadSensorDeltas(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + 'for now pressure and light only' + sensor_2_mtype = {"light": 4, "pressure": 1, "temperature": 2, "humidity": 3} + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + result = [] + if sensor == "radar": + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor[0] == "s": + sqlr = f"SELECT time, {sensor} AS smell FROM sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' and mtype=4 ORDER BY time ASC" + else: + if sensor == "temperature": + sqlr = f"SELECT time, {sensor} - 16 from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + elif sensor == "pressure": + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype=1 ORDER BY time ASC" + else: + mtype = sensor_2_mtype[sensor] + sqlr = f"SELECT time, {sensor} from sensor_readings WHERE device_id = {device_id} AND {sensor} >= '{legal_min}' AND {sensor} <= '{legal_max}' AND time >= '{time_from_str}' AND time <= '{time_to_str}' AND mtype={mtype} ORDER BY time ASC" + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadSensor3(device_id, sensor, time_from_epoch, time_to_epoch, data_type, radar_part, bucket_size="no"): + import datetime + from datetime import timezone + + # Convert epoch to datetime and format as ISO 8601 strings with timezone + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + legal_min, legal_max, window = sensor_legal_values[sensor] + + # If bucket_size is provided (i.e. not "no"), then use time bucketing. + use_bucket = bucket_size != "no" + if use_bucket: + # Map the shorthand bucket sizes to PostgreSQL interval strings. + mapping = { + "10s": "10 seconds", + "1m": "1 minute", + "5m": "5 minutes", + "10m": "10 minutes", + "15m": "15 minutes", + "30m": "30 minutes", + "1h": "1 hour" + } + bucket_interval = mapping.get(bucket_size, bucket_size) + + avgmax = "AVG" + # Build the SQL query based on sensor type. + if sensor == "radar": + # For radar sensors, a special aggregation is needed. + avgmax = "MAX" + if radar_part == "s28": + radar_expr = "(s2+s3+s4+s5+s6+s7+s8)/7" + else: + radar_expr = radar_part + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({radar_expr}) AS radar + FROM radar_readings + WHERE device_id = {device_id} + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {radar_expr} AS radar + FROM radar_readings + WHERE device_id = {device_id} + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor[0] == "s": + # For sensors whose name starts with "s" (for example, smell sensors) + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({sensor}) AS smell + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {sensor} AS smell + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "co2": + alias = sensor + sensor = "s4" + sqlr = f""" + WITH s4_values AS ( + SELECT s4 + FROM public.sensor_readings + WHERE device_id = 559 + AND s4 IS NOT NULL + ), + s4_percentile AS ( + SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY s4 DESC) AS s4_25_percentile + FROM s4_values + ) + SELECT s4_25_percentile + FROM s4_percentile; + """ + co2_max = 22536000#102400000 + co2_min = 2400000#16825674 #387585 + + real_co2_max = 2000 + real_co2_min = 430 + + + #logger.debug(f"sqlr = {sqlr}") + #with get_db_connection() as conn: + #with conn.cursor() as cur: + #cur.execute(sqlr) + #result = cur.fetchall() + #co2_max = result[0][0] + #co2_min = result[0][1] + #=E17+E20*(102400000-A24)/B18 + #col_expr = f"{real_co2_min}+({real_co2_max}-{real_co2_min})*(102400000-{sensor})/({co2_min}-{co2_max})" + col_expr = f"GREATEST({real_co2_min},{real_co2_min}+({real_co2_max}-{real_co2_min})*({co2_max}-percentile_cont(0.5) WITHIN GROUP (ORDER BY {sensor}))/({co2_max}-{co2_min}))" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + ({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "voc": + sensor = "s9" + alias = sensor + col_expr = f"{sensor} - 0" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + elif sensor == "humidity": + alias = sensor + #col_expr = f"2.3592 * {sensor} + 32.5546" #= 2.3592 * J2 + 33.5546 + col_expr = f"1* {sensor} + 5.0" #= 2.3592 * J2 + 33.5546 + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + else: + # For other sensors (including temperature, which requires a subtraction) + alias = sensor + col_expr = sensor + if sensor == "temperature": + col_expr = f"{sensor}" + alias = "temperature" + elif sensor == "light": + avgmax = "MAX" + + if use_bucket: + sqlr = f""" + SELECT time_bucket('{bucket_interval}', time) AS time, + {avgmax}({col_expr}) AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + AND mtype = 17 + GROUP BY time_bucket('{bucket_interval}', time) + ORDER BY time ASC; + """ + else: + sqlr = f""" + SELECT time, {col_expr} AS {alias} + FROM sensor_readings + WHERE device_id = {device_id} + AND {sensor} >= '{legal_min}' + AND {sensor} <= '{legal_max}' + AND time >= '{time_from_str}' + AND time <= '{time_to_str}' + ORDER BY time ASC; + """ + + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def ReadRadarDetail(device_id, sensor, time_from_epoch, time_to_epoch, alt_key_state): + + time_utc = datetime.datetime.fromtimestamp(float(time_from_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_from_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + + time_utc = datetime.datetime.fromtimestamp(float(time_to_epoch), tz=timezone.utc) + # Format in ISO 8601 format with timezone + time_to_str = time_utc.strftime("%Y-%m-%d %H:%M:%S%z") + #sensor_index = int(sensor_index) + + + result = [] + + #time_period_sec can be "10" (RAW) or "60" + if alt_key_state == "1": #"RAW = 10 sec" + radar_part = sensor + if sensor == "m08_max": + radar_part = "(m0+m1+m2+m3+m4+m5+m6+m7+m8)/9" + elif sensor == "s28_max" or sensor == "s28_min": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + sqlr = f"SELECT time, {radar_part} AS radar FROM radar_readings WHERE device_id = {device_id} AND time >= '{time_from_str}' AND time <= '{time_to_str}' ORDER BY time ASC" + else: + if sensor == "m08_max": + radar_part = "MAX((m0+m1+m2+m3+m4+m5+m6+m7+m8)/7) AS m08_max" + elif sensor == "s28_max": + radar_part = f"MAX((s2+s3+s4+s5+s6+s7+s8)/7) AS {sensor}" + elif sensor == "s28_min": + radar_part = f"MIN((s2+s3+s4+s5+s6+s7+s8)/7) AS {sensor}" + else: + radar_part = f"MAX({sensor}) AS {sensor}" + + sqlr = f""" + SELECT + minute, + {sensor} as {sensor} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + {radar_part} + FROM + radar_readings + WHERE + device_id = {device_id} + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute + ) rr + + ORDER BY + minute + """ + logger.debug(f"sqlr = {sqlr}") + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sqlr) + result = cur.fetchall() + + return result + +def check_and_parse(data_str): + # Remove whitespace to handle cases with spaces + cleaned = data_str.strip() + # Check if second character is '[' + is_list_of_lists = cleaned[1] == '[' + if cleaned[0] == '[': + # Parse the string regardless of type + parsed = json.loads(cleaned) + else: + #parsed = cleaned.split(",") + parsed = SmartSplit(cleaned) + return is_list_of_lists, parsed + +def clean_data_with_rolling_spline(line_part_t, window=5, threshold=2.0): + """ + Filter outliers using rolling median and replace with spline interpolation + Returns data in the same format as input: [(timestamp, value), ...] + """ + # Unzip the input tuples + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) # explicitly convert to float + + # Calculate rolling median and MAD using a safer approach + rolling_median = [] + rolling_mad = [] + + for i in range(len(y)): + start_idx = max(0, i - window//2) + end_idx = min(len(y), i + window//2 + 1) + window_values = y[start_idx:end_idx] + + # Skip if window is empty or contains invalid values + if len(window_values) == 0 or np.any(np.isnan(window_values)): + rolling_median.append(y[i]) + rolling_mad.append(0) + continue + + med = np.median(window_values) + mad = np.median(np.abs(window_values - med)) + + rolling_median.append(med) + rolling_mad.append(mad) + + rolling_median = np.array(rolling_median) + rolling_mad = np.array(rolling_mad) + + # Identify outliers (protect against division by zero) + outlier_mask = np.abs(y - rolling_median) > threshold * (rolling_mad + 1e-10) + good_data_mask = ~outlier_mask + + if np.sum(good_data_mask) < 4: + return line_part_t # return original data if we can't interpolate + + try: + # Create and apply spline + spline = interpolate.InterpolatedUnivariateSpline( + x[good_data_mask], + y[good_data_mask], + k=3 + ) + + y_cleaned = y.copy() + y_cleaned[outlier_mask] = spline(x[outlier_mask]) + except Exception as e: + print(f"Spline interpolation failed: {e}") + return line_part_t + + # Return in the same format as input + return list(zip(x, y_cleaned)) + +def DatesSpan(date_from: str, date_to: str) -> list: + """ + Generate a list of dates between date_from and date_to (inclusive). + Handles cases where date_from is later than date_to. + + Args: + date_from (str): Start date in 'YYYY-MM-DD' format + date_to (str): End date in 'YYYY-MM-DD' format + + Returns: + list: List of dates in 'YYYY-MM-DD' format + """ + # Convert string dates to datetime objects + start_date = datetime.datetime.strptime(date_from, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + start_date, end_date = end_date, start_date + + # Generate list of dates + dates_list = [] + current_date = start_date + + while current_date <= end_date: + dates_list.append(current_date.strftime('%Y-%m-%d')) + current_date += timedelta(days=1) + + # Reverse the list if original date_from was later than date_to + #if datetime.datetime.strptime(date_from, '%Y-%m-%d') > datetime.datetime.strptime(date_to, '%Y-%m-%d'): + # dates_list.reverse() + + return dates_list + +def zip_blobs(blob_paths, zip_blob_name, bucket_name, minio_client=None): + """ + Zip multiple blobs from MinIO storage into a single zip file without saving locally. + + Args: + blob_paths (list): List of blob paths to zip + zip_blob_name (str): Name/path for the output zip file in MinIO + bucket_name (str): MinIO bucket name + minio_client (Minio, optional): Existing MinIO client instance + + Returns: + bool: True if successful, False otherwise + """ + try: + # Create zip file in memory + zip_buffer = BytesIO() + + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + # Process each blob + for blob_path in blob_paths: + # Get file name from path for zip entry + file_name = blob_path.split('/')[-1] + + # Get blob data into memory + data = minio_client.get_object(bucket_name, blob_path.lstrip('/')) + + # Add file to zip + zip_file.writestr(file_name, data.read()) + + # Close the object to free memory + data.close() + + # Seek to start of zip file + zip_buffer.seek(0) + + # Upload zip file to MinIO + minio_client.put_object( + bucket_name, + zip_blob_name.lstrip('/'), + zip_buffer, + length=zip_buffer.getbuffer().nbytes + ) + + return True + + except Exception as e: + print(f"Error creating zip file: {str(e)}") + return False + finally: + # Clean up + zip_buffer.close() + +def clean_data_with_spline(x, y, threshold=2.0): + """ + Filter outliers and replace with spline interpolation + + Parameters: + x : array-like, timestamps or x-coordinates + y : array-like, values to be filtered + threshold : float, number of median absolute deviations for outlier detection + + Returns: + array-like : cleaned data with outliers replaced by spline interpolation + """ + # Convert inputs to numpy arrays + x = np.array(x) + y = np.array(y) + + # Calculate median and median absolute deviation + median = np.median(y) + mad = stats.median_abs_deviation(y) + + # Identify outliers + outlier_mask = np.abs(y - median) > threshold * mad + good_data_mask = ~outlier_mask + + # If we have too few good points for interpolation, adjust threshold + min_points_needed = 4 # minimum points needed for cubic spline + if np.sum(good_data_mask) < min_points_needed: + return y # return original data if we can't interpolate + + # Create spline with non-outlier data + spline = interpolate.InterpolatedUnivariateSpline( + x[good_data_mask], + y[good_data_mask], + k=3 # cubic spline + ) + + # Replace outliers with interpolated values + y_cleaned = y.copy() + y_cleaned[outlier_mask] = spline(x[outlier_mask]) + + return y_cleaned + +def clean_data(line_part_t, window=5, threshold=2.0): + """ + Remove obvious outliers based on window comparison + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) + + cleaned_data = [] + + for i in range(len(y)): + # Get window around current point + start_idx = max(0, i - window//2) + end_idx = min(len(y), i + window//2 + 1) + window_values = y[start_idx:end_idx] + + # Calculate median and MAD for the window + window_median = np.median(window_values) + deviation = abs(y[i] - window_median) + + # Keep point if it's not too far from window median + if deviation <= threshold * window_median: + cleaned_data.append((x[i], y[i])) + #else: + #print(window_values) + return cleaned_data + +def clean_data_fast(line_part_t, window=5, threshold=2.0): + """ + Remove obvious outliers based on window comparison - vectorized version + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + x = np.array(x) + y = np.array(y, dtype=float) + + # Calculate rolling median using numpy + half_window = window // 2 + medians = np.array([ + np.median(y[max(0, i-half_window):min(len(y), i+half_window+1)]) + for i in range(len(y)) + ]) + + # Calculate deviations for all points at once + deviations = np.abs(y - medians) + + # Create mask for good points + good_points = deviations <= threshold * medians + + # Return filtered data using boolean indexing + return list(zip(x[good_points], y[good_points])) + +def clean_data_pd(line_part_t, window=5, percentile=99): + """ + Remove obvious outliers based on window comparison - pandas version + Returns cleaned data in the same format: [(timestamp, value), ...] + """ + #line_part_t = line_part_t[2000:2100] + if len(line_part_t) < window: + return line_part_t + + x, y = zip(*line_part_t) + + # Create pandas Series and calculate rolling median + series = pd.Series(y) + medians = series.rolling(window=window, center=True, min_periods=1).median() + + # Calculate deviations + deviations = np.abs(series - medians) + + largest_deviations = deviations.nlargest(10) + #print(largest_deviations) + + # Create mask for good points + deviation_threshold = np.percentile(deviations, percentile) + good_points = deviations <= deviation_threshold + + # Convert back to numpy arrays for filtering + x = np.array(x) + y = np.array(y) + + # Return filtered data + return list(zip(x[good_points], y[good_points])) + +def CombineStripes(result_filename, stripes_files): + try: + # Open the first image to get the width and initialize the height + first_image = Image.open(stripes_files[0]) + width, height = first_image.size + + # Calculate the total height of the combined image + total_height = height * len(stripes_files) + + # Create a new blank image with the same width and the calculated height + result_image = Image.new('RGB', (width, total_height)) + + # Paste each image onto the result image vertically + y_offset = 0 + for file_name in stripes_files: + image = Image.open(file_name) + result_image.paste(image, (0, y_offset)) + y_offset += height + + # Save the result image + result_image.save(result_filename) + + # Return success flag + return True + + except Exception as e: + print("Error:", e) + return False + +def FindFirstLocalMinimum(counts, bins): + """ + Find the first local minimum in a histogram after the main peak and calculate its offset. + + Parameters: + hist: tuple of (counts, bin_edges) from np.histogram() + The histogram data to analyze + + Returns: + tuple: (TR, THR_OFFSET) + TR: float, the bin value (position) of the first local minimum after the main peak + THR_OFFSET: int, number of bins between the global maximum and the local minimum + """ + + # Find the main peak (global maximum) + main_peak_idx = np.argmax(counts) + + # Look for the first local minimum after the main peak + for i in range(main_peak_idx + 1, len(counts) - 1): + # Check if current point is less than or equal to both neighbors + if counts[i] <= counts[i-1] and counts[i] <= counts[i+1]: + # Calculate the bin center value for TR + TR = (bins[i] + bins[i+1]) / 2 + # Calculate offset from main peak in number of bins + THR_OFFSET = (bins[i] + bins[i+1]) / 2 - (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2 + return (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2, TR, THR_OFFSET + + # If no local minimum is found, return None for both values + return None, None + +def process_raw_data(data_tuples): + """ + Convert list of (timestamp, stationary, motion) tuples to separate arrays + + Parameters: + ----------- + data_tuples : list of tuples + Each tuple contains (datetime, stationary_value, motion_value) + + Returns: + -------- + timestamps : array of datetime + stationary : array of float + motion : array of float + """ + timestamps = np.array([t[0] for t in data_tuples]) + stationary = np.array([t[1] for t in data_tuples]) + motion = np.array([t[2] for t in data_tuples]) + + return timestamps, stationary, motion + +def rolling_std_fast(arr, window_size): + """ + Fast calculation of rolling standard deviation using NumPy's stride tricks. + + Parameters: + ----------- + arr : numpy array + Input array + window_size : int + Size of rolling window + + Returns: + -------- + numpy array + Rolling standard deviation + """ + # Compute rolling sum of squares + r = np.array(arr, dtype=float) + r2 = np.array(arr, dtype=float) ** 2 + + # Calculate cumulative sums + cum = np.cumsum(np.insert(r, 0, 0)) + cum2 = np.cumsum(np.insert(r2, 0, 0)) + + # Get rolling windows + x = (cum[window_size:] - cum[:-window_size]) + x2 = (cum2[window_size:] - cum2[:-window_size]) + + # Calculate standard deviation + n = window_size + return np.sqrt((x2/n) - (x/n) ** 2) + +def detect_presence(timestamps, stationary_signal, motion_signal, window_size=100, + motion_threshold=5, gmm_components=2): + """ + Detect presence using both stationary and motion signals with adaptive thresholding. + + Parameters: + ----------- + timestamps : array-like + Array of datetime objects + stationary_signal : array-like + Time series of stationary signal (0-100) + motion_signal : array-like + Time series of motion signal (0-100) + window_size : int + Size of rolling window for statistics (used only for temporal smoothing) + motion_threshold : float + Threshold for significant motion + gmm_components : int + Number of components for Gaussian Mixture Model + + Returns: + -------- + presence_mask : numpy array + Boolean array indicating presence + baseline : float + Computed baseline for stationary signal + threshold : float + Computed threshold for stationary signal + """ + + # Convert inputs to numpy arrays + stationary_signal = np.array(stationary_signal) + motion_signal = np.array(motion_signal) + + # 1. Fit Gaussian Mixture Model to stationary signal + gmm = GaussianMixture(n_components=gmm_components, random_state=42) + X = stationary_signal.reshape(-1, 1) + gmm.fit(X) + + # Get the component with lowest mean as baseline + baseline = min(gmm.means_)[0] + + # 2. Calculate adaptive threshold using GMM components + components_sorted = sorted(zip(gmm.means_.flatten(), gmm.covariances_.flatten())) + baseline_std = np.sqrt(components_sorted[0][1]) + threshold = baseline + 3 * baseline_std # 3 sigma rule + + # 3. Combine motion and stationary detection + presence_mask = np.zeros(len(stationary_signal), dtype=bool) + + # Vectorized operations instead of loop + presence_mask = (motion_signal > motion_threshold) | (stationary_signal > threshold) + + # 4. Apply temporal smoothing to reduce false transitions + smooth_window = min(window_size // 4, 10) # Smaller window for smoothing + presence_mask = np.convolve(presence_mask.astype(int), + np.ones(smooth_window)/smooth_window, + mode='same') > 0.5 + + return presence_mask, baseline, threshold + +def visualize_detection(timestamps, stationary_signal, motion_signal, presence_mask, + baseline, threshold, output_file='presence_detection.png'): + """ + Visualize the detection results and save to file. + + Parameters: + ----------- + timestamps : array-like + Array of datetime objects + stationary_signal : array-like + Time series of stationary signal + motion_signal : array-like + Time series of motion signal + presence_mask : array-like + Boolean array indicating presence + baseline : float + Computed baseline for stationary signal + threshold : float + Computed threshold for stationary signal + output_file : str + Path to save the output PNG file + """ + plt.figure(figsize=(15, 10)) + + # Configure time formatting + date_formatter = mdates.DateFormatter('%H:%M:%S') + + # Plot signals + plt.subplot(3, 1, 1) + plt.plot(timestamps, stationary_signal, label='Stationary Signal') + plt.axhline(y=baseline, color='g', linestyle='--', label='Baseline') + plt.axhline(y=threshold, color='r', linestyle='--', label='Threshold') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.legend() + plt.title('Stationary Signal with Baseline and Threshold') + plt.grid(True) + + plt.subplot(3, 1, 2) + plt.plot(timestamps, motion_signal, label='Motion Signal') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.legend() + plt.title('Motion Signal') + plt.grid(True) + + plt.subplot(3, 1, 3) + plt.plot(timestamps, presence_mask, label='Presence Detection') + plt.gca().xaxis.set_major_formatter(date_formatter) + plt.ylim(-0.1, 1.1) + plt.legend() + plt.title('Presence Detection Result') + plt.grid(True) + + plt.tight_layout() + + # Save to file and close figure to free memory + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.close() + +def FindZeroIntersection(counts, bins, save_plot, device_id): + """ + Find the zero intersection point by fitting a parabola to the descending slope + between 50% and 10% of the maximum peak height. Also returns the peak position. + + Parameters: + counts: array-like + The histogram counts + bins: array-like + The histogram bin edges + save_plot: str or None, optional + If provided, saves the visualization to the specified file path + + Returns: + tuple: (zero_intersections, peak_position) + zero_intersections: list of floats, x-coordinates where parabola intersects y=0 + peak_position: float, x-coordinate of the histogram maximum peak + """ + # Find the main peak + main_peak_idx = np.argmax(counts) + peak_height = counts[main_peak_idx] + peak_position = (bins[main_peak_idx] + bins[main_peak_idx+1]) / 2 + + # Calculate 75% and 25% of peak height + height_50 = 0.50 * peak_height + height_10 = 0.10 * peak_height + + # Find indices where counts cross these thresholds after the peak + idx_50 = main_peak_idx + idx_10 = main_peak_idx + + for i in range(main_peak_idx, len(counts)): + if counts[i] <= height_50 and idx_50 == main_peak_idx: + idx_50 = i + if counts[i] <= height_10: + idx_10 = i + break + + # If we couldn't find valid points, return None + if idx_50 == main_peak_idx or idx_10 == main_peak_idx: + return None, peak_position + + # Get x and y coordinates for fitting + # Use bin centers for x coordinates + x_points = np.array([(bins[i] + bins[i+1])/2 for i in range(idx_50, idx_10+1)]) + y_points = counts[idx_50:idx_10+1] + + # Define quadratic function for fitting + def quadratic(x, a, b, c): + return a*x**2 + b*x + c + + try: + popt, pcov = curve_fit(quadratic, x_points, y_points) + a, b, c = popt + + # Find zeros using quadratic formula + if a != 0: + discriminant = b**2 - 4*a*c + if discriminant >= 0: + x1 = (-b + np.sqrt(discriminant)) / (2*a) + x2 = (-b - np.sqrt(discriminant)) / (2*a) + zero_intersections = sorted([x1, x2]) + # Filter zeros to only include those after the peak + zero_intersections = [x for x in zero_intersections if x > peak_position] + else: + zero_intersections = [] + else: + # If a ≈ 0, fallback to linear solution + if b != 0: + zero_intersections = [-c/b] + else: + zero_intersections = [] + + if save_plot: + plt.figure(figsize=(10, 6)) + + # Plot histogram + bin_centers = [(bins[i] + bins[i+1])/2 for i in range(len(counts))] + plt.bar(bin_centers, counts, width=bins[1]-bins[0], alpha=0.6, + color='skyblue', label='Histogram') + + # Plot peak height lines + plt.axhline(y=height_50, color='g', linestyle='--', alpha=0.5, + label='50% Peak Height') + plt.axhline(y=height_10, color='r', linestyle='--', alpha=0.5, + label='10% Peak Height') + + # Plot fitted parabola + x_fit = np.linspace(min(x_points), max(x_points), 100) + y_fit = quadratic(x_fit, a, b, c) + plt.plot(x_fit, y_fit, 'r-', label='Fitted Parabola') + + # Plot points used for fitting + plt.plot(x_points, y_points, 'ro', alpha=0.5, label='Fitting Points') + + # Plot zero intersections + for x_zero in zero_intersections: + plt.plot(x_zero, 0, 'ko', label='Zero Intersection') + + # Plot peak position + plt.axvline(x=peak_position, color='purple', linestyle='--', alpha=0.5, + label='Peak Position') + + # Add labels and legend + plt.xlabel('Bin Values') + plt.ylabel('Counts') + plt.title(f'Histogram Analysis with Parabolic Fit {device_id}') + plt.legend() + + # Show zero line + plt.axhline(y=0, color='k', linestyle='-', alpha=0.2) + + # Add text with intersection and peak values + text = f'Peak Position: {peak_position:.2f}\n' + if zero_intersections: + text += f'Zero Intersection(s): {", ".join([f"{x:.2f}" for x in zero_intersections])}' + plt.text(0.02, 0.98, text, transform=plt.gca().transAxes, + verticalalignment='top', + bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)) + + # Save plot to file + plt.savefig(save_plot, dpi=300, bbox_inches='tight') + plt.close() + + return zero_intersections, peak_position + except RuntimeError: + print("Warning: Failed to fit parabola") + return None, peak_position + + +def GeneratePresenceHistory(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + #maps_dates, proximity = GetDeploymentDatesBoth(deployment_id) + minutes = 1440 + stripes_files = [] + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + maps_dates.reverse() + days = len(maps_dates) + stretch_by = int(1000 / days) + if stretch_by > 50: + stretch_by = 50 + + #lets use 1000 pixels + #arr_stretched = np.zeros((int(days*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + result_image = Image.new('RGB', (minutes, int(days*stretch_by))) + + + # Paste each image onto the result image vertically + y_offset = 0 + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + + file_exists, time_modified_utc = check_file_exists(filename_day) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + file_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if file_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) + + + image_bytes, content_type = GetBlob(filename_day) + if image_bytes != None: + image_stream = io.BytesIO(image_bytes) + image = Image.open(image_stream) + + #image = Image.open(file_name) + result_image.paste(image, (0, y_offset)) + image.close() + image_stream.close() + + y_offset += stretch_by + + # Save directly to MinIO instead of local file + success = save_to_minio(result_image, filename, DAILY_MAPS_BUCKET_NAME) + # Clean up + result_image.close() + return success + +def AddText(room_image_cv2, x, y, room_name, font_size): + pil_im = Image.fromarray(room_image_cv2) + draw = ImageDraw.Draw(pil_im) + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + #print(f"Attempting to load font from: {font_path}") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error(f"Poppins font not found in {font_path}. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + draw.text((x, y), room_name, font=font, fill=(150, 150, 150)) # Black color in RGB + room_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + return room_image_cv2 + +def AddTextList(room_image_cv2, strings_list, font_size): + pil_im = Image.fromarray(room_image_cv2) + draw = ImageDraw.Draw(pil_im) + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error("Poppins font not found. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + + for x, y, room_name in strings_list: + draw.text((x, y), room_name, font=font, fill=(150, 150, 150)) # Black color in RGB + + room_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + return room_image_cv2 + + +def AddRoomData(room_image, room_name, data): + + # Example usage: + radius = 10 + color_t = data["color"] # BGR format for red + color = (color_t[2], color_t[1], color_t[0]) + x_offset = 12 + + room_image = AddText(room_image, 13, 20, room_name, 50) + print(data) + for present in data["presence"]: + device_id, minute, duration = present + #duration = 10 + top_left = (x_offset + minute, 140) #bottom_right = (300, 200) + bottom_right = (x_offset + minute + duration, 260) + draw_rounded_rectangle(room_image, top_left, bottom_right, radius, color) + return room_image + +def AddFooterData(image): + '12, 370, 736, 1092, 1452' + step_size = 1440 / 4 + string_width = 60 + offset = 12 + yoffset = 30 + step = 0 + font_size = 40 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "3 AM", font_size) + step = 1 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "9 AM", font_size) + step = 2 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "3 PM", font_size) + step = 3 + image = AddText(image, offset+step * step_size + step_size/2 - string_width/2, yoffset, "9 PM", font_size) + return image + +def draw_rounded_rectangle(image, top_left, bottom_right, radius, color): + """ + Draw a filled rectangle with rounded corners, using simple rectangle for small dimensions + :param image: Image to draw on + :param top_left: Top-left corner coordinates (x, y) + :param bottom_right: Bottom-right corner coordinates (x, y) + :param radius: Desired corner radius (will be adjusted if needed) + :param color: Rectangle color in BGR format + """ + x1, y1 = top_left + x2, y2 = bottom_right + + # Calculate width + width = x2 - x1 + + + # Adjust radius if width or height is too small + # Maximum radius should be half of the smaller dimension + max_radius = abs(width) // 2 + radius = min(radius, max_radius) + + # If width is too small, fallback to regular rectangle + if width <= 4 or radius <= 1: + cv2.rectangle(image, top_left, bottom_right, color, -1) + return + + # Adjust radius if needed + radius = min(radius, width // 2) + + # Create points for the main rectangle + pts = np.array([ + [x1 + radius, y1], + [x2 - radius, y1], + [x2, y1 + radius], + [x2, y2 - radius], + [x2 - radius, y2], + [x1 + radius, y2], + [x1, y2 - radius], + [x1, y1 + radius] + ], np.int32) + + # Fill the main shape + cv2.fillPoly(image, [pts], color) + + # Fill the corners + cv2.ellipse(image, (x1 + radius, y1 + radius), (radius, radius), 180, 0, 90, color, -1) + cv2.ellipse(image, (x2 - radius, y1 + radius), (radius, radius), 270, 0, 90, color, -1) + cv2.ellipse(image, (x1 + radius, y2 - radius), (radius, radius), 90, 0, 90, color, -1) + cv2.ellipse(image, (x2 - radius, y2 - radius), (radius, radius), 0, 0, 90, color, -1) + +def filter_device(locations_list, device_id): + result = [] + for entry in locations_list: + if entry[0] == device_id: + result.append(entry) + + return result + +def GenerateLocationsMap(date_st, devices_list, devices_map, locations_list, time_zone_s): + + devices_list_t = [("date",date_st)] + + + for mac in devices_list: + well_id, device_id, room = devices_map[mac] + #room = devices[well_id][0] + if room in Loc2Color: + color = Loc2Color[room][0] + else: + color = Loc2Color[room.split()[0]][0] + presence_data = filter_device(locations_list, device_id) + room_details = (room, {"color": color, "presence": presence_data}) + devices_list_t.append(room_details) + + well_id = 0 + device_id = 0 + room = "Outside/?" + color = (0, 0, 0) + + #lets's not draw future unknown! + presence_data = filter_device(locations_list, device_id) + current_utc = datetime.datetime.now(pytz.UTC) + current_date_local = current_utc.astimezone(pytz.timezone(time_zone_s)) + current_minute_of_day = current_date_local.hour * 60 + current_date_local.minute + + if date_st == current_date_local.strftime('%Y-%m-%d'): + filtered_presence_data = [] + for entry in presence_data: + if entry[1] < current_minute_of_day : + if entry[1] + entry[2] < current_minute_of_day: + filtered_presence_data.append(entry) + else: + entry[2] = (current_minute_of_day - entry[2]) + if entry[2] > 0: + filtered_presence_data.append(entry) + #print(presence_data) + else: + filtered_presence_data = presence_data + + room_details = (room, {"color": color, "presence": filtered_presence_data}) + devices_list_t.append(room_details) + + return devices_list_t + +def CreateDailyLocationChart(filename_chart_image_day, locations): + result = False + header_image_file = "header.png" + room_image_file = "room.png" + footer_image_file = "footer.png" + + #ToDo: change it so it reads files from MinIo + header_image_file = os.path.join(filesDir, header_image_file) + header_image_file = header_image_file.replace("\\","/") + header_image = cv2.imread(header_image_file) + #header_height, header_width = header_image.shape[:2] + + room_image_file = os.path.join(filesDir, room_image_file) + room_image_file = room_image_file.replace("\\","/") + room_image = cv2.imread(room_image_file) + #room_height, room_width = room_image.shape[:2] + + footer_image_file = os.path.join(filesDir, footer_image_file) + footer_image_file = footer_image_file.replace("\\","/") + footer_image = cv2.imread(footer_image_file) + + all_images = [header_image] + for item_c in locations: + item = item_c[0] + if item == "date": + date = item_c[1] + else: + room_image = cv2.imread(room_image_file) + data = item_c[1] + room_image = AddRoomData(room_image, item, data) + all_images.append(room_image) + + footer_image = AddFooterData(footer_image) + all_images.append(footer_image) + final_image = np.vstack(all_images) + #this needs to write straight to MinIo ! + SaveImageInBlob(filename_chart_image_day, final_image) + result = True + #cv2.imwrite(filename_chart_image_day, final_image) + #print(rooms_count) + return result + + +def GetOptimumFontSize(target_width, text="00", min_size=1, max_size=100, tolerance=1): + """ + Find optimal font size to fit text within target width using binary search. + + Args: + target_width (int): Desired width in pixels + text (str): Text to measure (default "00") + min_size (int): Minimum font size to try + max_size (int): Maximum font size to try + tolerance (int): Acceptable difference from target width + + Returns: + int: Optimal font size + """ + while min_size <= max_size: + current_size = (min_size + max_size) // 2 + width, _ = GetStringSize(text, current_size) + + if abs(width - target_width) <= tolerance: + return current_size + elif width > target_width: + max_size = current_size - 1 + else: + min_size = current_size + 1 + + # Return the largest size that fits within target width + width, _ = GetStringSize(text, min_size) + return min_size if width <= target_width else min_size - 1 + +def GetStringSize(some_string, font_size): + font_path = os.path.join(os.path.dirname(__file__), "fonts", "Poppins-Regular.ttf") + try: + font = ImageFont.truetype(font_path, font_size) # 12px size + except: + logger.error("Poppins font not found. Please ensure the font file is in your working directory") + # Fallback to default font if Poppins is not available + font = ImageFont.load_default() + + bbox = font.getbbox(some_string) + return bbox[2] - bbox[0], bbox[3] - bbox[1] + +def GeneratePresenceHistoryChart(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + #maps_dates, proximity = GetDeploymentDatesBoth(deployment_id) + minutes = 1440 + stripes_files = [] + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + #maps_dates.reverse() + days = len(maps_dates) + #stretch_by = int(1000 / days) + #if stretch_by > 50: + #stretch_by = 50 + stretch_by = 30 + + #background_image_file = os.path.join(filesDir, "multi_day_template.png") + background_image_file = os.path.join(filesDir, "multi_day_template2.png") + background_image_file = background_image_file.replace("\\","/") + background_image = cv2.imread(background_image_file) + + rgb_image = background_image #cv2.cvtColor(background_image, cv2.COLOR_BGR2RGB) + result_image = Image.fromarray(rgb_image) # Convert to PIL Image + #result_image = Image.new('RGB', (minutes, int(days*stretch_by))) + + # Paste each image onto the result image vertically + y_offset = 0 + locations_list = [] + font_size = 50 + string_width, string_height = GetStringSize("00", font_size) + + + success = False + if len(maps_dates) == 1: + + filename_chart_image_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations_chart.png" + force_recreate = recreate_in + #it is faster to resize existing daily location chart (length is always 1440), than having to re-create it each time... + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_daily_locations.png" + filename_chart_data_day = filename_day+".bin" + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_chart_image_day) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = ddate + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + if not force_recreate: + file_exists1, time_modified_utc1 = check_file_exists(filename_chart_data_day) + if file_exists1: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = ddate + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list_a, device_ids = GetProximityList(deployment_id, timee) + CreateDailyLocationMap(filename_day, devices_list_a, ddate, filter_minutes, time_zone_s, stretch_by) + locations_list_s = ReadObjectMinIO("daily-maps", filename_chart_data_day) + locations_list = json.loads(locations_list_s) + + devices_map = {} + devices_list = [] + for device_entry in devices_list_a: + #if T: + if device_entry[3] == None or device_entry[3].strip() == "": + devices_map[device_entry[4]] = [device_entry[0], device_entry[1], device_entry[2]] + else: + devices_map[device_entry[4]] = [device_entry[0], device_entry[1], device_entry[2] + " " + device_entry[3]] + devices_list.append(device_entry[4]) + + locations = GenerateLocationsMap(ddate, devices_list, devices_map, locations_list, time_zone_s) + success = CreateDailyLocationChart(filename, locations) + else: + + day_counter = 0 + day_step_width = int(1780 / days) + x_offset = 563 + y_offset = 1615 + h_labels_bottom = 1720 + day_width = int(0.9 * day_step_width) + day_height = 1440 + font_size = GetOptimumFontSize(day_width, "00", 10, 50, 0) + string_width, string_height = GetStringSize("00", font_size) + #logger.debug(f"font_size={font_size} string_width={string_width}") + + y_offset = y_offset - day_height + filename_chart_image_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations_chart.png" + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_day) + file_existsS, time_modifiedS_utc = check_file_exists(filename_day[:-4]+"S.png") + if file_exists and file_existsS: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, stretch_by) + + #here we need to rotate and resize to: + + image_bytes, content_type = GetBlob(filename_day) + image_bytes_s, content_type_s = GetBlob(filename_day[:-4]+"S.png") + if image_bytes != None: + image_stream = io.BytesIO(image_bytes) + image = Image.open(image_stream) + numpy_image = np.array(image) + rotated_image = cv2.rotate(numpy_image, cv2.ROTATE_90_COUNTERCLOCKWISE) + scaled_image = cv2.resize(rotated_image, (day_width, day_height), interpolation=cv2.INTER_AREA) + + # Convert from BGR to RGB + rgb_image = cv2.cvtColor(scaled_image, cv2.COLOR_BGR2RGB) + # Convert to PIL Image + pil_image = Image.fromarray(rgb_image) + + #image = Image.open(file_name) + x_origin = x_offset + day_step_width * day_counter + int(0.05 * day_step_width) + result_image.paste(pil_image, (x_origin, y_offset)) + + image_stream = io.BytesIO(image_bytes_s) + image = Image.open(image_stream) + numpy_image = np.array(image) + rotated_image = cv2.rotate(numpy_image, cv2.ROTATE_90_COUNTERCLOCKWISE) + scaled_image = cv2.resize(rotated_image, (day_width, day_height), interpolation=cv2.INTER_AREA) + + # Convert from BGR to RGB + rgb_image = cv2.cvtColor(scaled_image, cv2.COLOR_BGR2RGB) + # Convert to PIL Image + pil_image = Image.fromarray(rgb_image) + + #image = Image.open(file_name) + x_origin = x_offset + day_step_width * day_counter + int(0.05 * day_step_width) + result_image.paste(pil_image, (x_origin, 1807+y_offset)) + + + image.close() + image_stream.close() + + day_counter += 1 + + pil_im = result_image + #result_image_cv2 = cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + result_image_cv2 = np.array(pil_im)#cv2.cvtColor(np.array(pil_im), cv2.COLOR_RGB2BGR) + strings_list = [] + day_counter = 0 + for ddate in maps_dates: + if string_width <= day_width: + date_str = ddate[8:10] + x_origin = x_offset + int(day_step_width * (day_counter + 0.5)) - int(string_width / 2) + strings_list.append((x_origin, h_labels_bottom, date_str)) + day_counter += 1 + result_image_cv2 = AddTextList(result_image_cv2, strings_list, font_size) + + + #Y 124 to 1636 + labels_bottom = 1636 - 1.5 * string_height + x_offset = 340 + step = -4 * 60 #4 hours + font_size = 50 + strings_list = [] + count = 0 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 AM")) + count = 1 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "4 AM")) + count = 2 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "8 AM")) + count = 3 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 PM")) + count = 4 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "4 PM")) + count = 5 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "8 PM")) + count = 6 + y_offset = labels_bottom + count * step + strings_list.append((x_offset, y_offset, "12 AM")) + + result_image_cv2 = AddTextList(result_image_cv2, strings_list, font_size) + + numpy_image = np.array(result_image_cv2) + + success = SaveImageInBlob(filename, numpy_image) + #SaveImageInBlob(filename, result_image) + # Save directly to MinIO instead of local file + #if success: + # success = save_to_minio(result_image, filename, DAILY_MAPS_BUCKET_NAME) + # Clean up + if success: + return filename + else: + return "" + +def GeneratePresenceHistoryFiles(filename, recreate_in, deployment_id, filter_minutes, ddate, to_date, now_date, time_zone_s): + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + stretch_by = 30 + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + + day_counter = 0 + + for ddate in maps_dates: + + force_recreate = recreate_in + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename_day+".bin") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename_day) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + CreateDailyLocationMap(filename_day, devices_list, ddate, filter_minutes, time_zone_s, 10) + + day_counter += 1 + + return filename + +def CalcStdevs(row, stdev_range, stdevs): + half_range = stdev_range // 2 + data_len = len(row) + + # Calculate standard deviations with proper window alignment + for i in range(data_len): + # Calculate window boundaries + start = max(0, i - half_range) + end = min(data_len, i + half_range + 1) + + # Get data within window + window_data = row[start:end] + + # Calculate standard deviation if we have data + if len(window_data) > 0: + stdevs[i] = np.std(window_data) + + # Find amplitude (max - min of standard deviations) + amplitude = np.max(stdevs) - np.min(stdevs) + + # Scale to range 0-1279 + if amplitude > 0: # Avoid division by zero + stdevs = ((stdevs - np.min(stdevs)) / amplitude * 1279).astype(np.float32) + + return stdevs, amplitude + +def CalcLife(row, stdev_range, stdevs): + half_range = stdev_range // 2 + data_len = len(row) + + # Calculate standard deviations with proper window alignment + for i in range(data_len): + # Calculate window boundaries + start = max(0, i - half_range) + end = min(data_len, i + half_range + 1) + + # Get data within window + window_data = row[start:end] + + # Calculate standard deviation if we have data + if len(window_data) > 0: + stdevs[i] = np.std(window_data) + + # Find amplitude (max - min of standard deviations) + amplitude = np.max(stdevs) - np.min(stdevs) + + # Scale to range 0-1279 + if amplitude > 0: # Avoid division by zero + stdevs = ((stdevs - np.min(stdevs)) / amplitude * 1279).astype(np.float32) + + return stdevs, amplitude + +def FindCalibrationDate(device_ids, ddate): + PCD = 50 #% (Peak Contained Data %) + PHB = 50 #% (Peak Height from Base %) + MPW = 10 #? (Max Peak Width) + MPSD =10 #? (Minimum Presence signal Standard Deviation) + #Find first day with, for all devices: + #- enough radar data points collected + #-Single histogram peak containing more than PCD% of data and peak width (at PHB% height) is < MPW + #Stdev of Data larger > MPSD + return ddate + +def FindThreshold(data, percent_list): + """ + Find the threshold value above which lies the specified percentage of points. + + Args: + data: numpy array of values + percent: percentage of points that should be above the threshold (0-100) + + Returns: + threshold value + """ + percent_from, percent_to = percent_list + # Sort data in descending order + sorted_data = np.sort(data)[::-1] + + # Calculate the index corresponding to the desired percentage + index_from = int((percent_from / 100) * len(data)) + index_to = int((percent_to / 100) * len(data)) + + # Return the threshold value + return sorted_data[index_from], sorted_data[index_to] + +def ShowThresholdGraph(data, filename, threshold_low, threshold_high, title, AveragePercentSpendsThere, location): + """ + Create and save a threshold analysis graph with maximum curvature point. + """ + dpi=600 + # Get min and max values + min_val = np.min(data) + max_val = np.max(data) + + # Create 1000 threshold levels from max to min + thresholds = np.linspace(max_val, min_val, 1000) + threshold_percentages = np.linspace(0, 100, 1000) + + # Calculate percentage of points above each threshold + points_above = [] + total_points = len(data) + + for thresh in thresholds: + above_count = np.sum(data > thresh) + percentage = (above_count / total_points) * 100 + points_above.append(percentage) + + points_above = np.array(points_above) + + # Calculate derivatives and smooth them + first_derivative = np.gradient(points_above) + second_derivative = np.gradient(first_derivative) + + #first_derivative = savgol_filter(np.gradient(points_above), window_length=51, polyorder=3) + #second_derivative = savgol_filter(np.gradient(first_derivative), window_length=51, polyorder=3) + + + # Find the point of maximum absolute second derivative + # Exclude edges (first and last 5% of points) to avoid edge effects + edge_margin = len(second_derivative) // 20 # 5% of points + valid_range = slice(edge_margin, -edge_margin) + max_curve_idx = edge_margin + np.argmax(np.abs(second_derivative[valid_range])) + + max_curve_x = threshold_percentages[max_curve_idx] + max_curve_y = points_above[max_curve_idx] + max_curve_second_deriv = second_derivative[max_curve_idx] + + # Calculate the actual threshold value for this point + threshold2 = max_val - (max_curve_x/100) * (max_val - min_val) + + # Create subplot figure + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[3, 2]) + + # Plot 1: Original curve with thresholds + ax1.plot(threshold_percentages, points_above, 'b-', linewidth=2, label='Distribution') + ax1.grid(True, linestyle='--', alpha=0.7) + + # Add original threshold line if provided + if threshold_low is not None: + threshold_percent = ((max_val - threshold_low) / (max_val - min_val)) * 100 + percent_above = (np.sum(data > threshold_low) / total_points) * 100 + + ax1.axvline(x=threshold_percent, color='r', linestyle='--', label=f'Minimum % spent in {location}: {AveragePercentSpendsThere[0]:.3f}') + ax1.axhline(y=percent_above, color='r', linestyle='--') + + #ax1.annotate(f'Threshold 1: {threshold_low:.3f}\nPoints above: {percent_above:.1f}%', + #xy=(threshold_percent, percent_above), + #xytext=(10, 10), textcoords='offset points', + #bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5), + #arrowprops=dict(arrowstyle='->')) + + if threshold_high is not None: + threshold_percent = ((max_val - threshold_high) / (max_val - min_val)) * 100 + percent_above = (np.sum(data > threshold_high) / total_points) * 100 + + ax1.axvline(x=threshold_percent, color='b', linestyle='--', label=f'Maximum % spent in {location}: {AveragePercentSpendsThere[1]:.3f}') + ax1.axhline(y=percent_above, color='b', linestyle='--') + + #ax1.annotate(f'Threshold 1: {threshold_high:.3f}\nPoints above: {percent_above:.1f}%', + #xy=(threshold_percent, percent_above), + #xytext=(10, 10), textcoords='offset points', + #bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5), + #arrowprops=dict(arrowstyle='->')) + + # Add maximum curvature point threshold + ax1.axvline(x=max_curve_x, color='g', linestyle='--', label=f'Threshold 2: {threshold2:.3f}') + ax1.axhline(y=max_curve_y, color='g', linestyle='--') + ax1.plot(max_curve_x, max_curve_y, 'go', markersize=10) + + ax1.annotate(f'Threshold 2: {threshold2:.3f}\nPoints above: {max_curve_y:.1f}%', + xy=(max_curve_x, max_curve_y), + xytext=(10, -20), textcoords='offset points', + bbox=dict(boxstyle='round,pad=0.5', fc='lightgreen', alpha=0.5), + arrowprops=dict(arrowstyle='->')) + + ax1.set_xlabel('Threshold Level (%)\n0% = Maximum, 100% = Minimum') + ax1.set_ylabel('Points Above Threshold (%)') + ax1.set_title(title) + ax1.set_xlim(0, 100) + ax1.set_ylim(0, 100) + ax1.legend() + + # Plot 2: Rate of change + ax2.plot(threshold_percentages, first_derivative, 'g-', label='First derivative', alpha=0.7) + ax2.plot(threshold_percentages, second_derivative, 'r-', label='Second derivative', alpha=0.7) + ax2.grid(True, linestyle='--', alpha=0.7) + + # Mark maximum curvature point on derivative plot + ax2.axvline(x=max_curve_x, color='g', linestyle='--') + # Plot point exactly on the second derivative curve + ax2.plot(max_curve_x, max_curve_second_deriv, 'go', markersize=10, + label=f'Max curvature at {max_curve_x:.1f}%') + + ax2.set_xlabel('Threshold Level (%)') + ax2.set_ylabel('Rate of Change') + ax2.set_title('Rate of Change Analysis') + ax2.legend() + + plt.tight_layout() + plt.savefig(filename, dpi=dpi, bbox_inches='tight') + plt.close() + + return threshold2, max_curve_x, max_curve_y + +def add_boundary_points(line_part_t, time_zone): + """ + Add boundary points (00:00:00 and 23:59:59) to a time series list. + + Args: + line_part_t: List of tuples (timestamp, value) + time_zone: String representing the timezone (e.g., "America/Los_Angeles") + + Returns: + List of tuples with added boundary points + """ + if not line_part_t: + return line_part_t + + tz = pytz.timezone(time_zone) + + # Get the date from the first point + first_dt = datetime.datetime.fromtimestamp(line_part_t[0][0], tz) + date = first_dt.date() + + last_dt = datetime.datetime.fromtimestamp(line_part_t[-1][0], tz) + last_date = last_dt.date() + + + # Create datetime objects for start and end of the day + start_dt = tz.localize(datetime.datetime.combine(date, datetime.datetime.min.time())) + end_dt = tz.localize(datetime.datetime.combine(last_date, datetime.datetime.max.time())) + + # Convert to timestamps + start_ts = start_dt.timestamp() + end_ts = end_dt.timestamp() + + result = list(line_part_t) + + # Handle start point (00:00:00) + first_point_dt = datetime.datetime.fromtimestamp(line_part_t[0][0], tz) + time_diff = first_point_dt - start_dt + + start_value = line_part_t[0][1] + + # Add start point at the beginning + #result.insert(0, (start_ts, start_value)) + + # Handle end point (23:59:59) + last_point_dt = datetime.datetime.fromtimestamp(line_part_t[-1][0], tz) + end_value = line_part_t[-1][1] + # Add end point + result.append((end_ts, end_value)) + + return result + +def calculate_life_and_average(my_data1, stdev_range=5): + # Convert data to numpy array for faster operations + data_array = np.array(my_data1) + + # Calculate half range + stdev_range_h = stdev_range // 2 + + # Pre-calculate indices for the sliding window + indices = np.arange(len(data_array) - 2 * stdev_range_h)[:, None] + np.arange(2 * stdev_range_h + 1) + + # Get sliding windows of data + windows = data_array[indices] + + # Calculate average (using column 3) + average = np.mean(windows[:, :, 3], axis=1) + + # Calculate life (using columns 2, 3, and 4) + deltas = windows[:, :, 3] - windows[:, :, 2] + windows[:, :, 4] + life = np.mean(deltas, axis=1) + + return life.tolist(), average.tolist() + +def TryJulia(prompt): + + if len(prompt) > 0: + + if prompt[0] == "#": + return prompt.upper() + + if prompt not in utterances: + return "" + else: + intent = utterances[prompt] + action = intents[intent] + + return action[0] + else: + return "" + +def AskGPT(in_prompt, language_from, language_to): + + if len(in_prompt) > 4: + + prompt = in_prompt.lower() + + if language_to.lower() not in language_from.lower(): + prompt = in_prompt + " Answer in " + language_to + + + print(prompt) + + #lets see if question is looking for OSM query + pattern = "what is only the node line for query for * on openstreetmap api? do not answer with url to nominatim, but with query!" + + + if match_with_wildcard(prompt, pattern): + differing_part = extract_differing_part(prompt, pattern) + + if differing_part != "": + + print(differing_part) + + if differing_part in searches_dict: + response = searches_dict[differing_part] + print(response) + return response, language_to + else: + #check if one of synonims: + if differing_part in searches_dict["synonims"]: + differing_part = searches_dict["synonims"][differing_part] + if differing_part != "": + if differing_part in searches_dict[differing_part]: + response = searches_dict[differing_part] + print(response) + return response, language_to + + hash_string = hashlib.sha256(str(prompt).encode('utf-8')).hexdigest() + #filename=os.path.join(cache_path, "chgpt_query_" + hash_string+".pkl") + + julia_present = False + if prompt.startswith("julia"): + prompt = prompt[len("julia") + 1:] + julia_present = True + + completion = "" + if julia_present == False: + completion = TryJulia(prompt) + #if completion == "": + # if os.path.exists(filename): + # #completion = pickle.load(open( filename, "rb" )) + # completion = (completion.choices[0].message.content.strip(), language_to)[0] + else: + completion = TryJulia(prompt) + + + if completion == "": + + st = time.time() + #import wandb + + #run = wandb.init(project='GPT-4 in Python') + #prediction_table = wandb.Table(columns=["prompt", "prompt tokens", "completion", "completion tokens", "model", "total tokens"]) + + print(time.time() - st) + + openai.api_key = OPENAI_API_KEY + client = OpenAI( + # This is the default and can be omitted + api_key = OPENAI_API_KEY + ) + + completion = client.chat.completions.create( + messages=[ + { + "role": "user", + "content": prompt, + } + ], + model="gpt-3.5-turbo", + ) + + + #with open(filename, 'wb') as handle: + #pickle.dump(completion, handle, protocol=pickle.HIGHEST_PROTOCOL) + + response = (completion.choices[0].message.content.strip(), language_to) + else: + response = (completion, language_to) + + else: + response = ("question is too short", language_to) + + + print(response) + return response + +def AskGPTPure(in_prompt): + + if len(in_prompt) > 4: + + prompt = in_prompt.lower() + + print(prompt) + + st = time.time() + + print(time.time() - st) + + openai.api_key = OPENAI_API_KEY + client = OpenAI( + # This is the default and can be omitted + api_key = OPENAI_API_KEY + ) + + completion = client.chat.completions.create( + messages=[ + { + "role": "user", + "content": prompt, + } + ], + model="gpt-3.5-turbo", + ) + + response = completion.choices[0].message.content.strip() + + + else: + response = "question is too short" + + + print(response) + return response + +def get_last_n_days(n=14, timezone_str='America/Los_Angeles'): + # Get current UTC time + utc_now = datetime.datetime.now(pytz.UTC) + + # Convert to the specified timezone + local_now = utc_now.astimezone(pytz.timezone(timezone_str)) + + # Get the current date in the specified timezone + current_date = local_now.date() + + # Determine the last whole day + if local_now.hour > 0 or local_now.minute > 0 or local_now.second > 0: + # Yesterday in the specified timezone + last_whole_day = current_date - timedelta(days=1) + else: + # If it's exactly midnight, the last whole day is two days ago + last_whole_day = current_date - timedelta(days=2) + + # Generate list of n days, ending with the last whole day + date_list = [] + for i in range(n-1, -1, -1): + day = last_whole_day - timedelta(days=i) + date_list.append(day.strftime('%Y-%m-%d')) + + return date_list + + +def numpy_to_json(arr, devices_list): + """ + Convert numpy array to JSON-serializable format + + Args: + arr (numpy.ndarray): 2D numpy array to serialize + + Returns: + str: JSON string containing array data and metadata + """ + if not isinstance(arr, np.ndarray): + raise TypeError("Input must be a numpy array") + + array_dict = { + 'dtype': str(arr.dtype), + 'shape': arr.shape, + 'devices_list': devices_list, + 'data': arr.tolist() # Convert to nested Python lists + } + + return json.dumps(array_dict) + +def format_time_difference(minutes): + # Calculate days, hours, minutes + days = int(minutes // (24 * 60)) + remaining_minutes = minutes % (24 * 60) + hours = int(remaining_minutes // 60) + mins = int(remaining_minutes % 60) + + parts = [] + + # Add days if any + if days > 0: + parts.append(f"{days} day{'s' if days != 1 else ''}") + + # Add hours if any + if hours > 0: + parts.append(f"{hours} hour{'s' if hours != 1 else ''}") + + # Add minutes if any + if mins > 0 or (days == 0 and hours == 0): + parts.append(f"{mins} minute{'s' if mins != 1 else ''}") + + # Combine the parts into a sentence + if len(parts) == 1: + return parts[0] + elif len(parts) == 2: + return f"{parts[0]} and {parts[1]}" + else: + return f"{parts[0]}, {parts[1]}, and {parts[2]}" + +def RunCommand(commmand, args_dictionary, deployment_id): + + to_return = "" + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + local_tz = pytz.timezone(time_zone_s) + + filter_minutes = 5 + dates = get_last_n_days(28, time_zone_s) + ddate = dates[0] #2025-02-02 req.params.get("date") + to_date = dates[-1] + date_s = datetime.datetime.now(pytz.UTC).astimezone(local_tz).date().strftime("%Y-%m-%d") + + if commmand == "#STATUS#": + force_recreate_orig = False #True + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{to_date}_{filter_minutes}_history_image.png" + filename = GeneratePresenceHistoryFiles(filename, force_recreate_orig, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + + date1_obj = datetime.datetime.strptime(ddate, '%Y-%m-%d') + date2_obj = datetime.datetime.strptime(to_date, '%Y-%m-%d') + + start_date = min(date1_obj, date2_obj) + end_date = max(date1_obj, date2_obj) + stretch_by = 30 + + # Generate list of all dates + maps_dates = [ + (start_date + timedelta(days=x)).strftime('%Y-%m-%d') + for x in range((end_date - start_date).days + 1) + ] + + day_counter = 0 + minutes_spent_there_list = [] + minutes_locations_list = [] + filename_4w = f"/{deployment_id}/{deployment_id}_{maps_dates[0]}_{maps_dates[-1]}_{filter_minutes}_{stretch_by}_4w_locations.png.bin" + for ddate in maps_dates: + + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + Id2Location = {} + for device in devices_list: + Id2Location[device[1]] = device[2] + Id2Location[0] = "Outside/?" + + filename_day = f"/{deployment_id}/{deployment_id}_{ddate}_{filter_minutes}_{stretch_by}_daily_locations.png.bin" + locations_list_s = ReadObjectMinIO("daily-maps", filename_day) + locations_list = ast.literal_eval(locations_list_s) + minutes_locations_list.append((ddate, locations_list)) + #print(locations_list_s) + minutes_spent_there = {} + + + for loc in Id2Location: + minutes_spent_there[Id2Location[loc]] = 0 + minutes_spent_there[Id2Location[0]] = 0 + + for loc in locations_list: + #print(loc[0]) + #if loc[0] == 559: + # print("Stop") + if loc[0] in Id2Location: + print(Id2Location[loc[0]]) + minutes_spent_there[Id2Location[loc[0]]] += loc[2] + + for loc in minutes_spent_there: + minutes_spent_there[loc] = int(1000 * minutes_spent_there[loc] / 1440) / 10 + minutes_spent_there_list.append((ddate, minutes_spent_there)) + data_part = str(minutes_spent_there_list) + minutes_locations_list_str = str(minutes_locations_list) + obj_to_save = {"Location_indexes": str(Id2Location), "Locations": minutes_locations_list_str} + print(obj_to_save) + #SaveObjectInBlob(filename_4w, obj_to_save) + #print(data_part) + + #prompt = "Attached is 4 weeks of data representing % of time where person living alone is spending each day" + #prompt = prompt + " Assess his last week compared to previous 3 weeks. Comment only on significant changes." + #prompt = prompt + " Ignore days where data is all (or mostly) 0!" + #prompt = prompt + " Consider that office and living room are equivalent for this individual. Entertainment is consumed on computer (office) and in living room TV." + #prompt = prompt + " But he is also napping in living room. Comment on his sleeping pattern as well" + #prompt = prompt + " Can you summarize all in 1 sentence?" + #prompt = prompt + " " + data_part + #result = AskGPTPure(prompt) + + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + #current_time = datetime.datetime.now() + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + before_last_location = details["before_last_location"] + last_present_duration = details["last_present_duration"] + detected_time = datetime.datetime.fromisoformat(details["last_detected_time"]) + local_time = local_tz.localize(detected_time) + + result = f"There are no significant changes in his routines. He slept {details['sleep_hours']} hours last night and woke 1 time for bathroom. He is in the {last_location} for last {last_present_duration} minutes, and I can smell coffee in the kitchen" + to_return = result + #to_return = "Your father appears to be fine. He was walking around the house 10 minutes ago and is currently in the living room. And I can smell coffee" + + elif commmand == "#STATUS_F#": + to_return = "Your mother is doing well. She slept 8hr and 23min last night. She used the restroom twice last night. She is now in the kitchen. I can smell coffee." + elif commmand == "#HELP#": + to_return = "There is number of things you can ask me about. For example: 'how is my dad doing?' Or 'How is his environment' or any other question you like" + elif commmand == "#SLEEP#": + to_return = "Your dad slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#SLEEP_F#": + to_return = "Your mom slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#ENVIRONMENT#": + to_return = "The temperature in the house is 23 degrees Celsius, CO2 level is 662 ppm, and I can smell coffee brewing. Your dad slept approximately 8 hours last night, took a shower before bed, and got up 4 times during the night." + elif commmand == "#WEEK#": + to_return = "Showing his weekly activity" + elif commmand == "#WEEK_F#": + to_return = "Showing her weekly activity" + elif commmand == "#ACTIVITY#": + to_return = "Your dad has been less active this week than usual. He spent more time sitting in the living room and he got up later than usual by 38min. He also did not go outside as frequently and had less visitors. He only showered once this week." + elif commmand == "#ACTIVITY_F#": + to_return = "Your mom has been less active this week than usual. She spent more time sitting in the living room and she got up later than usual by 38min. She also did not go outside as frequently and had less visitors. She only showered once this week." + elif commmand == "#ACTIVITY_COMPARE#": + to_return = "Overall your dad is less active this year compared to last year. He slept longer in the mornings and had less visitors. Also his shower activity is reduced from typically 2 times a week to once a week." + elif commmand == "#ACTIVITY_COMPARE_F#": + to_return = "Overall your mom is less active this year compared to last year. She slept longer in the mornings and had less visitors. Also her shower activity is reduced from typically 2 times a week to once a week." + elif commmand == "#LOCATION#": + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + #current_time = datetime.datetime.now() + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + before_last_location = details["before_last_location"] + last_present_duration = details["last_present_duration"] + detected_time = datetime.datetime.fromisoformat(details["last_detected_time"]) + local_time = local_tz.localize(detected_time) + # Convert to UTC + detected_utc_time = local_time.astimezone(pytz.UTC) + + time_diff = current_time - detected_utc_time + minutes = time_diff.total_seconds() / 60 + #patch... needs investigating todo + if minutes > 1400: + minutes = 0 + time_sentence = format_time_difference(minutes) + if minutes < 2: + to_return = f"He is now in the {last_location} for {last_present_duration} minutes. Before that he was in {before_last_location}" + else: + to_return = f"He was last detected in the {last_location} {time_sentence} ago" + elif commmand == "#SHOWER#": + to_return = "In the last 7 days, your Dad took a shower on Friday, Sunday and Tuesday" + elif commmand == "#SHOWER_F#": + to_return = "The last time your mom took a shower was Yesterda at 9:33AM" + elif commmand == "#BATHROOM#": + to_return = "Last night your Dad used the restroom only once at 6.10am" + elif commmand == "#KITCHEN#": + to_return = "Your Dad only cooked Dinner on Wednesday and he turned off the stove afterwards" + elif commmand == "#MOLD#": + to_return = "I cannot smell any mold. Also, the humidity is very low. In any of the rooms never exceeded 27% RH in the last 7 days." + elif commmand == "#VISITORS#": + to_return = "Yes, on Tuesday, I could detect motion in both office and kitchen at the same time and CO2 levels in the living room exceeded 900ppm." + elif commmand == "#TEMPERATURE#": + filterr = 5 + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + current_time = datetime.datetime.now(datetime.timezone.utc) + last_location = details["last_location"] + temperature = int(details["temperature"]) + if "America" in time_zone_s: + temperature_sentence = f"{int(CelsiusToFahrenheit(temperature))} degrees Farenhight" + else: + temperature_sentence = f"{temperature} degrees Celsius." + to_return = f"The temperature in the {last_location} is {temperature_sentence}." + elif commmand == "#TEMPERATURE_B#": + to_return = "The temperature in the main bathroom is 80 degrees Farenhight." + elif commmand == "#OXYGEN#": + to_return = "His last oxygen level was at 95%." + elif commmand == "#OXYGEN_F#": + to_return = "Her last oxygen level was at 95%." + elif commmand == "#HEART_RATE#": + to_return = "His last heart rate was 74 bpm." + elif commmand == "#BLOOD_PRESSURE#": + to_return = "His latest blood pressure was measured 5 hours ago and it was 137 over 83." + elif commmand == "#BLOOD_PRESSURE_F#": + to_return = "Her latest blood pressure was measured 5 hours ago and it was 137 over 83." + elif commmand == "#EKG#": + to_return = "His latest HeartBeam EKG was done on Monday and it was within his baseline!" + elif commmand == "#EKG_F#": + to_return = "Her latest HeartBeam EKG was done on Monday and it was within her baseline!" + return to_return + +def ScaleToCommon(data, sensor): + + if sensor == "temperature": + new_min = 0 + new_max = 100 + elif sensor == "humidity": + new_min = 100 + new_max = 200 + elif sensor == "light": + new_min = 200 + new_max = 300 + elif sensor == "radar": + new_min = 300 + new_max = 400 + elif sensor == "s0": + new_min = 400 + new_max = 500 + elif sensor == "s1": + new_min = 500 + new_max = 600 + elif sensor == "s2": + new_min = 600 + new_max = 700 + elif sensor == "s3": + new_min = 700 + new_max = 800 + elif sensor == "s4": + new_min = 800 + new_max = 900 + elif sensor == "s5": + new_min = 900 + new_max = 1000 + elif sensor == "s6": + new_min = 1000 + new_max = 1100 + elif sensor == "s7": + new_min = 1100 + new_max = 1200 + elif sensor == "s8": + new_min = 1200 + new_max = 1300 + else: #s9 + new_min = 1300 + new_max = 1400 + + # Split timestamps and values into separate arrays + timestamps = np.array([x[0] for x in data]) + values = np.array([x[1] for x in data]) + + # Get current min and max + if len(values) > 0: + current_min = np.min(values) + current_max = np.max(values) + else: + current_min = 0; + current_max = 0; + + # Scale the values using the min-max formula + if current_max - current_min > 0: + scaled_values = (values - current_min) * (new_max - new_min) / (current_max - current_min) + new_min + else: + mid_val = (new_max + new_min) / 2 + scaled_values = np.full_like(values, mid_val) + + # Zip back together with original timestamps + return list(zip(timestamps, scaled_values)) + +def CreateLocationsStripe(locations_file, time_zone_s): + + parts = locations_file.split("/") + parts1 = parts[2].split("_") + ddate = parts1[1] + deployment_id = parts1[0] + filter_minutes = parts1[2] + bw = False + chart_type = 4 + force_recreate = True + motion = False + scale_global = False + fast = True + GenerateFullLocationMap(locations_file, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes) + + +def CelsiusToFahrenheit(C): + F = (C * 9/5) + 32 + return F + +def CelsiusToFahrenheitList(compressed_readings: List[Tuple[datetime.datetime, np.float64]]) -> List[Tuple[datetime.datetime, np.float64]]: + + # Create a new list with converted temperatures + converted_readings = [ + [reading[0], CelsiusToFahrenheit(reading[1])] + for reading in compressed_readings + ] + + return converted_readings + +def GetPriviledgesOnly(user_name): + with get_db_connection() as conn: + if isinstance(user_name, (int)) or user_name.isdigit(): + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_id = " + user_name + else: + sql = "SELECT access_to_deployments FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "0" + +def GetPriviledgesAndUserId(user_name): + with get_db_connection() as conn: + sql = "SELECT access_to_deployments, user_id FROM public.person_details WHERE user_name = '" + user_name + "'" + + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0] + else: + return "[0,0]" + +def AddToLog(message): + """Add message to log""" + logger.info(message) + +def FillFields(blob_data, record, form_type): + """ + Fill in the input fields in the HTML blob_data with values from the caretaker dictionary. + + :param blob_data: str - The initial HTML string containing empty or placeholder input fields. + :param caretaker: dict - The dictionary containing values to populate the fields. + :return: str - The HTML string with the input fields filled with the appropriate values. + """ + # Ensure blob_data is a string + #blob_data = str(blob_data) + + # Populate the fields + for field in record: + logger.debug(f"field= {field}") + if field == "user_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_user_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "deployment_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_deployment_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "device_id": + if record[field] is not None: + escaped_string = html.escape(str(record[field])) + # Create a regex pattern to match the span with specific id + pattern = rf'(]+id="editing_device_id"[^>]*>)([^<]*)()' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(3)}', blob_data) + elif field == "user_name": + if record[field] != None: + escaped_string = html.escape(record[field]) + pattern = rf'(]+id="new_user_name"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="new_user_name"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + elif field == "location": + if record[field] != None: + blob_data = SelectOption(blob_data, 'location', record[field]) + + elif field == "gender": + if record[field] != None: + blob_data = SelectOption(blob_data, 'gender', record[field]) + + elif field == "race": + if record[field] != None: + blob_data = SelectOption(blob_data, 'race', record[field]) + + elif field == "time_zone_s": + if record[field] != None: + blob_data = SelectOption(blob_data, 'time_zone_s', record[field]) + + elif field == "time_edit" or field == "user_edit": + pass + else: + if record[field] != None: + escaped_string = html.escape(str(record[field])) + pattern = rf'(]+id="{field}"[^>]+value=")[^"]*(")' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)}{escaped_string}{m.group(2)}', blob_data) + + # Add value attribute if it does not exist + pattern = rf'(]+id="{field}"[^>]*)(>)' + blob_data = re.sub(pattern, lambda m: f'{m.group(1)} value="{escaped_string}"{m.group(2)}', blob_data) + + return blob_data + +def convert_timestamps_lc(data, time_zone_s): + target_tz = pytz.timezone(time_zone_s) + return [[datetime.datetime.fromtimestamp(epoch, pytz.UTC).astimezone(target_tz), value] + for epoch, value in data] + + +subbedToL = [("/wellget",1),("/wellget_cmp",1),("/well_hub",1)] +def on_connectL(client_, userdata, flags, rc): + print(MQTTSERVERL + " L. Connected with result code "+str(rc)) + + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client_.subscribe(subbedToL) + print("SubscribedL to: "+str(subbedToL)) + +def on_messageL(client_, userdata, msg): #message from GUI + print(msg.topic+" "+str(msg.payload)) + #msga = msg.payload.decode("ascii") + #print(msg.timestamp) + #in_queue.append((str(time.time()), msg.topic, msg.payload)) + +def MQSendL(topic, content, qos=1): + print(topic, content[0:100]) + #return MQSend(topic, content) + #currentTime = int(time.time()) + try: + if "_cmp" in topic: + enc_msg = zlib.compress(content.encode('utf-8')) + else: + enc_msg = content + clientL.publish(topic, enc_msg, qos=qos, retain=False) + except Exception as err: + print ("Err2B:", err) + try: + clientL.disconnect() + #client.username_pw_set('telegraf', 'well18') + clientL.connect(MQTTSERVERL, MQTT_PortL, 60) + except Exception as e: + print ("Err3b:", e) + +def StoreFloorPlan(deployment_id, layout): + + conn = get_db_connection() + cur = conn.cursor() + print(layout) + data = json.loads(layout) + + # Extract the overlapping list + overlapping_list = str(data["overlapping"]) + + try: + sql = f""" + UPDATE public.deployment_details SET floor_plan = '{CleanObject(layout)}' WHERE deployment_id = {deployment_id}; + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + conn.commit() + sql1 = f""" + INSERT INTO public.deployment_details (deployment_id, "overlapps") + VALUES ({deployment_id}, '{CleanObject(overlapping_list)}') + ON CONFLICT (deployment_id) + DO UPDATE SET "overlapps" = '{CleanObject(overlapping_list)}'; + """ + logger.debug(f"sql= {sql1}") + cur.execute(sql1) + conn.commit() + + cur.close() + conn.close() + + AddToLog("Written/updated!") + return 1 + except Exception as err: + return 0 + +def GetFloorPlan(deployment_id): + + conn = get_db_connection() + + try: + sql = f""" + SELECT floor_plan FROM public.deployment_details WHERE deployment_id = {deployment_id}; + """ + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchall()#cur.fetchone() + if result != None: + return result[0][0] + else: + return "" + + logger.debug(f"sql= {sql}") + conn.close() + return 1 + except Exception as err: + return 0 + +# CORS Middleware +class CORSMiddleware: + def process_request(self, req, resp): + resp.set_header('Access-Control-Allow-Origin', '*') + resp.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + resp.set_header('Access-Control-Allow-Headers', '*') + + def process_response(self, req, resp, resource, req_succeeded): + if req.method == 'OPTIONS': # Handle preflight requests + resp.status = falcon.HTTP_200 + +# Add this class to your code +class RequestParser: + def __init__(self): + # Detect if we're running in debug/development mode + self.debug_mode = __name__ == "__main__" or os.environ.get('DEBUG', 'false').lower() in ('true', '1', 'yes') + logger.debug(f"RequestParser initialized in {'DEBUG' if self.debug_mode else 'PRODUCTION'} mode") + + def process_request(self, req, resp): + """Pre-process the request to ensure media is parsed early""" + logger.debug(f"RequestParser processing: {req.method} {req.path}") + + # Initialize an empty form_data dict + req.context.form_data = {} + + # Only process POST requests with the right content type + if req.method != 'POST' or not req.content_type or 'form-urlencoded' not in req.content_type: + logger.debug("RequestParser: Skipping (not a form POST)") + return + + try: + # Different handling based on environment + if self.debug_mode: + self._process_debug(req) + else: + self._process_production(req) + + except Exception as e: + logger.error(f"RequestParser error: {str(e)}") + logger.error(traceback.format_exc()) + + def _process_debug(self, req): + """Process request in debug mode - optimized for local development""" + logger.debug("RequestParser: Using DEBUG mode processing") + + # In debug mode, we can use Content-Length and know it's reliable + content_length = req.get_header('content-length') + + if content_length: + # Content-Length is present + content_length = int(content_length) + logger.debug(f"RequestParser: Reading {content_length} bytes using Content-Length") + + raw_body = req.stream.read(content_length) + if raw_body: + body_text = raw_body.decode('utf-8') + logger.debug(f"RequestParser: Successfully read {len(body_text)} chars") + + # Parse the form data + import urllib.parse + form_data = dict(urllib.parse.parse_qsl(body_text)) + + # Store in context + req.context.form_data = form_data + logger.debug(f"RequestParser: Parsed form data: {form_data}") + + # Reset the stream with the original content + import io + req.stream = io.BytesIO(raw_body) + else: + logger.debug("RequestParser: No body data read") + else: + logger.debug("RequestParser (debug): No Content-Length header") + + def _process_production(self, req): + """Process request in production mode - optimized for OpenFaaS/faasd deployment""" + logger.debug("RequestParser: Using PRODUCTION mode processing") + + # Simple direct read approach for production (OpenFaaS/faasd) + # We'll limit the read to 1MB for safety + MAX_SIZE = 1024 * 1024 # 1MB + + # Just read directly from the stream without checking + raw_body = req.stream.read(MAX_SIZE) + if raw_body: + body_text = raw_body.decode('utf-8') + logger.debug(f"RequestParser: Successfully read {len(body_text)} chars") + + # Parse the form data + import urllib.parse + form_data = dict(urllib.parse.parse_qsl(body_text)) + + # Store in context + req.context.form_data = form_data + logger.debug(f"RequestParser: Parsed form data: {form_data}") + + # Reset the stream with the original content + import io + req.stream = io.BytesIO(raw_body) + else: + logger.debug("RequestParser: No body data read") + + +def FindDeviceByRole(deployment_id, location_list): + + #For purposes of activity report, Bedroom and Bathroom are determined in order of priority: + #Bedroom: "Bedroom Master", "Bedroom", "Bedroom Guest" (106, 56, 107) + #Bathroom: ""Bathroom Main","Bathroom","Bathroom Guest" (104, 103, 105) + + #location_names_inverted = {"All":-1 ,"?": 0,"Office": 5,"Hallway": 6,"Garage": 7,"Outside": 8,"Conference Room": 9,"Room": 10,"Kitchen": 34, + # "Bedroom": 56,"Living Room": 78,"Bathroom": 102,"Dining Room": 103,"Bathroom Main": ,104,"Bathroom Guest": 105, + # "Bedroom Master": 106, "Bedroom Guest": 107, "Conference Room": 108, "Basement": 109, "Attic": 110, "Other": 200} + + + ttime = datetime.datetime.utcnow().timestamp() + + devices_list, device_ids = GetProximityList(deployment_id, ttime) + + if location_list != []: + for location in location_list: + for device in devices_list: + well_id = device[0] + device_id = device[1] + location_t = device[2] + if location_t == location: + return (device_id, location, well_id) + + else: + conn = get_db_connection() + with conn.cursor() as cur: + + #we need to find beneficiaries from list of deployments + #sql = f'SELECT device_id FROM public.devices where device_id in {device_ids} and other="other"' + sql = "SELECT device_id, location, well_id FROM public.devices WHERE device_id = ANY(%s) AND other = %s" + #print(sql) + cur.execute(sql, (device_ids, "other")) + result = cur.fetchall()#cur.fetchone() + if len(result) > 0: + return result[0] + else: + + devices_list, device_ids = GetProximityList(deployment_id, ttime) + for device in devices_list: + well_id = device[0] + device_id = device[1] + location_t = device[2] + if "Bathroom" in location_t or "Bedroom" in location_t or "Kitchen" in location_t: + pass + else: + return (device_id, location_t, well_id) + + return (0, 0, 0) + + +def ensure_date_order(from_date, to_date): + """ + Ensures that from_date is earlier than to_date. + If not, swaps the dates. + + Args: + from_date: Date string in format 'YYYY-MM-DD' + to_date: Date string in format 'YYYY-MM-DD' + + Returns: + Tuple of (from_date, to_date) in correct order + """ + # Compare the date strings + # This works because the 'YYYY-MM-DD' format allows for string comparison + if from_date > to_date: + # Swap the dates + return to_date, from_date + else: + # Dates are already in correct order + return from_date, to_date + +def signum(x): + return (x > 0) - (x < 0) + + +def get_week_days_and_dates(days_back, timezone_str="America/Los_Angeles"): + """ + Generate weekdays and dates from 7 days ago until today for a given timezone. + + Args: + timezone_str (str): Timezone string like "America/Los_Angeles" + + Returns: + list: List of tuples containing (weekday_name, date_string) + """ + # Get the timezone object + tz = pytz.timezone(timezone_str) + + # Get current date in the specified timezone + today = datetime.datetime.now(tz).date() + + # Generate dates from days_back days ago to today + result = [] + for i in range(days_back-1, -1, -1): # days_back days ago to today (inclusive) + date = today - timedelta(days=i) + weekday_name = date.strftime("%A") # Full weekday name + date_string = date.strftime("%Y-%m-%d") # ISO format date + day_of_month = date.day + result.append((date_string, weekday_name, day_of_month)) + + return result + + +def filter_short_groups_numpy_orig(presence_list, filter_size, device_id, dates_str): + """ + Optimized version using NumPy to remove groups of consecutive zeros + or consecutive non-zeros (based on sign) shorter than filter_size. + Mimics the iterative, shortest-first logic of filter_short_groupss. + + Args: + presence_list: List of numbers (can include floats, ints, 0s). + filter_size: Minimum size of consecutive groups (by sign) to keep. + + Returns: + Filtered list with short groups removed. Output contains 0s and 1s. + (Note: Differs slightly from filter_short_groupss if negative numbers + were present, as this version converts them to 0, not 2). + """ + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"NumPy: Early exit/no processing time: {time.time() - st:.6f}s") + # Return a copy to avoid modifying the original list + return presence_list[:] if isinstance(presence_list, list) else list(presence_list) + + # Work with a NumPy array for efficiency, ensure float type for consistency + result = np.array(presence_list, dtype=float) + n = len(result) + + # Use a set to store hashable representations (tuples) of previous states for cycle detection + previous_states = set() + + while True: + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("NumPy: Cycle detected, breaking.") + break + previous_states.add(current_state_tuple) + + # 1. Calculate the sign of each element (-1, 0, 1) + signs = np.sign(result) + + # 2. Find indices where the sign changes + # np.diff calculates the difference between adjacent elements. + # A non-zero difference means the sign changed. + # np.where returns the indices *before* the change. Add 1 to get the start of the new run. + change_indices = np.where(np.diff(signs) != 0)[0] + 1 + + # 3. Define the boundaries of all consecutive runs (start and end indices) + # Include the start (0) and end (n) of the array. + boundaries = np.concatenate(([0], change_indices, [n])) + + # 4. Identify short runs + short_runs_to_process = [] + for i in range(len(boundaries) - 1): + start = boundaries[i] + end = boundaries[i+1] # Slicing is exclusive of the end index + length = end - start + + if length > 0: # Ensure the run is not empty + # Determine the characteristic sign of the run (use the first element) + run_sign = signs[start] + + if length < filter_size: + # --- Verification Step (Crucial) --- + # Check if the segment *still* consists of elements with the same sign. + # This handles cases where a previous modification might have altered + # part of what *was* a longer run. + current_segment_signs = np.sign(result[start:end]) + if np.all(current_segment_signs == run_sign): + # If the run is short and its sign consistency is verified, + # add it to the list of candidates for modification. + short_runs_to_process.append({ + 'start': start, + 'end': end, + 'sign': run_sign, + 'length': length + }) + # --- End Verification --- + + # 5. Check if any short runs were found + if not short_runs_to_process: + # No modifiable short runs found in this pass, the list is stable. + break + + # 6. Sort the short runs: shortest first, then by start index for determinism + # This ensures we process the same run as the original iterative function would. + short_runs_to_process.sort(key=lambda r: (r['length'], r['start'])) + + # 7. Process ONLY the *first* (shortest) identified run in this pass + run_to_process = short_runs_to_process[0] + start = run_to_process['start'] + end = run_to_process['end'] + run_sign = run_to_process['sign'] + + # Determine the replacement value based on the sign of the run being removed + # Short runs of 0 become 1 + # Short runs of non-zero (positive or negative) become 0 + replacement_value = 1.0 if run_sign == 0 else 0.0 + + # 8. Apply the replacement to the segment using NumPy slicing + result[start:end] = replacement_value + # Loop continues because a change was made + + print(f"filter_short_groups_numpy time: {time.time() - st:.6f}s") + if (time.time() - st) > 40: + print(presence_list) + # Convert back to a standard Python list for the return value + return result.tolist() + + + +def filter_short_groups_numpy(presence_list, filter_size, device_id, dates_str): + """ + Optimized version using NumPy to remove groups of consecutive zeros + or consecutive non-zeros (based on sign) shorter than filter_size. + Mimics the iterative, shortest-first logic. + + Optimization: + - Vectorized extraction of segment properties. + - Removed redundant sign verification within the segment analysis loop. + """ + # Start timer (optional, for benchmarking) + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"NumPy Optimized: Early exit/no processing time: {time.time() - st:.6f}s") + return presence_list[:] if isinstance(presence_list, list) else list(presence_list) + + result = np.array(presence_list, dtype=float) + n = len(result) + + previous_states = set() + + while True: + # Cycle detection + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("NumPy Optimized: Cycle detected, breaking.") + break + previous_states.add(current_state_tuple) + + # 1. Calculate the sign of each element (-1, 0, 1) + signs = np.sign(result) + + # 2. Find indices where the sign changes + change_indices = np.where(np.diff(signs) != 0)[0] + 1 + + # 3. Define the boundaries of all consecutive runs + boundaries = np.concatenate(([0], change_indices, [n])) + + # If there's only one segment (e.g., all zeros, all ones, or array is too short to have changes), + # or if the array was empty (n=0 leading to boundaries=[0,0]), no further processing is needed. + if len(boundaries) <= 2: # e.g., boundaries is [0, n] or [0,0] + break + + # 4. Vectorized extraction of run properties + run_starts = boundaries[:-1] + run_ends = boundaries[1:] + run_lengths = run_ends - run_starts + # The sign of the first element of a run (from the 'signs' array computed at the + # start of this 'while' iteration) is representative of the entire run's sign, + # by definition of how 'boundaries' were created. + run_signs = signs[run_starts] + + # 5. Identify short runs and collect their properties + short_runs_to_process = [] + for i in range(len(run_starts)): # Iterate over all identified runs + # Ensure run_length is positive (should be, due to boundary logic, but good check) + if run_lengths[i] > 0 and run_lengths[i] < filter_size: + short_runs_to_process.append({ + 'start': run_starts[i], + 'end': run_ends[i], + 'sign': run_signs[i], + 'length': run_lengths[i] + }) + + # 6. Check if any modifiable short runs were found + if not short_runs_to_process: + # No short runs found in this pass, the list is stable. + break + + # 7. Sort the short runs: shortest first, then by start index for determinism + short_runs_to_process.sort(key=lambda r: (r['length'], r['start'])) + + # 8. Process ONLY the *first* (shortest) identified run in this pass + run_to_process = short_runs_to_process[0] + start = run_to_process['start'] + end = run_to_process['end'] + run_sign = run_to_process['sign'] + + # Determine the replacement value + replacement_value = 1.0 if run_sign == 0 else 0.0 + + # 9. Apply the replacement + result[start:end] = replacement_value + # A change was made, so the 'while True' loop continues (unless a cycle is detected next) + + # End timer and print (optional) + # Your original print statements for timing: + print(f"filter_short_groups_numpy time: {time.time() - st:.6f}s") + # if (time.time() - st) > 40: + # print(presence_list) # This would print the original input on long runs + + return result.tolist() + +def filter_short_groups(presence_list, filter_size): + """ + Corrected version to perform the same task as filter_short_groupss, + including handling of non-zero/non-one values based on signum. + Iteratively removes the shortest group < filter_size by flipping its + signum representation (0->1, pos->0, neg->2). + + Args: + presence_list: List of numbers (0s, 1s, or any other number). + filter_size: Minimum size of groups (based on signum) to keep. + Returns: + Filtered list with short groups removed, potentially containing 0, 1, 2. + """ + st = time.time() + + if not presence_list or filter_size <= 1: + # print(f"filter_short_groups: Early exit/no processing time: {time.time() - st:.6f}s") + return presence_list.copy() + + result = presence_list.copy() + n = len(result) + + # Using a set for faster cycle detection lookups + previous_states = set() + + while True: + current_state_tuple = tuple(result) + if current_state_tuple in previous_states: + # print("Cycle detected in filter_short_groups, breaking.") + break + previous_states.add(current_state_tuple) + + # --- Start of logic mimicking filter_short_groupss --- + changes_made_outer = False + + # 1. Find all segments based on signum + segments = [] + i = 0 + while i < n: + start = i + # Use signum to define the characteristic value of the run + current_signum = signum(result[i]) + + # Find the end of the group based on *consistent signum* + while i < n and signum(result[i]) == current_signum: + i += 1 + + group_length = i - start + # Store the signum value associated with the run + segments.append((start, i - 1, current_signum, group_length)) + + # 2. Sort segments by length (ascending) to process shortest first + segments.sort(key=lambda x: x[3]) + + # 3. Process the segments (find the first short one to modify) + for start, end, run_signum, length in segments: + if length < filter_size: + # Verify the segment hasn't been fundamentally altered (signum-wise) + # This check mirrors filter_short_groupss's intent, using signum consistently. + is_still_original_signum_segment = True + for k_idx in range(start, end + 1): + if signum(result[k_idx]) != run_signum: + is_still_original_signum_segment = False + break + + if is_still_original_signum_segment: + # Calculate replacement value based on signum (0->1, pos->0, neg->2) + replacement_value = 1 - run_signum + + # Apply replacement + segment_modified = False + for j in range(start, end + 1): + # Use direct comparison as replacement values are integers (0, 1, 2) + if result[j] != replacement_value: + result[j] = replacement_value + segment_modified = True + + if segment_modified: + changes_made_outer = True + # Break after making *one* change and restart the whole process + # (finding segments, sorting, finding shortest modifiable) + break # Break from the 'for segment in segments' loop + + # --- End of logic mimicking filter_short_groupss --- + + if not changes_made_outer: + # If we went through all segments and made no changes, we're done. + break + + print(f"filter_short_groups time: {time.time() - st:.6f}s") + return result + + +def filter_short_groupss(presence_list, filter_size): + """ + Iteratively remove groups of consecutive 0s or 1s that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of 0s and 1s + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + st = time.time() + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = signum(result[i]) + + # Find the end of the group + while i < n and signum(result[i]) == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + # Sort segments by length (ascending) to process shortest first + segments.sort(key=lambda x: x[3]) + + # Process the segments + for start, end, value, length in segments: + # If segment is too short, replace with opposite value + if length < filter_size: + # Verify the segment hasn't been modified by previous replacements + if all(result[j] == value for j in range(start, end+1)): + replacement = 1 - value # Toggle between 0 and 1 + for j in range(start, end+1): + result[j] = replacement + changes_made = True + #print(start, end) + break # Break after making a change and restart + + print("s", time.time()-st) + return result + +def filter_short_segments(segments, filter_size): + """ + Iteratively remove segments that are shorter than filter_size, + replacing them with data from the previous segment. + + Args: + segments: List of tuples (start_time, end_time, num_persons, duration) + filter_size: Minimum duration to keep a segment + + Returns: + Filtered list of segments covering the entire time range + """ + if not segments or filter_size <= 0: + return segments.copy() + + result = segments.copy() + changes_made = True + + while changes_made: + changes_made = False + + i = 1 # Start from the second segment + while i < len(result): + _, _, _, duration = result[i] + + if duration < filter_size: + # Get the previous segment's person count + if i > 0: + _, _, prev_persons, _ = result[i-1] + start, end, _, dur = result[i] + + # Replace with previous person count + result[i] = (start, end, prev_persons, dur) + changes_made = True + + # Check if we can merge with previous segment + if i > 0: + prev_start, prev_end, prev_persons, prev_dur = result[i-1] + curr_start, curr_end, curr_persons, curr_dur = result[i] + + if prev_persons == curr_persons and prev_end + 1 == curr_start: + # Merge segments + merged = (prev_start, curr_end, prev_persons, prev_dur + curr_dur) + result[i-1] = merged + result.pop(i) + i -= 1 # Adjust index after removing an element + changes_made = True + + i += 1 + + # Sort segments by start time to ensure proper order + result.sort(key=lambda x: x[0]) + + return result + +def filter_out_short_high_segments(segments, filter_size): + """ + Iteratively remove segments that are shorter than filter_size, + replacing them with data from the previous segment. + + Args: + segments: List of tuples (start_time, end_time, num_persons, duration) + filter_size: Minimum duration to keep a segment + + Returns: + Filtered list of segments covering the entire time range + """ + if not segments: + return segments.copy() + + result = segments.copy() + changes_made = True + + while changes_made: + changes_made = False + + i = 1 # Start from the second segment + while i < len(result): + _, _, _, duration = result[i] + + if duration < filter_size: + # Get the previous segment's person count + if i > 0: + _, _, prev_persons, _ = result[i-1] + start, end, _, dur = result[i] + + # Replace with previous person count + result[i] = (start, end, prev_persons, dur) + changes_made = True + + # Check if we can merge with previous segment + if i > 0: + prev_start, prev_end, prev_persons, prev_dur = result[i-1] + curr_start, curr_end, curr_persons, curr_dur = result[i] + + if prev_persons == curr_persons and prev_end + 1 == curr_start: + # Merge segments + merged = (prev_start, curr_end, prev_persons, prev_dur + curr_dur) + result[i-1] = merged + result.pop(i) + i -= 1 # Adjust index after removing an element + changes_made = True + + i += 1 + + # Sort segments by start time to ensure proper order + result.sort(key=lambda x: x[0]) + + return result + +def filter_out_short_same_groups_iterative(presence_list, filter_size): + """ + Iteratively remove groups of consecutive sames that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of values + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list: + return presence_list.copy() + + result = presence_list.copy() + + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + + result = filter_out_short_high_segments(segments, filter_size) + + return result + +def filter_out_short_highs_iterative(presence_list, filter_size): + """ + Iteratively remove groups of consecutive sames that are shorter than filter_size. + Continues until no more changes are made. + + Args: + presence_list: List of values + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list: + return presence_list.copy() + + result = presence_list.copy() + + + # First identify all segments + segments = [] + i = 0 + n = len(result) + + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + segments.append((start, i-1, current_value, group_length)) + + + result = filter_out_short_high_segments(segments, filter_size) + + return result + +def filter_short_groups_iterative_analog(presence_list, filter_size): + """ + Iteratively remove groups of consecutive similar values that are shorter than filter_size. + For non-zero values, replaces with 0. For zero values, needs context to determine replacement. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # Identify all segments of consecutive similar values + segments = [] + i = 0 + n = len(result) + + while i < n: + start = i + is_zero = (result[i] == 0) + + # Find the end of the group with same characteristic (zero or non-zero) + while i < n and ((result[i] == 0) == is_zero): + i += 1 + + group_length = i - start + segments.append((start, i-1, is_zero, group_length)) + + # Process segments from shortest to longest + segments.sort(key=lambda x: x[3]) + + for start, end, is_zero, length in segments: + if length < filter_size: + # For short non-zero groups, replace with zeros + if not is_zero: + for j in range(start, end+1): + result[j] = 0 + changes_made = True + break + else: + # For short zero groups, replace with average of surrounding non-zero values + # First, find surrounding values + left_value = 0 + right_value = 0 + + # Look for non-zero value on the left + for j in range(start-1, -1, -1): + if result[j] != 0: + left_value = result[j] + break + + # Look for non-zero value on the right + for j in range(end+1, n): + if result[j] != 0: + right_value = result[j] + break + + # Calculate replacement value + if left_value > 0 and right_value > 0: + replacement = (left_value + right_value) / 2 + elif left_value > 0: + replacement = left_value + elif right_value > 0: + replacement = right_value + else: + replacement = 0 # No surrounding non-zero values + + # Apply replacement + for j in range(start, end+1): + result[j] = replacement + + if replacement != 0: # Only mark as changed if we actually changed something + changes_made = True + break + return result + + +def filter_short_high_groups_iterative_analog(presence_list, filter_size): + st = time.time() + """ + More efficient implementation that still handles cascading effects. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + i = 0 + n = len(result) + + # Use a single pass to find all non-zero segments + segments = [] + while i < n: + # Skip zeros + if result[i] == 0: + i += 1 + continue + + # Found non-zero, find the end of this segment + start = i + while i < n and result[i] != 0: + i += 1 + + # Add segment to our list + segments.append((start, i)) + + # Process all short segments in one iteration + for start, end in segments: + length = end - start + if length < filter_size: + # Set all elements in this segment to zero + for j in range(start, end): + result[j] = 0 + changes_made = True + # Don't break - process all short segments in this pass + + # If we've made changes, we need to check again for newly formed short segments + print(f"filter_short_high_groups_iterative_analog time: {time.time() - st:.6f}s") + return result + +def filter_short_high_groups_iterative_analog_orig(presence_list, filter_size): + """ + Iteratively remove groups of consecutive similar values that are shorter than filter_size. + For non-zero values, replaces with 0. For zero values, needs context to determine replacement. + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + st = time.time() + result = presence_list.copy() + changes_made = True + + while changes_made: + changes_made = False + + # Identify all segments of consecutive similar values + segments = [] + i = 0 + n = len(result) + + while i < n: + start = i + is_zero = (result[i] == 0) + + # Find the end of the group with same characteristic (zero or non-zero) + while i < n and ((result[i] == 0) == is_zero): + i += 1 + + group_length = i - start + segments.append((start, i-1, is_zero, group_length)) + + # Process segments from shortest to longest + segments.sort(key=lambda x: x[3]) + + for start, end, is_zero, length in segments: + if length < filter_size: + # For short non-zero groups, replace with zeros + if not is_zero: + for j in range(start, end+1): + result[j] = 0 + changes_made = True + break + + print(f"filter_short_high_groups_iterative_analog time: {time.time() - st:.6f}s") + #if (time.time() - st) > 40: + # print(presence_list) + return result + + + +def filter_short_groupsWhat(presence_list, filter_size): + """ + Remove groups of consecutive 0s or 1s that are shorter than filter_size. + For short groups of 0s, replace with 1s. + For short groups of 1s, replace with 0s. + + Args: + presence_list: List of 0s and 1s + filter_size: Minimum size of groups to keep + + Returns: + Filtered list with short groups removed + """ + if not presence_list or filter_size <= 1: + return presence_list.copy() + + result = presence_list.copy() + n = len(result) + + # Find groups and process them + i = 0 + while i < n: + # Find the start of a group + start = i + current_value = result[i] + + # Find the end of the group + while i < n and result[i] == current_value: + i += 1 + + # Calculate group length + group_length = i - start + + # If group is too short, replace with opposite value + if group_length < filter_size: + replacement = 1 - current_value # Toggle between 0 and 1 + for j in range(start, i): + result[j] = replacement + + return result + + +def GetOverlapps(deployment_id): + + with get_db_connection() as db_conn: + with db_conn.cursor() as cur: + sql = f"SELECT overlapps FROM public.deployment_details WHERE deployment_id = '{deployment_id}'" + cur.execute(sql) + result = cur.fetchone() #cur.fetchall() + if result != None: + return result[0] + + +def GetAmpitude(point_val, segment_lenght): + if point_val == 0: + return -segment_lenght + else: + return segment_lenght + +def CreateZGraph(well_id, presence_list): + """ + return size and position of consecutive groups of 0s and 1s + + Args: + presence_list: List of 0s and 1s + + Returns: + list of times and lengths + """ + if not presence_list: + return presence_list.copy() + + #if well_id == 290: + # print("Stop") + dekas_in_day = 6 * 1440 + result = [] + print(well_id) + #result will look like this: [(0,34),(34,-56),(92,6),...] where (A,B) + #A: is minute of section, B: height of section +=presence -=absence + #lets find point 0 first moving backward in time + segment_lenght = 0 + point_zero_val = signum(presence_list[dekas_in_day]) + for i in range(dekas_in_day-1, 0, -1): + if point_zero_val != signum(presence_list[i]): + segment_lenght = dekas_in_day -1 - i + break + x = 0 + y = GetAmpitude(point_zero_val, segment_lenght) + result.append((x, y)) + #x = x + segment_lenght + last_y = y + last_val = point_zero_val + last_source_minute = dekas_in_day + 1 + for i in range(last_source_minute, len(presence_list)): + if last_val != signum(presence_list[i]): + segment_lenght = i - dekas_in_day - x + x = x + segment_lenght + y = last_y + GetAmpitude(last_val, segment_lenght) + result.append((x, y)) + result.append((x, 0)) + last_y = 0 + last_val = signum(presence_list[i]) + + #last point i is NOT 1 + last above... it is last above so 2879! + segment_lenght = i - dekas_in_day - x + x = i - dekas_in_day #last point + y = GetAmpitude(last_val, segment_lenght) + result.append((x, y)) + + return result +def CreateZGraphAI(presence_list): + """ + Creates a step graph representation where: + - Presence periods: step up to +count, then drop to 0 + - Absence periods: step down to -count, then rise to 0 + - X-coordinate advances by the duration of each segment + + Pattern: [0,0] → [duration1, +/-count1] → [duration1, 0] → [duration1+duration2, +/-count2] → [duration1+duration2, 0] → ... + + Args: + presence_list: List of values (0 = absence, non-zero = presence) + + Returns: + List of [minute, height] coordinates for step graph + """ + if not presence_list: + return [] + + result = [[0, 0]] # Start at origin + + i = 0 + current_x = 0 + + while i < len(presence_list): + if presence_list[i] != 0: # Start of presence period + # Count consecutive non-zero values + count = 0 + while i < len(presence_list) and presence_list[i] != 0: + count += 1 + i += 1 + + # Move x forward by count, step up to +count + current_x += count + result.append([current_x, count]) + + # Add vertical line down to 0 + result.append([current_x, 0]) + + else: # Start of absence period + # Count consecutive zero values + count = 0 + while i < len(presence_list) and presence_list[i] == 0: + count += 1 + i += 1 + + # Move x forward by count, step down to -count + current_x += count + result.append([current_x, -count]) + + # Add vertical line up to 0 + result.append([current_x, 0]) + + return result + + +# Add this function to your code +def get_form_data(req): + """Helper function to get form data from either context or req.media""" + # First check if we pre-parsed the form data + if hasattr(req.context, 'form_data') and req.context.form_data: + logger.debug("Using pre-parsed form data from context") + return req.context.form_data + + # Otherwise try to get from req.media (for json) + try: + if req.content_type and ( + falcon.MEDIA_JSON in req.content_type or + falcon.MEDIA_URLENCODED in req.content_type + ): + logger.debug("Attempting to get form data from req.media") + return req.media or {} + except Exception as e: + logger.error(f"Error getting req.media: {str(e)}") + + logger.debug("No form data available, returning empty dict") + return {} + +def DetectMultiple(temporary_map_day_plus, overlaps_str_lst): + """ + Written by Robert Zmrzli + Detects time intervals of multiple vs single/no presence and outputs + the signed duration of each interval at its end time. + + Args: + temporary_map_day_plus: Map for each device radar reads that were detected to be above threshold + overlaps_lst: List of pairs of devices that have overlapping area + Returns: + A list of tuples representing the multiple presence timeline segments. + Each segment is represented by two tuples: + 1. (end_minute, signed_duration): signed_duration is the length of the + interval ending at end_minute (+ multiple, - single/none). + 2. (end_minute, 0): A marker for visualization. + """ + for location_id, data_list in temporary_map_day_plus.items(): + minutes_in_data = len(data_list) + break + events = [] + min_time = 0 + max_time = 0 + + + #['267:273', '273:291'] + seen_list = [0] * minutes_in_data + seen_where_list = [[] for _ in range(minutes_in_data)] + + for location_id, data_list in temporary_map_day_plus.items(): + for i in range(minutes_in_data): + if data_list[i] > 0: # Presence interval + seen_where_list[i].append(location_id) + + seen_where_list_uf = seen_where_list.copy() + overlap_pairs = set() + for overlap_str in overlaps_str_lst: + nums = [int(x) for x in overlap_str.split(':')] + # Add both orderings of the pair for easier checking + overlap_pairs.add((nums[0], nums[1])) + overlap_pairs.add((nums[1], nums[0])) + + # Process each sub-list in seen_where_list + for i in range(len(seen_where_list)): + locations = seen_where_list[i] + + # Skip empty lists and lists with only 0 or 1 item + if len(locations) <= 1: + continue + + has_non_overlapping_pair = False + + for j in range(len(locations)): + for k in range(j+1, len(locations)): + loc1, loc2 = locations[j], locations[k] + + # If this pair is not in our overlap_pairs, then they don't overlap + if (loc1, loc2) not in overlap_pairs: + has_non_overlapping_pair = True + break + + if has_non_overlapping_pair: + break + + # If all pairs overlap (no non-overlapping pairs found), clear the list + if not has_non_overlapping_pair: + seen_where_list[i] = [] + + + variations = [] + variation_index = {} + + for i in range(minutes_in_data): + if len(seen_where_list[i]) > 1: # Presence interval + if seen_where_list[i] not in variations: + variations.append(seen_where_list[i]) + variation_index[str(seen_where_list[i])] = len(variations) - 1 + + seen_list[i] = variation_index[str(seen_where_list[i])] + + + return seen_list, seen_where_list_uf + +def minutes_to_time(minutes): + """ + Convert minutes in a day (0-1439) to HH:MM format + + Args: + minutes (int): Minutes since midnight (0-1439) + + Returns: + str: Time in HH:MM format + """ + # Ensure the input is within valid range + #if not 0 <= minutes <= 1439: + # raise ValueError("Minutes must be between 0 and 1439") + + # Calculate hours and remaining minutes + minutes = minutes % 1440 + hours = minutes // 60 + mins = minutes % 60 + + # Format as HH:MM with leading zeros + return f"{hours:02d}:{mins:02d}" + +def decas_to_time(decas): + """ + Convert decas in a day (0-8639) to HH:MM format + + Args: + decas (int): decas since midnight (0-1439) + + Returns: + str: Time in HH:MM format + """ + # Ensure the input is within valid range + #if not 0 <= minutes <= 1439: + # raise ValueError("Minutes must be between 0 and 1439") + + # Calculate hours and remaining minutes + decas = decas % 8640 + hours = decas // (6 * 60) + mins = (decas // 6) % 60 + secs = 10 * (decas % 10) + + # Format as HH:MM with leading zeros + return f"{hours:02d}:{mins:02d}:{secs:02d}" + +def ClearOverlaps(temporary_map_day_plus, overlaps_str_lst): + """ + Detects reads that came from same person read by multiple devices that overlap, and removes weaker reads + Args: + temporary_map_day_plus: Map for each device radar reads that were detected to be above threshold + overlaps_lst: List of pairs of devices that have overlapping area + Returns: + An original temporary_map_day_plus with some reads removed + """ + ## Get the number of minutes + #for location_id, data_list in temporary_map_day_plus.items(): + #decas_in_data = len(data_list) + #break + + if temporary_map_day_plus: + decas_in_data = len(next(iter(temporary_map_day_plus.values()))) + else: + decas_in_data = 0 + + # Create seen_where_list with device-signal pairs + seen_where_list = [[] for _ in range(decas_in_data)] + for location_id, data_list in temporary_map_day_plus.items(): + for i in range(decas_in_data): + if data_list[i] > 0: # Presence interval + #if i == (8721): + # print("stop") + seen_where_list[i].append((location_id, data_list[i])) + + # Parse overlap pairs + overlap_pairs = set() + for overlap_str in overlaps_str_lst: + nums = [int(x) for x in overlap_str.split(':')] + overlap_pairs.add((nums[0], nums[1])) + overlap_pairs.add((nums[1], nums[0])) + + # Process each time slot + for i in range(len(seen_where_list)): + locations = seen_where_list[i] + + if len(locations) <= 1: + continue + + #if i == (5713 + 8640): + # print("stop") + #if i == (8721): + # print("stop") + # Create a new list to store the filtered results + filtered_list = [] + + # Make a copy of locations to process + to_process = locations.copy() + + # Process each device and decide whether to keep it + while to_process: + current = to_process.pop(0) + device_id, signal_strength = current + + should_keep = True + devices_to_remove = [] + + # Compare with all other devices (including those already in filtered_list) + for other in locations: + other_device_id, other_signal_strength = other + + # Skip if comparing with itself + if device_id == other_device_id: + continue + + # Check if these devices overlap + if (device_id, other_device_id) in overlap_pairs: + # They overlap, keep only the stronger signal + if signal_strength < other_signal_strength: + # Other device is stronger, don't keep current + should_keep = False + break + elif signal_strength == other_signal_strength and device_id > other_device_id: + # For equal signals, use device_id as tiebreaker + should_keep = False + break + + # If we should keep this device, add it to filtered list + if should_keep: + filtered_list.append(current) + + # Update the original list with filtered results + + #if i == (8721): + # print("stop") + seen_where_list[i] = filtered_list + + # Create a new temporary_map_day_plus with the filtered data + result = {} + for location_id, data_list in temporary_map_day_plus.items(): + result[location_id] = [0] * decas_in_data + + # Fill in the filtered data + for i in range(decas_in_data): + #if len(seen_where_list[i]) > 1: + #if i == (8721): + # print("stop") + #print(i, decas_to_time(i), seen_where_list[i]) + + for device_id, signal_strength in seen_where_list[i]: + result[device_id][i] = signal_strength + + return result + +# Path handling middleware +class StripPathMiddleware: + def process_request(self, req, resp): + # Strip the '/function/well-api' prefix if present + path = req.path + logger.info(f"Original request path: {path}") + + # Define patterns to match different URL formats + patterns = [ + r'^/function/well-api', # Standard OpenFaaS path + r'^/api/well_api', # API path + ] + + for pattern in patterns: + if re.match(pattern, path): + # Strip the matched prefix + path = re.sub(pattern, '', path) + # Ensure path starts with a slash + if not path.startswith('/'): + path = '/' + path + # Update the request path + req.path = path + logger.info(f"Modified request path: {path}") + break + + +def optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type): + last_device_id = None + + # Pre-compute seconds per minute + seconds_per_deka = 10 + + # Check if we need to process all data or just specific types + #process_all = data_type in ("all", "z-graph", "multiple") + process_all = True + + for radar_read in myz_data: + local_time = radar_read[0] + device_id = radar_read[1] + + # Calculate deca once + deca = int((local_time - start_time).total_seconds() / seconds_per_deka) + + # Use cached lookups when possible + if device_id != last_device_id: + last_device_id = device_id + + # Check if we've cached this device info + if device_id not in device_lookup_cache: + well_id = id2well_id[device_id] + radar_threshold_group_st = device_id_2_threshold[device_id]#well_id] + threshold_sig, threshold = radar_threshold_group_st + threshold_sig = threshold_sig.split("_")[0] + + # Cache the values + device_lookup_cache[device_id] = { + 'well_id': well_id, + 'threshold_sig': threshold_sig, + 'threshold': threshold + } + else: + # Use cached values + cached = device_lookup_cache[device_id] + well_id = cached['well_id'] + threshold_sig = cached['threshold_sig'] + threshold = cached['threshold'] + + days_decas = len(temporary_map_day_plus[well_id]) + else: + # Use already loaded values from last iteration + cached = device_lookup_cache[device_id] + well_id = cached['well_id'] + threshold_sig = cached['threshold_sig'] + threshold = cached['threshold'] + days_decas = len(temporary_map_day_plus[well_id]) + + # Get radar value using cached index + radar_val = radar_read[2 + device_field_indexes[threshold_sig]] + + # Process data if needed + if process_all and radar_val > threshold and deca < days_decas: + temporary_map_day_plus[well_id][deca] = radar_val + #if well_id == 269: + # print(local_time) + + return temporary_map_day_plus + +def optimized_radar_processing(my_data, start_time, id2well_id, device_id_2_threshold, + device_field_indexes, presence_map, data_type): + last_device_id = 0 + + # Cache for threshold_sig calculation which is expensive due to dictionary lookups and string splitting + threshold_sig_cache = {} + field_index_cache = {} + + for radar_read in my_data: + local_time = radar_read[0] + device_id = radar_read[1] + + # Calculate deca once + deca = int((local_time - start_time).total_seconds() / 10) + + # Device changed - update values that depend on device + if device_id != last_device_id: + last_device_id = device_id + well_id = id2well_id[device_id] + + # Calculate days_decas exactly like original + #if data_type == "raw" or data_type == "all": + days_decas = len(presence_map['raw'][well_id]) + #else: + # days_decas = len(presence_map['presence'][well_id]) + + # Calculate threshold_sig with caching + if device_id not in threshold_sig_cache: + radar_threshold_group_st = device_id_2_threshold[device_id] + threshold_sig, threshold = radar_threshold_group_st + threshold_sig = threshold_sig.split("_")[0] + threshold_sig_cache[device_id] = (threshold_sig, threshold) + else: + threshold_sig, threshold = threshold_sig_cache[device_id] + + # Calculate field index with caching + if threshold_sig not in field_index_cache: + field_index = 2 + device_field_indexes[threshold_sig] + field_index_cache[threshold_sig] = field_index + else: + field_index = field_index_cache[threshold_sig] + else: + # Use values from previous iteration for same device + #well_id = id2well_id[device_id] + + # Calculate days_decas exactly like original + #if data_type == "raw" or data_type == "all": + #days_decas = len(presence_map['raw'][well_id]) + #else: + # days_decas = len(presence_map['presence'][well_id]) + + # Use cached values + threshold_sig, threshold = threshold_sig_cache[device_id] + field_index = field_index_cache[threshold_sig] + + # Get radar value using cached field index + if field_index >= len(radar_read): + radar_val = radar_read[-1] + else: + radar_val = radar_read[field_index] + + + if well_id == 475: + print(".") + # Process presence data + if radar_val > threshold: + if deca < days_decas: + presence_map['longpresence'][well_id][deca] = 1 + + # Process raw data if needed + if data_type == "raw" or data_type == "all": + if deca < days_decas: + presence_map['raw'][well_id][deca] = radar_val + + return presence_map + +def CompressList(presence_devices_map): + + for key in presence_devices_map: + presence_map_list = presence_devices_map[key] + presence_map_list_compressed = Compress(presence_map_list) + presence_devices_map[key] = presence_map_list_compressed + + return presence_devices_map + +def Compress(presence_map_list): + + presence_map_list_compressed = [] + l = len(presence_map_list) + if l > 1: + last_data_point = presence_map_list[0] + presence_map_list_compressed.append([0, last_data_point]) + for i in range(1, l): + data_point = presence_map_list[i] + if data_point != last_data_point: + presence_map_list_compressed.append([i - 1, last_data_point]) + presence_map_list_compressed.append([i, data_point]) + last_data_point = data_point + presence_map_list_compressed.append([i, data_point]) + return presence_map_list_compressed + +def Decompress(pers_in_deka): + + last = pers_in_deka[-1] + last_index = 1 + last[1] + result = [0] * last_index + for points in pers_in_deka: + start_deca = points[0] + end_deca = points[1] + value_deca = points[2] + for i in range(start_deca, 1+end_deca): + result[i] = value_deca + return result + +def store_to_file(my_list, filename): + + try: + with open(filename, 'w') as f: + json.dump(my_list, f, indent=4) # indent for pretty printing + print(f"List saved to {filename} using JSON") + except IOError: + print(f"Error: Could not write to file {filename}") + except TypeError as e: + print(f"Error: Could not serialize list to JSON. {e}") # e.g. if list contains unsupported types like sets + +def find_custom_header(headers, name): + """Helper to find a custom header value (case-insensitive name).""" + if not headers: return None + for header in headers: + if header.get('name', '').lower() == name.lower(): return header.get('value') + return None + +def encode_state(parts): + """Joins parts with a pipe and base64 encodes the result.""" + plain_state = "|".join(map(str, parts)) + base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii') + # Assuming 'logger' is your app's logger instance + logger.debug(f"Encoded state: '{plain_state}' -> '{base64_state}'") + return base64_state + +def decode_state(b64_state): + """Decodes a base64 state and splits it by pipe.""" + if not b64_state: return [] + try: + decoded_plain = base64.b64decode(b64_state).decode('utf-8') + parts = decoded_plain.split('|') + logger.debug(f"Decoded state: '{b64_state}' -> '{decoded_plain}' -> {parts}") + return parts + except Exception as e: + logger.error(f"Failed to decode client_state '{b64_state}': {e}") + return [] + +def create_client_state(base_event, call_control_id, prefix): + """Create a base64 encoded client state string as required by Telnyx API""" + # Create the plain text client state string + plain_state = f"{prefix}_{base_event}_{call_control_id[:8]}" if call_control_id else f"{prefix}_{base_event}_unknownccid" + + # Encode to base64 as required by Telnyx API + base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii') + + logger.debug(f"Client state created: '{plain_state}' -> base64: '{base64_state}'") + return base64_state + +def send_telnyx_command(action_path, params, api_key): + """ + Sends a command to the Telnyx Call Control API actions endpoint. + This function should REPLACE your existing send_telnyx_command. + """ + if not api_key: + logger.error(f"CMDFAIL ('{action_path}'): API_KEY not available.") + return None + + ccid = params.get("call_control_id") + if not ccid: + logger.error(f"CMDFAIL ('{action_path}'): call_control_id missing in params.") + return None + + # Correct endpoint construction for V2 actions + endpoint = f"{TELNYX_API_BASE_URL}/calls/{ccid}/{action_path}" + + # Body should not contain call_control_id for actions API + body = {k: v for k, v in params.items() if k != 'call_control_id'} + + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + "Accept": "application/json" + } + + logger.info(f"SENDCMD ('{action_path}')") + logger.debug(f" Endpoint: POST {endpoint}") + logger.debug(f" JSON Payload: {json.dumps(body, indent=2)}") + + try: + response = requests.post(endpoint, json=body, headers=headers, timeout=10) + response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) + logger.info(f"CMDOK ('{action_path}'): Telnyx accepted. Status: {response.status_code}") + return response.json() + except requests.exceptions.HTTPError as e: + logger.error(f"CMDFAIL ('{action_path}'): Telnyx rejected. Status: {e.response.status_code}") + try: + logger.error(f" Telnyx Err Detail: {json.dumps(e.response.json(), indent=2)}") + except json.JSONDecodeError: + logger.error(f" Raw Err Body: {e.response.text[:500]}") + except requests.exceptions.RequestException as e: + logger.exception(f"CMDFAIL ('{action_path}'): Network error") + + return None + +def ParseAddress(address_string): + try: + payload = {"address": address_string} + + response = requests.post( + f"{base_url}/parse_address", + data=json.dumps(payload), + headers={"Content-Type": "application/json"} + ) + return response.json() + except Exception as e: + print(f"Error: {e}") + return {} + +def JoinAddress(address_map): + try: + payload = address_map + + response = requests.post( + f"{base_url}/join_address", + data=json.dumps(payload), + headers={"Content-Type": "application/json"} + ) + return response.json() + except Exception as e: + print(f"Error: {e}") + return {} + + +def StoreToDB(data): + + try: + + event_type = data.get('event_type') + sql = "" + + payload_json = json.dumps(data["payload"]) + call_session_id = data["payload"]['call_session_id'] + if event_type == "call.initiated": + timee = data.get('occurred_at') + sql = f""" + INSERT INTO public.alarms_voice ( + index, + "time", + call_session_id, + initiated + ) + VALUES ( + (SELECT COALESCE(MAX(index), 0) + 1 FROM public.alarms_voice), -- Auto-increment index + '{timee}'::timestamptz, -- occurred_at value + '{call_session_id}', -- call_session_id value + '{payload_json}' + ); """ + + elif event_type == "call.answered": + sql = f""" + UPDATE public.alarms_voice + SET answered = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.playback.started": + sql = f""" + UPDATE public.alarms_voice + SET playback_started = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.playback.ended": + sql = f""" + UPDATE public.alarms_voice + SET playback_ended = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + elif event_type == "call.hangup": + sql = f""" + UPDATE public.alarms_voice + SET hangup = '{payload_json}' + WHERE call_session_id = '{call_session_id}';""" + + if sql != "": + with get_db_connection() as conn: + with conn.cursor() as cur: + print(sql) + cur.execute(sql) + except Exception as e: + print ("Error in StoreToDB:", e) + +def handle_telnyx_webhook(webhook_data, remote_addr, request_id): + """Process Telnyx webhook events""" + logger.info(f"Processing Telnyx webhook from {remote_addr}, Request-ID: {request_id}") + + try: + data = webhook_data.get('data', {}) + event_type = data.get('event_type') + record_type = data.get('record_type') + payload = data.get('payload', {}) + + logger.info(f"Event: {event_type}, Record Type: {record_type}") + + if not event_type or not record_type: + logger.error("Missing event_type or record_type in webhook data") + return False + + call_control_id = payload.get('call_control_id') + call_session_id = payload.get('call_session_id') + + # Voice Event Handling + if record_type == 'event': + logger.info(f"Processing voice event: {event_type}") + + StoreToDB(data) + + if event_type == 'call.initiated': + logger.info(f"Call initiated: From: {payload.get('from')}, To: {payload.get('to')}") + elif event_type == 'call.answered': + logger.info(f"Call answered: From: {payload.get('from')}, To: {payload.get('to')}") + + # Get custom headers and log them + custom_headers = payload.get('custom_headers', []) + logger.debug(f"Custom headers: {json.dumps(custom_headers)}") + + # Check for audio URL + audio_url = find_custom_header(custom_headers, 'X-Audio-Url') + tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') + + logger.info(f"Audio URL: {audio_url}, TTS Payload: {tts_payload}") + + # Play audio if URL is provided + if ENABLE_AUDIO_PLAYBACK and audio_url: + logger.info(f"Playing audio: {audio_url}") + client_state = create_client_state("answered", call_control_id, CLIENT_STATE_PREFIX) + + play_params = { + "call_control_id": call_control_id, + "client_state": client_state, + "audio_url": audio_url + } + + result = send_telnyx_command("actions/playback_start", play_params, TELNYX_API_KEY) + logger.info(f"Play command result: {result}") + return True + + elif tts_payload: + logger.info(f"Speaking text: {tts_payload}") + client_state = create_client_state("answered", call_control_id, CLIENT_STATE_PREFIX) + + speak_params = { + "payload": tts_payload, + "voice": DEFAULT_TTS_VOICE, + "language": DEFAULT_TTS_LANGUAGE, + "call_control_id": call_control_id, + "client_state": client_state + } + + result = send_telnyx_command("actions/speak", speak_params, TELNYX_API_KEY) + logger.info(f"Speak command result: {result}") + return True + + else: + logger.warning("No audio URL or TTS payload found in call. Hanging up.") + hangup_params = { + "call_control_id": call_control_id, + "client_state": create_client_state("nohdr_hup", call_control_id, CLIENT_STATE_PREFIX) + } + send_telnyx_command("actions/hangup", hangup_params, TELNYX_API_KEY) + return True + + # Handle other voice events + elif event_type in ['call.speak.ended', 'call.playback.ended']: + status = payload.get('status') + ended_event_type = event_type.split('.')[-2] + logger.info(f"Call {ended_event_type} ended: Status={status}") + + # Hang up after media finished playing + hangup_params = { + "call_control_id": call_control_id, + "client_state": create_client_state(f"{ended_event_type}_hup", call_control_id, CLIENT_STATE_PREFIX) + } + send_telnyx_command("actions/hangup", hangup_params, TELNYX_API_KEY) + return True + + elif event_type == 'call.hangup': + logger.info(f"Call hung up: Cause={payload.get('cause')}") + return True + + else: + logger.info(f"Other voice event: {event_type}") + return True + + # SMS Event Handling + elif record_type == 'message': + logger.info(f"Processing SMS event: {event_type}") + # SMS handling code... + return True + + else: + logger.warning(f"Unknown record type: {record_type}") + return False + + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook: {e}") + return False + +# Assume these are defined globally or accessible (e.g., from app_args or .env) +# logger = logging.getLogger(...) +# ENABLE_AUDIO_PLAYBACK = True / False +# CLIENT_STATE_PREFIX = "app_state" +# DEFAULT_TTS_VOICE = "female" +# DEFAULT_TTS_LANGUAGE = "en-US" +# TELNYX_API_KEY = "YOUR_API_KEY" +# DTMF_GATHER_TIMEOUT_SECONDS = 15 # Wait 15 seconds for DTMF input + +# Placeholder for your DB function +# def StoreToDB(data): +# app_logger.debug(f"Placeholder: Storing to DB: {json.dumps(data)[:100]}") # Use app_logger + +# (Your existing find_custom_header, create_client_state, send_telnyx_command should be here) +# Make sure send_telnyx_command uses app_logger + +def handle_telnyx_webhook2(webhook_data, remote_addr, request_id): # Renamed logger to app_logger + """Process Telnyx webhook events with IVR logic.""" + logger.info(f"Processing Telnyx webhook from {remote_addr}, Request-ID: {request_id}") + + try: + data = webhook_data.get('data', {}) + event_type = data.get('event_type') + record_type = data.get('record_type') + payload = data.get('payload', {}) + + logger.info(f"Event: {event_type}, Record Type: {record_type}") + + if not event_type or not record_type: + logger.error("Missing event_type or record_type in webhook data") + return False # Indicate failure to process + + call_control_id = payload.get('call_control_id') + call_session_id = payload.get('call_session_id') + # Attempt to decode client_state if present + b64_client_state_rcvd = data.get("payload",{}).get("client_state") + plain_client_state_rcvd = "" + if b64_client_state_rcvd: + try: + plain_client_state_rcvd = base64.b64decode(b64_client_state_rcvd).decode('utf-8') + logger.info(f" Decoded Client State Received: '{plain_client_state_rcvd}'") + except Exception as e: + logger.warning(f" Could not decode client_state: {b64_client_state_rcvd}, Error: {e}") + plain_client_state_rcvd = "undecodable_state" + + + # Store all events to DB if needed + StoreToDB(webhook_data) # Pass the full webhook_data + + # Voice Event Handling + if record_type == 'event': + logger.info(f"Processing voice event: {event_type}, CCID: {call_control_id}") + + # --- Initial Call Setup --- + if event_type == 'call.initiated': + logger.info(f" Call initiated: From: {payload.get('from')}, To: {payload.get('to')}") + # No action needed here, wait for call.answered + + elif event_type == 'call.answered': + logger.info(f" Call answered: From: {payload.get('from')}, To: {payload.get('to')}") + custom_headers = payload.get('custom_headers', []) + logger.debug(f" Custom headers: {json.dumps(custom_headers)}") + + audio_url = find_custom_header(custom_headers, 'X-Audio-Url') + tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') + logger.info(f" X-Audio-Url: {audio_url}, X-TTS-Payload: {tts_payload}") + + # This state means the main message is about to be played. + # After it ends, we'll play the options prompt. + next_client_state = create_client_state("main_media_played", call_control_id, app_args.client_state_prefix) # Use app_args + + action_taken = False + if app_args.enable_audio_playback and audio_url: # Use app_args + logger.info(f" -> Playing main audio: {audio_url}") + play_params = {"call_control_id": call_control_id, "client_state": next_client_state, "audio_url": audio_url} + send_telnyx_command("actions/playback_start", play_params, app_args.api_key) # Use app_args + action_taken = True + elif tts_payload: + logger.info(f" -> Speaking main TTS: {tts_payload}") + speak_params = {"payload": tts_payload, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": call_control_id, "client_state": next_client_state} # Use app_args + send_telnyx_command("actions/speak", speak_params, app_args.api_key) # Use app_args + action_taken = True + + if not action_taken: + logger.warning(" -> No audio URL or TTS payload for main message. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("no_main_media_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + # --- Handling End of Main Media Playback --- + elif event_type in ['call.speak.ended', 'call.playback.ended']: + status = payload.get('status') + ended_event_type_root = event_type.split('.')[1] # speak or playback + + logger.info(f" Call {ended_event_type_root} ended: Status={status}, Current Decoded State='{plain_client_state_rcvd}'") + + # Check if the main media just finished playing + if plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_main_media_played"): + logger.info(" -> Main media finished. Playing DTMF options prompt.") + options_prompt_tts = "press 0 to repeat the message or press pound to hang up." + # This state means the options prompt is playing, and we're waiting for DTMF. + # gather_using_speak will trigger call.gather.ended + next_client_state = create_client_state("waiting_dtmf", call_control_id, app_args.client_state_prefix) + + gather_params = { + "call_control_id": call_control_id, + "client_state": next_client_state, + "payload": options_prompt_tts, + "voice": app_args.default_tts_voice, + "language": app_args.default_tts_language, + "valid_digits": "0#", # Only accept 0 or # + "max_digits": 1, # Expect only one digit + "timeout_millis": app_args.dtmf_timeout_seconds * 1000, # N seconds timeout + "terminating_digits": "#" # # will also terminate gather immediately + } + send_telnyx_command("actions/gather_using_speak", gather_params, app_args.api_key) + + elif plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_replaying_main_media"): + logger.info(" -> Replayed main media finished. Playing DTMF options prompt again.") + # Same logic as above for playing options prompt + options_prompt_tts = "press 0 to repeat the message or press pound to hang up." + next_client_state = create_client_state("waiting_dtmf", call_control_id, app_args.client_state_prefix) + gather_params = { + "call_control_id": call_control_id, "client_state": next_client_state, + "payload": options_prompt_tts, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, + "valid_digits": "0#", "max_digits": 1, "timeout_millis": app_args.dtmf_timeout_seconds * 1000, "terminating_digits": "#" + } + send_telnyx_command("actions/gather_using_speak", gather_params, app_args.api_key) + + else: + logger.warning(f" -> {ended_event_type_root} ended, but client_state ('{plain_client_state_rcvd}') doesn't match expected flow for options. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state(f"{ended_event_type_root}_unexpected_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + # --- Handling DTMF Input Result --- + elif event_type == 'call.gather.ended': + digits_received = payload.get('digits') + gather_status = payload.get('status') # e.g., 'completed_by_terminating_digit', 'timeout', 'call_hangup' + logger.info(f" Call Gather Ended: Digits='{digits_received}', Status='{gather_status}', Current Decoded State='{plain_client_state_rcvd}'") + + if plain_client_state_rcvd.startswith(f"{app_args.client_state_prefix}_waiting_dtmf"): + if digits_received == "0": + logger.info(" -> DTMF '0' received. Replaying main message.") + # Replay the original message. We need to fetch it again from custom headers. + # This assumes the call.gather.ended payload still contains the original custom_headers. + # If not, we might need to store the original TTS/Audio URL in the client_state. + # For simplicity, let's assume custom_headers are still available or we re-evaluate. + # A more robust way would be to store the main message type/content in the client_state + # when transitioning from main_media_played. + + # Let's try to get custom_headers from the current payload. + # Telnyx usually includes original call details in subsequent events. + custom_headers = payload.get('custom_headers', []) # This might not be reliable for original headers + + # A BETTER APPROACH: Store original media info in client_state or retrieve from DB + # For this example, we'll try to re-evaluate based on what might be in custom_headers + # of the *call.gather.ended* event, which is NOT guaranteed to be the original ones. + # This part needs careful testing or a strategy to pass original media info. + + # Simplified: Assume we need to re-fetch original custom headers if they are not in this payload. + # For now, let's just log and assume we'd need a mechanism to get original X-TTS-Payload/X-Audio-Url + logger.warning(" -> Replay logic needs access to original X-TTS-Payload/X-Audio-Url. This example will try to use current custom_headers if any, or a default.") + + original_audio_url = find_custom_header(custom_headers, 'X-Audio-Url') # May not be original + original_tts_payload = find_custom_header(custom_headers, 'X-TTS-Payload') # May not be original + + next_client_state = create_client_state("replaying_main_media", call_control_id, app_args.client_state_prefix) + action_taken = False + if app_args.enable_audio_playback and original_audio_url: + logger.info(f" -> Replaying audio: {original_audio_url}") + play_params = {"call_control_id": call_control_id, "client_state": next_client_state, "audio_url": original_audio_url} + send_telnyx_command("actions/playback_start", play_params, app_args.api_key) + action_taken = True + elif original_tts_payload: + logger.info(f" -> Replaying TTS: {original_tts_payload}") + speak_params = {"payload": original_tts_payload, "voice": app_args.default_tts_voice, "language": app_args.default_tts_language, "call_control_id": call_control_id, "client_state": next_client_state} + send_telnyx_command("actions/speak", speak_params, app_args.api_key) + action_taken = True + + if not action_taken: + logger.error(" -> Could not find original media to replay. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("replay_fail_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + + elif digits_received == "#" or (gather_status == 'completed_by_terminating_digit' and payload.get('terminating_digit') == '#'): + logger.info(" -> DTMF '#' received or terminating digit. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_pound_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + elif gather_status == 'timeout': + logger.info(" -> DTMF gather timed out. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_timeout_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + else: + logger.warning(f" -> Gather ended with unhandled digits '{digits_received}' or status '{gather_status}'. Hanging up.") + hangup_params = {"call_control_id": call_control_id, "client_state": create_client_state("dtmf_unhandled_hup", call_control_id, app_args.client_state_prefix)} + send_telnyx_command("actions/hangup", hangup_params, app_args.api_key) + else: + logger.warning(f" -> Gather ended, but client_state ('{plain_client_state_rcvd}') doesn't match waiting_dtmf. Ignoring.") + + + elif event_type == 'call.hangup': + app_logger.info(f" Call Hangup Event: Cause='{payload.get('cause')}', SIPCause='{payload.get('sip_hangup_cause')}', Source='{payload.get('hangup_source')}'") + # Call is already over, no command to send. + + # Log other voice events not explicitly handled above for visibility + elif event_type not in ['call.initiated', 'call.answered', 'call.speak.ended', 'call.playback.ended', 'call.gather.ended', 'call.hangup', 'call.speak.started', 'call.playback.started']: + logger.info(f" Other Voice Event: Type='{event_type}'. Payload: {json.dumps(payload, indent=2)}") + + + # --- SMS Event Handling (Placeholder from your snippet) --- + elif record_type == 'message': + logger.info(f"Processing SMS event: {event_type}") + # Your existing SMS handling code would go here... + # For now, just acknowledge + logger.info(" -> SMS ACK (204)") + return Response(status=204) # Ensure SMS events are also ACKed + + else: + logger.warning(f"Unknown record type: {record_type}") + # Acknowledge to prevent retries from Telnyx + logger.info(" -> Unknown Record Type ACK (204)") + return Response(status=204) + + # If we reached here for a voice event and didn't send a command through send_telnyx_command, + # it means we are just acknowledging the event. + logger.info(" -> Voice Event Processed (no immediate command sent or command sent async). ACK (204) to Telnyx.") + return Response(status=204) # ALWAYS ACK THE WEBHOOK + + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook: {e}") + # Still try to ACK Telnyx if possible, but log the error. + # Depending on the error, Telnyx might retry if it doesn't get a 2xx. + return "Internal Server Error", 500 + +def handle_telnyx_webhook3(webhook_data, remote_addr, request_id): + """ + Processes Telnyx webhook events with full IVR logic for repeating messages. + This function should be added to your well-api.py. + """ + logger.info(f"Processing webhook in handle_telnyx_webhook3 from {remote_addr}, Request-ID: {request_id}") + + # --- ADAPT THIS SECTION to your app's config management --- + # This example assumes config values are accessible as global constants or from a dict. + # Replace these with your actual config access method (e.g., self.config['...']) + config = { + 'api_key': TELNYX_API_KEY, + 'dtmf_timeout_seconds': 10, + 'initial_silence_ms': 500, + 'replay_silence_ms': 100, + 'default_tts_voice': 'female', + 'default_tts_language': 'en-US', + 'client_state_prefix': 'well_api_state', + 'inbound_greeting': 'Thank you for calling. We will be with you shortly.' + } + # --- END ADAPTATION SECTION --- + + try: + StoreToDB(webhook_data) # Call your DB storage function first + + data, payload = webhook_data.get('data', {}), webhook_data.get('data', {}).get('payload', {}) + event_type, record_type, ccid = data.get('event_type'), data.get('record_type'), payload.get('call_control_id') + logger.info(f"EVENT '{event_type}' ({record_type})" + (f", CCID: {ccid}" if ccid else "")) + + if record_type != 'event': + logger.info(f" -> Non-voice event ('{record_type}') received. Ignoring in this handler.") + return True + + b64_client_state = payload.get("client_state") + decoded_parts = decode_state(b64_client_state) + state_name = decoded_parts[0] if decoded_parts else None + if state_name: logger.info(f" State Name Received: '{state_name}'") + + current_api_key = config['api_key'] + + # --- State Machine Logic --- + if event_type == 'call.answered': + if payload.get('direction') == 'incoming': + logger.info(" -> Inbound call detected. Playing generic greeting and hanging up.") + next_state = encode_state(['INBOUND_GREETING_HUP']) + speak_params = {"payload": config['inbound_greeting'], "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", speak_params, current_api_key) + else: # Outgoing call + audio_url = find_custom_header(payload.get('custom_headers'), 'X-Audio-Url') + tts_payload = find_custom_header(payload.get('custom_headers'), 'X-TTS-Payload') + media_type = "audio" if audio_url else "tts" if tts_payload else "none" + media_value = audio_url or tts_payload + if media_value: + logger.info(f" -> Outbound call. Playing {config['initial_silence_ms']}ms silence buffer.") + next_state = encode_state(['INIT_PLAY_MAIN', media_type, media_value]) + send_telnyx_command("actions/play_silence", {"milliseconds": str(config['initial_silence_ms']), "call_control_id": ccid, "client_state": next_state}, current_api_key) + else: + logger.warning(" -> Outbound call, but no audio/tts payload. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.playback.ended': + if state_name == 'INIT_PLAY_MAIN': # Silence ended + logger.info(" -> Silence buffer ended. Playing main message.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['MAIN_MEDIA_PLAYED', media_type, media_value]) + if media_type == "audio": + send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key) + elif media_type == "tts": + params = {"payload": media_value, "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", params, current_api_key) + elif state_name == 'REPLAY_SILENCE': # Replay silence ended + logger.info(" -> Replay silence ended. Replaying main message.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['REPLAYING_MEDIA', media_type, media_value]) + if media_type == "audio": + send_telnyx_command("actions/playback_start", {"audio_url": media_value, "call_control_id": ccid, "client_state": next_state}, current_api_key) + elif media_type == "tts": + params = {"payload": media_value, "voice": config['default_tts_voice'], "language": config['default_tts_language'], "call_control_id": ccid, "client_state": next_state} + send_telnyx_command("actions/speak", params, current_api_key) + elif state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: # Actual audio file ended + logger.info(f" -> Main audio playback finished. Playing options menu.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['WAITING_DTMF', media_type, media_value]) + options_prompt = "press 0 to repeat the message or press pound to hang up." + gather_params = { + "payload": options_prompt, "voice": config['default_tts_voice'], "language": config['default_tts_language'], + "valid_digits": "0#", "max_digits": 1, "timeout_millis": config['dtmf_timeout_seconds'] * 1000, "terminating_digit": "#", + "call_control_id": ccid, "client_state": next_state + } + send_telnyx_command("actions/gather_using_speak", gather_params, current_api_key) + else: + logger.warning(f" -> Playback ended with unhandled state '{state_name}'. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.speak.ended': + if state_name in ['MAIN_MEDIA_PLAYED', 'REPLAYING_MEDIA']: + logger.info(f" -> Main message TTS finished. Playing options menu.") + _, media_type, media_value = decoded_parts + next_state = encode_state(['WAITING_DTMF', media_type, media_value]) + options_prompt = "press 0 to repeat the message or press pound to hang up." + gather_params = { + "payload": options_prompt, "voice": config['default_tts_voice'], "language": config['default_tts_language'], + "valid_digits": "0#", "max_digits": 1, "timeout_millis": config['dtmf_timeout_seconds'] * 1000, "terminating_digit": "#", + "call_control_id": ccid, "client_state": next_state + } + send_telnyx_command("actions/gather_using_speak", gather_params, current_api_key) + elif state_name == 'INBOUND_GREETING_HUP': + logger.info(" -> Inbound greeting finished. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + else: + logger.warning(f" -> Speak ended with unhandled state '{state_name}'. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.dtmf.received': + digit = payload.get('digit') + logger.info(f" DTMF Received: Digit='{digit}'") + if digit == '#': + logger.info(" -> '#' received. Terminating call immediately.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + + elif event_type == 'call.gather.ended': + logger.info(f" -> Gather ended. Digits received: '{payload.get('digits')}', Status: '{payload.get('status')}'") + if state_name == 'WAITING_DTMF': + digits = payload.get('digits') + _, media_type, media_value = decoded_parts + if digits == "0": + logger.info(f" -> '0' pressed. Playing {config['replay_silence_ms']}ms silence before replay.") + next_state = encode_state(['REPLAY_SILENCE', media_type, media_value]) + send_telnyx_command("actions/play_silence", {"milliseconds": str(config['replay_silence_ms']), "call_control_id": ccid, "client_state": next_state}, current_api_key) + else: + logger.info(" -> Gather ended with non-repeat condition. Hanging up.") + send_telnyx_command("actions/hangup", {"call_control_id": ccid}, current_api_key) + else: + logger.warning(f" -> Gather ended with unhandled state '{state_name}'.") + + elif event_type == 'call.hangup': + logger.info(f" Call Hangup Event: Cause='{payload.get('cause')}'") + else: + logger.info(f" -> Unhandled Voice Event: '{event_type}' with state '{state_name}'.") + + return True # Return app-specific success + except Exception as e: + logger.exception(f"Error in handle_telnyx_webhook3: {e}") + return False + + +def FilterDevicesByDeviceId(devices_list, device_id_str): + """ + Filter devices list to include only the specified device_id. + + Parameters: + devices_list: tuple of (device_details_list, device_ids_list) + device_id_str: string representation of device_id to filter by + + Returns: + tuple: filtered (device_details_list, device_ids_list) + """ + try: + target_device_id = int(device_id_str) + except ValueError: + return ([], []) + + device_details_list, device_ids_list = devices_list + + filtered_details = [] + filtered_ids = [] + + for i, device_details in enumerate(device_details_list): + device_id = device_details[1] # device_id is second element (index 1) + if device_id == target_device_id: + filtered_details.append(device_details) + filtered_ids.append(device_ids_list[i]) + + return (filtered_details, filtered_ids) + + +def FilterSensorsBySensorType(sensor_type): + """ + Filter s_table to include only the specified sensor type. + + Parameters: + sensor_type: string name of sensor type (e.g., 'temperature', 'radar', 'voc0', etc.) + + Returns: + list: filtered s_table containing only the mapped sensor name + """ + # Map user-friendly sensor names to their s_table equivalents + sensor_mapping = { + 'temperature': 'avg_temperature', + 'humidity': 'avg_humidity', + 'pressure': 'pressure_amplitude', + 'light': 'max_light', + 'radar': 'radar', + 'voc0': 'sensor_min_s0', + 'voc1': 'sensor_min_s1', + 'voc2': 'sensor_min_s2', + 'voc3': 'sensor_min_s3', + 'voc4': 'sensor_min_s4', + 'voc5': 'sensor_min_s5', + 'voc6': 'sensor_min_s6', + 'voc7': 'sensor_min_s7', + 'voc8': 'sensor_min_s8', + 'voc9': 'sensor_min_s9' + } + + # Get the actual sensor name used in s_table + mapped_sensor = sensor_mapping.get(sensor_type, sensor_type) + + # Return the mapped sensor name if it's valid, otherwise empty list + if mapped_sensor in sensor_mapping.values() or mapped_sensor == sensor_type: + return [mapped_sensor] + + return [] + + +def set_character(some_string, bit_nr, new_char): + """ + Replace a character in a string at position bit_nr from the right. + + Parameters: + some_string (str): The input string + bit_nr (int): Position from right (0 = rightmost, 1 = second from right, etc.) + new_char (str): The replacement character + + Returns: + str: The modified string + """ + if bit_nr < 0 or bit_nr >= len(some_string): + return some_string # Invalid position + + # Convert string to list for easier manipulation + chars = list(some_string) + + # Replace character at position bit_nr from right + chars[len(chars) - 1 - bit_nr] = new_char + + # Convert back to string + return ''.join(chars) + +def GetBit(alarm_armed_settings, bit_nr): + if bit_nr <0 or bit_nr >= len(alarm_armed_settings): + return False + return alarm_armed_settings[-(bit_nr+1)] == "1" + +def CreateSensorsMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_part, group_by, filtered_s_table): + """ + Create a sensor map with filtered devices and sensors. + Based on CreateMapFast but with filtering support. + + Parameters: + map_file: output file path + devices_list: filtered devices list + selected_date: date string + bw: black and white flag + time_zone_s: timezone string + radar_part: radar part specification + group_by: grouping strategy + filtered_s_table: filtered sensor table + + Returns: + tuple: (success_boolean, vocs_scaled_array) + """ + global Id2MACDict + + st = time.time() + if radar_part == "s28": + radar_part = "(s2+s3+s4+s5+s6+s7+s8)/7" + + try: + lower_than200 = 0 + larger_than200 = 0 + ids_list = [] + + for details in devices_list[0]: + well_id = details[0] + ids_list.append(details[1]) + if well_id < 200: + lower_than200 += 1 + else: + larger_than200 += 1 + + if lower_than200 > 0 and larger_than200 > 0: + return False, [] + + if larger_than200 > 0: + sensors_c = len(filtered_s_table) + else: # old sensors not supported + return False, [] + + devices_c = len(devices_list[0]) + devices_list_str = ",".join(map(str, devices_list[1])) + image_file = map_file + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s) + temp_offset = -10 + + # Use filtered sensor table for queries + if sensors_c > 1: + sql = get_deployment_query_filtered( + devices_list_str, + time_from_str, + time_to_str, + ids_list, + radar_part, + temp_offset, + filtered_s_table + ) + else: + sql = get_deployment_single_query( + devices_list_str, + time_from_str, + time_to_str, + ids_list, + radar_part, + temp_offset, + filtered_s_table[0] + ) + + print(sql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + day_data = cur.fetchall() + if day_data == None: + return False, [] + + stretch_by = 10 + minutes = 1440 + stripes = devices_c * sensors_c + arr_source_template = np.full((stripes, minutes + 4), -0.001, dtype=float) + arr_stretched_template = np.zeros((int(stripes * stretch_by), minutes, 3), dtype=np.uint8) + + # Use filtered sensor table + arr_source = fast_fill_array_from_timescale_filtered( + day_data, + time_from_str, + devices_list[1], + arr_source_template, + filtered_s_table, + time_zone_s + ) + + arr_source = AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_table, percentile=100) + scaled_day = CalcExtremes(arr_source, minutes, stripes) + arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw) + SaveImageInBlob(image_file, arr_stretched) + return True, vocs_scaled + + except Exception as e: + AddToLog(traceback.format_exc()) + return False, [] + + +def get_deployment_query_filtered(devices_list_str, time_from_str, time_to_str, ids_list, radar_part, temp_offset, filtered_s_table): + """ + Generate a filtered TimeScaleDB query for specific sensors only. + + Parameters: + devices_list_str (str): Comma-separated string of device IDs + time_from_str (str): Start time for the query + time_to_str (str): End time for the query + ids_list (list): List of device IDs in priority order for sorting + radar_part (str): Radar column name + temp_offset (float): Temperature offset + filtered_s_table (list): List of sensor names to include + + Returns: + str: Generated SQL query + """ + # Generate the CASE statement for ordering + case_statements = [] + for index, device_id in enumerate(ids_list, start=1): + case_statements.append(f"WHEN {device_id} THEN {index}") + case_order = "\n ".join(case_statements) + + # Build sensor-specific SELECT clauses + sensor_selects = [] + sensor_aggregates = [] + radar_needed = False + + for sensor in filtered_s_table: + if sensor == "temperature": + sensor_selects.append(f"sr.avg_temperature+ {temp_offset} as avg_temperature") + sensor_aggregates.append("AVG(temperature) AS avg_temperature") + elif sensor == "humidity": + sensor_selects.append("sr.avg_humidity") + sensor_aggregates.append("AVG(humidity) AS avg_humidity") + elif sensor == "pressure": + sensor_selects.append("sr.pressure_amplitude") + sensor_aggregates.append("AVG(pressure) AS pressure_amplitude") + elif sensor == "light": + sensor_selects.append("sr.max_light") + sensor_aggregates.append("MAX(light) AS max_light") + elif sensor == "radar": + sensor_selects.append("rr.radar") + radar_needed = True + elif sensor.startswith("voc"): + # Extract sensor number (e.g., sensor_min_s0 -> s0) + sensor_num = sensor.replace("voc", "") + sensor_selects.append(f"sr.{sensor}") + sensor_aggregates.append(f"MIN(s{sensor_num}) AS {sensor}") + + # Build the query + if radar_needed and sensor_aggregates: + # Need both sensor readings and radar readings + sql = f""" + SELECT + COALESCE(sr.minute, rr.minute) as minute, + COALESCE(sr.device_id, rr.device_id) as device_id, + {', '.join(sensor_selects)} + FROM ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {', '.join(sensor_aggregates)} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) sr + FULL OUTER JOIN ( + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ) rr + ON sr.minute = rr.minute AND sr.device_id = rr.device_id + ORDER BY + CASE COALESCE(sr.device_id, rr.device_id) + {case_order} + END, + COALESCE(sr.minute, rr.minute); + """ + elif radar_needed: + # Only radar needed + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + MAX({radar_part}) AS radar + FROM + radar_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + else: + # Only sensor readings needed + sql = f""" + SELECT + time_bucket('1 minute', time) AS minute, + device_id, + {', '.join(sensor_aggregates)} + FROM + sensor_readings + WHERE + device_id IN ({devices_list_str}) + AND time >= '{time_from_str}' + AND time < '{time_to_str}' + GROUP BY + minute, + device_id + ORDER BY + CASE device_id + {case_order} + END, + minute; + """ + + return sql + + + +def fast_fill_array_from_timescale_filtered(day_data, time_from_str, devices_list, arr_source, filtered_s_table, timezone_str="Europe/Berlin"): + """ + Optimized array filling for filtered sensors. + + Parameters: + day_data: query results + time_from_str: start time string + devices_list: list of device IDs + arr_source: array to fill + filtered_s_table: list of sensor names to process + timezone_str: timezone string + + Returns: + numpy array: filled array + """ + # Convert start time to timezone-aware datetime + start_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + # Create device index mapping + device_to_index = {device_id: idx for idx, device_id in enumerate(devices_list)} + + # Pre-process data into a more efficient structure + device_data = defaultdict(list) + for record in day_data: + if record[0] and record[1]: # If time and device_id exist + device_data[record[1]].append(record) + + # Build column mapping based on filtered sensors + columns = {} + col_idx = 2 # Start after time and device_id + + for sensor in filtered_s_table: + columns[sensor] = col_idx + col_idx += 1 + + # Process each device's data + for device_id, records in device_data.items(): + if device_id not in device_to_index: + continue + + base_idx = device_to_index[device_id] * len(filtered_s_table) + + # Convert records to numpy array for faster processing + records_array = np.array(records, dtype=object) + + # Calculate all minute deltas at once + times = records_array[:, 0] + minute_deltas = np.array([(t - start_time).total_seconds() / 60 for t in times], dtype=int) + + # Filter valid minute deltas + valid_mask = (minute_deltas >= 0) & (minute_deltas < arr_source.shape[1] - 4) + if not np.any(valid_mask): + continue + + minute_deltas = minute_deltas[valid_mask] + records_array = records_array[valid_mask] + + # Process each filtered sensor + for sensor_idx, sensor_name in enumerate(filtered_s_table): + if sensor_name in columns: + row_idx = base_idx + sensor_idx + values = records_array[:, columns[sensor_name]] + + # Filter out None values + valid_values = ~np.equal(values, None) + if not np.any(valid_values): + continue + + # Update array in bulk + arr_source[row_idx, minute_deltas[valid_values]] = values[valid_values] + + return arr_source + + +def AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_table, percentile): + """ + Vectorized version of AddLimits for filtered sensors. + + Parameters: + arr_source: array of shape (devices_c * sensors_c, 1444) + devices_c: number of devices + sensors_c: number of sensors per device + filtered_s_table: list of sensor names + percentile: parameter for clean_data_vectorized + """ + total_sensors = devices_c * sensors_c + + # Create arrays of sensor legal values for filtered sensors + min_vals = [] + max_vals = [] + windows = [] + + for sensor_name in filtered_s_table: + if sensor_name in sensor_legal_values: + min_vals.append(sensor_legal_values[sensor_name][0]) + max_vals.append(sensor_legal_values[sensor_name][1]) + windows.append(sensor_legal_values[sensor_name][2]) + else: + # Default values if sensor not found + min_vals.append(0) + max_vals.append(1000) + windows.append(1) + + # Repeat for each device + min_vals = np.tile(min_vals, devices_c) + max_vals = np.tile(max_vals, devices_c) + windows = np.tile(windows, devices_c) + + # Process rows that need cleaning (window > 2) + clean_mask = windows > 2 + if np.any(clean_mask): + for window in np.unique(windows[clean_mask]): + rows_to_clean = np.where(clean_mask & (windows == window))[0] + + for row_idx in rows_to_clean: + arr_source[row_idx, :1440] = clean_data_vectorized( + arr_source[row_idx, :1440], + window, + percentile + ) + + # Set min/max values for all rows + arr_source[:, 1440] = min_vals + arr_source[:, 1441] = max_vals + + return arr_source + +def GetNextWellId(min_well_id): + + conn = get_db_connection() + + sql = """ + SELECT COALESCE(MAX(well_id), 0) AS max_well_id + FROM public.devices + """ + + try: + with conn.cursor() as cur: + cur.execute(sql) + result = cur.fetchone() + if result == None: + return min_well_id + else: + max_wel_id = result[0] + if max_wel_id != None and min_well_id != None: + if max_wel_id + 1 > min_well_id: + return max_wel_id + 1 + else: + return min_well_id + else: + if max_wel_id != None: + return max_wel_id + 1 + + except Exception as e: + return min_well_id + + +def GetAlarmAllDetails(deployment_id): + + + device_alarms_json_map = {} + devices = GetVisibleDevices(deployment_id) + deployment_alarms_json, dummy = GetAlarmSimple(deployment_id, 0) + + for device in devices: + device_id = device[0] + dummy, device_alarm_json = GetAlarmSimple(0, device_id) + device_alarms_json_map[device_id] = device_alarm_json + return deployment_alarms_json, device_alarms_json_map + +def GetCalibMaps(device_ids_list): + temp_calib = {} + humid_calib = {} + with get_db_connection() as conn: + with conn.cursor() as cur: + #list all devices that user has access to + sql = f"SELECT device_id, temperature_calib, humidity_calib FROM public.devices WHERE device_id in ({str(device_ids_list)[1:-1]})" + print(sql) + cur.execute(sql) + calib_records = cur.fetchall()#cur.fetchone() + for record in calib_records: + temp_calib[record[0]] = record[1] + humid_calib[record[0]] = record[2] + + return temp_calib, humid_calib +#==================================== ADD FUNCTIONS BEFORE ============================================ + +# Main API class +class WellApi: + def on_get_healthz(self, req, resp): + """Health check endpoint""" + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_TEXT + resp.text = "OK" + + def on_get(self, req, resp, path=""): + """Handle GET requests""" + global s_table_temp + + logger.debug(f"GET request to path: {path}") + logger.debug(f"Sent variables: {req.params}") + logger.debug(f"All headers: {dict(req.headers)}") + if path == "" or path == "/": + # Serve the main portal page + blob_data = read_file("well_portal.html") + if blob_data: + resp.content_type = "text/html" + resp.text = blob_data + else: + # Fall back to JSON response if file not found + resp.media = {"message": "Hello from OpenFaaS Serverless Web Server!", "method": "GET"} + return + elif path == "favicon.ico": + favicon_path = "favicon.ico" + if os.path.isfile(favicon_path): + resp.content_type = 'image/x-icon' + resp.data = read_file(favicon_path, type_="BIN") + resp.status = HTTP_200 + else: + resp.status = HTTP_404 + return + elif path == "health": + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"status": "healthy"}) + return + + # Authentication and authorization + token = req.params.get('token') + user_name = req.params.get('user_name') + ps = req.params.get('ps') + + if ps != "" and ps != None: + #was token sent in ps field? This allows for token and ps be populated by token or ps + user_info = verify_token(ps) + if user_info["username"] == user_name: + token = ps + else: + #is this valid password? + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + + + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + get_function_name = req.params.get('name') + logger.debug(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] - {__name__}.GET_API->{get_function_name}") + privileges = GetPriviledgesOnly(user_name) + + if (token and user_name) or (token and user_name): + user_info = verify_token(token) + if user_info is None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + get_function_name = req.params.get('name') + logger.debug(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] - {__name__}.GET_API->{get_function_name}") + + if get_function_name == "deployment_add": + user_id = req.params.get('user_id') + blob_data = read_file("edit_deployment.html") + caretaker = {'deployment_id': 0, 'beneficiary_id': user_id, 'caretaker_id': user_id, 'owner_id': user_id, 'installer_id': user_id, 'user_id': 0, 'role_ids': '2', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "devices_list": + st = time.time() + user_name = req.params.get('user_name') + privileges = GetPriviledgesOnly(user_name) + first_s = req.params.get('first') + last_s = req.params.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + blob_data = read_file("my_devices.html") + + devices = GetVisibleDevices(privileges) + users = GetUsersFromDeployments(privileges) + blob_data = UpdateDevicesTable(blob_data, devices, users) + blob_data = UpdateDeploymentsSelector(blob_data, users) + resp.content_type = "text/html" + resp.text = blob_data + #print(blob_data) + return + + elif get_function_name == "deployment_edit": + deployment_id = req.params.get('deployment_id') + blob_data = read_file("edit_deployment.html") + + deployment = DeploymentDetails(deployment_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, deployment, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "caretaker_add": + + blob_data = read_file("edit_caretaker.html") + caretaker = {'user_id': 0, 'role_ids': '2', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "caretaker_edit": + user_id = req.params.get('user_id') + blob_data = read_file("edit_caretaker.html") + + caretaker = UserDetails(user_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, caretaker, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + elif get_function_name == "device_add": + + blob_data = read_file("edit_device.html") + device = {'device_id': 0, 'device_mac': '', 'well_id': '', 'description': '', 'location': '', 'close_to': '', 'radar_threshold': '["s3_max",12]', 'temperature_calib': '0.0,1.0,0.0', 'humidity_calib': '0.0,1.0,0.0'} + blob_data = FillFields(blob_data, device, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "device_edit": + mac = req.params.get('mac') + + blob_data = read_file("edit_device.html") + + device_det = DeviceDetails(mac) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, device_det, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + + + elif get_function_name == "beneficiary_edit": + user_id = req.params.get('user_id') + blob_data = read_file("edit_beneficiary.html") + + beneficiary = UserDetails(user_id) + #blob_data = blob_data.decode("utf-8") + blob_data = FillFields(blob_data, beneficiary, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "beneficiary_add": + + blob_data = read_file("edit_beneficiary.html") + beneficiary = {'user_id': 0, 'role_ids': '1', 'access_to_deployments': '', 'email': '', 'user_name': '', 'first_name': '', 'last_name': '', 'address_street': '', 'address_city': '', 'address_zip': '', 'address_state': '', 'address_country': '', 'phone_number': '', 'picture': '/', 'key': ''} + blob_data = FillFields(blob_data, beneficiary, 1) + resp.content_type = "text/html" + resp.text = blob_data + return + elif get_function_name == "get_image_file": + #image represents day in local time + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + group_by = req.params.get("group_by") + timee = StringToEpoch(ddate, time_zone_s) + force_recreate = req.params.get("re_create") == "true" + radar_part = req.params.get("radar_part") + map_type = int(req.params.get("map_type")) + + bw = req.params.get("bw") == "true" + unique_identifier = req.params.get("unique_identifier") + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{group_by}_{radar_part}_{map_type}_{bw}_dayly_image.png" + + #print(check_file_exists(filename)) + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + #ddate is in Local Time + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + #time that describes new devices in deployment_history is in UTC therefore timee is in UTC + + st = time.time() + vocs_scaled = {} + #file_date is in Local time, so we are comparing that and current Local (to install) Date + if force_recreate: + st = time.time() + vocs_scaled = {} + devices_list = GetProximityList(deployment_id, timee) + s_table_temp = s_table + stored, vocs_scaled = CreateMapFast(filename, devices_list, ddate, bw, time_zone_s, radar_part, group_by) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + if stored != True: + AddToLog("Map not created") + #logger.warning("Map not created") + resp.media = package_response("Map not created", HTTP_401) + return + else: + AddToLog("Map created") + #lets send over MQTT vocs_scaled + json_data = numpy_to_json(vocs_scaled, devices_list) + MQSendL("/"+unique_identifier, json_data) + #print(time.time() - st) + + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + sys.stdout.flush() + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_sensors_map": + # Get filtering parameters + device_id_str = req.params.get('device_id') + sensor = req.params.get('sensor') + + # Get standard image parameters + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_", "-") + group_by = ""#req.params.get("group_by") + timee = StringToEpoch(ddate, time_zone_s) + force_recreate = "true" + radar_part = req.params.get("radar_part") + bw = req.params.get("bw") == "true" + unique_identifier = req.params.get("unique_identifier") + + # Create filename with filtering parameters + filter_suffix = "" + if device_id_str: + filter_suffix += f"_dev{device_id_str}" + if sensor: + filter_suffix += f"_sens{sensor}" + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{radar_part}_{bw}{filter_suffix}_sensors_map.png" + + # Check if file exists and needs recreation + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + # Convert date to UTC epoch for device queries + timee = LocalDateToUTCEpoch(ddate, time_zone_s) + 5 # add so date boundary is avoided + + st = time.time() + vocs_scaled = {} + + if force_recreate: + st = time.time() + vocs_scaled = {} + + # Get initial device list + devices_list = GetProximityList(deployment_id, timee) + + # Apply device filtering if specified + if device_id_str: + filtered_devices = FilterDevicesByDeviceId(devices_list, device_id_str) + else: + filtered_devices = devices_list + + # Apply sensor filtering if specified + if sensor: + filtered_s_table = [sensor]#FilterSensorsBySensorType(sensor) + else: + filtered_s_table = s_table + + # Validate we have devices and sensors to process + if not filtered_devices[0] or not filtered_s_table: + AddToLog("No devices or sensors match the specified filters") + resp.media = package_response("No devices or sensors match the specified filters", HTTP_400) + return + + # Create the filtered map + stored, vocs_scaled = CreateSensorsMapFast( + filename, + filtered_devices, + ddate, + bw, + time_zone_s, + radar_part, + group_by, + filtered_s_table + ) + + if stored != True: + AddToLog("Sensors map not created") + resp.media = package_response("Sensors map not created", HTTP_401) + return + else: + AddToLog("Sensors map created") + # Send over MQTT vocs_scaled + json_data = numpy_to_json(vocs_scaled, filtered_devices) + MQSendL("/" + unique_identifier, json_data) + + # Read and send image from blob + image_bytes, content_type = GetBlob(filename) + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + sys.stdout.flush() + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_full_location_map": + + raw = req.params.get("raw") == "true" + + if raw: + #function=request_deployment_map_new + #token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvYnN0ZXIiLCJleHAiOjE3MzgxNzYzNTZ9.5wzC2dVQhKlMygHPZfombTINbltNq8vxdilLIugNTtA& + #user_name=robster& + #date=2025-01-27& + #deployment_id=21& + #map_type=2 + chart_type = 8 + else: + chart_type = int(req.params.get("map_type")) + + #image represents day in local time + logger.debug("get_full_location_map") + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + + to_date = ddate + + try: + to_date = req.params.get("to_date") + to_date = to_date.replace("_","-") + except: + pass + + if to_date != ddate: + chart_type = int(req.params.get("map_type")) + + force_recreate = req.params.get("re_create") == "true" + force_recreate_orig = force_recreate + scale_global = req.params.get("scale_global") == "true" + fast = req.params.get("fast") == "true" + bw = req.params.get("bw") == "true" + motion = req.params.get("motion") == "true" + timee = StringToEpoch(ddate, time_zone_s) + + filter_minutes = int(req.params.get("filter")) + + if "flavor" in req.params: #this is to be used only when creating + flavor = int(req.params.get("flavor")) + else: + flavor = 0 + + if bw: + bw_s = "BW" + else: + bw_s = "CLR" + + if fast: + fast_s = "FAST" + else: + fast_s = "SLOW" + + if motion: + motion_s = "M" + else: + motion_s = "S" + + if scale_global: + scl_s = "scl" + else: + scl_s = "nscl" + + + if chart_type == 5 or chart_type == 7: + #now_date = req.params.get("now_date") + #now_date = now_date.replace("_","-") + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{to_date}_{filter_minutes}_history_image.png" + else: + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{bw_s}_{motion_s}_{scl_s}_{chart_type}_Flocation_image.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + #file_exists1, time_modified_utc1 = check_file_exists(filename+".bin") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: #same date + current_time = datetime.datetime.now(pytz.timezone(time_zone_s)) + time_passed = current_time - time_modified_local + #if time_passed.seconds > 300: #recreate if older than 5 minutes + # force_recreate = True + + else: + force_recreate = True + + if force_recreate: + ddate = ddate.replace("_","-") + #filter_minutes = 5 + #filename = os.path.join(scriptDir+"/daily_maps/"+deployment, proximity_string+"_"+deployment+"_"+ddate+"_dayly_image.png") + + filename = filename.replace('\\','/') + if chart_type == 4: #"collapsed": + GenerateFullLocationMap(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast, time_zone_s, filter_minutes) + elif chart_type == 5: #"history": + GeneratePresenceHistory(filename, force_recreate, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + elif chart_type == 7: #"history full chart": + filename = GeneratePresenceHistoryChart(filename, force_recreate_orig, deployment_id, filter_minutes, ddate, to_date, ddate, time_zone_s) + elif chart_type == 8: #"set for mobile" + GenerateFullLocationMapLabelsOut(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast,time_zone_s, filter_minutes) + else: + GenerateFullLocationMap(filename, deployment_id, ddate, force_recreate, chart_type, bw, motion, scale_global, fast,time_zone_s, filter_minutes) + + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + return + + elif get_function_name == "get_presence_map": + #image represents day in local time + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = req.params.get("date") + ddate = ddate.replace("_","-") + force_recreate = req.params.get("re_create") == "true" + scale_global = req.params.get("scale_global") == "true" + fast = req.params.get("fast") == "true" + bw = req.params.get("bw") == "true" + motion = req.params.get("motion") == "true" + timee = StringToEpoch(ddate, time_zone_s) + chart_type = int(req.params.get("map_type")) + filter_minutes = int(req.params.get("filter")) + + if bw: + bw_s = "BW" + else: + bw_s = "CLR" + + if fast: + fast_s = "FAST" + else: + fast_s = "SLOW" + + if motion: + motion_s = "M" + else: + motion_s = "S" + + if scale_global: + scl_s = "scl" + else: + scl_s = "nscl" + + + filename = f"/{deployment_id}/{deployment_id}_{ddate}_{bw_s}_{motion_s}_{scl_s}_{chart_type}_Flocation_image.png" + + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(filename) + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = MapFileToDate(filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + + if force_recreate: + ddate = ddate.replace("_","-") + days = 7 + + filename = filename.replace('\\','/') + if chart_type == 6: #"AI Locations": + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + st = time.time() + if CreatePresenceMap(filename, devices_list, ddate, 1, force_recreate, chart_type, bw, motion, scale_global, fast, filter_minutes, time_zone_s) == 0: #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + print(ddate, "Not found") + else: + print(ddate, time.time() - st) + #lets read and send image from blob + image_bytes, content_type = GetBlob(filename) + + if image_bytes is None: + raise falcon.HTTPNotFound( + title='Image not found', + description=f'Image {filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = image_bytes + resp.status = falcon.HTTP_200 + + return + elif get_function_name == "download": + + deployment_id = req.params.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + date_from = req.params.get("date_from") + date_to = req.params.get("date_to") + date_from = date_from.replace("_","-") + date_to = date_to.replace("_","-") + consolidated_by = req.params.get("consolidated_by") + force_recreate = req.params.get("re_create") == "true" + radar_part = req.params.get("radar_part") + zip_filename = f"/{deployment_id}/{deployment_id}_{date_from}_{date_to}_{consolidated_by}_data.zip" + + #print(check_file_exists(filename)) + if not force_recreate: + file_exists, time_modified_utc = check_file_exists(zip_filename, bucket_name="data-downloads") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = CSVFileToDate(zip_filename) + if time_modified_date <= file_date: + force_recreate = True + else: + force_recreate = True + + #ddate is in Local Time + dates = DatesSpan(date_from, date_to) + to_zip = [] + for ddate in dates: + force_recreate_csv = force_recreate + csv_dayly_filename = f"/{deployment_id}/{deployment_id}_{ddate}_{consolidated_by}_data.csv" + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + if not force_recreate_csv: + #time that describes new devices in deployment_history is in UTC therefore timee is in UTC + file_exists, time_modified_utc = check_file_exists(csv_dayly_filename, bucket_name="data-downloads") + if file_exists: + time_modified_local = time_modified_utc.astimezone(pytz.timezone(time_zone_s)) + time_modified_date = time_modified_local.date() + file_date = CSVFileToDate(csv_dayly_filename) + if time_modified_date <= file_date: + force_recreate_csv = True + else: + force_recreate_csv = True + st = time.time() + vocs_scaled = {} + #file_date is in Local time, so we are comparing that and current Local (to install) Date + if force_recreate_csv: + st = time.time() + vocs_scaled = {} + + devices_list = GetProximityList(deployment_id, timee) + temp_offset = -10 + file_stored = CreateDailyCSV(csv_dayly_filename, devices_list, ddate, vocs_scaled, time_zone_s, radar_part, consolidated_by, temp_offset) #"[bit] 1=same sensors together, 2=same device together, 4=1 der, 8=2 der + to_zip.append(file_stored) + else: + to_zip.append(csv_dayly_filename) + + if to_zip: + success = zip_blobs( + blob_paths=to_zip, + zip_blob_name=zip_filename, + bucket_name="data-downloads", + minio_client=miniIO_blob_client + ) + + if success: + print("Files successfully zipped") + else: + print("Error occurred while zipping files") + + #pack CSV files from BLOB into ZIP + #lets read and send image from blob + zip_bytes, content_type = GetBlob(zip_filename, bucket_name="data-downloads") + if debug: + resp.media = package_response(f'Log: {debug_string}', HTTP_200) + else: + if zip_bytes is None: + raise falcon.HTTPNotFound( + title='File not found', + description=f'File {zip_filename} could not be found or retrieved' + ) + + # Set response content type and body + resp.content_type = content_type + resp.data = zip_bytes + resp.status = falcon.HTTP_200 + return + resp.media = package_response("Use POST method for this endpoint", HTTP_400) + + # Default response for unmatched paths + #resp.media = package_response(f"Path: /{path}", HTTP_200) + + def on_post(self, req, resp, path=""): + #ToDo make sure that any read/write data functions are authorized for this user_name + global threshold_cache, device_lookup_cache + + """Handle POST requests""" + logger.debug(f"on_post called with path: {path}") + logger.debug(f"Request method: {req.method}") + logger.debug(f"Request path: {req.path}") + logger.debug(f"Request query string: {req.query_string}") + logger.debug(f"Request headers: {req.headers}") + logger.debug(f"Request content type: {req.content_type}") + + + # First, check if this is a Telnyx webhook request + is_telnyx_webhook = ( + req.content_type and 'application/json' in req.content_type and + req.headers.get('USER-AGENT') == 'telnyx-webhooks' + ) + + if is_telnyx_webhook: + logger.info("Processing Telnyx webhook request") + try: + # Read the raw request body + raw_body = req.stream.read().decode('utf-8') + logger.debug(f"Raw webhook request body: {raw_body}") + + if not raw_body: + logger.error("Empty request body received from Telnyx") + resp.status = falcon.HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Empty request body"}) + return + + # Parse JSON + webhook_data = json.loads(raw_body) + logger.debug(f"Parsed webhook data: {json.dumps(webhook_data)}") + + # Get remote address and request ID + remote_addr = req.headers.get('X-REAL-IP') or req.headers.get('X-FORWARDED-FOR') or 'unknown' + request_id = req.headers.get("X-Request-Id") or req.headers.get("Telnyx-Request-Id") or req.headers.get("X-CALL-ID") or "N/A" + + # Process the webhook + handle_telnyx_webhook(webhook_data, remote_addr, request_id) + + # Set response status - always acknowledge webhooks with 204 No Content + resp.status = falcon.HTTP_204 + return + + except json.JSONDecodeError as e: + logger.error(f"Failed to decode JSON from webhook request body: {e}") + resp.status = falcon.HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Invalid JSON payload"}) + return + except Exception as e: + logger.exception(f"Error processing webhook: {e}") + resp.status = falcon.HTTP_500 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Internal Server Error"}) + return + + # If we get here, it's not a Telnyx webhook, so process as normal + try: + # For non-webhook requests, get form data + form_data = get_form_data(req) + logger.debug(f"Form data: {form_data}") + + except Exception as e: + logger.exception(f"Error in on_post: {e}") + resp.status = falcon.HTTP_500 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Internal Server Error"}) + + # Get form data using our helper function - but don't read stream again + #form_data = get_form_data(req) + logger.debug(f"Form data: {form_data}") + + + try: + + # Get basic parameters + function = form_data.get('function') + user_name = form_data.get('user_name') + logger.debug(f"Function: {function}, User: {user_name}") + + if function != "credentials" and function != "new_user_form" and function != "set_deployment": + token = form_data.get('token') + ps = form_data.get('ps') + + if ps != "" and ps != None: + #was token sent in ps field? This allows for token and ps be populated by token or ps + user_info = verify_token(ps) + if user_info != None: + if user_info["username"] == user_name: + token = ps + else: + #is this valid password? + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + + #with get_db_connection() as db_conn: + privileges = GetPriviledgesOnly(user_name) + + # Handle credentials function - most common case + if function == "credentials": + + clientId = form_data.get('clientId') + nonce = form_data.get('nonce') + ps = form_data.get('ps') + + if not user_name: + resp.media = package_response("Required field 'user_name' is missing", HTTP_400) + return + + if not clientId: + resp.media = package_response("Required field 'clientId' is missing", HTTP_400) + return + + if not nonce: + resp.media = package_response("Required field 'nonce' is missing", HTTP_400) + return + + if not ps: + resp.media = package_response("Required field 'ps' is missing", HTTP_400) + return + + + + if False: + pass + else: + #lets check for real + privileges, user_id = ValidUser(user_name, ps) + if privileges == "0": + access_token = 0 + privileges = 0 + else: + access_token = generate_token(user_name) + + if privileges == "-1": + max_role = -1 + else: + max_role = GetMaxRole(user_name) + if "2" in max_role: + max_role = 2 + else: + max_role = 1 + + token_payload = {'access_token': access_token, 'privileges': privileges, 'user_id': user_id, 'max_role': max_role} + resp.media = package_response(token_payload) + resp.status = falcon.HTTP_200 + return + + # Handle token-protected functions + elif function == "messages_age": + + macs = form_data.get('macs') + + with get_db_connection() as conn: + + #print (sqlr) + with conn.cursor() as cur: + + devices = MACsStrToDevIds(cur, macs) + + devices_string = ",".join(f"{device_id}" for mac, device_id in devices) + + + sqlr = f""" + SELECT + device_id, + GREATEST( + radar_last_time, + sensor_last_time + ) AS latest_time + FROM + (SELECT unnest(ARRAY[{devices_string}]) AS device_id) d + LEFT JOIN LATERAL ( + SELECT time AS radar_last_time + FROM radar_readings + WHERE device_id = d.device_id + ORDER BY time DESC + LIMIT 1 + ) r ON true + LEFT JOIN LATERAL ( + SELECT time AS sensor_last_time + FROM sensor_readings + WHERE device_id = d.device_id + ORDER BY time DESC + LIMIT 1 + ) s ON true;""" + logger.debug(f"sqlr= {sqlr}") + cur.execute(sqlr) + times_list = cur.fetchall() + result = {} + for i in range(len(times_list)): + if times_list[i][1] is not None: + result[devices[i][0]] = times_list[i][1].timestamp() + else: + result[devices[i][0]] = 0 + + dataa = {} + dataa['Command'] = "REPORT" + dataa['body'] = result + dataa['time'] = time.time() + #json_data = json.dumps(dataa) + payload = {'ok': True, 'response': dataa} + resp.media = package_response(payload) + logger.warning(f"Responded: {str(payload)}") + resp.status = falcon.HTTP_200 + return + + elif function == "voice_ask": + + question = form_data.get('question') + deployment_id = form_data.get('deployment_id') + + if ('language_from' in form_data): + language_from = form_data.get('language_from').strip() + else: + language_from = "English" + + if ('language_to' in form_data): + language_to = form_data.get('language_to').strip() + else: + language_to = "English" + + + result, language = AskGPT(question, language_from, language_to) + + if result[0] == "#": + result = RunCommand(result, {}, deployment_id) + + dataa = {} + dataa['Command'] = "REPORT" + dataa['body'] = result + dataa['name'] = "" + dataa['reflected'] = "" + dataa['language'] = language + dataa['time'] = time.time() + #json_data = json.dumps(dataa) + payload = {'ok': True, 'response': dataa} + resp.media = package_response(payload) + logger.warning(f"Responded: {str(payload)}") + resp.status = falcon.HTTP_200 + return + + + elif function == "calibrate_thresholds": + #this will use current date to calibrate radar presence thresholds. + #make sure that data is well defined (has clear absence/presence signature) for all rooms for chosen day + #Format of radar_threshold field = [gates_to_use_Presence_list, p_threshold] + #We need to automate this functionality!!! + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + ddate = form_data.get("date") + ddate = ddate.replace("_","-") + selected_date = ddate + + + + stdev_range = int(form_data.get("stdev_range")) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 + devices_list, device_ids = GetProximityList(deployment_id, timee) + + selected_date = FindCalibrationDate(device_ids, ddate) + + devices_c = len(devices_list[0]) + + time_from_str, time_to_str = GetLocalTimeForDate(selected_date, time_zone_s, stdev_range) + fields = ["radar_s_min", "radar_s_max", "radar_m_max", "radar_stdev"] + cnt = 0 + ids_list = [] + for details in devices_list: + ids_list.append(details[1]) + devices_list_str = ",".join(map(str, ids_list)) + device_to_index = {device: idx for idx, device in enumerate(ids_list)} + + minutes = 1440 + + with get_db_connection() as conn: + with conn.cursor() as cur: + for device_index in range(devices_c): + well_id = devices_list[device_index][0] + device_id = devices_list[device_index][1] + location = devices_list[device_index][2] + + sql = get_device_radar_s28_only_query(time_from_str, time_to_str, device_id) + print(sql) + + #sql1 = get_deployment_radar_only_colapsed_query(str(device_id), time_from_str, time_to_str, [device_id]) + #print(sql1) + st = time.time() + cur.execute(sql) + my_data = cur.fetchall() + + timestamps, stationary, motion = process_raw_data(my_data) + print(type(stationary)) + # Find threshold above which 20% of points lie + AveragePercentSpendsThere = AveragePercentPerLocation[Consolidataed_locations[location]] + threshold_high, threshold_low = FindThreshold(stationary, AveragePercentSpendsThere) + file_save = f"threshold_graph_{location}.png" + title = f"{well_id}_{location}" + + threshold2, x_percent, y_percent = ShowThresholdGraph(stationary, file_save, threshold_low, threshold_high, title, AveragePercentSpendsThere, location) + + print(f"Maximum curvature point found at:") + print(f"Threshold value: {threshold2:.3f}") + print(f"X: {x_percent:.1f}% of range") + print(f"Y: {y_percent:.1f}% of points above") + + ShowArray(stationary, threshold2, filename=f"stationary_{devices_list[device_index][0]}.png", title=f"stationary_{devices_list[device_index][0]}_{devices_list[device_index][2]}", style='line') + + + ##threshold + ##presence_mask, baseline, threshold = detect_presence(timestamps, stationary, motion) + + ### Save visualization to file + ##visualize_detection(timestamps, stationary, motion, presence_mask, + ## baseline, threshold) + + #cur.execute(sql1) + #my_data1 = cur.fetchall()#cur.fetchone() + #print(time.time() - st) + #if my_data == None or my_data1 == None: + #logger.warning(f"No data found for device_id {device_id}") + #else: + #print(type(my_data)) + ##minute, + ##device_id, + ##s_min as radar_s_min, + ##s_max as radar_s_max, + ##m_max as radar_m_max + + #values = [tup[1] for tup in my_data] #10 sec (RAW) data + + #hist, bins = np.histogram(values, bins=1000, range=(0, 100)) + #TR, BR = FindZeroIntersection(hist, bins, f'raw_{device_id}_histogram.png', device_id) + #if True:#device_id == 560: + #plot(values, filename=f"radar_{device_id}_s28.png", title=f"Radar s28 {device_id}", style='line') + #plot(hist, filename=f"radar_{device_id}_s28_hist.png", title=f"Radar s28 {device_id} histogram", style='line') + + ##life = [tup[3] - tup[2] + tup[4] for tup in my_data1] + #life, average = calculate_life_and_average(my_data1, stdev_range) #5 min data + #lhist, lbins = np.histogram(life, bins=1000) + #TLIFE, BLIFE = FindZeroIntersection(lhist, lbins, f'life_{device_id}_histogram.png', device_id) + + #StoreThresholds2DB(device_id, TR, BR, TLIFE, BLIFE) + ##for now not needed... + ##ahist, abins = np.histogram(average, bins=1000) + ##dummy1, dummy = FindZeroIntersection(ahist, abins) + #if True:#device_id == 560: + #plot(average, filename=f"average_{device_id}.png", title=f"Average {device_id}", style='line') + #plot(life, filename=f"life_{device_id}.png", title=f"Life {device_id}", style='line') + #plot(lhist, filename=f"life_{device_id}_hist.png", title=f"life {device_id} histogram", style='line') + ##plot(ahist, filename=f"average_{device_id}_hist.png", title=f"average {device_id} histogram", style='line') + + + sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list) + print(sql) + my_data = [] + with get_db_connection() as conn: + with conn.cursor() as cur: + cur.execute(sql) + my_data = cur.fetchall()#cur.fetchone() + #print(result) + if my_data == None: + return False + + fields_n = len(fields) + stripes = devices_c * fields_n #radar_min and radar_max + print(my_data) + base_minute = ConvertToBase(time_from_str, time_zone_s) + #base_minute = my_data[0][0]# min(record[0] for record in my_data) + #remember: base_minute is offset (smaller) by numbr of minutes in stdev_range + st = time.time() + wave_m = np.zeros((stripes, 1440+2*stdev_range, 1), dtype=np.float32) + + for record in my_data: + #(minute,device_id,s28_min,s28_max) = record + minute, device_id = record[0:2] + values = record[2:] # All the min/max values + x = int((minute - base_minute).total_seconds()/60) + + device_idx = device_to_index[device_id] + #value[0] are mins, value[1] are maxes + #when trying to illustrate presence, use s28_max, when absence (night leaving bed) use s28s_min + for field_idx, value in enumerate(values): + # Calculate y position + y = device_idx * fields_n + field_idx + wave_m[y, x] = value + + print(time.time()-st) + + #we need to reliably determine presence and LIFE (motion) in every 5 minutes of data... + #presence is determined by average value being significntly different from last known base + #last known base is determined by average value during extended periods ( >= H hours) of low stdev (<) while it is determined that: + #person is moving elsewhere, and only 1 person is determined to be in monitored area. + + #lets calculate stdevs + for device_index in range(devices_c): + y = device_index * fields_n + row = wave_m[y] + stdevs = np.zeros((1440+2*stdev_range, 1), dtype=np.float32) + stdevs, amplitude = CalcStdevs(row, stdev_range, stdevs) + wave_m[y+3] = stdevs + plot(stdevs, filename=f"radar{device_index}_stdevs.png", title=f"Radar Stedevs {device_index}", style='line') + + minutes = 1440 + + + device_index = 0 + y = 0 + for device in devices_list: + wave = wave_m[y][stdev_range: stdev_range + minutes] + plot(wave, + filename="radar_wave_min.png", + title="Radar Signal Min", + style='line') + # Create histogram with 1000 bins + hist, bins = np.histogram(wave, bins=1000, range=(0, 100)) + + #bin_centers = (bins[:-1] + bins[1:]) / 2 + hist_line = hist # These are your y values + + # Plot with proper axis labels + plot(hist_line, + filename="radar_histogram_min.png", + title="Radar Signal Histogram Min (1000 bins)", + style='line') + + wave = wave_m[y+1] + plot(wave, + filename="radar_wave_max.png", + title="Radar Signal", + style='line') + # Create histogram with 1000 bins + hist, bins = np.histogram(wave, bins=1000, range=(0, 100)) + + #bin_centers = (bins[:-1] + bins[1:]) / 2 + hist_line = hist # These are your y values + + # Plot with proper axis labels + plot(hist_line, + filename="radar_histogram_max.png", + title="Radar Signal Histogram Max(1000 bins)", + style='line') + + print(wave) + device_to_index += 1 + + #lets see this map + stretch_by = 5 + arr_stretched = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) #array to be written as image 3 for RGB channels + st = time.time() + for yy in range(stripes): + rgb_row = [] + row = wave_m[yy] + for x in range(minutes): + value = 1280 * row[x] / 100 + rgb_row.append(BestColor(value)) + for stretch_index in range(stretch_by): + y = yy * stretch_by + stretch_index + arr_stretched[y, :] = rgb_row + + print(time.time()-st) + filename = f"{deployment_id}/{deployment_id}_{ddate}_min_max_radar.png" + SaveImageInBlob(filename, arr_stretched, []) + + + return + elif function == "get_time_deltas": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + sensor = form_data.get('sensor') + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + radar_part = "" + sensor_data = {} + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + device_id = form_data.get('device_id') + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + + all_slices = {} + + cleaned_values = {} + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + st = time.time() + cleaned_values = [ + (line_part[i][0], (line_part[i][0] - line_part[i-1][0]).total_seconds() * 1000) + for i in range(1, len(line_part)) + ] + print(time.time()-st) + + if True: + # Create CSV content as a string + csv_content = "Record_Index,Timestamp,Value,Time_Diff_Seconds,Time_Diff_Milliseconds\n" + + for i in range(len(line_part)): + timestamp, value = line_part[i] + + if i == 0: + # First record has no previous record to compare + time_diff_seconds = 0 + time_diff_ms = 0 + else: + # Calculate time difference from previous record + prev_timestamp = line_part[i-1][0] + time_diff = timestamp - prev_timestamp + time_diff_seconds = time_diff.total_seconds() + time_diff_ms = time_diff_seconds * 1000 + + # Format the row + row = f"{i},{timestamp.isoformat()},{value},{round(time_diff_seconds, 6)},{round(time_diff_ms, 3)}\n" + csv_content += row + + # Write to file + with open(f'time_differences_{sensor}_{device_id}.csv', 'w', encoding='utf-8') as f: + f.write(csv_content) + + print(f"CSV file 'time_differences_{sensor}_{device_id}.csv' created successfully!") + + line_part_t = [(x[0].timestamp(), x[1]) for x in cleaned_values] + + sensor_data[sensor] = line_part_t + dataa = {} + all_slices = {} + all_slices[device_id] = sensor_data + dataa['Function'] = "time_deltas" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['device_id'] = device_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "get_sensor_deltas": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + sensor = form_data.get('sensor') + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + radar_part = "" + sensor_data = {} + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + device_id = form_data.get('device_id') + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + + all_slices = {} + + cleaned_values = {} + line_part = ReadSensorDeltas(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + st = time.time() + cleaned_values =line_part + #[ + #(line_part[i][0], (line_part[i][1] - line_part[i-1][1]) / (line_part[i][0] - line_part[i-1][0]).total_seconds()) + #for i in range(1, len(line_part)) + #if (line_part[i][0] - line_part[i-1][0]).total_seconds() > 0 + #and abs((line_part[i][1] - line_part[i-1][1]) / (line_part[i][0] - line_part[i-1][0]).total_seconds()) <= 100 + #] + #print(time.time()-st) + + + + line_part_t = [(x[0].timestamp(), x[1]) for x in cleaned_values] + + sensor_data[sensor] = line_part_t + dataa = {} + all_slices = {} + all_slices[device_id] = sensor_data + dataa['Function'] = "time_deltas" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['device_id'] = device_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "request_single_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + date_to = form_data.get('to_date') + if date_to == None: + date_to = selected_date + + start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d') + + # Determine direction and swap dates if necessary + if start_date > end_date: + selected_date, date_to = date_to, selected_date + + devices_list = form_data.get('devices_list') + radar_details = {} + #devices_list = '[267,560,"?",null,"64B70888F6F0"]' + #devices_list = '[[267,560,"?",null,"64B70888F6F0"],[268,561,"?",null,"64B70888F6F1"]]' + sensor_list_loc = [form_data.get('sensor_list')] + is_nested, device_details = check_and_parse(devices_list) + if not is_nested: + device_ids_list = [device_details[1]] + well_ids_list = [device_details[0]] + else: + device_ids_list = list(map(lambda x: x[1], device_details)) + well_ids_list =list(map(lambda x: x[0], device_details)) + + data_type = form_data.get('data_type') + epoch_from_utc, _ = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + _, epoch_to_utc = GetLocalTimeEpochsForDate(date_to, time_zone_s) #>= #< + + #we need to + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + + days = (epoch_to_utc - epoch_from_utc) / (60 * 1440) + + well_id = well_ids_list[0] + all_slices = {} + radar_part = "" + if len(device_details) > 4: + device_id2_mac = {device_details[1]: device_details[4]} + #device_id2_mac = {device_details[1]: device_details[3]} + #epoch_to = '1730592010' #smal sample to test + #radar_part = form_data.get('radar_part') we need to find what radar part is configured in device settings + if len(device_details) > 5: + radar_part_all = device_details[5] + + if type(radar_part_all) == str: + radar_part_all = ["s3_max",int(radar_part_all)] + elif type(radar_part_all) == int: + radar_part_all = ["s3_max",radar_part_all] + elif type(radar_part_all) == list: + pass + + + + else: + radar_part_all = ["s3_max",12] + + + if len(radar_part_all) > 1: + radar_part = radar_part_all[0] + #we need only column name and not min or max here + if "_" in radar_part: + radar_parts = radar_part.split("_") + radar_part = radar_parts[0] + radar_details[device_details[1]] = radar_part_all + + #devices = GetVisibleDevices(deployment_id) + temp_calib, humid_calib = GetCalibMaps(device_ids_list) + + for device_id in device_ids_list: + + sensor_data = {} + for sensor in sensor_list_loc: + st = time.time() + if days < 3: + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + elif days < 14: + bucket_size = "1m" + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + else: + bucket_size = "10m" + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + #Lets apply calibration: + if sensor == "temperature": + temperature_calib = temperature_offset #float(temp_calib[device_id].split(",")[2]) + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + if sensor == "humidity": + line_part = [(timestamp, value + humidity_offset) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + + if False: + # Create CSV content as a string + csv_content = "Record_Index,Timestamp,Value,Time_Diff_Seconds,Time_Diff_Milliseconds\n" + + for i in range(len(line_part)): + timestamp, value = line_part[i] + + if i == 0: + # First record has no previous record to compare + time_diff_seconds = 0 + time_diff_ms = 0 + else: + # Calculate time difference from previous record + prev_timestamp = line_part[i-1][0] + time_diff = timestamp - prev_timestamp + time_diff_seconds = time_diff.total_seconds() + time_diff_ms = time_diff_seconds * 1000 + + # Format the row + row = f"{i},{timestamp.isoformat()},{value},{round(time_diff_seconds, 6)},{round(time_diff_ms, 3)}\n" + csv_content += row + + # Write to file + with open('time_differences.csv', 'w', encoding='utf-8') as f: + f.write(csv_content) + + print("CSV file 'time_differences.csv' created successfully!") + + #print("@1", time.time() - st) + #first = 3300 + #last = 3400 + #line_part = line_part[first:last] + line_part_t = [] + #st = time.time() + #line_part_t = [tuple(x[:2]) for x in line_part] + #print(time.time() - st) + #st = time.time() + #line_part_t = list({(dt.timestamp(), value) for dt, value in line_part}) + #print(time.time() - st) + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + #print("@2", time.time() - st) + + #Lets add point in minute 0 and minute 1439 + + #st = time.time() + #cleaned_values = clean_data_fast(line_part_t, window=5, threshold=2.0) + #print("@3", time.time() - st) + + sensor_data[sensor] = cleaned_values + + + if len(device_details) > 4: + all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + else: + all_slices[device_id] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + dataa = {} + dataa['Function'] = "single_slicedata" + dataa['devices_list'] = devices_list + dataa['all_slices'] = all_slices + dataa['radar_details'] = radar_details + dataa['time_zone_st'] = time_zone_s + dataa['well_id'] = well_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + elif function == "get_sensor_bucketed_data_by_room_sensor": + # Inputs: + # user_name and token + # deployment_id - from which report gets deployment set (all rooms and devices) to get timezone + # date - one day in a format YYYY-MM-DD + # sensor - temperature/radar/etc.. see full list + # (tells what sensor data to be retrieved) + # "voc" for all smell use s4 (lower reading is higher smell, max=0 find min for 100%) + # "radar" returns s28 + # radar_part - optional and applies only to radar (tells which segment of radar to be retrieved) + # bucket_size - ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + # location - room name (has to be unique) + # data_type - ML + # Output: son structure with the following info + # chart_data with rooms : [list] + deployment_id = form_data.get('deployment_id') + selected_date = form_data.get('date') + sensor = form_data.get('sensor') # one sensor + radar_part = form_data.get('radar_part') + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + bucket_size = "no" if (result := form_data.get('bucket_size')) in (None, "") else (result.strip() if result.strip() in buckets else "no") + #bucket_size = res2 if (res := form_data.get('bucket_size')) is not None and (res2 := str(res).strip()) and res2 in {'no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'} else 'no' + location = form_data.get('location') + data_type = form_data.get('data_type') + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) + + # obtain devices_list for deployment_id + selected_date = selected_date.replace("_","-") + devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + sensor_data = {} + units = "°C" + if "America" in time_zone_s: + units = "°F" + # see https://www.w3schools.com/cssref/css_colors.php + sensor_props = {"temperature": ["red", units], + "humidity": ["blue", "%"], + "voc": ["orange", "PPM"], + "co2": ["orange", "PPM"], + "pressure": ["magenta", "Bar"], + "radar": ["cyan", "%"], + "light": ["yellow", "Lux"]} + + current_time_la = datetime.datetime.now(pytz.timezone(time_zone_s)) + formatted_time = current_time_la.strftime('%Y-%m-%dT%H:%M:%S') #"2025-02-06T20:09:00" + + result_dictionary = { + "last_report_at": formatted_time, + "color": sensor_props[sensor][0] if sensor in s_table else "grey", + "units": sensor_props[sensor][1] if sensor in s_table else "?" + } + #sensor_mapping = {"co2": "s4", "voc": "s9"} + #sensor = sensor_mapping.get(sensor, sensor) + temp_calib, humid_calib = GetCalibMaps(device_ids) + #print(device_ids) + #print(temp_calib) + #print(humid_calib) + #print("++++++++++++++++++") + chart_data = [] + # example data in each element of devices_list is (266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + for well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to in devices_list: + loc_and_desc = location_name + if description != None and description != "": + loc_and_desc = loc_and_desc + " " + description + + if loc_and_desc == location: + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + if sensor == "temperature": + if "," in temp_calib[device_id]: + temperature_calib = float(temp_calib[device_id].split(",")[2]) + else: + temperature_calib = -10 + + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + + if sensor == "humidity": + line_part = [(timestamp, value + humidity_offset) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + line_part_t = [] + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + compressed_readings = convert_timestamps_lc(cleaned_values, time_zone_s) + if sensor == "temperature": + if units == "°F":#"America" in time_zone_s: + compressed_readings = CelsiusToFahrenheitList(compressed_readings) + + sensor_data[sensor] = compressed_readings + chart_data.append({'name': location_name, 'data': compressed_readings}) + result_dictionary['chart_data'] = chart_data + payload = result_dictionary + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "get_sensor_data_by_deployment_id": + # Inputs: + # user_name and token + # deployment_id - from which report gets deployment set (all rooms and devices) + # date - one day in a format YYYY-MM-DD + # sensor - temperature/radar/etc.. see full list (tells what sensor data to be retrieved) + # radar_part - optional and applies only to radar (tells which segment of radar to be retrieved) + # bucket_size - ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + # data_type - ML + # Output: son structure with the following info + # chart_data with rooms : [list] + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + sensor = form_data.get('sensor') # one sensor + radar_part = form_data.get('radar_part') + buckets = ['no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'] + bucket_size = "no" if (result := form_data.get('bucket_size')) in (None, "") else (result.strip() if result.strip() in buckets else "no") + #bucket_size = res2 if (res := form_data.get('bucket_size')) is not None and (res2 := str(res).strip()) and res2 in {'no', '10s', '1m', '5m', '10m', '15m', '30m', '1h'} else 'no' + data_type = form_data.get('data_type') + + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + # obtain devices_list for deployment_id + selected_date = selected_date.replace("_","-") + #timee = LocalDateToUTCEpoch(selected_date, time_zone_s)+5 + devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + sensor_data = {} + # see https://www.w3schools.com/cssref/css_colors.php + sensor_props = {"temperature": ["red", "°C"], + "humidity": ["blue", "%"], + "voc": ["orange", "PPM"], + "co2": ["orange", "PPM"], + "pressure": ["magenta", "Bar"], + "radar": ["cyan", "%"], + "light": ["yellow", "Lux"]} + result_dictionary = { + "last_report_at": "2025-02-06T20:09:00", + "color": sensor_props[sensor][0] if sensor in s_table else "grey", + "units": sensor_props[sensor][1] if sensor in s_table else "?" + } + #sensor_mapping = {"co2": "s4", "voc": "s9"} + #sensor = sensor_mapping.get(sensor, sensor) + + chart_data = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + line_part = ReadSensor3(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part, bucket_size) + + if sensor == "temperature": + temperature_calib = float(temp_calib[device_id].split(",")[2]) + line_part = [(timestamp, value + temperature_calib) for timestamp, value in line_part] + + + window = sensor_legal_values[sensor][2] + line_part_t = [] + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + compressed_readings = convert_timestamps_lc(cleaned_values, time_zone_s) + + #compressed_readings = [(time.strftime("%H:%M", time.gmtime(lst[0][0])), float(sum(t for _, t in lst)/len(lst))) + #for _, lst in ((k, list(g)) + #for k, g in itertools.groupby(cleaned_values, key=lambda x: time.gmtime(x[0]).tm_hour))] + sensor_data[sensor] = compressed_readings + chart_data.append({'name': location_name, + 'data': compressed_readings}) + result_dictionary['chart_data'] = chart_data + #all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + #is_neste, device_details = check_and_parse(devices_list) + #if not is_nested: + #device_ids_list = [device_details[1]] + #well_ids_list = [device_details[0]] + #else: + #device_ids_list = list(map(lambda x: x[1], device_details)) + #well_ids_list =list(map(lambda x: x[0], device_details)) + #well_id = well_ids_list[0] + #all_slices = {} + #device_id2_mac = {device_details[1]: device_details[4]} + #for device_id in device_ids_list: + #device_id2_mac + #sensor_data = {} + #for sensor in sensor_list_loc: + #st = time.time() + #line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + #window = sensor_legal_values[sensor][2] + #line_part_t = [] + #line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + #st = time.time() + #cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + #cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + #sensor_data[sensor] = cleaned_values + #all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + #dataa = {} + #dataa['Function'] = "single_slicedata" + #dataa['devices_list'] = devices_list + #dataa['all_slices'] = all_slices + #dataa['time_zone_st'] = time_zone_s + #dataa['well_id'] = well_id + #resp.media = package_response(dataa) + #resp.status = falcon.HTTP_200 + result_dictionary2 = { + "alert_text": "No alert", + "alert_color": "bg-green-100 text-green-700", + "last_report_at": "ISO TIMESTAMP", + "chart_data": [ + { + "rooms": [ + { "name": "Bathroom", + "data": [ + {"title": "12AM","value": 20}, + {"title": "01AM","value": 20}, + {"title": "02AM","value": 26}, + {"title": "03AM","value": 16}, + {"title": "04AM","value": 27}, + {"title": "05AM","value": 23}, + {"title": "06AM","value": 26}, + {"title": "07AM","value": 17}, + {"title": "08AM","value": 18}, + {"title": "09AM","value": 21}, + {"title": "10AM","value": 28}, + {"title": "11AM","value": 24}, + {"title": "12PM","value": 18}, + {"title": "01PM","value": 27}, + {"title": "02PM","value": 27}, + {"title": "03PM","value": 19}, + {"title": "04PM","value": 0}, + {"title": "05PM","value": 0}, + {"title": "06PM","value": 0}, + {"title": "07PM","value": 0}, + {"title": "08PM","value": 0}, + {"title": "09PM","value": 0}, + {"title": "10PM","value": 0}, + {"title": "11PM","value": 0} + ] + }, + { "name": "Kitchen", + "data": [ + {"title": "00AM","value": 19}, + {"title": "01AM","value": 10}, + {"title": "02AM","value": 8}, + {"title": "03AM","value": 14}, + {"title": "04AM","value": 20}, + {"title": "05AM","value": 8}, + {"title": "06AM","value": 7}, + {"title": "07AM","value": 17}, + {"title": "08AM","value": 3}, + {"title": "09AM","value": 19}, + {"title": "10AM","value": 4}, + {"title": "11AM","value": 6}, + {"title": "12PM","value": 4}, + {"title": "01PM","value": 14}, + {"title": "02PM","value": 17}, + {"title": "03PM","value": 20}, + {"title": "04PM","value": 19}, + {"title": "05PM","value": 15}, + {"title": "06PM","value": 5}, + {"title": "07PM","value": 19}, + {"title": "08PM","value": 3}, + {"title": "09PM","value": 30}, + {"title": "10PM","value": 1}, + {"title": "11PM","value": 12 } + ] + }, + { "name": "Living Room", + "data": [ + {"title": "00AM","value": 25}, + {"title": "01AM","value": 24}, + {"title": "02AM","value": 19}, + {"title": "03AM","value": 20}, + {"title": "04AM","value": 22}, + {"title": "05AM","value": 20}, + {"title": "06AM","value": 11}, + {"title": "07AM","value": 5}, + {"title": "08AM","value": 16}, + {"title": "09AM","value": 22}, + {"title": "10AM","value": 23}, + {"title": "11AM","value": 14}, + {"title": "12PM","value": 0}, + {"title": "01PM","value": 7}, + {"title": "02PM","value": 25}, + {"title": "03PM","value": 29}, + {"title": "04PM","value": 23}, + {"title": "05PM","value": 27}, + {"title": "06PM","value": 27}, + {"title": "07PM","value": 20}, + {"title": "08PM","value": 2}, + {"title": "09PM","value": 24}, + {"title": "10PM","value": 21}, + {"title": "11PM","value": 14 } + ] + } + ] + } + ] + } + payload = result_dictionary + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + #AddToLog(payload) + #return + elif function == "request_device_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + epoch_from_utc = form_data.get('epoch_from') + epoch_to_utc = form_data.get('epoch_to') + device_id = form_data.get('device_id') + well_id = form_data.get('well_id') + MAC = form_data.get('MAC') + sensor_list_loc = form_data.get('sensors_list') + sensor_list = sensor_list_loc.split(",") + device_ids_list = [device_id] + well_ids_list = [well_id] + maps_dates, positions_list, timezone_s = GetDeploymentDatesBoth(deployment_id) + + data_type = "RL" + #epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + + #epoch_to = '1730592010' #smal sample to test + radar_part = form_data.get('radar_part') + well_id = well_ids_list[0] + all_slices = {} + #device_id2_mac = {device_details[1]: device_details[4]} + for device_id in device_ids_list: + #device_id2_mac + sensor_data = {} + for sensor in sensor_list: + st = time.time() + line_part = ReadSensor(device_id, sensor, epoch_from_utc, epoch_to_utc, data_type, radar_part) + window = sensor_legal_values[sensor][2] + #print("@1", time.time() - st) + #first = 3300 + #last = 3400 + #line_part = line_part[first:last] + line_part_t = [] + #st = time.time() + #line_part_t = [tuple(x[:2]) for x in line_part] + #print(time.time() - st) + #st = time.time() + #line_part_t = list({(dt.timestamp(), value) for dt, value in line_part}) + #print(time.time() - st) + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + #cleaned_values = cleaned_values_t #add_boundary_points(cleaned_values_t, time_zone_s) + #print("@2", time.time() - st) + + #Lets add point in minute 0 and minute 1439 + + #st = time.time() + #cleaned_values = clean_data_fast(line_part_t, window=5, threshold=2.0) + #print("@3", time.time() - st) + cleaned_values = ScaleToCommon(cleaned_values_t, sensor) + sensor_data[sensor] = cleaned_values + all_slices[device_id] = sensor_data + + dataa = {} + dataa['Function'] = "device_slicedata" + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['proximity'] = positions_list + dataa['well_id'] = well_id + dataa['MAC'] = MAC + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + #return + elif function == "request_single_radar_slice": + deployment_id = form_data.get('deployment_id') + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + selected_date = form_data.get('date') + devices_list = form_data.get('devices_list') + ctrl_key_state = form_data.get('ctrl_key_state') + alt_key_state = form_data.get('alt_key_state') + #devices_list = '[267,560,"?",null,"64B70888F6F0"]' + #devices_list = '[[267,560,"?",null,"64B70888F6F0"],[268,561,"?",null,"64B70888F6F1"]]' + sensor_index_list = [form_data.get('sensor_index_list')] + is_nested, device_details = check_and_parse(devices_list) + if not is_nested: + device_ids_list = [device_details[1]] + well_ids_list = [device_details[0]] + else: + device_ids_list = list(map(lambda x: x[1], device_details)) + well_ids_list =list(map(lambda x: x[0], device_details)) + + epoch_from_utc, epoch_to_utc = GetLocalTimeEpochsForDate(selected_date, time_zone_s) #>= #< + + #epoch_to = '1730592010' #smal sample to test + radar_part = form_data.get('radar_part') + well_id = well_ids_list[0] + all_slices = {} + device_id2_mac = {device_details[1]: device_details[4]} + for device_id in device_ids_list: + device_id2_mac + sensor_data = {} + for sensor_index in sensor_index_list: + st = time.time() + sensor = ["m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m08_max", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s28_max", "s28_min"][int(sensor_index)] + + line_part = ReadRadarDetail(device_id, sensor, epoch_from_utc, epoch_to_utc, alt_key_state) + window = sensor_legal_values["radar"][2] + + line_part_t = [(x[0].timestamp(), x[1]) for x in line_part] + st = time.time() + cleaned_values_t = clean_data_pd(line_part_t, window=window, percentile=99) + cleaned_values = add_boundary_points(cleaned_values_t, time_zone_s) + if len(sensor) < 4: + sensor_data[sensor+"_max"] = cleaned_values + else: + sensor_data[sensor] = cleaned_values + all_slices[device_id2_mac[device_id]] = sensor_data #use MAC instead of device_id, since device is sending data with MAC only + + dataa = {} + dataa['Function'] = "single_slicedata" + dataa['devices_list'] = devices_list + dataa['all_slices'] = all_slices + dataa['time_zone_st'] = time_zone_s + dataa['well_id'] = well_id + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + elif function == "get_deployment": + blob_data = read_file("deployment.html") + deployment_id = form_data.get('deployment_id') + #lets update "Deployments" select + users = GetUsersFromDeployments(privileges) + blob_data = UpdateDeploymentsSelector(blob_data, users, False, deployment_id) + + resp.content_type = "text/html" + resp.text = blob_data + return + elif function == "get_deployment_j": + deployment_id = form_data.get('deployment_id') + time_zone_st = GetTimeZoneOfDeployment(deployment_id) + date = form_data.get('date') + if date == None: + + # Get today's date + local_timezone = pytz.timezone(time_zone_st) # Replace with your local timezone + date = datetime.datetime.now(local_timezone).strftime('%Y-%m-%d') + + #epoch_from_utc = int(datetime.datetime.strptime(date, "%Y-%m-%d").timestamp()) + #devices_list, device_ids = GetProximityList(deployment_id, epoch_from_utc) + + dataa = {} + dataa['Function'] = "deployment_details" + if privileges == "-1": + deployment = DeploymentDetails(deployment_id) + dataa['deployment_details'] = deployment + else: + privileges = privileges.split(",") + if deployment_id in privileges: + deployment = DeploymentDetails(deployment_id) + dataa['deployment_details'] = deployment + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + elif function == "set_floor_layout": + deployment_id = form_data.get('deployment_id') + layout = form_data.get('layout') + + if privileges == "-1" or deployment_id in privileges: + ok = StoreFloorPlan(deployment_id, layout) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + else: + payload = {'ok': 0, 'error': "not allowed"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + return + elif function == "get_floor_layout": + deployment_id = form_data.get('deployment_id') + + dataa = {} + dataa['Function'] = "deployment_details" + if privileges == "-1": + layout = GetFloorPlan(deployment_id) + dataa['layout'] = layout + else: + privileges = privileges.split(",") + if deployment_id in privileges: + layout = GetFloorPlan(deployment_id) + dataa['layout'] = layout + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + elif function == "get_beneficiary": + user_id = form_data.get('user_id') + all_beneficiaries = ListBeneficiaries(privileges, user_id) + beneficiaries_list = [] + for beneficiary_temp in all_beneficiaries: + beneficiaries_list.append(str(beneficiary_temp[0])) + + dataa = {} + dataa['Function'] = "beneficiary_details" + if user_id in beneficiaries_list: + beneficiary = UserDetails(user_id) + #lets remove fields not relevant for beneficiary + try: + del beneficiary['time_edit'] + except: + pass + + try: + del beneficiary['user_edit'] + except: + pass + + try: + del beneficiary['access_to_deployments'] + except: + pass + dataa['beneficiary_details'] = beneficiary + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + elif function == "get_caretaker": + + user_name = form_data.get('user_name') + + + all_caretakers = ListCaretakers(privileges, user_name) + if len(all_caretakers) > 1: + user_id = form_data.get('user_id') + else: + user_id = str(all_caretakers[0][0]) + + caretakers_list = [] + for caretakers_temp in all_caretakers: + caretakers_list.append(str(caretakers_temp[0])) + + dataa = {} + dataa['Function'] = "caretaker_details" + if user_id in caretakers_list: + caretaker = UserDetails(user_id) + #lets remove fields not relevant for beneficiary + try: + del caretaker['time_edit'] + except: + pass + + try: + del caretaker['user_edit'] + except: + pass + + dataa['caretaker_details'] = caretaker + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + return + + elif function == "get_device": + device_id = form_data.get('device_id') + device_mac = form_data.get('mac') + min_well_id = form_data.get('min_well_id') + if min_well_id != None: + min_well_id = int(min_well_id) + + if device_mac != None: + device_det = GetDeviceDetailsSingleFromMac(device_mac) + print(device_det) + dataa = {} + dataa['Function'] = "device_details" + dataa['device_details'] = device_det + if device_det == {}: + dataa['next_well_id'] = GetNextWellId(min_well_id) + else: + devices = GetVisibleDevices(privileges) + dataa = {} + dataa['Function'] = "device_details" + dataa['device_details'] = {} + if privileges == "-1": + #device_det = GetDeviceDetails(device_id) + device_det = GetDeviceDetailsSingle(device_id) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + dataa['device_details'] = device_det + else: + devices_list = [] + for device_id_temp in devices: + devices_list.append(str(device_id_temp[0])) + + if device_id in devices_list: + device_det = GetDeviceDetailsSingle(device_id) + if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "": + device_det['radar_threshold'] = '["s3_max",12]' + + + dataa['device_details'] = device_det + + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + return + + + elif function == "set_deployment": + + user_name = form_data.get('user_name') + token = form_data.get('token') + password = form_data.get('password') + + + if user_name == "" or user_name == None: #new user + + user_name + resp.media = package_response("Log-Out", HTTP_401) + return + + if password != "" and password != None: + privileges, user_id = ValidUser(user_name, password) + if privileges == "0": + resp.media = package_response("Log-Out", HTTP_401) + return + else: + token = generate_token(user_name) + + + + if token != "" and token != None: + user_info = verify_token(token) + + if user_info == None or user_info["username"] != user_name: + resp.media = package_response("Log-Out", HTTP_401) + return + + + deployment = form_data.get('deployment') + beneficiary_name = form_data.get('beneficiary_name') + beneficiary_email = form_data.get('beneficiary_email') + beneficiary_address = form_data.get('beneficiary_address') + caretaker_username = form_data.get('caretaker_username') + caretaker_email = form_data.get('caretaker_email') + num_people = int(form_data.get('num_people')) + pets = int(form_data.get('pets')) + year_born = int(form_data.get('year_born')) + gender = form_data.get('gender') + race = form_data.get('race') + gps_lat = form_data.get('gps_lat') + gps_lng = form_data.get('gps_lng') + devices = form_data.get('devices') + devices_list = json.loads(devices) + wifis = form_data.get('wifis') + wifis_list = json.loads(wifis) + + print(deployment) + print(beneficiary_name) + print(beneficiary_email) + print(beneficiary_address) + print(caretaker_username) + print(caretaker_email) + print(num_people) + print(pets) + print(year_born) + print(gender) + print(race) + print(gps_lat) + print(gps_lng) + print(devices_list) + print(wifis_list) + + #lets check if devices listed are not part of existing deployment + success, result = DevicesNotUsed(devices, user_name) + + if success: + if result["deployed"]: + error_string = f"These devices are already deployed: {result['deployed']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if result["not_found"]: + error_string = f"These devices are not available: {result['not_found']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if not result["deployed"] and not result["not_found"]: + print("All devices are available for deployment") + ok, error_string = StoreDisclaimer2DB(form_data) + + if ok == 1: + #Lets check if this user already has account or not + if not AccountExists(user_name): + #lets create new account for this caretaker + if password == None or password == "": + password = CreatePassword(8) + + #lets suplement form_data with parts needed for existing StoreCaretaker2DB function + editing_user_id = "0" #specify if editing existing user, otherwise "0" + user_id = "0" #user that is adding this record. New user so "0" + form_data['role_ids'] = "1,2" + form_data['access_to_deployments'] = "45" + #form_data['email'] = "" #this one matches + form_data['new_user_name'] = form_data['user_name'] + form_data['first_name'] = form_data['firstName'] + form_data['last_name'] = form_data['lastName'] + form_data['address_street'] = "" + form_data['address_city'] = "" + form_data['address_zip'] = "" + form_data['address_state'] = "" + form_data['address_country'] = "" + form_data['phone_number'] = form_data['phone'] + form_data['picture'] = "/" + form_data['key'] = password + + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + + if ok == 1: + + #we need to call cd ~/mqtt-auth-service/acl_manager.py + + SendWelcomeEmail(form_data['email'], form_data['first_name'], form_data['last_name'], devices, form_data['phone_number'], form_data['new_user_name'], form_data['key']) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + privileges, user_id = ValidUser(user_name, password) + if user_id == "0": #bad password + error_string = f"Password does not match user {user_name}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + else: + error_string = f"Error: {result}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + + elif function == "purge_phantom_records": + last_valid_id = int(form_data.get('last_valid_device_id')) + + try: + conn = get_db_connection() + with conn.cursor() as cur: + # 1. Check for non-null radar_threshold records + cur.execute(""" + SELECT COUNT(*) + FROM public.devices + WHERE device_id > %s AND radar_threshold IS NOT NULL + """, (last_valid_id,)) + + non_null_count = cur.fetchone()[0] + + if non_null_count > 0: + resp.media = { + "status": "aborted", + "reason": f"Found {non_null_count} records with device_id > {last_valid_id} that have radar_threshold NOT NULL" + } + return + + # 2. Delete phantom records + cur.execute(""" + DELETE FROM public.devices + WHERE device_id > %s AND radar_threshold IS NULL + """, (last_valid_id,)) + + deleted_count = cur.rowcount + + if deleted_count > 0: + # 3. Reset sequence to the ACTUAL maximum device_id in the table + cur.execute("SELECT COALESCE(MAX(device_id), 0) FROM public.devices") + actual_max_id = cur.fetchone()[0] + + # Reset sequence to actual max + cur.execute("SELECT setval('devices_device_id_seq', %s, true)", (actual_max_id,)) + + # Get next ID to verify + cur.execute("SELECT nextval('devices_device_id_seq')") + next_id = cur.fetchone()[0] + + conn.commit() + + resp.media = { + "status": "success", + "deleted_count": deleted_count, + "actual_max_device_id": actual_max_id, + "sequence_reset_to": actual_max_id, + "next_device_id": next_id + } + else: + resp.media = { + "status": "no_action", + "message": "No phantom records found to delete" + } + + except Exception as e: + conn.rollback() + resp.media = {"error": str(e)} + resp.status = falcon.HTTP_500 + + elif function == "request_deployment_map_new": + st = time.time() + print(f"$0 ----{time.time() - st}") + deployment_id = form_data.get('deployment_id') + map_type = form_data.get('map_type') + print(f"$1 ----{time.time() - st}") + maps_dates, positions_list, timezone_s = GetDeploymentDatesBoth(deployment_id) + print(f"$2 ----{time.time() - st}") + datee = form_data.get('date') + if maps_dates != []: + + if datee == "2022-4-2": #that one is default in HTML so disregard + datee = maps_dates[0] + + locations_desc_map = {} + for details in positions_list: + well_id = details[0] + location = details[2] + if details[3] != None and details[3] != "": + location = location +" "+ details[3] + + if details[6] != None and details[6] != "": + location = location +" "+ details[6] + + MAC = details[4] + locations_desc_map[well_id] = location + print(f"$3 ----{time.time() - st}") + + dataa = {} + dataa['Function'] = "deployments_maps_report" + dataa['proximity'] = positions_list + maps_dates.sort(reverse = True) + dataa['maps_dates'] = maps_dates + dataa['device_count'] = len(positions_list) + dataa['time_zone'] = timezone_s + dataa['map_type'] = map_type + + #MACs_list = GetMACsListSimple(positions_list) + #MACs_map = {} + + #for details in positions_list: + # id = details[0] + # MAC = details[3] + # MACs_map[id] = MAC + #for i in range(len(MACs_list)): + # MACs_map[devices_list[i]] = MACs_list[i][0] + + id = positions_list[0][0] + #dataa['MACs_map'] = MACs_map + dataa['locations_desc_map'] = locations_desc_map + #proximity_list = proximity.split(",") + print(f"$4 ----{time.time() - st}") + + if id < 200: + checkmarks_string = 'T>\n' + checkmarks_string = checkmarks_string + 'H>\n' + checkmarks_string = checkmarks_string + 'P>\n' + checkmarks_string = checkmarks_string + 'C>\n' + checkmarks_string = checkmarks_string + 'V>\n' + checkmarks_string = checkmarks_string + 'L>\n' + checkmarks_string = checkmarks_string + 'R>
    ' + else: #>200 = ["Temperature", "Humidity", "Pressure", "Light", "Radar", "VOC"] + + checkmarks_string = 'T>\n' + checkmarks_string = checkmarks_string + 'H>\n' + checkmarks_string = checkmarks_string + 'P>\n' + checkmarks_string = checkmarks_string + 'L>\n' + checkmarks_string = checkmarks_string + 'R>\n' + + checkmarks_string = checkmarks_string + 'S0>\n' + checkmarks_string = checkmarks_string + 'S1>\n' + checkmarks_string = checkmarks_string + 'S2>\n' + checkmarks_string = checkmarks_string + 'S3>\n' + checkmarks_string = checkmarks_string + 'S4>\n' + checkmarks_string = checkmarks_string + 'S5>\n' + checkmarks_string = checkmarks_string + 'S6>\n' + checkmarks_string = checkmarks_string + 'S7>\n' + checkmarks_string = checkmarks_string + 'S8>\n' + checkmarks_string = checkmarks_string + 'S9>
    ' + + checked_or_not = " checked" + + for index in range(len(positions_list)): + details = positions_list[index] + device_id = details[0] + location = details[2] + if details[3] != None and details[3] != "": + location = location + " " + details[3] + + if details[6] != None and details[6] != "": + location = location + " " + details[6] + + checkmarks_string = checkmarks_string + str(device_id) + '>\n' + checked_or_not = '' + + print(f"$5 ----{time.time() - st}") + + dataa['checkmarks'] = checkmarks_string + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "request_proximity": + deployment = form_data.get('deployment_id') + timee = form_data.get('time') + #timee = StringToEpoch(datee) + #print(deployment, timee) + well_ids, device_ids = GetProximityList(deployment, timee) + #print(proximity) + dataa = {} + dataa['Function'] = "proximity_report" + if len(well_ids) > 0: + dataa['proximity'] = well_ids + else: + dataa['proximity'] = [] + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + elif function == "request_devices": + deployment_id = form_data.get('deployment_id') + group_id = form_data.get('group_id') + location = form_data.get('location') + if location == "0": + location = "All" + is_fresh = form_data.get('is_fresh') + matching_devices = GetMatchingDevices(privileges, group_id, deployment_id, location) + dataa = {} + dataa['Function'] = "devices_report" + if len(matching_devices) > 0: + dataa['devices'] = matching_devices + else: + dataa['devices'] = [] + + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "get_deployment_details": + deployment_id = form_data.get('deployment_id') + group_id = form_data.get('group_id') + location = form_data.get('location') + if location == "0": + location = "All" + is_fresh = form_data.get('is_fresh') + matching_devices = GetMatchingDevicesComplete(privileges, group_id, deployment_id, location) + deployment = DeploymentDetails(deployment_id) + dataa = {} + dataa['Function'] = "devices_report" + if len(matching_devices) > 0: + dataa['devices'] = matching_devices + else: + dataa['devices'] = [] + + if len(deployment) > 0: + dataa['details'] = deployment + else: + dataa['details'] = {} + resp.media = package_response(dataa) + resp.status = falcon.HTTP_200 + + elif function == "device_form": + import uuid + request_id = str(uuid.uuid4())[:8] + logger.debug(f"[{request_id}] device_form ENTRY") + + device_id = None + if 'editing_device_id' in form_data: + device_id = int(form_data.get('editing_device_id')) + logger.debug(f"[{request_id}] Found editing_device_id: {device_id}") + else: + logger.debug(f"[{request_id}] No editing_device_id found, device_id = {device_id}") + + ok = 0 + logger.debug(f"[{request_id}] privileges = {privileges}") + + if privileges == "-1": + logger.debug(f"[{request_id}] CALLING StoreDevice2DB with device_id: {device_id}") + ok = StoreDevice2DB(form_data, device_id) + logger.debug(f"[{request_id}] StoreDevice2DB returned: {ok}") + payload = {'ok': 1} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + logger.debug(f"[{request_id}] Non-admin path...") + if device_id != None: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id: + logger.debug(f"[{request_id}] CALLING StoreDevice2DB in loop with device_id: {device_id}") + ok = StoreDevice2DB(form_data, device_id) + logger.debug(f"[{request_id}] StoreDevice2DB in loop returned: {ok}") + break + else: + logger.debug(f"[{request_id}] device_id is None, returning error") + payload = {'ok': 0} + resp.media = package_response(payload) + resp.status = falcon.HTTP_500 + return + + logger.debug(f"[{request_id}] Final ok value: {ok}") + elif function == "device_set_group": + group_id = int(form_data.get('group_id')) + + MAC = form_data.get('mac') + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = StoreGroupToDevice(device_id_or_mac, group_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreGroupToDevice(device_id_or_mac, group_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_set_well_id": + well_id = int(form_data.get('well_id')) + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + + ok = "" + if privileges == "-1": + ok = StoreWellIdToDevice(device_id_or_mac, well_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreWellIdToDevice(device_id_or_mac, well_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_get_live": + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = GetDeviceLive(device_id_or_mac, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = GetDeviceLive(device_id_or_mac, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_set_network_id": + network_id = int(form_data.get('network_id')) + + MAC = form_data.get('mac') + + if MAC != None: + device_id_or_mac = MAC + else: + device_id_or_mac = int(form_data.get('device_id')) + + ok = "" + if privileges == "-1": + ok = StoreNetworkIdToDevice(device_id_or_mac, network_id, user_name) + else: + devices = GetVisibleDevices(privileges) + for device in devices: + if device[0] == device_id_or_mac: + ok = StoreNetworkIdToDevice(device_id_or_mac, network_id, user_name) + break + + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_reboot": + if 'mac' in form_data: + MAC = form_data.get('mac').upper() + device_id_or_mac = MAC + else: + device_id = int(form_data.get('device_id')) + device_id_or_mac = device_id + + ok = "" + if privileges == "-1": + ok = DeviceReboot(device_id_or_mac, user_name) + else: + devices = GetVisibleDevices(privileges) + #for this to work, device_id needs to be specified, not MAC! + for device in devices: + if device[0] == device_id_or_mac: + ok = DeviceReboot(device_id_or_mac, user_name) + break + + print(f"OK = {ok}") + if ok != "": + + payload = ok + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + debug_string = "This device_id is not editable" + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTPError + return + elif function == "device_delete": + + #check if admin! + + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "alarm_on_off": + deployment_id = form_data.get('deployment_id') + alarm_on = int(form_data.get('alarm_on')) + + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + # Lets prepare data to do same as store_alarms function + #read alarm_deployment_settings and all alarm_device_settings from db, and armm all bits that are enabled + deployment_alarms_json, device_alarms_json_map = GetAlarmAllDetails(deployment_id) + deployment_alarms = json.loads(deployment_alarms_json) + enabled = deployment_alarms["enabled"] + if alarm_on == 0: + if GetBit(enabled, 2): + enabled = set_character(enabled, 2, "0") + deployment_alarms["enabled"] = enabled + deployment_alarms_json = json.dumps(deployment_alarms) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + ok = StoreAlarms2DBSimple(deployment_id, 0, deployment_alarms_json, "") + else: + if not GetBit(enabled, 2): + enabled = set_character(enabled, 2, "1") + deployment_alarms["enabled"] = enabled + deployment_alarms_json = json.dumps(deployment_alarms) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + ok = StoreAlarms2DBSimple(deployment_id, 0, deployment_alarms_json, "") + + + if False: #no need to do it since every new_alarms call reads alarm_deployment_settings_ always + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + record_json = json.dumps(record) + redis_conn.lpush('new_alarms', record_json) + + if alarm_on != 0: + for device_id in device_alarms_json_map: + device_alarms_json = device_alarms_json_map[device_id] + device_alarms = json.loads(device_alarms_json) + enabled_alarms = device_alarms["enabled_alarms"] + armed_states = device_alarms["armed_states"] + + if GetBit(enabled_alarms, 8): + armed_states = set_character(armed_states, 8, "1") + if GetBit(enabled_alarms, 9): + armed_states = set_character(armed_states, 9, "1") + if GetBit(enabled_alarms, 10): + armed_states = set_character(armed_states, 10, "1") + + device_alarms["armed_states"] = armed_states + device_alarms_json = json.dumps(device_alarms) + redis_conn.set(f'alarm_device_settings_{device_id}', device_alarms_json) + ok = StoreAlarms2DBSimple(0, device_id, "", device_alarms_json) + + #of course it is needed, how will well-alerts know that new data is stored to db? + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('new_alarms', record_json) + + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_alarm_state": + deployment_id = form_data.get('deployment_id') + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + # Lets prepare data to do same as store_alarms function + #read alarm_deployment_settings and all alarm_device_settings from db, and armm all bits that are enabled + deployment_alarms_json, device_alarms_json_map = GetAlarmAllDetails(deployment_id) + deployment_alarms = json.loads(deployment_alarms_json) + enabled = deployment_alarms["enabled"] + if not GetBit(enabled, 2): + alarm_state = 2 #off + else: + #if any device was trigerred, show 0, otherwise 1 + alarm_state = 1 + for device_id in device_alarms_json_map: + device_alarms_json = device_alarms_json_map[device_id] + device_alarms = json.loads(device_alarms_json) + enabled_alarms = device_alarms["enabled_alarms"] + armed_states = device_alarms["armed_states"] + + if GetBit(enabled_alarms, 8): + if not GetBit(armed_states, 8): #if 0 + alarm_state = 0 + break + if GetBit(enabled_alarms, 9): + if not GetBit(armed_states, 9): + alarm_state = 0 + break + if GetBit(enabled_alarms, 10): + if not GetBit(armed_states, 10): + alarm_state = 0 + break + + + payload = {'ok': 1, 'alarm_state':alarm_state} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "submit_mobile_message": + message = form_data.get('message') + mqtt_id = form_data.get("mqtt_id") + privileges, user_id = GetPriviledgesAndUserId(user_name) + if "function" in message: + current_utc_time = datetime.datetime.now(timezone.utc) + message_map = json.loads(message) + func = message_map["function"] + + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + ok = 1 + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = int(current_utc_time.timestamp() *1000) + + + sql = f""" + INSERT INTO public.mobile_clients_messages + (time, mqtt_id, message, function) + VALUES + ({current_epoch_time}, '{CleanObject(mqtt_id)}','{CleanObject(message)}','{CleanObject(func)}'); + """ + logger.debug(f"sql= {sql}") + # Execute update query + cur.execute(sql) + + # Commit the changes to the database + conn.commit() + + # Close the cursor and connection + + + except Exception as e: + logger.error(f"Error inserting to mobile_clients_messages: {str(e)}") + ok = 0 + + + try: + + current_utc_time = datetime.datetime.now(timezone.utc) + + # Convert to epoch time + current_epoch_time = int(current_utc_time.timestamp() *1000) + + + sql1 = f""" + INSERT INTO public.mobile_clients + (mqtt_id, user_name, user_id, last_message, last_message_time) + VALUES + ('{CleanObject(mqtt_id)}', '{CleanObject(user_name)}', {user_id}, '{CleanObject(message)}', {current_epoch_time}) + ON CONFLICT (mqtt_id) + DO UPDATE SET + user_name = EXCLUDED.user_name, + user_id = EXCLUDED.user_id, + last_message = EXCLUDED.last_message, + last_message_time = EXCLUDED.last_message_time; + """ + logger.debug(f"sql= {sql1}") + # Execute update query + cur.execute(sql1) + + # Commit the changes to the database + conn.commit() + + except Exception as e: + logger.error(f"Error inserting to mobile_clients: {str(e)}") + ok = 0 + + cur.close() + conn.close() + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "get_raw_data": + #container = GetReference("/MAC") + #MAC = req_dict["MAC"][0] + #sensor = req_dict["sensor"][0] + #if "part" in req_dict: + #part = req_dict["part"][0] + #else: + #part = "" + #from_time = req_dict["from_time"][0] + #to_time = req_dict["to_time"][0] + #timezone_str = req_dict["tzone"][0] + #AddToLog("get_raw_data:" + str(MAC) +","+ str(sensor) + "," + str(from_time) + "," + str(to_time) + "," + part+ "," + timezone_str) + ##raw_data = GetRawSensorData(container, MAC, sensor, from_time, to_time, timezone_str) + raw_data = []#GetRawSensorDataFromBlobStorage(MAC, sensor, part, from_time, to_time, timezone_str) + data_payload = {'raw_data': raw_data} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_presence_data": + + deployment_id = form_data.get('deployment_id') + device_id_in_s = form_data.get('device_id') + device_id_in = None + refresh = True#form_data.get('refresh') == "1" + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + filter = int(form_data.get('filter')) + ddate = form_data.get('date') + ddate = ddate.replace("_","-") + to_date = form_data.get('to_date') + + if to_date == None: + to_date = ddate + else: + to_date = to_date.replace("_","-") + + ddate, to_date = ensure_date_order(ddate, to_date) + + + date_obj = datetime.datetime.strptime(ddate, "%Y-%m-%d") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + # Convert back to string + prev_date = previous_day.strftime("%Y-%m-%d") + + data_type = form_data.get('data_type') #all, raw, presence, z-graph + if data_type == None or data_type == "": + data_type = "presence" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + if device_id_in_s != None: #lets remove other devices, since asking for one + device_id_in = int(device_id_in_s) + device_ids = [id for id in device_ids if id == device_id_in] + devices_list = [device for device in devices_list if device[1] == device_id_in] + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + + + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + + + + # Calculate the difference in days + + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from_z.utctimetuple()) + + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + + + #epoch_time = calendar.timegm(time_from.utctimetuple()) + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + + if device_id_in == None or details[1] == device_id_in: + if "," in details[5]: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + #threshold_lst = ["s3_max",12] + else: + threshold_lst = ["s3",int(details[5])] + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + #print(sql) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + cur.execute(zsql) + myz_data = cur.fetchall() + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + row_nr_2_device_id = {} + cnt = 0 + row_nr_2_device_id[0] = 0 + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + presence_map['longpresence'] = {} + presence_map['raw'] = {} + + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour - 7, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + #) + start_time = parsed_time.astimezone(pytz.UTC) + + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3",12] + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * days_difference_long + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + presence_map['raw'][well_id] = [0] * 6 * 1440 * days_difference_long #just place holder + + + print(f"start_time: {start_time}") + print(f"epoch_time being sent: {epoch_time}") + print(f"epoch_time as date: {datetime.datetime.fromtimestamp(epoch_time, tz=pytz.UTC)}") + + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + presence_map = optimized_radar_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + #save_list_to_csv_method1(presence_map['longpresence'][475], "longpresence_initial_data.csv") + + + + overlaps_str = GetOverlapps(deployment_id) + overlaps_lst = [] + if overlaps_str != None: + if ":" in overlaps_str: + overlaps_lst = json.loads(overlaps_str) + temporary_map_day_plus = ClearOverlaps(temporary_map_day_plus, overlaps_lst) + + for device_id in ids_list: + device_id_str = str(device_id) + + if filter > 1: + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s, refresh) + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + + #if data_type != "presence": + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + presence_map["z_graph"][id2well_id[device_id]] = z_graph + + + if data_type == "all" or data_type == "multiple": + #lets create "multiple" series + seen_at_lst, seen_where_list_uf = DetectMultiple(temporary_map_day_plus, overlaps_lst) + #here seen_at is straight decas + #seen_at = [1 if x >= 2 else 0 for x in seen_at] + pers_in_deka = [] + dekas_in_day = 6 * 1440 + for i in range(dekas_in_day, len(seen_where_list_uf)): + n_pers = seen_where_list_uf[i] + pers_in_deka.append(100*len(n_pers)) + + seen_at = filter_out_short_highs_iterative(seen_at_lst, filter) #this converts decas into compressed format! + seen_at_lst = Decompress(seen_at) + pers_in_deka = filter_out_short_same_groups_iterative(pers_in_deka, filter) + persons_decompressed = Decompress(pers_in_deka) + persons = Compress(persons_decompressed) + + multiple_list = CreateZGraphAI(seen_at_lst) + presence_map["multiple"] = multiple_list + presence_map["persons"] = persons + + presence_map["presence"] = CompressList(presence_map["longpresence"]) + + if data_type == "z-graph": + if "raw" in presence_map: + del presence_map["raw"] + + #if "presence" in presence_map: + # del presence_map["presence"] + + if "longpresence" in presence_map: + del presence_map["longpresence"] + + if data_type == "multiple": + if "raw" in presence_map: + del presence_map["raw"] + #if "presence" in presence_map: + # del presence_map["presence"] + + if "z_graph" in presence_map: + del presence_map["z_graph"] + + #if "presence" in presence_map: + if "longpresence" in presence_map: + del presence_map["longpresence"] + + data_payload = presence_map + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_zgraph_data": + + deployment_id = form_data.get('deployment_id') + + if privileges != "-1": + privileges_lst = privileges.split(",") + if deployment_id not in privileges_lst: + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + device_id = int(form_data.get('device_id')) + + devices = GetVisibleDevices(privileges) + + if not any(item[0] == device_id for item in devices): + data_payload = {} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + filter = int(form_data.get('filter')) + ddate = form_data.get('date') + ddate = ddate.replace("_","-") + to_date = form_data.get('to_date') + + if to_date == None: + to_date = ddate + else: + to_date = to_date.replace("_","-") + + ddate, to_date = ensure_date_order(ddate, to_date) + data_type = "z-graph" + + time_zone_s = GetTimeZoneOfDeployment(deployment_id) + timee = LocalDateToUTCEpoch(ddate, time_zone_s)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + time_from_str, _ = GetLocalTimeForDate(ddate, time_zone_s) + _, time_to_str = GetLocalTimeForDate(to_date, time_zone_s) + + time_from_z = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + epoch_time = calendar.timegm(time_from_z.utctimetuple()) + + #time_from = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + #epoch_time = calendar.timegm(time_from.utctimetuple()) + time_to = datetime.datetime.strptime(time_to_str, '%Y-%m-%d %H:%M:%S%z') + + presence_map = {} + presence_map["time_start"] = epoch_time + presence_map["time_zone"] = time_zone_s + + # Calculate the difference in days + days_difference = (time_to - time_from).days + days_difference_long = days_difference + 1 + # Convert string to datetime object + date_obj = datetime.datetime.strptime(time_from_str, "%Y-%m-%d %H:%M:%S%z") + # Subtract one day + previous_day = date_obj - timedelta(days=1) + + # Format back to string in the same format + time_from_z_str = previous_day.strftime("%Y-%m-%d %H:%M:%S%z") + + + device_id_2_threshold = {} + device_id_2_location = {0: "Outside"} + + for details in devices_list: + + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + + if radar_threshold_group_st == None: + radar_threshold_group_st = '["s3_max",12]' #last value is threshold to s28 composite + + if len(radar_threshold_group_st) > 8: + radar_threshold_group = json.loads(radar_threshold_group_st) + else: + radar_threshold_group = ["s3_max",12] + + print(well_id, radar_threshold_group) + + device_id_2_location[device_id] = location_name + device_id_2_threshold[device_id] = radar_threshold_group + + ids_list = [] + well_ids = [] + id2well_id = {} + radar_fields_of_interest = [] + device_field_indexes = {} + for details in devices_list: + threshold_str = details[5] + try: + threshold_lst = json.loads(threshold_str) + except: + threshold_lst = ["s3",12] + #threshold_lst = ["s3_max",12] + + radar_field = threshold_lst[0] + #since we are getting 10 sec dat, no more need for min or max... + radar_field = radar_field.split("_")[0] + if radar_field not in radar_fields_of_interest: + device_field_indexes[radar_field] = len(radar_fields_of_interest) + radar_fields_of_interest.append(radar_field) + + ids_list.append(details[1]) + id2well_id[details[1]] = details[0] + well_ids.append(details[0]) + presence_map["well_ids"] = well_ids + + + devices_list_str = ','.join(str(device[1]) for device in devices_list) + #sql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + sql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_str, time_to_str, ids_list, radar_fields_of_interest) + print(sql) + if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zsql = get_deployment_radar_only_colapsed_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + zsql = get_deployment_radar_10sec_snapped_query(devices_list_str, time_from_z_str, time_to_str, ids_list, radar_fields_of_interest) + print(zsql) + + with get_db_connection() as conn: + with conn.cursor() as cur: + #cur.execute(sql) + #my_data = None + myz_data = None + + #my_data = cur.fetchall() + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + cur.execute(zsql) + myz_data = cur.fetchall() + + #if my_data != None: + + #device_id_2_threshold = {} + #device_id_2_location = {0: "Outside"} + #row_nr_2_device_id = {} + #cnt = 0 + #row_nr_2_device_id[0] = 0 + + ##presence_map['longpresence'] and temporary_map_day_plus are similar, except one is used for Z-graph, and another for multiple persons detection + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #presence_map['presence'] = {} + #presence_map['longpresence'] = {} + + #if data_type == "raw" or data_type == "all": + #presence_map['raw'] = {} + + #for details in devices_list: + ##(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + #well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "raw" or data_type == "all": + #zeros_list = [0] * 6 * 1440 * days_difference + #presence_map['raw'][well_id] = zeros_list + + #if data_type == "presence" or data_type == "all" or data_type == "z-graph" or data_type == "multiple": + #zeros_list = [0] * 6 * 1440 * days_difference + #presence_map['presence'][well_id] = zeros_list + + + ##presence_map[][well_id] = zeros_list + #cnt += 1 + #row_nr_2_device_id[cnt] = well_id + + #if radar_threshold_group_st == None: + #radar_threshold_group_st = '["s3",12]' #last value is threshold to s28 composite + + #if len(radar_threshold_group_st) > 8: + #radar_threshold_group = json.loads(radar_threshold_group_st) + #else: + #radar_threshold_group = ["s3",12] + + #device_id_2_location[well_id] = location_name + #device_id_2_threshold[well_id] = radar_threshold_group + + #start_time_ = my_data[0][0] + #parsed_time = datetime.datetime.strptime(time_from_str, '%Y-%m-%d %H:%M:%S%z') + + #start_time = datetime.datetime( + #parsed_time.year, + #parsed_time.month, + #parsed_time.day, + #parsed_time.hour - 7, # Adjust for UTC-7 + #parsed_time.minute, + #parsed_time.second, + #tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + #) + + #presence_map = optimized_radar_processing(my_data, start_time_, id2well_id, device_id_2_threshold, device_field_indexes, presence_map, data_type) + + ##last_device_id = 0 + ##for radar_read in my_data: #(datetime.datetime(2025, 4, 28, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200))), 559, 6.512857142857143, 6.91, 9.28) + ##local_time = radar_read[0] + ##deca = int((local_time - start_time).total_seconds() / 10) + ##device_id = radar_read[1] + ##if device_id != last_device_id: + ##last_device_id = device_id + ##if data_type == "raw" or data_type == "all": + ##days_decas = len(presence_map['raw'][id2well_id[device_id]]) + ##else: + ##days_decas = len(presence_map['presence'][id2well_id[device_id]]) + ##well_id = id2well_id[device_id] + ##radar_threshold_group_st = device_id_2_threshold[device_id] + ##threshold_sig, threshold = radar_threshold_group_st + ##threshold_sig = threshold_sig.split("_")[0] + + ##radar_val = radar_read[2+device_field_indexes[threshold_sig]] + ##if data_type == "presence" or data_type == "z-graph" or data_type == "all" or data_type == "multiple": + ##if radar_val > threshold: + ##if deca < days_decas: + ##presence_map['presence'][id2well_id[device_id]][deca] = 1 + + ##if data_type == "raw" or data_type == "all": + ##if deca < days_decas: + ##presence_map['raw'][id2well_id[device_id]][deca] = radar_val + + + if myz_data != None: + temporary_map_day_plus = {} + presence_map['z_graph'] = {} + for details in devices_list: + #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]','') + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = details + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + #zeros_list = [0] * 6 * 1440 * (days_difference_long) #+1 is for previous day + + presence_map['z_graph'][well_id] = [] #just place holder + temporary_map_day_plus[well_id] = [0] * 6 * 1440 * (days_difference_long) + presence_map['longpresence'][well_id] = [0] * 6 * 1440 * (days_difference_long) #just place holder + + + parsed_time = datetime.datetime.strptime(time_from_z_str, '%Y-%m-%d %H:%M:%S%z') + + start_time = datetime.datetime( + parsed_time.year, + parsed_time.month, + parsed_time.day, + parsed_time.hour - 7, # Adjust for UTC-7 + parsed_time.minute, + parsed_time.second, + tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)) + ) + + + #start_time_ = myz_data[0][0] + st = time.time() + device_lookup_cache = {} + threshold_cache = {} + temporary_map_day_plus = optimized_processing(myz_data, start_time, id2well_id, device_id_2_threshold, device_field_indexes, temporary_map_day_plus, data_type) + + if data_type == "all" or data_type == "z-graph" or data_type == "presence" or data_type == "multiple": + overlaps_str = GetOverlapps(deployment_id) + overlaps_lst = [] + if overlaps_str != None: + if ":" in overlaps_str: + overlaps_lst = json.loads(overlaps_str) + temporary_map_day_plus = ClearOverlaps(temporary_map_day_plus, overlaps_lst) + + #if data_type == "all" or data_type == "z-graph" or data_type == "presence" or data_type == "multiple": + for device_id in ids_list: + device_id_str = str(device_id) + #if data_type == "presence" or data_type == "all" or data_type == "z-graph": + if filter > 1: + #presence_list = filter_short_groups_numpy(presence_map["presence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + #presence_list = filter_short_groups_c_wc(presence_map["presence"][id2well_id[device_id]], filter, device_id_str, ddate, to_date, time_zone_s) + #presence_listt = filter_short_groupss(presence_map["presence"][id2well_id[device_id]], filter) + #if presence_list != presence_listt: + # print("stop") + #if data_type != "presence": + #longpresence_list = filter_short_groups_numpy(presence_map["longpresence"][id2well_id[device_id]], filter, device_id, ddate+"-"+to_date) + longpresence_list = filter_short_groups_c_wc(presence_map["longpresence"][id2well_id[device_id]], filter, device_id_str, prev_date, to_date, time_zone_s) + #longpresence_listt = filter_short_groupss(presence_map["longpresence"][id2well_id[device_id]], filter) + #if longpresence_list != longpresence_listt: + # print("stop") + # store_to_file(presence_map["longpresence"][id2well_id[device_id]], "test_list") + #presence_map["presence"][id2well_id[device_id]] = presence_list + #if data_type != "presence": + presence_map["longpresence"][id2well_id[device_id]] = longpresence_list + + else: #straight decas + #presence_list = presence_map["presence"][id2well_id[device_id]] + + #if data_type != "presence": + longpresence_list = presence_map["longpresence"][id2well_id[device_id]] + + + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + if filter > 1: #straight decas + presence_list1 = filter_short_high_groups_iterative_analog(temporary_map_day_plus[id2well_id[device_id]], filter) + else: + presence_list1 = temporary_map_day_plus[id2well_id[device_id]] + + temporary_map_day_plus[id2well_id[device_id]] = presence_list1 + + + #if data_type == "z-graph" or data_type == "all" or data_type == "multiple": + for device_id in ids_list: + #print(device_id_2_threshold[id2well_id[device_id]]) + z_graph = CreateZGraphAI(presence_map["longpresence"][id2well_id[device_id]]) #temporary_map_day_plus[id2well_id[device_id]]) + presence_map["z_graph"][id2well_id[device_id]] = z_graph + + + if data_type == "all" or data_type == "multiple": + #lets create "multiple" series + seen_at_lst, seen_where_list_uf = DetectMultiple(temporary_map_day_plus, overlaps_lst) + #here seen_at is straight decas + #seen_at = [1 if x >= 2 else 0 for x in seen_at] + pers_in_deka = [] + dekas_in_day = 6 * 1440 + for i in range(dekas_in_day, len(seen_where_list_uf)): + n_pers = seen_where_list_uf[i] + pers_in_deka.append(100*len(n_pers)) + + seen_at = filter_out_short_highs_iterative(seen_at_lst, filter) #this converts decas into compressed format! + seen_at_lst = Decompress(seen_at) + pers_in_deka = filter_out_short_same_groups_iterative(pers_in_deka, filter) + persons_decompressed = Decompress(pers_in_deka) + persons = Compress(persons_decompressed) + + multiple_list = CreateZGraphAI(seen_at_lst) + presence_map["multiple"] = multiple_list + presence_map["persons"] = persons + + presence_map["presence"] = presence_map["longpresence"] + if data_type == "z-graph": + if "raw" in presence_map: + del presence_map["raw"] + if "presence" in presence_map: + del presence_map["presence"] + if "longpresence" in presence_map: + del presence_map["longpresence"] + + if data_type == "multiple": + if "raw" in presence_map: + del presence_map["raw"] + if "presence" in presence_map: + del presence_map["presence"] + if "longpresence" in presence_map: + del presence_map["longpresence"] + if "z_graph" in presence_map: + del presence_map["z_graph"] + + if "presence" in presence_map: + presence_map["presence"] = CompressList(presence_map["presence"]) + + data_payload = presence_map + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_candle_data": + container = GetReference("/MAC") + MAC = req_dict["MAC"][0] + sensor = req_dict["sensor"][0] + from_time = req_dict["from_time"][0] + to_time = req_dict["to_time"][0] + part = req_dict["part"][0] + tzone = req_dict["tzone"][0] + AddToLog(str(req_dict)) + candle_data = GetCandleSensorData(container, MAC, sensor, from_time, to_time, part, tzone) + data_payload = {'candle_data': candle_data} + resp.media = package_response(data_payload) + resp.status = falcon.HTTP_200 + return + + elif function == "deployment_form": + editing_deployment_id = form_data.get('editing_deployment_id') + + ok = StoreDeployment2DB(form_data, editing_deployment_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "deployment_delete": + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "deployments_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + user_id = form_data.get('user_id') + first = 0 + last = 1000000 + + try: + if first_s != None: + first = int(first_s) + except ValueError: + pass + + try: + if last_s != None: + last = int(last_s) + except ValueError: + pass + + #user_id = form_data.get('user_id') + if user_id == "" or user_id == None: + #user_id = GetUserId(user_name) + privileges, user_id = GetPriviledgesAndUserId(user_name) + else: + privileges = GetPriviledgesOnly(user_name) + + all_deployments = ListDeployments(privileges, user_id) + + cnt = 0 + + for deployment in all_deployments: + cnt += 1 + if cnt >= first: + if deployment['beneficiary_id'] in user_id_2_user.keys(): + caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": user_id_2_user[deployment['beneficiary_id']][3], "first_name": user_id_2_user[deployment['beneficiary_id']][5], "last_name": user_id_2_user[deployment['beneficiary_id']][6]} + else: + caretaker_min_object = {"deployment_id": deployment['deployment_id'], "email": deployment['beneficiary_id'][3], "first_name": deployment['beneficiary_id'][5], "last_name": deployment['beneficiary_id'][6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "device_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + #user_id = form_data.get('user_id') + + devices = GetVisibleDevices(privileges) + + payload = {'result_list': devices} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "get_devices_locations": + well_ids = form_data.get('well_ids') + details_list = WellId2Details(well_ids) + #print(details_list) + + to_report = [] + + for details in details_list: + if details[3] == -1: + report_record = str(details[0]) + " ?" + else: + report_record = str(details[0]) + " " + location_names[details[3]] + if details[4] != "" and details[4] != "initial": + report_record = report_record + " " +details[4] + if details[3] == -1: + to_report.append((details[0], "?", details[4], report_record)) + else: + to_report.append((details[0], location_names[details[3]], details[4], report_record)) + + #print(to_report) + payload = {'deployments': to_report} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + elif function == "find_deployments": + #For all devices, find deployments that they are part of + #For all those deployments, return: + #deployment_id First_name Last_name (of beneficiary) + #list of (well_id, location_descriptions) all devices in each deployment + + + well_ids = form_data.get('well_ids') + #well_ids_lst = well_ids.split(",") + details_list = WellId2Details(well_ids) + to_report = [] + + privileges_lst = [] + if "," in privileges: + privileges_lst = privileges.split(",") + + if len(details_list) > 0: + macs_list_clean = [] + devices_details = {} + + for entry in details_list: + macs_list_clean.append(entry[2]) + macs_formatted = "', '".join(macs_list_clean) + + with get_db_connection() as conn: + with conn.cursor() as cur: + sql = f"SELECT deployment_id, beneficiary_id, devices FROM public.deployment_details WHERE devices::text ~* ANY(ARRAY['{macs_formatted}']);" + print(sql) + cur.execute(sql) + result = cur.fetchall() + + if result != None and result != []: + users_list_clean = [] + for entry in result: #for every deployment + macs_list_clean = [] + deployment_id = str(entry[0]) + if privileges == '-1': + deployment = DeploymentDetails(deployment_id) + address_str = ExtractAddress(deployment) + deployment = PurgeDeployment(deployment) + deployment["address"] = address_str + + users_list_clean.append(str(entry[1])) + users_formatted = ", ".join(users_list_clean) + + mac_devices_in_deployment = ast.literal_eval(entry[2]) + for mac in mac_devices_in_deployment: + macs_list_clean.append(mac) + + device_ids, device_list = MACsToWellIds(cur, macs_list_clean) + + devices_details[deployment_id] = deployment + + elif deployment_id in privileges_lst: + deployment = DeploymentDetails(deployment_id) + address_str = ExtractAddress(deployment) + deployment = PurgeDeployment(deployment) + deployment["address"] = address_str + users_list_clean.append(str(entry[1])) + users_formatted = ", ".join(users_list_clean) + + mac_devices_in_deployment = ast.literal_eval(entry[2]) + for mac in mac_devices_in_deployment: + macs_list_clean.append(mac) + + device_ids, device_list = MACsToWellIds(cur, macs_list_clean) + devices_details[deployment_id] = deployment + + device_list_to_report = [] + + for device in device_list: + #we need well_id, location and decription only + device_list_to_report.append((device[0], device[2], device[3])) + sql = f"SELECT first_name, last_name, email FROM public.person_details WHERE user_id IN ({users_formatted});" + print(sql) + cur.execute(sql) + result1 = cur.fetchall() + counter = 0 + + for entry in result: + deployment_id = str(entry[0]) + deployment = devices_details[deployment_id] + first_name, last_name, email = result1[counter] + deployment["beneficiary_first_name"] = first_name + deployment["beneficiary_last_name"] = last_name + deployment["beneficiary_email"] = last_name + devices_details[deployment_id] = deployment + to_report.append((entry, device_list_to_report, devices_details[deployment_id])) + + + print(to_report) + payload = {'deployments': to_report} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "device_list_by_deployment": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + deployment_id = form_data.get('deployment_id') + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + + if privileges == "-1": + devices = GetVisibleDevices(deployment_id) + else: + privileges = privileges.split(",") + if deployment_id in privileges: + devices = GetVisibleDevices(deployment_id) + + payload = {'result_list': devices} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "device_list_4_gui": + result_list = [] + deploymentData = [] + deviceData = [] + macs_list = [] + user_id = GetUserId(user_name) + all_deployments = ListDeployments(privileges, user_id) + #{'deployment_id': 21, 'beneficiary_id': 25, 'caretaker_id': 1, 'owner_id': 1, 'installer_id': 1, 'address_street': '661 Encore Way', 'address_city': 'San Jose', 'address_zip': '95134', 'address_state': 'CA', 'address_country': 'USA', 'devices': '["64B70888FAB0","64B70888F860","64B70888F6F0","64B708896BDC","64B708897428","64B70888FA84","64B70889062C"]', 'wifis': '', 'persons': 1, 'gender': 1, 'race': 1, 'born': 1940, 'pets': 0, 'time_zone': 'America/Los_Angeles'} + MAC2Deployment = {} + + for deployment in all_deployments: + beneficiary_id = deployment['beneficiary_id'] + user = GetNameFromUserId(beneficiary_id) + name = f"{user[1]} {user[2]}" + deploymentData.append({'deployment_id': str(deployment['deployment_id']), 'name': name}) + devices = deployment['devices'] + if devices != None and devices != None != "": + + devices_list = ToList(devices) + for device in devices_list: + macs_list.append(device) + MAC2Deployment[device] = deployment['deployment_id'] + #deviceData.append({'well_id': device[0], 'mac': device[1]}) + + deployment_id_list = [] + deviceData = [] + + #row_data = [device_id, well_id, mac, last_message_epoch, location_names[location_id], description, deployment_ids[cnt][0]] + + with get_db_connection() as conn: + with conn.cursor() as cur: + device_ids, device_list = MACsToWellIds(cur, macs_list) + + + for device in device_list: + if MAC2Deployment[device[4]] != "": + deviceData.append({'well_id': device[0], 'mac': device[4], 'room_name': device[2], 'deployment_id': MAC2Deployment[device[4]]}) + + #deploymentData = [{'deployment_id': '21', 'name': 'Robert Zmrzli House'}, {'deployment_id': '36', 'name': 'Fred Zmrzli Apartment'}] + #deviceData = [{ 'well_id': '300', 'mac': '64B70888F6F0', 'room_name': 'Living Room', 'deployment_id': '21' }, { 'well_id': '301', 'mac': '64B70888F6F1', 'room_name': 'Bathroom Main', 'deployment_id': '36' }] + + payload = { + 'status': "success", 'deploymentData': deploymentData, 'deviceData': deviceData + } + + logger.debug(f"device_list_4_gui------ {payload} ------------------------------------------") + + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "caretaker_form": + editing_user_id = form_data.get('editing_user_id') + email = form_data.get('email') + user_id = form_data.get('user_id') + if "@" not in email: + resp.media = package_response("Missing or illegal 'email' parameter", HTTP_400) + return + + print(privileges) + if privileges == "-1": + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif "-1" in privileges: + payload = {'ok': 0, 'error': "Not allowed!"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + + elif function == "caretaker_delete": + if privileges == "-1": + ok = DeleteRecordFromDB(form_data) + else: + ok = 0 + AddToLog(ok) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "caretakers_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + if privileges == "-1": + all_caretakers = ListCaretakers(privileges, user_name) + + cnt = 0 + + for caretaker in all_caretakers: + cnt += 1 + if cnt >= first: + caretaker_min_object = {"user_id": caretaker[0], "email": caretaker[3], "first_name": caretaker[5], "last_name": caretaker[6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + elif "-1" in privileges: + all_caretakers = ListCaretakers(privileges, user_name) + + cnt = 0 + + for caretaker in all_caretakers: + cnt += 1 + if cnt >= first: + caretaker_min_object = {"user_id": caretaker[0], "email": caretaker[3], "first_name": caretaker[5], "last_name": caretaker[6]} + result_list.append(caretaker_min_object) + if cnt > last: + break + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "new_user_form": + devices = form_data.get('devices') + user_name = form_data.get('user_name') + password = form_data.get('password') + #lets check if devices listed are not part of existing deployment + success, result = DevicesNotUsed(devices, user_name) + + if success: + if result["deployed"]: + error_string = f"These devices are already deployed: {result['deployed']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if result["not_found"]: + error_string = f"These devices are not available: {result['not_found']}" + print(error_string) + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + if not result["deployed"] and not result["not_found"]: + print("All devices are available for deployment") + ok, error_string = StoreDisclaimer2DB(form_data) + + if ok == 1: + #Lets check if this user already has account or not + if not AccountExists(user_name): + #lets create new account for this caretaker + if password == None or password == "": + password = CreatePassword(8) + + #lets suplement form_data with parts needed for existing StoreCaretaker2DB function + editing_user_id = "0" #specify if editing existing user, otherwise "0" + user_id = "0" #user that is adding this record. New user so "0" + form_data['role_ids'] = "1,2" + form_data['access_to_deployments'] = "45" + #form_data['email'] = "" #this one matches + form_data['new_user_name'] = form_data['user_name'] + form_data['first_name'] = form_data['firstName'] + form_data['last_name'] = form_data['lastName'] + form_data['address_street'] = "" + form_data['address_city'] = "" + form_data['address_zip'] = "" + form_data['address_state'] = "" + form_data['address_country'] = "" + form_data['phone_number'] = form_data['phone'] + form_data['picture'] = "/" + form_data['key'] = password + + ok = StoreCaretaker2DB(form_data, editing_user_id, user_id) + + if ok == 1: + + #we need to call cd ~/mqtt-auth-service/acl_manager.py + + SendWelcomeEmail(form_data['email'], form_data['first_name'], form_data['last_name'], devices, form_data['phone_number'], form_data['new_user_name'], form_data['key']) + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + privileges, user_id = ValidUser(user_name, password) + if user_id == "0": #bad password + error_string = f"Password does not match user {user_name}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + else: + error_string = f"Error: {result}" + payload = {'ok': 0, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiary_form": + editing_user_id = form_data.get('editing_user_id') + email = form_data.get('email') + user_id = GetUserId(user_name) + if "@" in email: + ok, error_string = StoreBeneficiary2DB(form_data, editing_user_id, user_id) + if ok == 1: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok, 'error': error_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + else: + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiary_delete": + + + ok = DeleteRecordFromDB(form_data) + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "beneficiaries_list": + result_list = [] + first_s = form_data.get('first') + last_s = form_data.get('last') + + try: + first = int(first_s) + except ValueError: + first = 0 + + try: + last = int(last_s) + except ValueError: + last = 1000000 + + user_id = form_data.get('user_id') + all_beneficiaries = ListBeneficiaries(privileges, user_id) + + cnt = 0 + + for beneficiary in all_beneficiaries: + cnt += 1 + if cnt >= first: + beneficiary_min_object = {"user_id": beneficiary[0], "email": beneficiary[3], "first_name": beneficiary[5], "last_name": beneficiary[6]} + result_list.append(beneficiary_min_object) + if cnt > last: + break + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + elif function == "activities_report_details": + deployment_id = form_data.get('deployment_id') + + timezone_str = GetTimeZoneOfDeployment(deployment_id) + filterr = form_data.get('filter') + if filterr == None: + filterr = 6 + else: + filterr = int(filterr) + + refresh = form_data.get('refresh') == "1" + ddate = current_date_at_tz(timezone_str) + timee = LocalDateToUTCEpoch(ddate, timezone_str)+5 #add so date boundary is avoided + devices_list, device_ids = GetProximityList(deployment_id, timee) + + #Here we need to add per day: (all based on Z-graph data!) + #Bathroom visits number + #Bathroom time spent + #Sleep weakes number (As breaks in Z-graph indicates in 10PM to 9AM period) + #Sleep length (For now add all times seen in bedroom) + #Kitchen visits number + #Kitchen time spent + #Most frequented room visits number + #Most frequented room time spent + + #Lets find device_id of bathroom sensor + + + bathroom_device_id, location_ba, bathroom_well_id = FindDeviceByRole(deployment_id, ["Bathroom Main", "Bathroom", "Bathroom Guest"]) + bedroom_device_id, location_be, bedroom_well_id = FindDeviceByRole(deployment_id, ["Bedroom Master", "Bedroom", "Bedroom Guest"]) + kitchen_device_id, location_ke, kitchen_well_id = FindDeviceByRole(deployment_id, ["Kitchen"]) + most_present_device_id, location_ot, most_present_well_id = FindDeviceByRole(deployment_id, []) #this will find most_present (as defined in other filed of device record) + + if isinstance(location_ot, int): + other_location = location_names[location_ot] + else: + other_location = location_ot + + #weekly + week_dates = get_week_days_and_dates(7, timezone_str) + month_dates = get_week_days_and_dates(30, timezone_str) + six_months_dates = get_week_days_and_dates(180, timezone_str) + + other_color = Loc2Color[other_location][0] + rgb_string = f"rgb({other_color[0]}, {other_color[1]}, {other_color[2]})" + + rooms_reports = [("Bathroom", "blue", bathroom_device_id, bathroom_well_id), ("Bedroom", "green", bedroom_device_id, bedroom_well_id), ("Kitchen", "red", kitchen_device_id, kitchen_well_id), (other_location, rgb_string, most_present_device_id, most_present_well_id)] + + six_months_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in six_months_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + if hours > 18: + print("Too long 6m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + six_months_report.append(room) + + weekly_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in week_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + data_record = { "title": day_activity[1], "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + weekly_report.append(room) + + monthly_report = [] + for room_details in rooms_reports: + device_id = room_details[2] + if device_id > 0: + well_id = room_details[3] + radar_threshold_group_st = {device[1]: device[5] for device in devices_list}[device_id] + room = {"name": room_details[0],"color": room_details[1]} + data = [] + + for day_activity in month_dates: + datee = day_activity[0] + hours, events_count = GetActivities(device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + #if datee == "2025-05-20" and device_id == 572: + # print(hours) + if hours > 18: + print("Too long m!!!", device_id, well_id, datee, filterr, refresh, timezone_str, radar_threshold_group_st) + + data_record = { "title": str(day_activity[2]), "events": events_count, "hours": hours} + data.append(data_record) + + room["data"] = data + monthly_report.append(room) + + + + + result_dictionary = { + "alert_text": "No alert", + "alert_color": "bg-green-100 text-green-700", + "chart_data": [ + { + "name": "Weekly", + "rooms": [ + { + "name": "Bathroom", + "color": "blue", + "data": [ + { "title": "Monday", "events": 186, "hours": 80.56 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Bedroom", + "color": "green", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Kitchen", + "color": "red", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + }, + { + "name": "Other", + "color": "yellow", + "data": [ + { "title": "Monday", "events": 186, "hours": 80 }, + { "title": "Tuesday", "events": 305, "hours": 200 }, + { "title": "Wednesday", "events": 237, "hours": 120 }, + { "title": "Thursday", "events": 73, "hours": 190 }, + { "title": "Friday", "events": 209, "hours": 130 }, + { "title": "Saturday", "events": 214, "hours": 140 }, + { "title": "Sunday", "events": 150, "hours": 100 } + ] + } + ] + }, + { + "name": "Monthly", + "rooms": [ + { + "name": "Bathroom", + "color": "purple", + "data": [ + { "title": "01", "events": 67, "hours": 45 }, + { "title": "02", "events": 97, "hours": 67 }, + { "title": "03", "events": 87, "hours": 23 }, + { "title": "04", "events": 42, "hours": 12 }, + { "title": "05", "events": 64, "hours": 48 }, + { "title": "06", "events": 53, "hours": 34 }, + { "title": "07", "events": 75, "hours": 23 }, + { "title": "08", "events": 45, "hours": 56 }, + { "title": "09", "events": 85, "hours": 47 }, + { "title": "10", "events": 34, "hours": 29 }, + { "title": "11", "events": 49, "hours": 30 }, + { "title": "12", "events": 62, "hours": 33 }, + { "title": "13", "events": 75, "hours": 44 }, + { "title": "14", "events": 88, "hours": 57 }, + { "title": "15", "events": 94, "hours": 65 }, + { "title": "16", "events": 45, "hours": 21 }, + { "title": "17", "events": 76, "hours": 54 }, + { "title": "18", "events": 85, "hours": 62 }, + { "title": "19", "events": 43, "hours": 28 }, + { "title": "20", "events": 59, "hours": 34 }, + { "title": "21", "events": 78, "hours": 56 }, + { "title": "22", "events": 64, "hours": 39 }, + { "title": "23", "events": 93, "hours": 72 }, + { "title": "24", "events": 52, "hours": 28 }, + { "title": "25", "events": 71, "hours": 48 }, + { "title": "26", "events": 85, "hours": 63 } + ] + }, + { + "name": "Bedroom", + "color": "#3b82f6", + "data": [ + { "title": "01", "events": 61, "hours": 42 }, + { "title": "02", "events": 72, "hours": 36 }, + { "title": "03", "events": 94, "hours": 49 }, + { "title": "04", "events": 67, "hours": 59 }, + { "title": "05", "events": 54, "hours": 20 }, + { "title": "06", "events": 77, "hours": 64 }, + { "title": "07", "events": 81, "hours": 70 }, + { "title": "08", "events": 53, "hours": 25 }, + { "title": "09", "events": 79, "hours": 42 }, + { "title": "10", "events": 84, "hours": 65 }, + { "title": "11", "events": 62, "hours": 54 }, + { "title": "12", "events": 45, "hours": 23 }, + { "title": "13", "events": 88, "hours": 71 }, + { "title": "14", "events": 74, "hours": 44 }, + { "title": "15", "events": 91, "hours": 59 }, + { "title": "16", "events": 46, "hours": 31 }, + { "title": "17", "events": 73, "hours": 40 }, + { "title": "18", "events": 85, "hours": 63 }, + { "title": "19", "events": 78, "hours": 66 }, + { "title": "20", "events": 66, "hours": 42 }, + { "title": "21", "events": 95, "hours": 78 }, + { "title": "22", "events": 57, "hours": 39 }, + { "title": "23", "events": 72, "hours": 48 }, + { "title": "24", "events": 48, "hours": 21 }, + { "title": "25", "events": 89, "hours": 61 }, + { "title": "26", "events": 77, "hours": 44 } + ] + }, + { + "name": "Kitchen", + "color": "orange", + "data": [ + { "title": "01", "events": 94, "hours": 59 }, + { "title": "02", "events": 62, "hours": 48 }, + { "title": "03", "events": 76, "hours": 38 }, + { "title": "04", "events": 81, "hours": 62 }, + { "title": "05", "events": 64, "hours": 27 }, + { "title": "06", "events": 53, "hours": 31 }, + { "title": "07", "events": 92, "hours": 65 }, + { "title": "08", "events": 85, "hours": 42 }, + { "title": "09", "events": 74, "hours": 35 }, + { "title": "10", "events": 67, "hours": 55 }, + { "title": "11", "events": 49, "hours": 23 }, + { "title": "12", "events": 88, "hours": 75 }, + { "title": "13", "events": 93, "hours": 66 }, + { "title": "14", "events": 76, "hours": 34 }, + { "title": "15", "events": 59, "hours": 39 }, + { "title": "16", "events": 72, "hours": 51 }, + { "title": "17", "events": 83, "hours": 44 }, + { "title": "18", "events": 74, "hours": 33 }, + { "title": "19", "events": 69, "hours": 28 }, + { "title": "20", "events": 85, "hours": 56 }, + { "title": "21", "events": 53, "hours": 22 }, + { "title": "22", "events": 92, "hours": 70 }, + { "title": "23", "events": 71, "hours": 41 }, + { "title": "24", "events": 67, "hours": 25 }, + { "title": "25", "events": 86, "hours": 74 }, + { "title": "26", "events": 94, "hours": 68 } + ] + }, + { + "name": "Other", + "color": "hotpink", + "data": [ + { "title": "01", "events": 57, "hours": 27 }, + { "title": "02", "events": 74, "hours": 33 }, + { "title": "03", "events": 84, "hours": 53 }, + { "title": "04", "events": 95, "hours": 68 }, + { "title": "05", "events": 71, "hours": 48 }, + { "title": "06", "events": 92, "hours": 76 }, + { "title": "07", "events": 85, "hours": 62 }, + { "title": "08", "events": 49, "hours": 25 }, + { "title": "09", "events": 66, "hours": 38 }, + { "title": "10", "events": 63, "hours": 31 }, + { "title": "11", "events": 75, "hours": 47 }, + { "title": "12", "events": 94, "hours": 72 }, + { "title": "13", "events": 79, "hours": 49 }, + { "title": "14", "events": 72, "hours": 45 }, + { "title": "15", "events": 88, "hours": 61 }, + { "title": "16", "events": 83, "hours": 52 }, + { "title": "17", "events": 92, "hours": 76 }, + { "title": "18", "events": 73, "hours": 40 }, + { "title": "19", "events": 65, "hours": 28 }, + { "title": "20", "events": 76, "hours": 63 }, + { "title": "21", "events": 58, "hours": 30 }, + { "title": "22", "events": 84, "hours": 67 }, + { "title": "23", "events": 72, "hours": 41 }, + { "title": "24", "events": 79, "hours": 46 }, + { "title": "25", "events": 63, "hours": 29 }, + { "title": "26", "events": 68, "hours": 39 } + ] + } + ] + }, + { + "name": "6 Months", + "rooms": [ + { + "name": "Bathroom", + "color": "purple", + "data": [ + { "title": "October", "events": 62, "hours": 23 }, + { "title": "November", "events": 76, "hours": 42 }, + { "title": "December", "events": 85, "hours": 54 }, + { "title": "January", "events": 94, "hours": 67 }, + { "title": "February", "events": 63, "hours": 35 }, + { "title": "March", "events": 81, "hours": 46 } + ] + }, + { + "name": "Bedroom", + "color": "#3b82f6", + "data": [ + { "title": "October", "events": 64, "hours": 35 }, + { "title": "November", "events": 88, "hours": 71 }, + { "title": "December", "events": 79, "hours": 54 }, + { "title": "January", "events": 72, "hours": 49 }, + { "title": "February", "events": 53, "hours": 32 }, + { "title": "March", "events": 93, "hours": 67 } + ] + }, + { + "name": "Kitchen", + "color": "orange", + "data": [ + { "title": "October", "events": 92, "hours": 65 }, + { "title": "November", "events": 85, "hours": 62 }, + { "title": "December", "events": 74, "hours": 49 }, + { "title": "January", "events": 63, "hours": 33 }, + { "title": "February", "events": 78, "hours": 56 }, + { "title": "March", "events": 69, "hours": 41 } + ] + }, + { + "name": "Other", + "color": "hotpink", + "data": [ + { "title": "October", "events": 88, "hours": 54 }, + { "title": "November", "events": 72, "hours": 39 }, + { "title": "December", "events": 84, "hours": 63 }, + { "title": "January", "events": 76, "hours": 46 }, + { "title": "February", "events": 93, "hours": 72 }, + { "title": "March", "events": 68, "hours": 29 } + ] + } + ] + } + ] + } + + result_dictionary["chart_data"][0]["rooms"] = weekly_report + result_dictionary["chart_data"][1]["rooms"] = monthly_report + result_dictionary["chart_data"][2]["rooms"] = six_months_report + + + + payload = result_dictionary #{'result_dictionary': result_dictionary} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + + elif function == "dashboard_list": + # works in UTC only + + logger.error(f"------------------------------- dashboard_list ------------------------------------------") + + caretaker = user_name + #date_s = form_data.get('date') + time_s = form_data.get('time') + date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + filterr = form_data.get('filter') + if filterr == None: + filterr = 5 + + privileges = GetPriviledgesOnly(caretaker) + + deployments_list = GetUsersFromDeployments(privileges) + + #all_beneficiaries = ListBeneficiariesOfCaretaker(caretaker) #GetPriviledgesOnly + #AddToLog(all_beneficiaries) + + result_list = [] + + for deployment_id, first_name, last_name in deployments_list: + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr) + if details != {}: + + details["units"] = "°C" + if "America" in details["time_zone"]: + details["temperature"] = CelsiusToFahrenheit(details["temperature"]) + details["units"] = "°F" + devices_list, device_ids = GetProximityList(deployment_id, date_s) + # convert dates back to UTC + #details['bathroom_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bathroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['kitchen_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bathroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['bedroom_at'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['bedroom_at'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + #details['last_detected_time'] = pytz.timezone(details['time_zone']).localize(datetime.datetime.strptime(details['last_detected_time'], "%Y-%m-%dT%H:%M:%S")).astimezone(pytz.UTC).strftime("%Y-%m-%dT%H:%M:%S") + location_list = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if description == None or description == "": + location_list.append(location_name) + else: + location_list.append(location_name + " " + description) + details["deployment_id"] = deployment_id + details["location_list"] = location_list + result_list.append(details) + + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + + elif function == "dashboard_single": + caretaker = user_name + #date_s = form_data.get('date') + date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + deployment_id = form_data.get('deployment_id') + filterr = form_data.get('filter') + if filterr == None: + filterr = 5 + + + #all_beneficiaries = ListBeneficiariesOfCaretaker(caretaker) #GetPriviledgesOnly + #AddToLog(all_beneficiaries) + + result_list = [] + + details = GetSensorsDetailsFromDeployment(deployment_id, date_s, filterr, False) + details["units"] = "°C" + if "America" in details["time_zone"]: + details["temperature"] = CelsiusToFahrenheit(details["temperature"]) + details["bedroom_temperature"] = CelsiusToFahrenheit(details["bedroom_temperature"]) + details["units"] = "°F" + devices_list, device_ids = GetProximityList(deployment_id, date_s) + location_list = [] + for room_details in devices_list: + well_id, device_id, location_name, description, MAC, radar_threshold_group_st, close_to = room_details #(266, 559, 'Bathroom', None, '64B70888FAB0', '["s3_max",12]') + if description == None or description == "": + location_list.append(location_name) + else: + location_list.append(location_name + " " + description) + details["deployment_id"] = deployment_id + details["location_list"] = location_list + settings = {"wellness_score": False, "last_seen": False, "sleep_report": True, "activity_report": True, "temperature": True, "humidity": True, "air_pressure": True, "light": True, "air_quality": True, "radar": True, "other_activities": False} + details["settings"] = settings + result_list.append(details) + payload = {'result_list': result_list} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + AddToLog(payload) + return + + elif function == "request_node_red": + logger.error(f"------------------------------- {function} ------------------------------------------") + #this will: + # 1.prepare folder and settings.js + # 2.start instance on node-red and return it's return port + #caretaker = user_name + #date_s = form_data.get('date') + time_s = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + #deployment_id = form_data.get('deployment_id') + #redis_conn.set('node_red_requests', str([radar_threshold_signal, radar_threshold_value])) + # Hashes (dictionaries) + logger.error(f"Storing to node_red_requests {user_name}") + redis_conn.hset('node_red_requests', mapping={ + 'user_name': user_name, + 'token': token, + 'time': time_s, + 'requests': 1 + }) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "get_node_red_port": + #this will: + # 1.prepare folder and settings.js + # 2.start instance on node-red and return it's return port + hash_data = GetRedisMap(f'node_red_status_{user_name}') + port = 0 + if hash_data != {}: + port = hash_data['port'] + #date_s = form_data.get('date') + #date_s = datetime.datetime.utcnow().strftime("%Y-%m-%d") + #deployment_id = form_data.get('deployment_id') + payload = {'port': port} + logger.debug(f"get_node_red_port: {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "activity_detected": + #this will: + # 1.store to REDIS time of last activity + time_s = form_data.get('time') + + hash_data = GetRedisMap(f'node_red_status_{user_name}') + port = 0 + if hash_data != {}: + port = hash_data['port'] + + redis_conn.hset(f'node_red_status_{user_name}', mapping={ + 'port': port, + 'last_activity': time_s + }) + + payload = {'ok': 1} + logger.debug(f"activity_detected: {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + elif function == "store_flow": + #this will: + # 1.store flow into DB + time_s = form_data.get('time') + flow_json = form_data.get('flow') + logger.debug(f"store_flow: {flow_json}") + StoreFlow2DB(user_name, time_s, flow_json) + payload = {'ok': 1} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "store_alarms": + #this will: + # 1.store flow into DB + deployment_id = form_data.get('deployment_id') + device_id = form_data.get('device_id') + deployment_alarms_json = form_data.get('deployment_alarms') + device_alarms_json = form_data.get('device_alarms') + + logger.debug(f"store_alarms: {deployment_alarms_json}") + + + if privileges == "-1" or deployment_id in privileges: + ok = StoreAlarms2DB(deployment_id, device_id, deployment_alarms_json, device_alarms_json) + + redis_conn.set('alarm_device_settings_'+device_id, device_alarms_json) + redis_conn.set('alarm_deployment_settings_'+deployment_id, deployment_alarms_json) + + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'device_id': device_id + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('new_alarms', record_json) + + + payload = {'ok': ok} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + else: + payload = {'ok': 0, 'error': "not allowed"} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + return + + elif function == "send_walarm": + # Extract data from form + deployment_id = form_data.get('deployment_id') + device_id = form_data.get('device_id') + location = form_data.get('location') + method = form_data.get('method') + feature = form_data.get('feature') + currentAlertTableMode = form_data.get('currentAlertTableMode') + time_s = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + content = form_data.get('content') + enabledCellContent = form_data.get('enabledCellContent') + currentUnits = form_data.get('currentUnits') + test_only = form_data.get('test_only') + action = form_data.get('action') + + logger.debug(f"send_requests: {user_name}") + + # Create record dictionary + record = { + 'user_name': user_name, + 'deployment_id': deployment_id, + 'location': location, + 'method': method, + 'feature': feature, + 'currentAlertTableMode': currentAlertTableMode, + 'time': time_s, + 'content': content, + 'currentUnits': currentUnits, + 'test_only': test_only, + 'action': action, + 'enabledCellContent': enabledCellContent + } + + # Convert dictionary to JSON string for storage in Redis list + record_json = json.dumps(record) + + # Add to queue (list) - lpush adds to the left/front of the list + redis_conn.lpush('send_requests', record_json) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + elif function == "node-red_deployed": + #this will: + # 1.store flow into DB + time_s = form_data.get('time') + logger.debug(f"node-red_deployed: {user_name}") + redis_conn.hset('node-red_deployed', mapping={ + 'user_name': user_name, + 'token': token, + 'time': time_s, + 'requests': 1 + }) + + payload = {'ok': 1} + logger.error(f"Responding {payload}") + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + + else: + debug_string = "Error: function not recognized!" + AddToLog(debug_string) + payload = {'ok': 0, 'error': debug_string} + resp.media = package_response(payload) + resp.status = falcon.HTTP_200 + return + + except Exception as e: + print(traceback.format_exc()) + resp.media = package_response(f"Error: {str(e)} {traceback.format_exc()}", HTTP_500) + + + def on_put(self, req, resp, path=""): + """Handle PUT requests""" + if path == "users": + logger.info("PUT request to users endpoint") + try: + # Parse the request body + request_data = json.loads(req.stream.read().decode('utf-8')) + + # TODO: Implement user update logic + + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"id": request_data.get("id"), "message": "User updated"}) + except json.JSONDecodeError: + resp.status = HTTP_400 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"error": "Invalid JSON"}) + else: + resp.media = package_response(f"PUT to /{path} not implemented", HTTP_400) + + def on_delete(self, req, resp, path=""): + """Handle DELETE requests""" + if path == "users": + logger.info("DELETE request to users endpoint") + resp.status = HTTP_200 + resp.content_type = falcon.MEDIA_JSON + resp.text = json.dumps({"message": "User deleted"}) + else: + resp.media = package_response(f"DELETE to /{path} not implemented", HTTP_400) + + +def SendWelcomeEmail(email, first_name, last_name, devices, phone_number, user_name, password): + logger.error(f"Requesting welcome email to {email}") + redis_conn.hset('messaging_requests', mapping={ + 'email': email, + 'user_name': user_name, + 'first_name': first_name, + 'last_name': last_name, + 'devices': devices, + 'phone_number': phone_number, + 'password': password, + 'requests': 1 + }) + + + +def DevicesNotUsed(devices, user_name): + """ + Check if devices exist in DB and if they are already deployed (excluding user's own deployments). + + Args: + devices (str): Comma-separated string of well_id or device_mac values + user_name (str): Username to check access permissions + + Returns: + tuple: (success_flag, result) + - If success: (1, {"deployed": [well_ids], "not_found": [device_identifiers]}) + - If error: (0, error_string) + """ + + if not devices: + return 1, {"deployed": [], "not_found": []} + + # Clean and split the devices string + device_list = [CleanObject(device.strip()) for device in devices.split(',') if device.strip()] + + if not device_list: + return 1, {"deployed": [], "not_found": []} + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + # First, get user's accessible deployments + user_deployments = [] + is_superuser = False + + if user_name: + user_sql = f""" + SELECT access_to_deployments + FROM public.person_details + WHERE user_name = '{CleanObject(user_name)}' + """ + + logger.debug(f"user_sql= {user_sql}") + cur.execute(user_sql) + user_result = cur.fetchone() + + if user_result and user_result[0]: + access_deployments = user_result[0].strip() + + if access_deployments == "-1": + # Superuser has access to all deployments + is_superuser = True + logger.debug("User is superuser - has access to all deployments") + else: + # Parse comma-separated deployment IDs + user_deployments = [int(dep_id.strip()) for dep_id in access_deployments.split(',') if dep_id.strip().isdigit()] + logger.debug(f"User has access to deployments: {user_deployments}") + + deployed_well_ids = [] + not_found_devices = [] + + for device in device_list: + # Check if this device is a well_id or device_mac by string length + if len(device) == 12: + # Exactly 12 characters = device_mac + device_mac = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE device_mac = '{device_mac}' + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + device_well_id = device_result[0] + + # Query to check if this device_mac is deployed (excluding user's accessible deployments) + if is_superuser: + # Superuser has access to all deployments, so no devices are "deployed elsewhere" + sql = "SELECT NULL WHERE FALSE" # Returns no results + elif user_deployments: + deployment_exclusion = f"AND dd.deployment_id NOT IN ({','.join(map(str, user_deployments))})" + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' {deployment_exclusion} + """ + else: + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' + """ + + else: + # Any other length = well_id + well_id = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE well_id = {well_id} + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + # Query to check if any device with this well_id is deployed (excluding user's accessible deployments) + if is_superuser: + # Superuser has access to all deployments, so no devices are "deployed elsewhere" + sql = "SELECT NULL WHERE FALSE" # Returns no results + elif user_deployments: + deployment_exclusion = f"AND dd.deployment_id NOT IN ({','.join(map(str, user_deployments))})" + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} {deployment_exclusion} + """ + else: + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + results = cur.fetchall() + + # Add any found well_id's to our list + for row in results: + if row[0] is not None and row[0] not in deployed_well_ids: + deployed_well_ids.append(row[0]) + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog(f"DevicesNotUsed check completed. Found {len(deployed_well_ids)} deployed devices, {len(not_found_devices)} not found.") + return 1, {"deployed": deployed_well_ids, "not_found": not_found_devices} + + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + if cur: + cur.close() + if conn: + conn.close() + return 0, error_string + +def CreatePassword(chat_len): + """ + Generate a reasonable password of specified length using common words + with character substitutions (leet speak style). + + Args: + chat_len (int): Desired password length + + Returns: + str: Generated password + """ + + # Seed random with current time for better randomness + random.seed(int(time.time() * 1000000) % 2**32) + + # Common words to use as base + words = [ + "password", "secure", "strong", "safe", "guard", "shield", "lock", + "key", "code", "access", "login", "enter", "open", "door", + "house", "home", "family", "friend", "happy", "sunny", "bright", + "quick", "fast", "smart", "clever", "power", "energy", "magic", + "super", "ultra", "mega", "cool", "awesome", "great", "best", + "gold", "silver", "diamond", "star", "moon", "sun", "fire", + "water", "earth", "wind", "storm", "thunder", "lightning", + "mountain", "ocean", "forest", "river", "bridge", "castle", + "knight", "dragon", "wizard", "hero", "champion", "winner" + ] + + # Character substitution mapping (leet speak) + substitutions = { + 'a': '@', 'A': '@', + 'e': '3', 'E': '3', + 'i': '1', 'I': '1', + 'o': '0', 'O': '0', + 's': '$', 'S': '$', + 't': '7', 'T': '7', + 'b': '8', 'B': '8', + 'g': '9', 'G': '9', + 'l': '!', 'L': '!', + 'z': '2', 'Z': '2' + } + + password = "" + + while len(password) < chat_len: + # Pick a random word + word = random.choice(words) + + # Apply random substitutions (not all characters, to keep it readable) + modified_word = "" + for char in word: + # 40% chance to substitute if substitution exists + if char in substitutions and random.random() < 0.4: + modified_word += substitutions[char] + else: + modified_word += char + + # Randomly capitalize some letters for variety + if random.random() < 0.3: + modified_word = modified_word.capitalize() + + # Add the word to password + if len(password) + len(modified_word) <= chat_len: + password += modified_word + else: + # If word is too long, take only what we need + remaining = chat_len - len(password) + password += modified_word[:remaining] + break + + # Add a random number or symbol between words (if space allows) + if len(password) < chat_len and random.random() < 0.5: + separators = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '#', '*', '+', '='] + separator = random.choice(separators) + if len(password) + 1 <= chat_len: + password += separator + + return password + +def DevicesNotUsedNoOwnershipCheck(devices): + """ + Check if devices exist in DB and if they are already deployed. + + Args: + devices (str): Comma-separated string of well_id or device_mac values + + Returns: + tuple: (success_flag, result) + - If success: (1, {"deployed": [well_ids], "not_found": [device_identifiers]}) + - If error: (0, error_string) + """ + + if not devices: + return 1, {"deployed": [], "not_found": []} + + # Clean and split the devices string + device_list = [CleanObject(device.strip()) for device in devices.split(',') if device.strip()] + + if not device_list: + return 1, {"deployed": [], "not_found": []} + + conn = get_db_connection() + cur = conn.cursor() + error_string = "" + + try: + deployed_well_ids = [] + not_found_devices = [] + + for device in device_list: + # Check if this device is a well_id or device_mac by string length + if len(device) == 12: + # Exactly 12 characters = device_mac + device_mac = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE device_mac = '{device_mac}' + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + #device_well_id = device_result[0] + + # Query to check if this device_mac is deployed + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.device_mac = '{device_mac}' + """ + + else: + # Any other length = well_id + well_id = device + + # First check if device exists in devices table + check_exists_sql = f""" + SELECT well_id FROM public.devices WHERE well_id = {well_id} + """ + + logger.debug(f"check_exists_sql= {check_exists_sql}") + cur.execute(check_exists_sql) + device_result = cur.fetchone() + + if not device_result: + # Device not found in DB + not_found_devices.append(device) + continue + + # Query to check if any device with this well_id is deployed + sql = f""" + SELECT DISTINCT d.well_id + FROM public.devices d + JOIN public.deployment_details dd ON dd.devices LIKE '%' || d.device_mac || '%' + WHERE d.well_id = {well_id} + """ + + logger.debug(f"sql= {sql}") + cur.execute(sql) + results = cur.fetchall() + + # Add any found well_id's to our list + for row in results: + if row[0] is not None and row[0] not in deployed_well_ids: + deployed_well_ids.append(row[0]) + + # Close the cursor and connection + cur.close() + conn.close() + + AddToLog(f"DevicesNotUsed check completed. Found {len(deployed_well_ids)} deployed devices, {len(not_found_devices)} not found.") + return 1, {"deployed": deployed_well_ids, "not_found": not_found_devices} + + except Exception as err: + error_string = traceback.format_exc() + AddToLog(error_string) + if cur: + cur.close() + if conn: + conn.close() + return 0, error_string + + +def ExtractAddress(deployment_map): + address_map = {} + ''' +{ + "city": "mountain view", + "country": "usa", + "house_number": "760", + "postcode": "95070", + "road": "hope st.", + "state": "ca" +} + ''' + print(deployment_map) #{'deployment_id': 24, 'time_edit': 1753129300.0, 'user_edit': 32, 'persons': 2, 'gender': 1, 'race': 1, 'born': 1972, 'pets': 0, + # 'address_street': '', 'address_city': '', 'address_zip': '95070', 'address_state': '', 'address_country': '', 'wifis': '{"CBX_F": "69696969", "CBX": "69696969"}', 'lat': 37.267117, 'lng': -121.99548, 'gps_age': 0, 'note': 'me', 'overlapps': None}' + address_map["city"] = deployment_map["address_city"] + address_map["country"] = deployment_map["address_country"] + address_map["road"] = deployment_map["address_street"] + address_map["postcode"] = deployment_map["address_zip"] + address_map["state"] = deployment_map["address_state"] + + #address_map["city"] = "San Francisco" + #address_map["country"] = "USA" + #address_map["road"] = "230 Hope str." + #address_map["postcode"] = "95070" + #address_map["state"] = "CA" + + address_str = JoinAddress(address_map) + return(address_str["formatted_address"].strip()) + +# Initialize data files + +def PurgeDeployment(deployment): + deployment.pop('floor_plan', None) + deployment.pop('context', None) + deployment.pop('alarm_details', None) + deployment.pop('devices', None) + deployment.pop('floor_plan', None) + + deployment.pop('time_zone_s', None) + deployment.pop('beneficiary_id', None) + deployment.pop('caretaker_id', None) + deployment.pop('owner_id', None) + deployment.pop('installer_id', None) + #print(deployment) + deployment.pop('address_street', None) + deployment.pop('address_city', None) + deployment.pop('address_zip', None) + deployment.pop('address_state', None) + deployment.pop('address_country', None) + + deployment.pop('gps_age', None) + deployment.pop('note', None) + deployment.pop('overlapps', None) + deployment.pop('time_edit', None) + deployment.pop('user_edit', None) + + + return deployment + +def save_list_to_csv_method1(data_list, filename): + """Save list data to CSV with one item per row using csv module""" + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile) + # Optional: Add header + writer.writerow(['Value']) + # Write each item in a separate row + for item in data_list: + writer.writerow([item]) + print(f"Data saved to {filename} using csv module") + + +logger.error(f"------------------------------- STARTED ------------------------------------------") +try: + searches_text = read_file("searches.json") + searches_dict = json.loads(searches_text) if searches_text else {} + + dialogs_data = read_file("dialog.json") + dialog_dict = json.loads(dialogs_data) if dialogs_data else {"utterances": {}, "intents": {}} + + intent_map = dialog_dict.get("utterances", {}) + utterances = {} + for key in intent_map: + logger.debug(key) + list_of_utterances = intent_map[key] + for utterance in list_of_utterances: + utterances[utterance] = key + + intents = dialog_dict.get("intents", {}) +except Exception as e: + logger.error(f"Error initializing data files: {str(e)}") + searches_dict = {} + utterances = {} + intents = {} + +# Create Falcon application with middleware +middlewares = [CORSMiddleware(), RequestParser(), StripPathMiddleware()] +try: + # For newer Falcon versions + app = falcon.App(middleware=middlewares) +except: + # For older Falcon versions + app = falcon.API(middleware=middlewares) + +#logger.error(f"@1") +# Add routes for well-api +well_api_instance = WellApi() + +# New routes for well_api with multiple access paths +app.add_route('/function/well-api', well_api_instance) +app.add_route('/function/well-api/{path}', well_api_instance) +app.add_route('/api/well_api', well_api_instance) +app.add_route('/api/well_api/{path}', well_api_instance) +app.add_route('/healthz', well_api_instance, suffix='healthz') + +# Add routes for the standard API paths +app.add_route('/health', well_api_instance) +app.add_route('/users', well_api_instance) +app.add_route('/items', well_api_instance) + +# Keep the original routes for backward compatibility +app.add_route('/', well_api_instance) +app.add_route('/{path}', well_api_instance) + +MQTTSERVERL = "eluxnetworks.net" +MQTT_PortL = 443 + +MyName = "well-api" + +clientL = mqtt.Client(client_id=MyName+str(time.time()), transport="websockets") +clientL.tls_set(cert_reqs=ssl.CERT_NONE) # For self-signed certs, use proper CA in production +clientL.ws_set_options(path="/mqtt") # Important! Same path as in your JS code +clientL.username_pw_set("well_user","We3l1_best!") + +clientL.on_connect = on_connectL +clientL.on_message = on_messageL + +#clientL.connect(MQTTSERVERL, MQTT_PortL, 60) +#lientL.loop_start() +#logger.error(f"@2") + +# This code runs when executed directly (for development/debugging) +if __name__ == "__main__": + from wsgiref.simple_server import make_server + redis_conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB) + # Use port 8000 for local debugging + port = int(os.environ.get('PORT', 8000)) + #port = int(os.environ.get('PORT', 1998)) + + # Create a WSGI server + with make_server('', port, app) as httpd: + print(f'Serving on port {port}...') + + # Serve until process is killed + httpd.serve_forever() +else: + redis_conn = redis.Redis(host=redis_host, port=6379, db=0) diff --git a/well_web_files/deployment.html b/well_web_files/deployment.html index ec16bb1..bfd2b2c 100644 --- a/well_web_files/deployment.html +++ b/well_web_files/deployment.html @@ -209,7 +209,100 @@ var client = ""; var options = {} var hasColonInLast6 = false; var sensor_tables = ["temperature", "humidity", "pressure", "co2", "voc", "light", "radar"]; -var sensor_tablesn = ["temperature", "humidity", "pressure", "light", "radar", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9"]; + +var sensor_tablesn = ["temperature", "humidity", "pressure", "light", "radar"]; +for (let i = 0; i < 80; i++) { + sensor_tablesn.push("s" + i); +} + +var smell_chem = [ + "Temperature", + "Humidity", + "", + "", + "", + "Acetone, light ketones", + "Isopropanol, cleaning alcohol vapors", + "Formaldehyde, light aldehydes", + "Ethyl acetate, light esters", + "Benzene, light aromatic hydrocarbons", + "Toluene, paint solvents", + "Xylene, heavier paint thinners", + "Styrene, plastic outgassing", + "Extended detection of persistent light VOCs", + "Acetic acid, vinegar vapors", + "Ethylene glycol, antifreeze vapors", + "Propylene glycol, vaping compounds", + "Butanol, heavier alcohols", + "Cooking oils, fatty acid vapors", + "Ammonia, cleaning product vapors", + "Extended cooking oil detection", + "Diesel fuel vapors", + "Naphthalene, mothball compounds", + "Extended glycol detection", + "Chloroform, dry cleaning solvents", + "Gasoline vapors, light hydrocarbons", + "Methylene chloride, paint strippers", + "Tetrachloroethylene, dry cleaning", + "Carbon monoxide, combustion gases", + "Natural gas (methane) leaks", + "Propane, LPG vapors", + "Extended gasoline vapor detection", + "Hydrogen sulfide, sewer gases", + "Extended natural gas detection", + "Hydrogen gas, battery outgassing", + "Sulfur dioxide, industrial emissions", + "Nitrogen oxides, car exhaust", + "Ozone, electrical arc detection", + "Extended hydrogen detection", + "High-temperature combustion products", + "Extended SO2 detection", + "Extended NOx detection", + "Chlorine gas, pool chemicals", + "Extended battery gas detection", + "Ethanol breath detection", + "Cannabis/marijuana compounds", + "Tobacco smoke compounds", + "Burning paper/wood smoke", + "Cooking spice vapors", + "Electrical burning smell", + "Mold/mildew compounds", + "Plastic burning detection", + "Food spoilage compounds", + "Extended tobacco detection", + "Perfume/cologne compounds", + "Laundry detergent vapors", + "Fabric softener chemicals", + "Bleach/chlorine compounds", + "Room deodorizer chemicals", + "Disinfectant vapors", + "Medical alcohol/sanitizer", + "High-temp disinfection products", + "Air freshener compounds", + "Extended bleach detection", + "Printer toner compounds", + "Copier ozone/chemicals", + "Electronic component outgassing", + "Flux vapors from soldering", + "Hot plastic from electronics", + "Adhesive/glue vapors", + "High-temp electronic burning", + "Extended electronics detection", + "Thermal paste vapors", + "Extended adhesive detection", + "Diesel exhaust particles", + "Gasoline engine exhaust", + "Motorcycle exhaust compounds", + "Industrial furnace emissions", + "Wood stove/fireplace smoke", + "Barbecue/grilling smoke", + "Coal burning compounds", + "Extended wood smoke detection", + "Kerosene/jet fuel vapors", + "Extended BBQ smoke detection" +]; + + var presence_graphs = ["raw", "presence", "z-graph"]; //var possible_graphs = ["temperature", "humidity", "pressure", "light", "radar", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9"]; var radar_tablesn = ["m0_max", "m1_max", "m2_max", "m3_max", "m4_max", "m5_max", "m6_max", "m7_max", "m8_max", "m08_max", "s2_max", "s3_max", @@ -243,6 +336,8 @@ var alt_key_state = 0; var ctrl_key_state = 0; var hours_delta = 0; +var devices_count = 1; + polar1 = []; polar2 = []; messages = []; @@ -265,6 +360,66 @@ setInterval(ProcessQueue, 10); img1 = new Image(); //img2 = new Image(); + +const sensorHeights = [8, 8, 8, 8, 8]; // temperature, humidity, pressure, light, radar +for (let i = 0; i < 80; i++) { + sensorHeights.push(1); // s0 to s79 +} + +// Calculate cumulative heights for quick lookup +let cumulativeHeights = [0]; +function updateCumulativeHeights() { + cumulativeHeights = [0]; + for (let i = 0; i < sensorHeights.length; i++) { + cumulativeHeights.push(cumulativeHeights[i] + sensorHeights[i]); + } +} + + +let cumulativeHeightsD = []; +function updateCumulativeHeightsD(devices_count) { + cumulativeHeightsD = [0]; + for (let i = 0; i < sensorHeights.length; i++) { + cumulativeHeightsD.push(cumulativeHeightsD[i] + (sensorHeights[i] * devices_count)); + } +} + +// Function to get sensor index from mouse y position +function getSensorIndex(my) { + const localY = my % 120; // Y position within current 120px block + + // Find which sensor this Y position corresponds to + for (let i = 0; i < cumulativeHeights.length - 1; i++) { + if (localY >= cumulativeHeights[i] && localY < cumulativeHeights[i + 1]) { + return i; + } + } + return -1; // Invalid position +} + +function getSensorIndexD(my) { + // Find which sensor type + let sensor_index = -1; + for (let i = 0; i < cumulativeHeightsD.length - 1; i++) { + if (my >= cumulativeHeightsD[i] && my < cumulativeHeightsD[i + 1]) { + sensor_index = i; + break; + } + } + + if (sensor_index === -1) return { sensor_index: -1, position: -1 }; + + // Find which device within that sensor type + const localY = my - cumulativeHeightsD[sensor_index]; + const position = 1 + parseInt(localY / sensorHeights[sensor_index]); + + return { sensor_index, position }; +} + + + +// Calculate cumulative heights for device-grouped layout (each sensor type * devices_count) + function sendToBackend(topicl, send_what) { // Once a connection has been made, make a subscription and send a message. @@ -647,7 +802,9 @@ function handleMouseUp(e){ devices_count = parseInt(Canvas1.height/70); } else{ - devices_count = parseInt(Canvas1.height/150); + devices_count = parseInt(Canvas1.height/120); + updateCumulativeHeights(); // This one doesn't depend on devices_count + updateCumulativeHeightsD(devices_count); } PrepareChart(devices_count); } @@ -669,16 +826,19 @@ function handleMouseUp(e){ } } else{ - devices_count = parseInt(Canvas1.height/150); + devices_count = parseInt(Canvas1.height/120); if (document.getElementById("GroupBy").value == "sensortype") { - sensor_index = parseInt(my/(devices_count*10)); - sensor_type = sensor_tablesn[sensor_index]; - position = 1+parseInt(my/10) % devices_count; + const result = getSensorIndexD(my); + sensor_index = result.sensor_index; + position = result.position; + sensor_type = sensor_tablesn[sensor_index]; + smell_name = smell_chem[sensor_index]; } else { - position = 1+parseInt(my/(15*10)); - sensor_index = parseInt(my/(10) % 15); - sensor_type = sensor_tablesn[sensor_index]; + position = 1+parseInt(my/(120)); + sensor_index = getSensorIndex(my); + smell_name = smell_chem[sensor_index]; + sensor_type = sensor_tablesn[sensor_index]; } } @@ -695,7 +855,7 @@ function handleMouseUp(e){ //clicked_sensor_description = hours+":"+minutes+":"+seconds + " "+sensor_type+ " @ "+location_desc; - clicked_sensor_description_fixed = device_id + " "+MAC + " "+sensor_type+ " @ "+location_desc; + clicked_sensor_description_fixed = device_id + " "+MAC + " "+sensor_type+ " @ "+location_desc+"---> "+smell_name; UniversalChart.title.text = clicked_sensor_description_fixed + " "+ hours+":"+minutes+":"+seconds; //UniversalChart.draw(); @@ -1023,51 +1183,16 @@ function handleMouseUp(e){ sensor_check = "r-check"; one_sensor_list = ["radar"]; break; - case 5: - sensor_check = "v0-check"; - one_sensor_list = ["s0"]; + default: + sensor_check = ""; + one_sensor_list = [`s${sensor_index-5}`]; break; - case 6: - sensor_check = "v1-check"; - one_sensor_list = ["s1"]; - break; - case 7: - sensor_check = "v2-check"; - one_sensor_list = ["s2"]; - break; - case 8: - sensor_check = "v3-check"; - one_sensor_list = ["s3"]; - break; - case 9: - sensor_check = "v4-check"; - one_sensor_list = ["s4"]; - break; - case 10: - sensor_check = "v5-check"; - one_sensor_list = ["s5"]; - break; - case 11: - sensor_check = "v6-check"; - one_sensor_list = ["s6"]; - break; - case 12: - sensor_check = "v7-check"; - one_sensor_list = ["s7"]; - break; - case 13: - sensor_check = "v8-check"; - one_sensor_list = ["s8"]; - break; - case 14: - sensor_check = "v9-check"; - one_sensor_list = ["s9"]; - break; - } + } } - - document.getElementById(sensor_check).checked = true; + if( sensor_check != ""){ + document.getElementById(sensor_check).checked = true; + } device_check = "device_check"+(position-1).toString(); document.getElementById(device_check).checked = true; UpdateSelections(); @@ -3017,8 +3142,8 @@ function UpdateRoseChart(minute_in_day){ function handleMouseMove(e){ reOffset(); - var mx=parseInt(e.clientX) - offsetX; - var my=parseInt(e.clientY) - offsetY; + var mx=parseInt(e.clientX - offsetX); + var my=parseInt(e.clientY - offsetY); device_id = "0"; sensor_type = ""; location_desc = ""; @@ -3042,15 +3167,18 @@ function handleMouseMove(e){ } } else{ - devices_count = parseInt(Canvas1.height/150); + devices_count = parseInt(Canvas1.height/120); if (document.getElementById("GroupBy").value == "sensortype") { - sensor_index = parseInt(my/(devices_count*10)); - sensor_type = sensor_tablesn[sensor_index]; - position = 1+parseInt(my/10) % devices_count; + const result = getSensorIndexD(my); + sensor_index = result.sensor_index; + position = result.position; + sensor_type = sensor_tablesn[sensor_index]; + smell_name = smell_chem[sensor_index]; } else { - position = 1+parseInt(my/(150)); - sensor_index = parseInt(my/10 % 15); + position = 1+parseInt(my/(120)); + sensor_index = getSensorIndex(my); + smell_name = smell_chem[sensor_index]; sensor_type = sensor_tablesn[sensor_index]; } } @@ -3063,7 +3191,7 @@ function handleMouseMove(e){ hours = String(parseInt(time)).padStart(2, '0'); minutes = String(parseInt((time*60) % 60)).padStart(2, '0'); seconds = "00"; - clicked_sensor_description = device_id + " "+MAC + " "+sensor_type+ " @ "+location_desc+ " "+ hours+":"+minutes+":"+seconds; + clicked_sensor_description = device_id + " "+MAC + " "+sensor_type+ " @ "+location_desc+ " "+ hours+":"+minutes+":"+seconds+" --->"+smell_name; @@ -3127,7 +3255,7 @@ function handleMouseMove(e){ } } const h3Element = document.querySelector('.page-title .title_left h3'); - h3Element.textContent = device_id+" "+sensor_type+" "+location_desc+" "+hours+":"+minutes+":"+seconds; + h3Element.textContent = device_id+" "+sensor_type+" "+location_desc+" "+hours+":"+minutes+":"+seconds+" --->"+smell_name; } @@ -3494,6 +3622,7 @@ async function ShowHistoryMap() { } if (Canvas1.height != days_count * stretch_by){ Canvas1.height = days_count * stretch_by; + resize(UniversalChart); } @@ -3522,7 +3651,7 @@ async function ShowHistoryMap() { if (maps_dates.length > 0) { locations_map = data["locations_desc_map"]; var checkmarks_string = data["checkmarks"]; - var devices_count = data["device_count"]; + devices_count = data["device_count"]; document.getElementById("checkmarks").innerHTML = checkmarks_string; document.getElementById("devices_count").value = devices_count; @@ -3539,8 +3668,8 @@ async function ShowHistoryMap() { } else{ if(map_type < 2){ - if (Canvas1.height != devices_count * 150){ - Canvas1.height = devices_count * 150; + if (Canvas1.height != devices_count * 120){ + Canvas1.height = devices_count * 120; resize(UniversalChart); } } @@ -3646,7 +3775,28 @@ async function ShowHistoryMap() { } - img1.src = image_address1; + //img1.src = image_address1; + + fetch(image_address1) + .then(response => { + // Extract custom headers + const metadata = response.headers.get('X-Image-Metadata'); + const processingTime = response.headers.get('X-Processing-Time'); + const imageInfo = JSON.parse(response.headers.get('X-Image-Info') || '{}'); + + console.log('Metadata:', metadata); + console.log('Image info:', imageInfo); + + // Convert response to blob and create object URL for the image + return response.blob(); + }) + .then(blob => { + img1.src = URL.createObjectURL(blob); + // Remember to revoke the object URL when done to free memory: + // URL.revokeObjectURL(img1.src); + }) + .catch(error => console.error('Error:', error)); + } else { GetZGraph(); @@ -4130,6 +4280,7 @@ function RequestMapSlice(devices_list, epoch_from, epoch_to) { user_name:user_name, ps:key }; + console.log(devices_list); var json = JSON.stringify(obj); sendToBackend("/wellget", json); } @@ -4168,7 +4319,7 @@ async function RequestAPIDeviceSlice(devices_details, sensors_list, epoch_from, time_zone_st = data.time_zone_st; //Since all_slices is //device_id = data.well_id; - devices_count = parseInt(Canvas1.height/150); + devices_count = parseInt(Canvas1.height/120); @@ -4244,7 +4395,7 @@ async function RequestSingleSlice(devices_list, sensor_list, ddate, date_to, ctr if(device_id<200) devices_count = parseInt(Canvas1.height/70); else - devices_count = parseInt(Canvas1.height/150); + devices_count = parseInt(Canvas1.height/120); PrepareChart(devices_count); @@ -4315,7 +4466,7 @@ async function RequestSingleRadarSlice(devices_list, sensor_index_list, ddate, c if(device_id<200) devices_count = parseInt(Canvas1.height/70); else - devices_count = parseInt(Canvas1.height/150); + devices_count = parseInt(Canvas1.height/120); } else { if(device_id<200) @@ -4517,9 +4668,11 @@ async function DeploymentChange() { } } else{ - if (Canvas1.height != devices_count * 150){ - Canvas1.height = devices_count * 150; + if (Canvas1.height != devices_count * 120){ + Canvas1.height = devices_count * 120; resize(UniversalChart); + updateCumulativeHeights(); // This one doesn't depend on devices_count + updateCumulativeHeightsD(devices_count); } } @@ -4687,8 +4840,8 @@ function openAnimation() { -------- Group by: -------- Radar part:

    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 veu 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%0D`|F&UoM`h@7W6#oDY;hI20 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 literal 0 HcmV?d00001 diff --git a/longpresence_after_filter_data.csv b/longpresence_after_filter_data.csv new file mode 100644 index 0000000..7bbc9c2 --- /dev/null +++ b/longpresence_after_filter_data.csv @@ -0,0 +1,43201 @@ +Value +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 diff --git a/longpresence_before_filter_data.csv b/longpresence_before_filter_data.csv new file mode 100644 index 0000000..98ebec0 --- /dev/null +++ b/longpresence_before_filter_data.csv @@ -0,0 +1,43201 @@ +Value +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +0 +1 +1 +1 +1 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +0 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +1 +1 +0 +0 +1 +1 +0 +1 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +0 +1 +1 +1 +1 +1 +0 +0 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +1 +1 +0 +1 +0 +1 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +0 +1 +1 +0 +1 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +1 +0 +0 +1 +0 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +0 +0 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +0 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +0 +0 +1 +0 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +1 +0 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +0 +1 +1 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +1 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +1 +1 +0 +0 +1 +1 +1 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +0 +1 +1 +1 +0 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +0 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +0 +1 +0 +1 +1 +0 +0 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +1 +0 +1 +1 +0 +0 +1 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +1 +1 +0 +0 +1 +1 +1 +0 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +1 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/longpresence_initial_data.csv b/longpresence_initial_data.csv new file mode 100644 index 0000000..81eabcf --- /dev/null +++ b/longpresence_initial_data.csv @@ -0,0 +1,43201 @@ +Value +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +0 +0 +1 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +1 +1 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +1 +1 +0 +1 +0 +0 +1 +0 +1 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +0 +0 +1 +1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/presence_after_filter_data.csv b/presence_after_filter_data.csv new file mode 100644 index 0000000..96b15d1 --- /dev/null +++ b/presence_after_filter_data.csv @@ -0,0 +1,8641 @@ +Value +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +0.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 +1.0 diff --git a/presence_before_filter_data.csv b/presence_before_filter_data.csv new file mode 100644 index 0000000..46b410a --- /dev/null +++ b/presence_before_filter_data.csv @@ -0,0 +1,8641 @@ +Value +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +0 +0 +0 +0 +1 +1 +0 +1 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +1 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +1 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +0 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +1 +1 +0 +1 +1 +1 +0 +0 +1 +0 +0 +1 +1 +1 +0 +0 +1 +1 +1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/raw_decoded.jpg b/raw_decoded.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3116ffc312109d37bb1f94c7d10567afd23805e2 GIT binary patch literal 214188 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�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

    I=5BC^H}iz07_~W$Kv=kBshMY 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 ZQ*Cy0Vf!Ab_=fuYJLf-Y>MV*s|Jh}>g&6<< literal 0 HcmV?d00001 diff --git a/50_Robert_Zmrzli.jpg b/50_Robert_Zmrzli.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3b89f3d9267306c1110d20c8e2bca44c952363ab GIT binary patch literal 359202 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