From dc34207fb3274289a9f8bdabac50c91e184b7c45 Mon Sep 17 00:00:00 2001 From: Fando Evgeniy Date: Wed, 21 May 2025 15:43:47 +0300 Subject: [PATCH 1/2] bd --- .idea/misc.xml | 5 +- .idea/vision-macula.iml | 2 +- database.db | Bin 0 -> 36864 bytes database_schema.sql | Bin 0 -> 8496 bytes src/bsmu/macula/app/2.png | Bin 0 -> 4648 bytes src/bsmu/macula/app/database.db | Bin 0 -> 36864 bytes src/bsmu/macula/app/edit.png | Bin 0 -> 2819 bytes src/bsmu/macula/app/example.db | 0 src/bsmu/macula/app/images/icons/edit.png | Bin 0 -> 2819 bytes .../bsmu.macula/app.MaculaApp.conf.yaml | 2 + .../default/bsmu.macula/plugins.BD.conf.yaml | 0 .../plugins.database_manager.conf.yaml | 0 src/bsmu/macula/plugins/BD.py | 52 ++ src/bsmu/macula/plugins/SQLiteTableViewer.py | 473 ++++++++++++++++++ .../macula/plugins/add_appointment_dialog.py | 72 +++ src/bsmu/macula/plugins/add_patient_dialog.py | 80 +++ src/bsmu/macula/plugins/database_manager.py | 415 +++++++++++++++ .../macula/plugins/edit_pacirnt_delegate.py | 20 + src/bsmu/macula/plugins/images/edit.png | Bin 0 -> 2819 bytes src/bsmu/macula/records/patient.py | 9 + 20 files changed, 1125 insertions(+), 5 deletions(-) create mode 100644 database.db create mode 100644 database_schema.sql create mode 100644 src/bsmu/macula/app/2.png create mode 100644 src/bsmu/macula/app/database.db create mode 100644 src/bsmu/macula/app/edit.png create mode 100644 src/bsmu/macula/app/example.db create mode 100644 src/bsmu/macula/app/images/icons/edit.png create mode 100644 src/bsmu/macula/configs/default/bsmu.macula/plugins.BD.conf.yaml create mode 100644 src/bsmu/macula/configs/default/bsmu.macula/plugins.database_manager.conf.yaml create mode 100644 src/bsmu/macula/plugins/BD.py create mode 100644 src/bsmu/macula/plugins/SQLiteTableViewer.py create mode 100644 src/bsmu/macula/plugins/add_appointment_dialog.py create mode 100644 src/bsmu/macula/plugins/add_patient_dialog.py create mode 100644 src/bsmu/macula/plugins/database_manager.py create mode 100644 src/bsmu/macula/plugins/edit_pacirnt_delegate.py create mode 100644 src/bsmu/macula/plugins/images/edit.png create mode 100644 src/bsmu/macula/records/patient.py diff --git a/.idea/misc.xml b/.idea/misc.xml index e17b4fa..bef5a0a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,4 @@ - - - + \ No newline at end of file diff --git a/.idea/vision-macula.iml b/.idea/vision-macula.iml index 4ada2a6..62b7f51 100644 --- a/.idea/vision-macula.iml +++ b/.idea/vision-macula.iml @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/database.db b/database.db new file mode 100644 index 0000000000000000000000000000000000000000..f749bb3bc641c80a3d4c49ee9814b3d608114a82 GIT binary patch literal 36864 zcmeHQZ;ab!8MoskwsT2*S$7A~X1Vxk zJK3%XjXVB)fe+))x-pd%!B>O|8&g?D5fY#qFewKTUjS(mFfj>D0=`TXBpy4l6DM&l zHAUHF&)Yh-pZ9%#e%{~jb&~h=_{q7CFH4%pugS`Wpz-s>BteqI0?!i!aR@dRHnUK0 z;5AQRPj(zT9UdZ%*T3Y`8*{hyHZ*rTRCQ{Mc4PnscqW@$J!W(-39$6*Q%>m@g_2uY<#Xqk zms_oCil9kywIHt*w6a(zNvbHQB5z*I-g;9gN-&;6w~PH-KeCd~E}hO9j?T8b&hh!| zBiVd5x0pT0x6CR(E0yK~b7wvN=?5PqFBlA}mnyJ~EvVw9npiE0U8j_d$F9oRs<0s* zgPwESmq>d0@L}?EDFf5Ip1JqfP)w_n66QGfkwCw=O@U8j@{1qMk(^96ck;ki;|+1OTw0=l7V1I1vAl4h2S+1{wu-#ncL;Q%RR+u+@suwIG*!@ z5eqVa3?Ku@05X6KAOpw%GJp&q1IPd}a2FYPU%*G4j(8eRH*Pk5*!XedI^Ve3xUu(z z#`VUJz;vte130=u{}L|wXd2pmvvH&GGMI1b4fw|Md!KE!x=Q~X+MIm|+C1O5x%cJ9 z3p&;{X!?^VT;l^^zt*_E_eJOr*V)_Q8_(*vQMPa0GY!CtGJWf=K)+u^>ArQ_(C=2% z-?#2*==W;W*SGHL(C?>FZ{NDFLBHK7)wk}e(C=qaPv5$)K)+u_$-Z?@LBCyn{ZDbf zBe;KXufYxrGJp&q1IPd}fD9l5$N(~c3?Ku@05X6K92f=|ikc$DEm6IH(nB33J0nN9 zAW0o2&FF=IzW(RJe<8Rxxj%5Pa4&J+ff#_#aG&Bn$(`a7+ z89)Y*0b~FfKn9QjWB?g>`!f*Xe8drwOvaO`So}mRk)#8WaEEC=HXjeNkx+*z8B3hF zhm8a~OebRT`2Z7{=rGO4k|{qOf!G2Iem)jYFmwb$3M>;~o#gxxh$OH~Ct}GY>x)1D zfn|b@sR>^M;sz{JB9=(q>y1FTfMo*j;}jKvC;`iqj3rOfR0M(pEED(@4|^sdCg2Fk zTEkBHCLt1_!xB$`=hn0NEO$Hj#KcR17udfuZ!*W|Isdw^ZD(nkMxf$%2ugNrqvTJNOP{^4U}860zj* z_)4)@t7zmg;#!Cd9x0;9P*IFjCj&?Bgk75=7T4nvgJzGaMJ&5I%~RUI=|p=I=Vo$>RZD% z>(j58z|61p@@o3ut^MoGj_E(OI<_w8Fu}#6q zMfAH9ks2creeOhTQ!sK7z3xQxjFE_xI}zIyj9A3&Cp|ENck~M5BV?0-J6$DOvv_n8 z!t#xY81et1I>80QFNf-wo3FX1#aCni89)Y*0b~FfKn9QjWB?gJ2JRLEnE$UUwH@aF zGc)^HIsN|c`TroXV_;5S|C8j41pLQxKp42X1u7dh*e#DAxrQ0$&70x@_c*HT#--XN(6R2h4Mj#^sp@%(jE} zx$|eT`K3jpiJjMCSyp7JBvc@0tyFxhDynLs2#IM$r5kSmhNc$QsDNX{6=4YBaQVyL1b*De?IA(cc;D3c?XurcBi!^Vg+MoFouVin$%J8u{L?&-M^U0~R_&}Y31jGkM@ z1%{1_A^cE#M{mDphr)OqZua)H?gaqLK4Vb}`sm$bTn25`LeJ=XU2*nftx|)zQLC2V zlTx1+&S1l1^h_ z-)Fza{EdEtem|J8AOpw%GJp)cgBjQ%?sxaBX4rbl?ZAJ|r|)z3tY({n(LLyAe9-R@ zQFkJS#z@2&cOtea7`ce=b|+$Rj6|GvCt{m|k&E~)cOp)Vk%&|7L~K(qauM%!Ct_fX ZL_Fk9#5M&Z7x5lkmcU_)Zrd4i!%XhHcmP8^M zOAq@CFq(kRKGVzfI+*4AILBJxB0cjPq;kNAPA0A37Gd z=l8-E+cLp6hD|Dg9?&XcF*Y7$M_yJRWR#;$_fmZJ0V=V&x6fqxPOGmA78BFM09=e7 zJW5}Z=`Q#o+ikOdIy%4g^lXb@%Rjra-1)8VL;alRhjIYDE@s{){Q7v7+kUBRKE010 zTh7Wo`GTwIr)^9i(k-LOu##dZJ_bL_$C6?%7Tw7*R90OUdr@z?>)ELC&cv!vOv#^* zVa@n3X4!Jj`gOJJjB{3fZFW|=XG2Jqo1M<8uhnrfLv)zPrHK(f>&x8R!TdcjdAVuk zuY1?e32|67V`K#PPPtZxgc&eKoZ!r_YRk?Y=K64^Cyt(p;~j8gBY9awIwUj1!U@p0 z%3y@!vD}#a(Lt^m;wS;ad$i9*szb6NYmKOCR`X$tt|9&|n zQ)puXM6AZh1Zzu&Zdqj2AQ=PEtON?Or9;B#Hk<3?#`xwsutM;AMvtUz)WQ1j2YeFY zj7lkoM#o7qk1A8c%nTUHN8NHrhTx)Oyu=Fm`Oz%TS2`peqlyR~d1)kEAuz68Bd$j{ zvKgR@tJE>$o?Bl%AFYm)WsuyOILru~=aa>?)g$3rYk)b{2VaYRpB}9qiLGhE5m$$V z&EzSjP9%QC=#ca<8!k*l$ySbft<;UASSxiS zDb`BeNQ$+hL(+vtSP8IRwpBI1Smk9Mmy^P|vMwaW`fzc-E+paXQx}qAeN@E>S3O}3 zxZZqiSx$)LcAoe1Vz{sG^t~#OI1RiElKc5ACMRJHq(`5BrsJe%RySOoM?f?(v4ks8 z7de2ws`j*}~V<+Ax^R2e(fAgO{K9g@U+w<*fM zI$Wzj63)AMYFPymUCdnt5=~sw&Yk}J3o+M#{AxYK-hQ>_m&gd~L;HUxzY^PTq+#vo cI9VP^m?s^Q;(s!I|B4m=qnZC!(LbC20UQV)RR910 literal 0 HcmV?d00001 diff --git a/src/bsmu/macula/app/2.png b/src/bsmu/macula/app/2.png new file mode 100644 index 0000000000000000000000000000000000000000..834570c9f7f2a8120607d029c60840febde3c142 GIT binary patch literal 4648 zcmV+@64&jCP)0001KP)t-s|Ns90 z008dp?(OaE?Ck9C@9*^V^#1<-{QUg)_xCU`Fo}tY!NI}Q)YM^NVNg&|Mn*S65fu+}xg?o*Eh&k&%%J2?-Js5+NZW&d$!5n3#Ked#$alZEbD0x3?)NDf`M= zssI2HdPzh6&$+8HeU7;1G zs*t3GtRE3?g25cA(6}*Eg5wJ9D!3#qc-4Z1Z-UW5CcsT9kU-WI?wOvByawanBg3C0 zQNjl`^W6(wJdPCry}26>W#24tH-dg;$Hi%(!S4N#8*5% zF>70qlni5N+h4~_^74aUtY<78H2oKB*wADd*ep*=F~;`*pa|5SEv7G9>tI$_}yHM zAY?d}+VDqqu753G#wf119%!EH0P#&yF_9PhCOL4YV7`{M9w!!y>XKI>-;U| z!!V|qI@?GQCm?punq#wCoJ&jl3D+u{!97bc*tuD4FO0Znt5Vbm(<~QXNdfbAj`QTJ znL7$$5Hd1u7Uq39FrXuxe9VZB1eDAS7+JFd$UZU#*#4P$yd$;rZjHpm_b9~qu+Pz{;S zN663?BgjY-BvFT(^9t_w_zu+KXXUpA8v}S`c%l zA+ue%FQN)*JZFluF+!o^<0oZOl+!Mi%!eT%8H3FkH{E*4luhOpXO>sy_nRDUG4o&$ zGJ)S}?jbX;1PG*D_J$S?d-fM(o{#_yhDq8kAX1L(9~l{zjBw2rdgUJ=6OVHLE{)`q z$h>4VlOx^o*4Ul2nn4X@5E*5DN1Pe0E~L|k-SxV>Ua!>CcSu{xnxP^z%?*T5qS0AO^N4L0%s|G0_FBy$JHp3JFfPsB86yl#2KD2wt!90x7v^=`M`qQGVA4b8 zr4j%*@t2|K!3dfaJbF_yFd|Vnv%MIyA(N`#BlC=GhGf#QWcF`CrX%z95i(0XkxZGP zfytDcl&+&aZm(>nS+(JEQqgKrV;D)NnFoUj5RX6@9jjTT$!bzX0t&VOT=t-bEqc=l zTd9#UY+*EHu3-;`h#QrbNrVB(bob+zBtSqhL?dR_u^)qL5W-)X;FVUhUjmr)UUy~H zg8>y>p=fyD$aInU13hDaIg@HHh7gT8v&i?Ow;UNLyf%RsL&Ffhc!qNCG6BV~;;hkKKd}b`inHwmaaM!;(b6mv;2GO>V;GnpZf6sg()daB`$x{k2AeK8*z^lz#+-g76)lY6 z@N8bLT(pihwpX*RGiQErRvqoCr5CJVSNF!%Hj1g>(WsX0^z!m^@&sJrGq1jGJQGskW^hfQjXKi}+El%{S@+1*p~ z02ynBEjO(ct;KpDE8)lZosogY?R_sFA!8Tm^t#?w+x}_$bz5dZ{#*~4aU(O446>pQ zM>$5EwWq3Grlagw9mqb%%%wGYF&dY8pE%=GoE7m9nvz(HGkTiqXfzK|3nr^ad>F;Y zT0vqr`n=fM{IG97#{6lGhNX0_pRRF-(i&F*QdWi@F+JkUjnJSugA#$PWY6xDj5U9C z(*l5cFp8QUlgNY7XU>czgA7fsv(80z8@rAKxT|v-t8-h;9O!VuCi&;c7=Iq${XB}y z6ViKT31ILUL;T~(SD5!7ncAVjLg)$l*ksuL{fcz!IOv=a>%M67^t48FTGB4zFvwMm zRI25OB<)92Bm2@CYaLY{IfG=Xq(#_5Gjvw+V;gD3$V~`~eW9vE=Pe_i3MGbGR28Nr z&YHBw>L10&YiI$u4oTEPYP9h$GHE}<+KY(IX=zmRNDHH@La3n@RRy-q5Jo*h<4$RW zEQNXEhl`NKDGhPN>EqY(k>OJs51jPqF=uL!;-@rZXyT3kbS!QfDPog*%x102j&xw; z<(aU^O!1kJ;p8j->c*mreZIkr^O^G7{}V zR((#@kqB>}+w)Uoo;s#AIL*lA0zZ<`j;yftxsA%Bo-Y^~-Xg;>2uBpfLe@9 zYYc_F;9i=>r43Dy7#jnJRMr?ArACs(M{3T*%g;!Zhvs2Fy3!gaN6xrLD*5iuNQ%*9 zw8lV+ z@DP4}6-}80?)g?FK4*~s~?$zjNxSCuhw-x8Ommp7)-{u0C1(L ztE6Hxj*PqS#1}aud&fdI9jO9U2&0Icu>>1xi*q>^-qgqvL+Dl89k&#$N^2A?G6`(oe!5rXgoABR#_s;WLa>Tq(v@>&G#~PLqjnHOOc4RzxoJ&5vObX z6d9GE-{UouJwM(~GTM-c)XlNM7+k77`MDeeaEnP<*70~UVL)6>N}_f0Gp>A33O*9YaM0sBYg%Yccm49TOxc2q7S&zcM7l;>&FH&KHGeAVm8)P_GUZ%9CcfaE za(ht_!?HP(#bKA$v+^M_sS)EPZx@*kV?Y+31$r&?XDweG1`kL)$nH4JIgba(TnhL2 zQz1M}{2X>emt0t_k3VU5mb#6>$!1lT2gtaM|G5yJA73nowOB#&ZdN zYXR^r&CMr-E~Ed^rMVyz3WM2vUwLb#3WX12)ybx{G=sE8WX@D&7kgiGGwnqa!GarU939$wo83sSrZ#BEJi8b*PKjFbYcXX)cmi1zoup@hTw4qs+?ODy~!9RB^ zNm~wKKL(fP*t)1{w4NzjpIJC-IKubhgk3y{VN~k{5|c*eCu1|mR;&c;xCrd$8p~k} z#WJ#zPZ^n0%J|GtFReTBnHSdJABRC6o-#6p;-C4HdI~Tw^E&S5U!%SkDqafk(U~@Ik^rnm~6Jsr6@mUJch}jp&5V{^t}Lo zbSI#xVu}|w6{DS_`Bc8y9Cw_%sA9zAWQ?~v*<@?%C)tk(r(?Vy8EvQjEit?=75Goc z%yN%r{)^;) zwOb8dD5(cq%!oleny^JZLNB^}`ju&od3nmvoB?CZ`I%tyF|8L}rT^Ern@qT{{Y9C8 z|Jca<$)%vrlNs@w`9G{?NCTmLraA^TU7Y=D#gl1`uF1?-=|74w%T@X3VEAtcC3J{r`5h%>1`Ug53E<9{o02Hc_B^2nJ_oP{OR1RE&si!tZy4}d z40!J4W(Y=$j2#-7&9vnDjg+QXf@u38V+5n)CDg4K>eek@`J2fNZu&yq9x~O(F!HM7 zI?=P^Mj+Y|n4}H9)j-lmCSnNLPQ{Hi{X)Qd0qVr|_DiTEp6>&-T5|saFOEk4Y5v($ zF%5&q8NJ8t$Xf3w5FL3hqU-hGTkwwW4SnhNa7F7q;gc|SU9Si4$l+CO|Ih-2Lbg&5-l*IXHHAzdOnmX3|9EjZ z9o;fMnoNFjhUVhGN7 zOynm&s-D(*uJgm1Qo{mGDrUeKAtNS{c?TcBqh$WH zBQuln2&+df^LPA~X1Vxk zJK3%XjXVB)fe+))x-pd%!B>O|8&g?D5fY#qFewKTUjS(mFfj>D0=`TXBpy4l6DM&l zHAUHF&)Yh-pZ9%#e%{~jb&~h=_{q7CFH4%pugS`Wpz-s>BteqI0?!i!aR@dRHnUK0 z;5AQRPj(zT9UdZ%*T3Y`8*{hyHZ*rTRCQ{Mc4PnscqW@$J!W(-39$6*Q%>m@g_2uY<#Xqk zms_oCil9kywIHt*w6a(zNvbHQB5z*I-g;9gN-&;6w~PH-KeCd~E}hO9j?T8b&hh!| zBiVd5x0pT0x6CR(E0yK~b7wvN=?5PqFBlA}mnyJ~EvVw9npiE0U8j_d$F9oRs<0s* zgPwESmq>d0@L}?EDFf5Ip1JqfP)w_n66QGfkwCw=O@U8j@{1qMk(^96ck;ki;|+1OTw0=l7V1I1vAl4h2S+1{wu-#ncL;Q%RR+u+@suwIG*!@ z5eqVa3?Ku@05X6KAOpw%GJp&q1IPd}a2FYPU%*G4j(8eRH*Pk5*!XedI^Ve3xUu(z z#`VUJz;vte130=u{}L|wXd2pmvvH&GGMI1b4fw|Md!KE!x=Q~X+MIm|+C1O5x%cJ9 z3p&;{X!?^VT;l^^zt*_E_eJOr*V)_Q8_(*vQMPa0GY!CtGJWf=K)+u^>ArQ_(C=2% z-?#2*==W;W*SGHL(C?>FZ{NDFLBHK7)wk}e(C=qaPv5$)K)+u_$-Z?@LBCyn{ZDbf zBe;KXufYxrGJp&q1IPd}fD9l5$N(~c3?Ku@05X6K92f=|ikc$DEm6IH(nB33J0nN9 zAW0o2&FF=IzW(RJe<8Rxxj%5Pa4&J+ff#_#aG&Bn$(`a7+ z89)Y*0b~FfKn9QjWB?g>`!f*Xe8drwOvaO`So}mRk)#8WaEEC=HXjeNkx+*z8B3hF zhm8a~OebRT`2Z7{=rGO4k|{qOf!G2Iem)jYFmwb$3M>;~o#gxxh$OH~Ct}GY>x)1D zfn|b@sR>^M;sz{JB9=(q>y1FTfMo*j;}jKvC;`iqj3rOfR0M(pEED(@4|^sdCg2Fk zTEkBHCLt1_!xB$`=hn0NEO$Hj#KcR17udfuZ!*W|Isdw^ZD(nkMxf$%2ugNrqvTJNOP{^4U}860zj* z_)4)@t7zmg;#!Cd9x0;9P*IFjCj&?Bgk75=7T4nvgJzGaMJ&5I%~RUI=|p=I=Vo$>RZD% z>(j58z|61p@@o3ut^MoGj_E(OI<_w8Fu}#6q zMfAH9ks2creeOhTQ!sK7z3xQxjFE_xI}zIyj9A3&Cp|ENck~M5BV?0-J6$DOvv_n8 z!t#xY81et1I>80QFNf-wo3FX1#aCni89)Y*0b~FfKn9QjWB?gJ2JRLEnE$UUwH@aF zGc)^HIsN|c`TroXV_;5S|C8j41pLQxKp42X1u7dh*e#DAxrQ0$&70x@_c*HT#--XN(6R2h4Mj#^sp@%(jE} zx$|eT`K3jpiJjMCSyp7JBvc@0tyFxhDynLs2#IM$r5kSmhNc$QsDNX{6=4YBaQVyL1b*De?IA(cc;D3c?XurcBi!^Vg+MoFouVin$%J8u{L?&-M^U0~R_&}Y31jGkM@ z1%{1_A^cE#M{mDphr)OqZua)H?gaqLK4Vb}`sm$bTn25`LeJ=XU2*nftx|)zQLC2V zlTx1+&S1l1^h_ z-)Fza{EdEtem|J8AOpw%GJp)cgBjQ%?sxaBX4rbl?ZAJ|r|)z3tY({n(LLyAe9-R@ zQFkJS#z@2&cOtea7`ce=b|+$Rj6|GvCt{m|k&E~)cOp)Vk%&|7L~K(qauM%!Ct_fX ZL_Fk9#5M&Z7x5lgwg?nhqpP!#xTwG^oXQid3hK7c6a&ncGm11IIEiEn7)YKLh7U1CE$H&LMzP?ROO%D$b ztE;O*LP9DkDmXYe9v&Xc%gcRzedF%#wg3PMJxN4CRCt{2T?==kDi8+61WawV&12JU zbJJ|Q+im~<54?by=m^FK;-qoT-g8eff_z~Z=9>{qXh9f_NLeEJ-8E~akO|@Vqnr>< z2q%Q&k8(mdA-pYwl!oBy@ana!#!5Mmg|v|Du7-X21nmgd|_(Z!Z7 z<5Habpcl6&QYk#ca>)aLr zXPclX66gaz87dYl?bBD$MAJE(2b<7E&sY%N2bqh>r%-*k%iYcOBjisCQ3;_&d^Z~! zgD-?n8LFb0&b7PYs1SDaT7v%y%%a>4Q-&IK>$$hq5khIA<@qbQn|t-UVnNu^9&9ss zb~kz)tbNbDwNN@mfZEB-&2yN}qjWc8ktryhqWGs^ZbNu>H}RQET~X~x(IrmrjWY*6 z|Kq+OxvV|O1t95?&B>Gi7NeU6J!7do<#g$ifas<{&segFDP1xy!8!a;h;AD6j3t{G zEi_%K8tHNk@TTr&!<+VuC7TH8Qf`q?g!~N7MDaY64+^5sK)f$PYH%|0^D>sYXK1V^ zGIccwg`!K#~@Sv5LW4ulBYORlXz9u zXm=C%B~uV#8@gn7GPzK))?XRjjW?>%1z~E1y9z+OQM}|nkeKs*xDaA{bnbaEZbddJb zn7jG!oGR`O$?>@-meA^2tN>K=dUr@0Y2pP;wHVzryRWC(Q>2t}#ZXNUv!|m@sp3lI z<`msDxv!6GVye|YV!|)Y;%Nqj{_nY>Ek-v@?%P7!AECq;vuYI>V0bv>6D0qifDFMr z7_c(s;7NA*FR5`idhmakD2>BXnPSe#loNzhaVe|2NxkSnuwuTP*iyw&yZib=SnF<- z8-1XH7TeZTrd*RLM+lXv+T2Y#gOW4C)l#iY4MC>dAWWyx;%?*`oRKWVD~YW##Rf&# z!)w{e)J`bA$Y9%=%9K+wt2s1nrXQ@oNBvYaDckbE9)H0D;b<_*C9iB`<=}B9xWGYJ) zSKAIwrl9oNh-}eK>TVVhgKd3~DJZ>ydMlYq-3_nQZHNalxrP5e`eH20&;dQ>V(5sqJuN$~{7A zAydW5RJHAJWD1I-6i{7d$}^c72w`ny$}gFM;)YyfB2!QtrPC=MG6ls=Ndyy_8up!g zqm!v{=y&doOr{*vp3sviI_x`!P>`vi5spu$27lL=Q!Ihl=TCFQGwP9+V;zQ_= zOg*)^uSbN=$W$=jqeMuZk*Oef?~QP{(>X54lw66T{iysPRNF>>G9@MMi*U&EN0Llc zUoFv+Oa+Jb_x1Jnp7c0+k|`;xcK3Bzd%76oyC73Rux))`pMUR3ez#CBT53b4N>8cE zc7G2n!u$80Wbne`mSl?l0~dY@c@bQA9&1mv+K_6G`or*Ot%XcU`2g3hiG))9?MBEZ z8nrsQVP=nNAydVbsD7CbeR{0beLW+j%*cV?>4R=_Rvna~J9-J)L06q&N0nJTx1TjEwQ^m9m6?#AM7 zs$}XFT=pc?GgaIVUcePQ($kmvKzJfk6-!m_ zCYH{qfB_^IgFDi5KlHK=sX9YzB~wIh8|P-PLD}&>#EFBLW_U8iB4Hp?`W4H6p7+Jq z4_osI7&AmSfrrJ}zc2Rp~>f`xyMnpn(;JEunV5&V` zB2&y9-Q@DJS?%LJyduU^d%B>_q8qcE4%EJFK*B9Jpv)$ksRpY%#83cRp2QWxY@$({ zR;EmE&IFSQ&eaCnjEHqX=+{b9ZL=eqMR}QVdhQxUR=-Be>}1L=pK&_>SAtN6;SG*x z7OgaRC-iy{YPozwvyAW$IRnIwkfmXGe97GaA#u|&Lxg^Ji-IKQmrOQUk*^Eh_`Vnr z>RDt@E#~l-)mYWeDGbb*rACc-jDK<}i0WT0{)I&s^72+w_bM zVfKu8Q4X(<4+Tfc!Sl2zvxyy^MnA6Z<+}X4+mp)Ceq34oZrV_JA3i%AW<RrpaXFwWp3+y_K*GC*CI&#?js$LZnvOZ>K5AsB!5aS|}rPivhF< z_#x)M_k>+LcJ0Ye1_(p0ZCLHCA;f8EF0B;jC?|vy!rMZK)gMl~MRSBWEv>JWe*d`i z`*RC~!y5nZJzbX;3I_M!Ay0D>oEyNDO{^E3mgdq*agK6AI3dI^j%l~(uDv~kI4y0e zJ=JNU;N%BW?a8VQr`i)z=P_jyOLAifaa!7x)t?Yf2*)4ggm6MQAsl~{6T%A!{|9gwg?nhqpP!#xTwG^oXQid3hK7c6a&ncGm11IIEiEn7)YKLh7U1CE$H&LMzP?ROO%D$b ztE;O*LP9DkDmXYe9v&Xc%gcRzedF%#wg3PMJxN4CRCt{2T?==kDi8+61WawV&12JU zbJJ|Q+im~<54?by=m^FK;-qoT-g8eff_z~Z=9>{qXh9f_NLeEJ-8E~akO|@Vqnr>< z2q%Q&k8(mdA-pYwl!oBy@ana!#!5Mmg|v|Du7-X21nmgd|_(Z!Z7 z<5Habpcl6&QYk#ca>)aLr zXPclX66gaz87dYl?bBD$MAJE(2b<7E&sY%N2bqh>r%-*k%iYcOBjisCQ3;_&d^Z~! zgD-?n8LFb0&b7PYs1SDaT7v%y%%a>4Q-&IK>$$hq5khIA<@qbQn|t-UVnNu^9&9ss zb~kz)tbNbDwNN@mfZEB-&2yN}qjWc8ktryhqWGs^ZbNu>H}RQET~X~x(IrmrjWY*6 z|Kq+OxvV|O1t95?&B>Gi7NeU6J!7do<#g$ifas<{&segFDP1xy!8!a;h;AD6j3t{G zEi_%K8tHNk@TTr&!<+VuC7TH8Qf`q?g!~N7MDaY64+^5sK)f$PYH%|0^D>sYXK1V^ zGIccwg`!K#~@Sv5LW4ulBYORlXz9u zXm=C%B~uV#8@gn7GPzK))?XRjjW?>%1z~E1y9z+OQM}|nkeKs*xDaA{bnbaEZbddJb zn7jG!oGR`O$?>@-meA^2tN>K=dUr@0Y2pP;wHVzryRWC(Q>2t}#ZXNUv!|m@sp3lI z<`msDxv!6GVye|YV!|)Y;%Nqj{_nY>Ek-v@?%P7!AECq;vuYI>V0bv>6D0qifDFMr z7_c(s;7NA*FR5`idhmakD2>BXnPSe#loNzhaVe|2NxkSnuwuTP*iyw&yZib=SnF<- z8-1XH7TeZTrd*RLM+lXv+T2Y#gOW4C)l#iY4MC>dAWWyx;%?*`oRKWVD~YW##Rf&# z!)w{e)J`bA$Y9%=%9K+wt2s1nrXQ@oNBvYaDckbE9)H0D;b<_*C9iB`<=}B9xWGYJ) zSKAIwrl9oNh-}eK>TVVhgKd3~DJZ>ydMlYq-3_nQZHNalxrP5e`eH20&;dQ>V(5sqJuN$~{7A zAydW5RJHAJWD1I-6i{7d$}^c72w`ny$}gFM;)YyfB2!QtrPC=MG6ls=Ndyy_8up!g zqm!v{=y&doOr{*vp3sviI_x`!P>`vi5spu$27lL=Q!Ihl=TCFQGwP9+V;zQ_= zOg*)^uSbN=$W$=jqeMuZk*Oef?~QP{(>X54lw66T{iysPRNF>>G9@MMi*U&EN0Llc zUoFv+Oa+Jb_x1Jnp7c0+k|`;xcK3Bzd%76oyC73Rux))`pMUR3ez#CBT53b4N>8cE zc7G2n!u$80Wbne`mSl?l0~dY@c@bQA9&1mv+K_6G`or*Ot%XcU`2g3hiG))9?MBEZ z8nrsQVP=nNAydVbsD7CbeR{0beLW+j%*cV?>4R=_Rvna~J9-J)L06q&N0nJTx1TjEwQ^m9m6?#AM7 zs$}XFT=pc?GgaIVUcePQ($kmvKzJfk6-!m_ zCYH{qfB_^IgFDi5KlHK=sX9YzB~wIh8|P-PLD}&>#EFBLW_U8iB4Hp?`W4H6p7+Jq z4_osI7&AmSfrrJ}zc2Rp~>f`xyMnpn(;JEunV5&V` zB2&y9-Q@DJS?%LJyduU^d%B>_q8qcE4%EJFK*B9Jpv)$ksRpY%#83cRp2QWxY@$({ zR;EmE&IFSQ&eaCnjEHqX=+{b9ZL=eqMR}QVdhQxUR=-Be>}1L=pK&_>SAtN6;SG*x z7OgaRC-iy{YPozwvyAW$IRnIwkfmXGe97GaA#u|&Lxg^Ji-IKQmrOQUk*^Eh_`Vnr z>RDt@E#~l-)mYWeDGbb*rACc-jDK<}i0WT0{)I&s^72+w_bM zVfKu8Q4X(<4+Tfc!Sl2zvxyy^MnA6Z<+}X4+mp)Ceq34oZrV_JA3i%AW<RrpaXFwWp3+y_K*GC*CI&#?js$LZnvOZ>K5AsB!5aS|}rPivhF< z_#x)M_k>+LcJ0Ye1_(p0ZCLHCA;f8EF0B;jC?|vy!rMZK)gMl~MRSBWEv>JWe*d`i z`*RC~!y5nZJzbX;3I_M!Ay0D>oEyNDO{^E3mgdq*agK6AI3dI^j%l~(uDv~kI4y0e zJ=JNU;N%BW?a8VQr`i)z=P_jyOLAifaa!7x)t?Yf2*)4ggm6MQAsl~{6T%A!{|9 BinaryEnsemblePlugin | None: + return self._ensemble_segmenter_gui + + def _enable_gui(self): + self._main_window = self._main_window_plugin.main_window + + self._main_window.add_menu_action( + AlgorithmsMenu, + self.tr('BD'), + self._re + ) + def _re(self): + self.window = TableWidgetExample() + self.window.setWindowModality(Qt.ApplicationModal) + self.window.show() + + def _disable(self): + self._ensemble_segmenter_gui = None + self._main_window = None \ No newline at end of file diff --git a/src/bsmu/macula/plugins/SQLiteTableViewer.py b/src/bsmu/macula/plugins/SQLiteTableViewer.py new file mode 100644 index 0000000..dfc4b57 --- /dev/null +++ b/src/bsmu/macula/plugins/SQLiteTableViewer.py @@ -0,0 +1,473 @@ +from functools import partial + +from PySide6.QtGui import QIcon, Qt, QPixmap +from PySide6.QtSql import QSqlQueryModel, QSqlDatabase, QSqlQuery +from PySide6.QtWidgets import ( + QWidget, QTableWidget, QPushButton, QVBoxLayout, QHBoxLayout, QFrame, QLineEdit, QLabel, QScrollArea, QGridLayout, + QTableView, + QStyledItemDelegate, QMessageBox, QGroupBox, QFormLayout, QTabWidget, QDialog +) + +from bsmu.macula.plugins.add_patient_dialog import AddRecordDialog +from bsmu.macula.plugins.edit_pacirnt_delegate import EditPacientDelegate + + +class TableWidgetExample(QWidget): + + def __init__(self): + super().__init__() + self.init_db() + self.createMainTables() + self.setWindowTitle("Пациенты") + self.resize(1200, 800) + self.appointments_table_created = False + self.appointment_data_table_created = False + + def init_db(self): + db = QSqlDatabase.addDatabase("QSQLITE") + db.setDatabaseName("database.db") + if not db.open(): + print("Ошибка подключения к базе данных") + return None + self.db = db + + def patient_clicked(self, index): + row = index.row() + user_id = self.patients_model.index(row, 0).data() + self.load_appointments_from_db(user_id) + + def apointment_clicked(self, index): + row = index.row() + apointment_id = self.appointments_model.index(row, 0).data() + self.load_appointment_data_from_db(apointment_id) + + self.init_ui() + # self.main_lay.addLayout(self.init_ui()) + + def createMainTables(self): + self.setWindowTitle("Пациенты") + self.resize(1200, 800) + + self.main_lay = QHBoxLayout(self) + left_layout = QVBoxLayout(self) + + db = QSqlDatabase.addDatabase("QSQLITE") + db.setDatabaseName("database.db") + if not db.open(): + print("Ошибка подключения к базе данных") + return None + + self.patients_model = QSqlQueryModel() + self.patients_model.setQuery("SELECT id, name, sex, year_of_birthday FROM pacients") + + dict_patient = { + # "id": "Ид", + "name": "Имя", + "sex": "Пол", + "year_of_birthday": "Год рождения", + "acts": "Действия" + } + + # for i in range(0, self.patients_model.columnCount()): + # self.patients_model.setHeaderData(i, Qt.Orientation.Horizontal, dict_patient[self.patients_model.headerData(i, Orientation.Horizontal )]) + + self.patients_table = QTableView(self) + self.patients_table.setModel(self.patients_model) + + self.patients_model.insertColumn(self.patients_model.columnCount()) + + self.patients_model.setHeaderData(0, Qt.Orientation.Horizontal, "Ид") + self.patients_model.setHeaderData(1, Qt.Orientation.Horizontal, "Имя") + self.patients_model.setHeaderData(2, Qt.Orientation.Horizontal, "Пол") + self.patients_model.setHeaderData(3, Qt.Orientation.Horizontal, "Год рождения") + self.patients_model.setHeaderData(4, Qt.Orientation.Horizontal, "Редактировать") + + self.patients_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) + self.patients_table.clicked.connect(self.patient_clicked) + + self.appointment_edit_delegate = EditPacientDelegate(r"edit.png", self.patients_table, self.open_dialog) + self.patients_table.setItemDelegateForColumn(self.patients_model.columnCount() - 1, self.appointment_edit_delegate) + + + self.patients_table.setColumnHidden(0, True) + + add_patient = QPushButton("Добавить пациента") + add_patient.clicked.connect(partial(self.open_dialog, 0)) + button_layout = QHBoxLayout() + button_layout.addWidget(add_patient) + + self.appointments_table = QTableView(self) + add_appointment_button = QPushButton("Добавить результаты приема") + button_layout2 = QHBoxLayout() + button_layout2.addWidget(add_appointment_button) + + left_layout.addWidget(self.patients_table) + left_layout.addLayout(button_layout) + left_layout.addWidget(self.appointments_table) + left_layout.addLayout(button_layout2) + self.main_lay.addLayout(left_layout) + layout = QVBoxLayout() + self.tab_widget = QTabWidget() + layout.addWidget(self.tab_widget) + self.main_lay.addLayout(layout) + # layout2 = QVBoxLayout() + # + # # QLabel для превью + # self.preview_label = QLabel("Нажмите на изображение для увеличения") + # pixmap = QPixmap(rf"C:\Users\Evgeniy\OneDrive\Pictures\32022.jpg") # Подставьте путь к своему изображению + # self.preview_label.setPixmap(pixmap.scaled(200, 150)) # Масштабируем превью + # layout2.addWidget(self.preview_label) + # + # # Добавляем обработчик клика + # self.preview_label.mousePressEvent = self.open_full_image + # self.main_lay.addLayout(layout2) + # main_lay.addLayout(self.addlay()) + + def open_full_image(self, event): + """Открывает изображение в отдельном окне.""" + self.dialog = PreviewWindow(rf"C:\Users\Evgeniy\OneDrive\Pictures\32022.jpg") # Указываем путь к изображению + self.dialog.exec() + + def init_ui(self): + if (self.tab_widget.count() > 0) : + self.tab_widget.removeTab(0) + self.tab_widget.removeTab(0) + self.tab_widget.addTab(self.create_scrollable_area("Правый глаз", 0), "Правый глаз") + self.tab_widget.addTab(self.create_scrollable_area("Левый глаз", 1), "Левый глаз") + + + def create_scrollable_area(self, layout_name, index_eye): + """Создаёт область прокрутки с блоками""" + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + + # Виджет-контейнер для полей + scroll_content = QWidget() + scroll_layout = QVBoxLayout() + + # Добавляем блоки в прокручиваемую область + scroll_layout.addWidget(self.create_block("Обследование", [ + ("Дата посещения", self.get_app_data_in(index_eye, 3)), + ("Продолжительность заболевания", self.get_app_data_in(index_eye, 4)), + ("Тип томографа ","Топкон"), ("Критерий AREDS", self.get_app_data_in(index_eye, 7)), + ("Рефракция", self.get_app_data_in(index_eye, 8)), ("Тип неоваскуляризации", self.get_app_data_in(index_eye, 9)) + ])) + scroll_layout.addWidget(self.create_block("Ретинальные показатели", [ + ("Толщина хориоидеи в центре", self.get_app_data_in(index_eye, 10)), ("Толщина сетчатки в фовеоле", self.get_app_data_in(index_eye, 11)), + ("Общий объем", self.get_app_data_in(index_eye, 14)), + ("Средний объем", self.get_app_data_in(index_eye, 15)) + ])) + scroll_layout.addWidget(self.create_block("", [ + ("Состояние РПЭ", self.get_app_data_in(index_eye, 16)), ("Локализация дефектов РПЭ", self.get_app_data_in(index_eye, 17)), + ("Локализация кистозного макулярного отека", self.get_app_data_in(index_eye, 18)) + ])) + scroll_layout.addWidget(QLabel("Отслойки РПЭ")) + scroll_layout.addWidget(self.create_block("Серозная ОПЭ", [ + ("Локализация", self.get_app_data_in(index_eye, 19)), + ("Ширина", self.get_app_data_in(index_eye, 20)), + ("Высота", self.get_app_data_in(index_eye, 21)), + ("Площадь", self.get_app_data_in(index_eye, 22)) + ])) + scroll_layout.addWidget(self.create_block("Геморрагическая ОПЭ", [ + ("Локализация", self.get_app_data_in(index_eye, 23)), ("Ширина", self.get_app_data_in(index_eye, 24)), + ("Высота", self.get_app_data_in(index_eye, 25)), ("Площадь", self.get_app_data_in(index_eye, 26)) + ])) + scroll_layout.addWidget(self.create_block("Фиброваскулярная ОПЭ", [ + ("Локализация", self.get_app_data_in(index_eye, 27)), ("Ширина", self.get_app_data_in(index_eye, 28)), + ("Высота", self.get_app_data_in(index_eye, 29)), ("Площадь", self.get_app_data_in(index_eye, 30)) + ])) + scroll_layout.addWidget(self.create_block("Друзеноидная ОПЭ", [ + ("Локализация", self.get_app_data_in(index_eye, 31)), ("Ширина", self.get_app_data_in(index_eye, 32)), + ("Высота", self.get_app_data_in(index_eye, 33)), ("Площадь", self.get_app_data_in(index_eye, 34)) + ])) + scroll_layout.addWidget(self.create_block("Друзы", [ + ("Локализация", self.get_app_data_in(index_eye, 35)), + ("Ширина", self.get_app_data_in(index_eye, 36)), ("Высота", self.get_app_data_in(index_eye, 37)), + ("Площадь", self.get_app_data_in(index_eye, 38)) + ])) + scroll_layout.addWidget(self.create_block("Жидкость под РПЭ", [ + ("Пощадь", self.get_app_data_in(index_eye, 39)), ("Локализация", self.get_app_data_in(index_eye, 40)) + ])) + scroll_layout.addWidget(self.create_block("Эллипсоидная зона", [ + ("Состояние", self.get_app_data_in(index_eye, 41)), ("Локализация дефектов", self.get_app_data_in(index_eye, 42)) + ])) + scroll_layout.addWidget(self.create_block("Миоидная зона", [ + ("Состояние", self.get_app_data_in(index_eye, 43)), + ("Локализация дефектов", self.get_app_data_in(index_eye, 44)) + ])) + scroll_layout.addWidget(self.create_block("Отслойка нейросенсорной сетчатки", [ + ("Локализация", self.get_app_data_in(index_eye, 45)), ("Ширина", self.get_app_data_in(index_eye, 46)), + ("Высота", self.get_app_data_in(index_eye, 47)), ("Площадь", self.get_app_data_in(index_eye, 48)) + ])) + scroll_layout.addWidget(self.create_block("Гиперрефлективный материал", [ + ("Локализация", self.get_app_data_in(index_eye, 49)), + ("Площадь", self.get_app_data_in(index_eye, 50)) + ])) + + # Устанавливаем макет для контейнера + scroll_content.setLayout(scroll_layout) + scroll_area.setWidget(scroll_content) + + return scroll_area + + def get_app_data_in(self, x, y): + return str(self.appointment_data_model.index(x, y).data()) + def create_block(self, title, fields): + """Создание тематического блока с полями и значениями""" + group_box = QGroupBox(title) + form_layout = QFormLayout() + + # Добавляем поля с заранее заданными значениями + for field, value in fields: + input_field = QLineEdit() + input_field.setText(value) # Подстановка значения + form_layout.addRow(QLabel(field + ":"), input_field) + + group_box.setLayout(form_layout) + return group_box + + def open_dialog(self, patient_id): + self.dialog = AddRecordDialog(self.db, patient_id) + self.dialog.exec_() # Запуск модального окна + + def open_dialog_appointmernt(self, patient_id): + self.dialog = AddRecordDialog(self.db, patient_id) + self.dialog.exec_() # Запуск модального окна + + def load_appointments_from_db(self, pacientId): + db = self.open_connection() + if db is None: + return # Завершаем, если подключение не удалось + + self.appointments_model = QSqlQueryModel() + query = QSqlQuery() + query.prepare("SELECT * FROM appointments WHERE pacient_id = :pacient_id") + query.bindValue(":pacient_id", pacientId) + + if query.exec(): + self.appointments_model.setQuery(query) + else: + print("Ошибка выполнения запроса:", query.lastError().text()) + + table1Description = ["Ид", "Дата приема", "duration_of_the_disease", "Действия"] + + + # for col, field in enumerate(table1Description.keys()): + # self.patients_model.setHeaderData(col, Qt.Orientation.Horizontal, table1Description[field]) + + self.appointments_table.setModel(self.appointments_model) + + self.appointments_model.insertColumn(self.appointments_model.columnCount()) + + + self.appointments_model.setHeaderData(0, Qt.Orientation.Horizontal, "Ид") + self.appointments_model.setHeaderData(1, Qt.Orientation.Horizontal, "Дата приема") + self.appointments_model.setHeaderData(2, Qt.Orientation.Horizontal, "Длит. болезни") + # self.appointments_model.setHeaderData(3, Qt.Orientation.Horizontal, "Ид пациента") + self.appointments_model.setHeaderData(3, Qt.Orientation.Horizontal, "Редактировать") + self.appointments_model.setHeaderData(4, Qt.Orientation.Horizontal, "Показать") + + self.appointments_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) + self.appointments_table.clicked.connect(self.apointment_clicked) + + self.appointment_edit_delegate2 = EditPacientDelegate(r"edit.png", self.patients_table, self.open_dialog_appointmernt) + self.appointments_table.setItemDelegateForColumn(self.patients_model.columnCount() - 2, self.appointment_edit_delegate2) + + self.appointment_edit_delegate3 = EditPacientDelegate(r"2.png", self.patients_table, self.open_dialog_appointmernt) + self.appointments_table.setItemDelegateForColumn(self.patients_model.columnCount() - 1, self.appointment_edit_delegate3) + + self.appointments_table.setColumnHidden(0, True) + + def load_appointment_data_from_db(self, appiontmentId): + self.appointment_data_model = QSqlQueryModel() + query = QSqlQuery() + query.prepare("SELECT * FROM eyes WHERE appointment_id = :appiontmentId") + query.bindValue(":appiontmentId", appiontmentId) + + if query.exec(): + self.appointment_data_model.setQuery(query) + else: + print("Ошибка выполнения запроса:", query.lastError().text()) + + print(self.appointment_data_model.index(0, 0).data()) + + def addlay(self): + fields = [ + "id", "eye", "appointment_id", "date", "duration_of_the_disease", + "topkon", "optopol", "areds", "refraction", "type_of_neovascularization", + "choroidal_thickness_center", "cts_foveola", "cts_sup_inner_fovea", + "cts_sup_out_fovea", "total_volume", "average_volume", "rpe_status", + "rpe_localisation", "cme_localisation", "serouz_rpe_detachment_localisation", + "serouz_rpe_detachment_width", "serouz_rpe_detachment_height", "serouz_rpe_detachment_area", + "hemorrhagic_rpe_detachment_localisation", "hemorrhagic_rpe_detachment_width", + "hemorrhagic_rpe_detachment_heidgt", "hemorrhagic_rpe_detachment_area", + "fibrovascular_rpe_detachment_localisation", "fibrovascular_rpe_detachment_width", + "fibrovascular_rpe_detachment_heidgt", "fibrovascular_rpe_detachment_area", + "drusenoid_detachment_rpe_localisation", "drusenoid_detachment_rpe_width", + "drusenoid_detachment_rpe_height", "drusenoid_detachment_rpe_area", + "druses_localisation", "druses_weigt", "druses_heigt", "dzuses_area", + "fluid_under_rpe_area", "fluid_under_rpe_localisation", "ez_status", + "ez_localisation", "myoidnz_status", "myoidnz_localisation", + "rne_detachment_localisation", "rne_detachment_width", "rne_detachment_heigt", + "rne_detachment_area", "hyperreflective_material_localisation", "hyperreflective_material_area" + ] + + layout = QVBoxLayout() + + # Прокрутка для длинных форм + scroll_area = QScrollArea() + scroll_widget = QWidget() + grid_layout = QGridLayout() + + # Динамическое добавление всех полей + row = 0 + input_fields = {} + for field in fields: + # Добавляем метку для каждого поля + label = QLabel(field.replace('_', ' ').capitalize()) + grid_layout.addWidget(label, row, 0) # Первый столбец + + # Добавляем редактируемое поле + input_field = QLineEdit() + input_fields[field] = input_field + grid_layout.addWidget(input_field, row, 1) # Второй столбец + + row += 1 + + # Добавляем разделительную линию + separator = QFrame() + separator.setFrameShape(QFrame.Shape.HLine) + separator.setFrameShadow(QFrame.Shadow.Sunken) + grid_layout.addWidget(separator, row, 0, 1, 2) + + row += 1 + + # Кнопка сохранения + save_button = QPushButton("Сохранить") + grid_layout.addWidget(save_button, row, 0, 1, 2) # На всю ширину + + scroll_widget.setLayout(grid_layout) + scroll_area.setWidget(scroll_widget) + scroll_area.setWidgetResizable(True) + + layout.addWidget(scroll_area) + return layout + + + def open_connection(self): + db = QSqlDatabase.addDatabase("QSQLITE") # Указываем тип базы данных (SQLite в данном случае) + db.setDatabaseName("database.db") # Указываем имя или путь к базе данных + + if not db.open(): # Проверяем успешность открытия + print("Ошибка подключения к базе данных!") + return None + else: + print("Соединение с базой данных установлено.") + return db + + +class ButtonDelegate(QStyledItemDelegate): + def paint(self, painter, option, index): + """Рисуем кнопку в ячейке""" + icon_path = r"C:\Users\Evgeniy\Projects\macula\vision-macula\src\bsmu\macula\app\images\icons\edit.png" + icon = QIcon(icon_path) # 🔹 Здесь нужна иконка (например, карандаш) + icon.paint(painter, option.rect) + + def editorEvent(self, event, model, option, index): + """Обрабатываем нажатие на кнопку""" + if event.type() == event.Type.MouseButtonPress: + QMessageBox.information(option.widget, "Редактирование", f"Редактируем строку {index.row()}") + return True + + # def init_ui(self): + # # Основной виджет + # layout = QVBoxLayout() + # + # # Добавляем блоки с прокруткой + # layout.addWidget(self.create_scrollable_area()) + # + # # Устанавливаем главный макет + # return layout + # + # def create_scrollable_area(self): + # """Создаёт область прокрутки с блоками""" + # scroll_area = QScrollArea() + # scroll_area.setWidgetResizable(True) + # + # # Виджет-контейнер для полей + # scroll_content = QWidget() + # scroll_layout = QVBoxLayout() + # + # # Добавляем блоки в прокручиваемую область + # scroll_layout.addWidget(self.create_block("Ид и основное", [ + # "Ид", "Глаз", "Ид посещения", "Дата посещения", "Продолжительность заболевания" + # ])) + # scroll_layout.addWidget(self.create_block("Обследования", [ + # "топкон", "оптопол", "критерий AREDS", "рефракция", "тип неоваскуляризации" + # ])) + # scroll_layout.addWidget(self.create_block("Хороидальная толщина", [ + # "толщина хориоидеи в центре", "толщина ЦТС", "толщина ЦТС верхнего внутреннего слоя ямки", + # "толщина ЦТС верхнего наружного слоя ямки", "общий объем", "средний объем" + # ])) + # scroll_layout.addWidget(self.create_block("Состояние ЭПС", [ + # "состояние ЭПС", "локализация ЭПС", "локализация ЦМЭ", + # "локализация серозного отслоения ЭПС", "ширина серозного отслоения ЭПС", + # "высота серозного отслоения ЭПС", "площадь серозного отслоения ЭПС" + # ])) + # scroll_layout.addWidget(self.create_block("Геморрагическое ЭПС", [ + # "локализация геморрагического отслоения ЭПС", "ширина геморрагического отслоения ЭПС", + # "высота геморрагического отслоения ЭПС", "площадь геморрагического отслоения ЭПС" + # ])) + # scroll_layout.addWidget(self.create_block("Фиброваскулярное ЭПС", [ + # "локализация фиброваскулярного отслоения ЭПС", "ширина фиброваскулярного отслоения ЭПС", + # "высота фиброваскулярного отслоения ЭПС", "площадь фиброваскулярного отслоения ЭПС" + # ])) + # scroll_layout.addWidget(self.create_block("Друзы и ЭПС", [ + # "локализация друзеноидного отслоения ЭПС", "ширина друзеноидного отслоения ЭПС", + # "высота друзеноидного отслоения ЭПС", "площадь друзеноидного отслоения ЭПС", + # "локализация друз", "вес друз", "высота друз", "площадь друз" + # ])) + # scroll_layout.addWidget(self.create_block("Жидкость", [ + # "площадь жидкости под ЭПС", "локализация жидкости под ЭПС" + # ])) + # scroll_layout.addWidget(self.create_block("зоны Эллера и прочее", [ + # "состояние EZ", "локализация EZ", "состояние миоидной зоны", "локализация миоидной зоны" + # ])) + # scroll_layout.addWidget(self.create_block("сетчатки и гиперрефлективный материал", [ + # "локализация отслоения РНЭ", "ширина отслоения РНЭ", "высота отслоения РНЭ", + # "площадь отслоения РНЭ", "локализация гиперотражающего материала", + # "площадь гиперотражающего материала" + # ])) + # + # # Устанавливаем макет для виджета + # scroll_content.setLayout(scroll_layout) + # scroll_area.setWidget(scroll_content) + # + # return scroll_area + # + # def create_block(self, title, fields): + # """Создание тематического блока с полями""" + # group_box = QGroupBox(title) + # form_layout = QFormLayout() + # + # # Добавляем поля + # for field in fields: + # form_layout.addRow(QLabel(field + ":"), QLineEdit()) + # + # group_box.setLayout(form_layout) + # return group_box +class PreviewWindow(QDialog): + """Окно для отображения полного изображения.""" + def __init__(self, image_path): + super().__init__() + self.setWindowTitle("Полное изображение") + self.resize(800, 600) + + layout = QVBoxLayout(self) + + # QLabel для изображения + full_image_label = QLabel(self) + pixmap = QPixmap(image_path) + full_image_label.setPixmap(pixmap) + full_image_label.setScaledContents(True) # Масштабируем изображение + layout.addWidget(full_image_label) \ No newline at end of file diff --git a/src/bsmu/macula/plugins/add_appointment_dialog.py b/src/bsmu/macula/plugins/add_appointment_dialog.py new file mode 100644 index 0000000..dedba79 --- /dev/null +++ b/src/bsmu/macula/plugins/add_appointment_dialog.py @@ -0,0 +1,72 @@ +from functools import partial + +from PySide6.QtSql import QSqlQuery +from PySide6.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QLabel + +from bsmu.macula.plugins.database_manager import DatabaseManager + + +class AddApponitmentDialog(QDialog): + def __init__(self, appotintment_id): + super().__init__() + self.db_manager = DatabaseManager() + self.is_new_appointment = appotintment_id == 0 + self.setWindowTitle("Добавить прием" if self.is_new_appointment else "Изменить данные приема") + + self.name_input = QLineEdit() + self.sex_input = QLineEdit() + self.age_input = QLineEdit() + self.save_button = QPushButton("Сохранить" if self.is_new_appointment else "Редактировать") + if (self.is_new_appointment): + self.save_button.clicked.connect(self.save_data) + else: + self.save_button.clicked.connect(partial(self.edit_data, appotintment_id)) + + layout = QVBoxLayout() + layout.addWidget(QLabel("Имя:")) + layout.addWidget(self.name_input) + layout.addWidget(QLabel("Пол:")) + layout.addWidget(self.sex_input) + layout.addWidget(QLabel("Год рождения:")) + layout.addWidget(self.age_input) + layout.addWidget(self.save_button) + + if (not self.is_new_appointment): + record = self.db_manager.fetch_record_by_id("pacients", appotintment_id) + # query = QSqlQuery(self.db) + # query.prepare("SELECT id, name, sex, year_of_birthday FROM pacients WHERE id = :pacient_id") + # query.bindValue(":pacient_id", patient_id) + # query.exec_() + # self.appointments_model = QSqlQueryModel() + # self.appointments_model.setQuery(query) + self.name_input.setText(record[0][1]) + self.sex_input.setText(record[0][2]) + self.age_input.setText(str(record[0][3])) + self.setLayout(layout) + + def save_data(self): + name = self.name_input.text() + sex = self.sex_input.text() + age = self.age_input.text() + + if name and age.isdigit(): + query = QSqlQuery(self.db) + query.prepare("INSERT INTO pacients (name, sex, year_of_birthday) VALUES (?, ?, ?)") + query.addBindValue(name) + query.addBindValue(sex) + query.addBindValue(int(age)) + query.exec_() + self.close() + else: + print("Ошибка: введите корректные данные") + + def edit_data(self, patient_id): + name = self.name_input.text() + sex = self.sex_input.text() + age = self.age_input.text() + + if name and sex and age.isdigit(): + self.db_manager.update_pacient(name, sex, int(age), patient_id) + self.close() + else: + print("Ошибка: введите корректные данные") \ No newline at end of file diff --git a/src/bsmu/macula/plugins/add_patient_dialog.py b/src/bsmu/macula/plugins/add_patient_dialog.py new file mode 100644 index 0000000..c3657a0 --- /dev/null +++ b/src/bsmu/macula/plugins/add_patient_dialog.py @@ -0,0 +1,80 @@ +from functools import partial + +from PySide6.QtSql import QSqlQuery +from PySide6.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QLabel + +from bsmu.macula.plugins.database_manager import DatabaseManager + + +class AddRecordDialog(QDialog): + def __init__(self, db, patient_id): + super().__init__() + self.db = db + self.db_manager = DatabaseManager() + self.is_new_user = patient_id == 0 + self.setWindowTitle("Добавить пациента" if self.is_new_user else "Изменить данные пациента") + + self.name_input = QLineEdit() + self.sex_input = QLineEdit() + self.age_input = QLineEdit() + self.save_button = QPushButton("Сохранить" if self.is_new_user else "Редактировать") + if (self.is_new_user): + self.save_button.clicked.connect(self.save_data) + else: + self.save_button.clicked.connect(partial(self.edit_data, patient_id)) + + layout = QVBoxLayout() + layout.addWidget(QLabel("Имя:")) + layout.addWidget(self.name_input) + layout.addWidget(QLabel("Пол:")) + layout.addWidget(self.sex_input) + layout.addWidget(QLabel("Год рождения:")) + layout.addWidget(self.age_input) + layout.addWidget(self.save_button) + + if (not self.is_new_user): + record = self.db_manager.fetch_record_by_id("pacients", patient_id) + # query = QSqlQuery(self.db) + # query.prepare("SELECT id, name, sex, year_of_birthday FROM pacients WHERE id = :pacient_id") + # query.bindValue(":pacient_id", patient_id) + # query.exec_() + # self.appointments_model = QSqlQueryModel() + # self.appointments_model.setQuery(query) + self.name_input.setText(record[0][1]) + self.sex_input.setText(record[0][2]) + self.age_input.setText(str(record[0][3])) + self.setLayout(layout) + + def save_data(self): + name = self.name_input.text() + sex = self.sex_input.text() + age = self.age_input.text() + + if name and age.isdigit(): + query = QSqlQuery(self.db) + query.prepare("INSERT INTO pacients (name, sex, year_of_birthday) VALUES (?, ?, ?)") + query.addBindValue(name) + query.addBindValue(sex) + query.addBindValue(int(age)) + query.exec_() + self.close() + else: + print("Ошибка: введите корректные данные") + + def edit_data(self, patient_id): + name = self.name_input.text() + sex = self.sex_input.text() + age = self.age_input.text() + + if name and sex and age.isdigit(): + self.db_manager.update_pacient(name, sex, int(age), patient_id) + # query = QSqlQuery(self.db) + # query.prepare("UPDATE pacients SET name = ? WHERE id = ?") + # query.addBindValue(name) + # query.addBindValue(sex) + # query.addBindValue(int(age)) + # query.addBindValue(patient_id) + # bol = query.exec_() + self.close() + else: + print("Ошибка: введите корректные данные") \ No newline at end of file diff --git a/src/bsmu/macula/plugins/database_manager.py b/src/bsmu/macula/plugins/database_manager.py new file mode 100644 index 0000000..196b730 --- /dev/null +++ b/src/bsmu/macula/plugins/database_manager.py @@ -0,0 +1,415 @@ +import sqlite3 + +from PySide6.QtSql import QSqlQuery, QSqlDatabase +from bsmu.vision.core.plugins import Plugin + + +class DatabaseManager(Plugin): + CREATE_PATIENTS = '''CREATE TABLE IF NOT EXISTS pacients ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(100) NOT NULL, + sex VARCHAR(1) NOT NULL, + year_of_birthday INTEGER NOT NULL +)''' + + CREATE_APPOINTMENTS = '''CREATE TABLE IF NOT EXISTS appointments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date DATE NOT NULL, + duration_of_the_disease INTEGER NOT NULL, + pacient_id INTEGER NOT NULL, + FOREIGN KEY (pacient_id) REFERENCES pacients (id) +)''' + + CREATE_EYES = '''CREATE TABLE IF NOT EXISTS eyes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + eye VARCHAR(2) NOT NULL, + appointment_id INTEGER NOT NULL, + date DATE NOT NULL, + duration_of_the_disease INTEGER NOT NULL, + topkon BOOLEAN NOT NULL, + optopol BOOLEAN NOT NULL, + areds VARCHAR(10) NOT NULL, + refraction VARCHAR(10) NOT NULL, + type_of_neovascularization VARCHAR(10) NOT NULL, + "МКОЗ" NUMERIC NOT NULL, + choroidal_thickness_center NUMERIC NOT NULL, + cts_foveola NUMERIC NOT NULL, + cts_sup_inner_fovea NUMERIC NOT NULL, + cts_sup_out_fovea NUMERIC NOT NULL, + total_volume NUMERIC NOT NULL, + average_volume NUMERIC NOT NULL, + rpe_status NUMERIC NOT NULL, + rpe_localisation NUMERIC NOT NULL, + cme_localisation NUMERIC NOT NULL, + serouz_rpe_detachment_localisation NUMERIC NOT NULL, + serouz_rpe_detachment_width NUMERIC NOT NULL, + serouz_rpe_detachment_height NUMERIC NOT NULL, + serouz_rpe_detachment_area NUMERIC NOT NULL, + hemorrhagic_rpe_detachment_localisation NUMERIC NOT NULL, + hemorrhagic_rpe_detachment_width NUMERIC NOT NULL, + hemorrhagic_rpe_detachment_heidgt NUMERIC NOT NULL, + hemorrhagic_rpe_detachment_area NUMERIC NOT NULL, + fibrovascular_rpe_detachment_localisation NUMERIC NOT NULL, + fibrovascular_rpe_detachment_width NUMERIC NOT NULL, + fibrovascular_rpe_detachment_heidgt NUMERIC NOT NULL, + fibrovascular_rpe_detachment_area NUMERIC NOT NULL, + drusenoid_detachment_rpe_localisation NUMERIC NOT NULL, + drusenoid_detachment_rpe_width NUMERIC NOT NULL, + drusenoid_detachment_rpe_height NUMERIC NOT NULL, + drusenoid_detachment_rpe_area NUMERIC NOT NULL, + druses_localisation NUMERIC NOT NULL, + druses_weigt NUMERIC NOT NULL, + druses_heigt NUMERIC NOT NULL, + dzuses_area NUMERIC NOT NULL, + fluid_under_rpe_area NUMERIC NOT NULL, + fluid_under_rpe_localisation NUMERIC NOT NULL, + ez_status NUMERIC NOT NULL, + ez_localisation NUMERIC NOT NULL, + myoidnz_status NUMERIC NOT NULL, + myoidnz_localisation NUMERIC NOT NULL, + rne_detachment_localisation NUMERIC NOT NULL, + rne_detachment_width NUMERIC NOT NULL, + rne_detachment_heigt NUMERIC NOT NULL, + rne_detachment_area NUMERIC NOT NULL, + hyperreflective_material_localisation NUMERIC NOT NULL, + hyperreflective_material_area NUMERIC NOT NULL, + FOREIGN KEY (appointment_id) REFERENCES appointments (id) +)''' + + CREATE_MEDICINES = '''CREATE TABLE IF NOT EXISTS medicines ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type VARCHAR(5) NOT NULL, + amount INTEGER NOT NULL, + appointment_id INTEGER NOT NULL, + FOREIGN KEY (appointment_id) REFERENCES appointments (id) +)''' + + # Pacients = "INSERT INTO pacients (name, sex, year_of_birthday) VALUES (?, ?, ?)" + Pacients = """INSERT INTO pacients (name, sex, year_of_birthday) VALUES ('Иван Иванов', 'М', 1990) +,('Мария Смирнова', 'Ж', 1995) +,('Алексей Петров', 'М', 1988) +,('Екатерина Фролова', 'Ж', 1992) +,('Дмитрий Сидоров', 'М', 1985) +,('Анна Кузнецова', 'Ж', 1997) +,('Сергей Васильев', 'М', 1982) +,('Ольга Попова', 'Ж', 1990) +,('Николай Орлов', 'М', 1989) +,('Елена Михайлова', 'Ж', 1994);""" + # Appointments = "INSERT INTO appointments (date, duration_of_the_disease, pacient_id) VALUES (?, ?, ?)" + Appointments = """INSERT INTO appointments (date, duration_of_the_disease, pacient_id) VALUES ('2025-04-01', 10, 1) +,('2025-04-15', 5, 1) + +,('2025-03-20', 14, 2) + +,('2025-02-28', 7, 3) +,('2025-04-10', 3, 3) + +,('2025-01-15', 20, 4) + +,('2025-03-05', 11, 5) +,('2025-04-22', 9, 5) + +,('2025-02-18', 15, 6) + +,('2025-03-01', 8, 7) +,('2025-03-25', 6, 7) + +,('2025-04-03', 10, 8) + +,('2025-02-14', 18, 9) +,('2025-03-30', 12, 9) + +,('2025-04-12', 7, 10); + """ + Appointmen_1_1 = """INSERT INTO eyes ( + eye, appointment_id, date, duration_of_the_disease, topkon, optopol, areds, + refraction, type_of_neovascularization, "МКОЗ", choroidal_thickness_center, + cts_foveola, cts_sup_inner_fovea, cts_sup_out_fovea, total_volume, average_volume, + rpe_status, rpe_localisation, cme_localisation, serouz_rpe_detachment_localisation, + serouz_rpe_detachment_width, serouz_rpe_detachment_height, serouz_rpe_detachment_area, + hemorrhagic_rpe_detachment_localisation, hemorrhagic_rpe_detachment_width, + hemorrhagic_rpe_detachment_heidgt, hemorrhagic_rpe_detachment_area, + fibrovascular_rpe_detachment_localisation, fibrovascular_rpe_detachment_width, + fibrovascular_rpe_detachment_heidgt, fibrovascular_rpe_detachment_area, + drusenoid_detachment_rpe_localisation, drusenoid_detachment_rpe_width, + drusenoid_detachment_rpe_height, drusenoid_detachment_rpe_area, + druses_localisation, druses_weigt, druses_heigt, dzuses_area, + fluid_under_rpe_area, fluid_under_rpe_localisation, ez_status, ez_localisation, + myoidnz_status, myoidnz_localisation, rne_detachment_localisation, + rne_detachment_width, rne_detachment_heigt, rne_detachment_area, + hyperreflective_material_localisation, hyperreflective_material_area +) VALUES ( + 'L', 1, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 1, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 2, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 2, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 3, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 3, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 4, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 4, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 5, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 5, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 6, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 6, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 7, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 7, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 8, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 1, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 9, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 9, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 10, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 10, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 11, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 11, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 12, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 12, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 13, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 13, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +),( + 'L', 14, '2025-04-15', 10, FALSE, TRUE, 'AREDS1', + '-2.0', 'Occult', 300, 160, 210, 190, 200, 9.0, 3.5, + 0, 2, 1, 1, 3.0, 2.0, 1.0, + 1, 1.5, 1.0, 0.5, 2, 3.5, 3.0, 1.5, + 1, 0.7, 0.5, 0.3, 3, 4.0, 2.5, 1.0, + 0.8, 2.0, 1, 2, 1, 1, 0, + 3.8, 2.0, 0, 0, 0 +),( + 'R', 14, '2025-04-16', 5, TRUE, FALSE, 'AREDS2', + '-1.5', 'Classic', 250, 150, 200, 180, 190, 8.5, 3.2, + 1, 1, 2, 0, 2.5, 1.5, 0.5, + 0, 1.2, 0.8, 0.3, 1, 2.8, 2.4, 1.0, + 0, 0.5, 0.3, 0.2, 2, 3.1, 1.8, 0.7, + 0.6, 1.5, 1, 2, 1, 1, 0, + 3.2, 1.5, 0, 0, 0 +);""" + + def __init__(self, db_name="database.db"): + super().__init__() + self.db = QSqlDatabase.addDatabase("QSQLITE") + self.db.setDatabaseName(db_name) + self.connection = sqlite3.connect(db_name) + self.cursor = self.connection.cursor() + self.execute_query(self.CREATE_PATIENTS) + self.execute_query(self.CREATE_APPOINTMENTS) + self.execute_query(self.CREATE_EYES) + # self.execute_query(self.Pacients, ) + # self.execute_query(self.Appointments, ) + # self.execute_query(self.Appointmen_1_1) + self.close_connection() + + def start_connection(self, db_name="database.db"): + self.connection = sqlite3.connect(db_name) + self.cursor = self.connection.cursor() + + def execute_query(self, query, params=()): + try: + self.cursor.execute(query, params) + self.connection.commit() + except sqlite3.Error as e: + print(f"Ошибка выполнения запроса: {e}") + + def fetch_results(self, query, params=()): + try: + self.cursor.execute(query, params) + return self.cursor.fetchall() + except sqlite3.Error as e: + print(f"Ошибка получения данных: {e}") + return [] + + def fetch_record_by_id(self, table, record_id): + self.start_connection() + query = "SELECT * FROM pacients WHERE id = :record_id" + params = (record_id) + self.cursor.execute(query, {"record_id": record_id}) + return self.cursor.fetchall() + + def update_pacient(self, name, sex, year_of_birthday, id): + self.start_connection() + query = "UPDATE pacients SET name = :name, sex = :sex, year_of_birthday = :year_of_birthday WHERE id = :id" + self.cursor.execute(query, {"name": name, "sex": sex, "year_of_birthday": year_of_birthday, "id": id,}) + self.connection.commit() + self.close_connection() + + def close_connection(self): + self.connection.close() diff --git a/src/bsmu/macula/plugins/edit_pacirnt_delegate.py b/src/bsmu/macula/plugins/edit_pacirnt_delegate.py new file mode 100644 index 0000000..5309cee --- /dev/null +++ b/src/bsmu/macula/plugins/edit_pacirnt_delegate.py @@ -0,0 +1,20 @@ +from PySide6.QtGui import QIcon +from PySide6.QtWidgets import QStyledItemDelegate, QWidget, QHBoxLayout, QPushButton, QMessageBox + + +class EditPacientDelegate(QStyledItemDelegate): + def __init__(self, icon_path, parent=None, callback=None): + super().__init__(parent) + self.icon_path = icon_path + self.callback = callback # Функция, которая вызывается при нажатии кнопки + + def paint(self, painter, option, index): + """Рисуем кнопку в ячейке""" + icon = QIcon(self.icon_path) # 🔹 Здесь нужна иконка (например, карандаш) + icon.paint(painter, option.rect) + + def editorEvent(self, event, model, option, index): + """Обрабатываем нажатие на кнопку""" + if event.type() == event.Type.MouseButtonPress: + self.callback(model.data(model.index(index.row(), 0))) + return True \ No newline at end of file diff --git a/src/bsmu/macula/plugins/images/edit.png b/src/bsmu/macula/plugins/images/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..6b446145fff20808ac9715652b9dc29b3cbdbd66 GIT binary patch literal 2819 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 Date: Tue, 29 Jul 2025 16:06:50 +0300 Subject: [PATCH 2/2] added eye widget and dataclass --- src/bsmu/macula/app/database.db | Bin 36864 -> 36864 bytes .../bsmu.macula/app.MaculaApp.conf.yaml | 4 +- src/bsmu/macula/plugins/{ => db}/BD.py | 2 +- .../plugins/{ => db}/SQLiteTableViewer.py | 57 +++++--- src/bsmu/macula/plugins/db/__init__.py | 0 .../{ => db}/add_appointment_dialog.py | 2 +- .../plugins/{ => db}/add_patient_dialog.py | 2 +- .../plugins/{ => db}/database_manager.py | 0 .../plugins/{ => db}/edit_pacirnt_delegate.py | 0 src/bsmu/macula/records/eye_info_data.py | 126 ++++++++++++++++++ src/bsmu/macula/version.py | 2 +- src/bsmu/macula/widgets/eye_data_widget.py | 120 +++++++++++++++++ 12 files changed, 291 insertions(+), 24 deletions(-) rename src/bsmu/macula/plugins/{ => db}/BD.py (95%) rename src/bsmu/macula/plugins/{ => db}/SQLiteTableViewer.py (92%) create mode 100644 src/bsmu/macula/plugins/db/__init__.py rename src/bsmu/macula/plugins/{ => db}/add_appointment_dialog.py (97%) rename src/bsmu/macula/plugins/{ => db}/add_patient_dialog.py (97%) rename src/bsmu/macula/plugins/{ => db}/database_manager.py (100%) rename src/bsmu/macula/plugins/{ => db}/edit_pacirnt_delegate.py (100%) create mode 100644 src/bsmu/macula/records/eye_info_data.py create mode 100644 src/bsmu/macula/widgets/eye_data_widget.py diff --git a/src/bsmu/macula/app/database.db b/src/bsmu/macula/app/database.db index f749bb3bc641c80a3d4c49ee9814b3d608114a82..7aaefac57463cf91e91ef6af33959d80349b167d 100644 GIT binary patch delta 58 zcmV-A0LA}+paOuP0+1U46pn+a delta 58 zcmV-A0LA}+paOuP0+1U46OkN40TZ!crw;)Dvj`A~4;mT-1pqG*0??n(u+fIlxX`uG Qz0rao(38-Xv#K9fa4@$Q4*&oF diff --git a/src/bsmu/macula/configs/default/bsmu.macula/app.MaculaApp.conf.yaml b/src/bsmu/macula/configs/default/bsmu.macula/app.MaculaApp.conf.yaml index 96baa3f..d3e9922 100644 --- a/src/bsmu/macula/configs/default/bsmu.macula/app.MaculaApp.conf.yaml +++ b/src/bsmu/macula/configs/default/bsmu.macula/app.MaculaApp.conf.yaml @@ -28,6 +28,6 @@ plugins: - bsmu.vision.plugins.task_storage_view.TaskStorageViewPlugin - - bsmu.macula.plugins.BD.BD + - bsmu.macula.plugins.db.BD.BD - bsmu.macula.plugins.gui.ensemble_segmenter_gui.EnsembleSegmenterGuiPlugin - - bsmu.macula.plugins.database_manager.DatabaseManager + - bsmu.macula.plugins.db.database_manager.DatabaseManager diff --git a/src/bsmu/macula/plugins/BD.py b/src/bsmu/macula/plugins/db/BD.py similarity index 95% rename from src/bsmu/macula/plugins/BD.py rename to src/bsmu/macula/plugins/db/BD.py index 0661a65..cec7873 100644 --- a/src/bsmu/macula/plugins/BD.py +++ b/src/bsmu/macula/plugins/db/BD.py @@ -7,7 +7,7 @@ from bsmu.vision.plugins.doc_interfaces.mdi import MdiPlugin from bsmu.vision.plugins.windows.main import AlgorithmsMenu, MainWindowPlugin, MainWindow -from bsmu.macula.plugins.SQLiteTableViewer import TableWidgetExample +from bsmu.macula.plugins.db.SQLiteTableViewer import TableWidgetExample from bsmu.macula.plugins.ensemble_segmenter import BinaryEnsemblePlugin if TYPE_CHECKING: diff --git a/src/bsmu/macula/plugins/SQLiteTableViewer.py b/src/bsmu/macula/plugins/db/SQLiteTableViewer.py similarity index 92% rename from src/bsmu/macula/plugins/SQLiteTableViewer.py rename to src/bsmu/macula/plugins/db/SQLiteTableViewer.py index dfc4b57..3eed016 100644 --- a/src/bsmu/macula/plugins/SQLiteTableViewer.py +++ b/src/bsmu/macula/plugins/db/SQLiteTableViewer.py @@ -8,8 +8,10 @@ QStyledItemDelegate, QMessageBox, QGroupBox, QFormLayout, QTabWidget, QDialog ) -from bsmu.macula.plugins.add_patient_dialog import AddRecordDialog -from bsmu.macula.plugins.edit_pacirnt_delegate import EditPacientDelegate +from bsmu.macula.plugins.db.add_patient_dialog import AddRecordDialog +from bsmu.macula.plugins.db.edit_pacirnt_delegate import EditPacientDelegate +from bsmu.macula.widgets.eye_data_widget import EyeDataWidget +from bsmu.macula.records.eye_info_data import PatientExamData class TableWidgetExample(QWidget): @@ -134,6 +136,10 @@ def init_ui(self): self.tab_widget.removeTab(0) self.tab_widget.addTab(self.create_scrollable_area("Правый глаз", 0), "Правый глаз") self.tab_widget.addTab(self.create_scrollable_area("Левый глаз", 1), "Левый глаз") + eye_data = PatientExamData.from_query_model(self.appointment_data_model, 1) + eye_widget = EyeDataWidget(eye_data) + self.tab_widget.addTab(eye_widget, "Левый глаз") + def create_scrollable_area(self, layout_name, index_eye): @@ -149,16 +155,20 @@ def create_scrollable_area(self, layout_name, index_eye): scroll_layout.addWidget(self.create_block("Обследование", [ ("Дата посещения", self.get_app_data_in(index_eye, 3)), ("Продолжительность заболевания", self.get_app_data_in(index_eye, 4)), - ("Тип томографа ","Топкон"), ("Критерий AREDS", self.get_app_data_in(index_eye, 7)), - ("Рефракция", self.get_app_data_in(index_eye, 8)), ("Тип неоваскуляризации", self.get_app_data_in(index_eye, 9)) + ("Тип томографа ","Топкон"), + ("Критерий AREDS", self.get_app_data_in(index_eye, 7)), + ("Рефракция", self.get_app_data_in(index_eye, 8)), + ("Тип неоваскуляризации", self.get_app_data_in(index_eye, 9)) ])) scroll_layout.addWidget(self.create_block("Ретинальные показатели", [ - ("Толщина хориоидеи в центре", self.get_app_data_in(index_eye, 10)), ("Толщина сетчатки в фовеоле", self.get_app_data_in(index_eye, 11)), + ("Толщина хориоидеи в центре", self.get_app_data_in(index_eye, 10)), + ("Толщина сетчатки в фовеоле", self.get_app_data_in(index_eye, 11)), ("Общий объем", self.get_app_data_in(index_eye, 14)), ("Средний объем", self.get_app_data_in(index_eye, 15)) ])) scroll_layout.addWidget(self.create_block("", [ - ("Состояние РПЭ", self.get_app_data_in(index_eye, 16)), ("Локализация дефектов РПЭ", self.get_app_data_in(index_eye, 17)), + ("Состояние РПЭ", self.get_app_data_in(index_eye, 16)), + ("Локализация дефектов РПЭ", self.get_app_data_in(index_eye, 17)), ("Локализация кистозного макулярного отека", self.get_app_data_in(index_eye, 18)) ])) scroll_layout.addWidget(QLabel("Отслойки РПЭ")) @@ -169,35 +179,46 @@ def create_scrollable_area(self, layout_name, index_eye): ("Площадь", self.get_app_data_in(index_eye, 22)) ])) scroll_layout.addWidget(self.create_block("Геморрагическая ОПЭ", [ - ("Локализация", self.get_app_data_in(index_eye, 23)), ("Ширина", self.get_app_data_in(index_eye, 24)), - ("Высота", self.get_app_data_in(index_eye, 25)), ("Площадь", self.get_app_data_in(index_eye, 26)) + ("Локализация", self.get_app_data_in(index_eye, 23)), + ("Ширина", self.get_app_data_in(index_eye, 24)), + ("Высота", self.get_app_data_in(index_eye, 25)), + ("Площадь", self.get_app_data_in(index_eye, 26)) ])) scroll_layout.addWidget(self.create_block("Фиброваскулярная ОПЭ", [ - ("Локализация", self.get_app_data_in(index_eye, 27)), ("Ширина", self.get_app_data_in(index_eye, 28)), - ("Высота", self.get_app_data_in(index_eye, 29)), ("Площадь", self.get_app_data_in(index_eye, 30)) + ("Локализация", self.get_app_data_in(index_eye, 27)), + ("Ширина", self.get_app_data_in(index_eye, 28)), + ("Высота", self.get_app_data_in(index_eye, 29)), + ("Площадь", self.get_app_data_in(index_eye, 30)) ])) scroll_layout.addWidget(self.create_block("Друзеноидная ОПЭ", [ - ("Локализация", self.get_app_data_in(index_eye, 31)), ("Ширина", self.get_app_data_in(index_eye, 32)), - ("Высота", self.get_app_data_in(index_eye, 33)), ("Площадь", self.get_app_data_in(index_eye, 34)) + ("Локализация", self.get_app_data_in(index_eye, 31)), + ("Ширина", self.get_app_data_in(index_eye, 32)), + ("Высота", self.get_app_data_in(index_eye, 33)), + ("Площадь", self.get_app_data_in(index_eye, 34)) ])) scroll_layout.addWidget(self.create_block("Друзы", [ ("Локализация", self.get_app_data_in(index_eye, 35)), - ("Ширина", self.get_app_data_in(index_eye, 36)), ("Высота", self.get_app_data_in(index_eye, 37)), + ("Ширина", self.get_app_data_in(index_eye, 36)), + ("Высота", self.get_app_data_in(index_eye, 37)), ("Площадь", self.get_app_data_in(index_eye, 38)) ])) scroll_layout.addWidget(self.create_block("Жидкость под РПЭ", [ - ("Пощадь", self.get_app_data_in(index_eye, 39)), ("Локализация", self.get_app_data_in(index_eye, 40)) + ("Пощадь", self.get_app_data_in(index_eye, 39)), + ("Локализация", self.get_app_data_in(index_eye, 40)) ])) scroll_layout.addWidget(self.create_block("Эллипсоидная зона", [ - ("Состояние", self.get_app_data_in(index_eye, 41)), ("Локализация дефектов", self.get_app_data_in(index_eye, 42)) + ("Состояние", self.get_app_data_in(index_eye, 41)), + ("Локализация дефектов", self.get_app_data_in(index_eye, 42)) ])) scroll_layout.addWidget(self.create_block("Миоидная зона", [ ("Состояние", self.get_app_data_in(index_eye, 43)), ("Локализация дефектов", self.get_app_data_in(index_eye, 44)) ])) scroll_layout.addWidget(self.create_block("Отслойка нейросенсорной сетчатки", [ - ("Локализация", self.get_app_data_in(index_eye, 45)), ("Ширина", self.get_app_data_in(index_eye, 46)), - ("Высота", self.get_app_data_in(index_eye, 47)), ("Площадь", self.get_app_data_in(index_eye, 48)) + ("Локализация", self.get_app_data_in(index_eye, 45)), + ("Ширина", self.get_app_data_in(index_eye, 46)), + ("Высота", self.get_app_data_in(index_eye, 47)), + ("Площадь", self.get_app_data_in(index_eye, 48)) ])) scroll_layout.addWidget(self.create_block("Гиперрефлективный материал", [ ("Локализация", self.get_app_data_in(index_eye, 49)), @@ -369,7 +390,7 @@ def open_connection(self): class ButtonDelegate(QStyledItemDelegate): def paint(self, painter, option, index): """Рисуем кнопку в ячейке""" - icon_path = r"C:\Users\Evgeniy\Projects\macula\vision-macula\src\bsmu\macula\app\images\icons\edit.png" + icon_path = r"/bsmu/macula/app/images/icons/edit.png" icon = QIcon(icon_path) # 🔹 Здесь нужна иконка (например, карандаш) icon.paint(painter, option.rect) diff --git a/src/bsmu/macula/plugins/db/__init__.py b/src/bsmu/macula/plugins/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/bsmu/macula/plugins/add_appointment_dialog.py b/src/bsmu/macula/plugins/db/add_appointment_dialog.py similarity index 97% rename from src/bsmu/macula/plugins/add_appointment_dialog.py rename to src/bsmu/macula/plugins/db/add_appointment_dialog.py index dedba79..22449aa 100644 --- a/src/bsmu/macula/plugins/add_appointment_dialog.py +++ b/src/bsmu/macula/plugins/db/add_appointment_dialog.py @@ -3,7 +3,7 @@ from PySide6.QtSql import QSqlQuery from PySide6.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QLabel -from bsmu.macula.plugins.database_manager import DatabaseManager +from bsmu.macula.plugins.db.database_manager import DatabaseManager class AddApponitmentDialog(QDialog): diff --git a/src/bsmu/macula/plugins/add_patient_dialog.py b/src/bsmu/macula/plugins/db/add_patient_dialog.py similarity index 97% rename from src/bsmu/macula/plugins/add_patient_dialog.py rename to src/bsmu/macula/plugins/db/add_patient_dialog.py index c3657a0..c18b29b 100644 --- a/src/bsmu/macula/plugins/add_patient_dialog.py +++ b/src/bsmu/macula/plugins/db/add_patient_dialog.py @@ -3,7 +3,7 @@ from PySide6.QtSql import QSqlQuery from PySide6.QtWidgets import QDialog, QLineEdit, QPushButton, QVBoxLayout, QLabel -from bsmu.macula.plugins.database_manager import DatabaseManager +from bsmu.macula.plugins.db.database_manager import DatabaseManager class AddRecordDialog(QDialog): diff --git a/src/bsmu/macula/plugins/database_manager.py b/src/bsmu/macula/plugins/db/database_manager.py similarity index 100% rename from src/bsmu/macula/plugins/database_manager.py rename to src/bsmu/macula/plugins/db/database_manager.py diff --git a/src/bsmu/macula/plugins/edit_pacirnt_delegate.py b/src/bsmu/macula/plugins/db/edit_pacirnt_delegate.py similarity index 100% rename from src/bsmu/macula/plugins/edit_pacirnt_delegate.py rename to src/bsmu/macula/plugins/db/edit_pacirnt_delegate.py diff --git a/src/bsmu/macula/records/eye_info_data.py b/src/bsmu/macula/records/eye_info_data.py new file mode 100644 index 0000000..c0efc41 --- /dev/null +++ b/src/bsmu/macula/records/eye_info_data.py @@ -0,0 +1,126 @@ +from dataclasses import dataclass, field +from datetime import date +from typing import Optional + +from PySide6.QtSql import QSqlQueryModel + + +@dataclass +class Measurement: + """Базовый класс для измерений с локализацией и размерами""" + location: Optional[str] = None + width: Optional[float] = None + height: Optional[float] = None + area: Optional[float] = None + + @classmethod + def from_model(cls, model: QSqlQueryModel, row: int, + loc_idx: int, w_idx: int, h_idx: int, a_idx: int): + return cls( + location=model.data(model.index(row, loc_idx)), + width=float(model.data(model.index(row, w_idx))) if model.data(model.index(row, w_idx)) else None, + height=float(model.data(model.index(row, h_idx))) if model.data(model.index(row, h_idx)) else None, + area=float(model.data(model.index(row, a_idx))) if model.data(model.index(row, a_idx)) else None + ) + +@dataclass +class ZoneStatus: + """Состояние анатомических зон""" + condition: Optional[str] = None + defect_location: Optional[str] = None + + @classmethod + def from_model(cls, model: QSqlQueryModel, row: int, + cond_idx: int, loc_idx: int): + return cls( + condition=model.data(model.index(row, cond_idx)), + defect_location=model.data(model.index(row, loc_idx)) + ) + +@dataclass +class PatientExamData: + # Основные метаданные + visit_date: Optional[date] = None + disease_duration: Optional[str] = None + tomograph_type: Optional[str] = "Топкон" # Добавлено поле типа томографа + areds_criteria: Optional[str] = None + refraction: Optional[str] = None + neovascularization_type: Optional[str] = None + + # Общие измерения сетчатки + choroidal_center_thickness: Optional[float] = None + foveal_retinal_thickness: Optional[float] = None + total_retinal_volume: Optional[float] = None + average_retinal_volume: Optional[float] = None + + # Состояние РПЭ + rpe_status: ZoneStatus = field(default_factory=ZoneStatus) + cmo_location: Optional[str] = None + + # Типы ОПЭ + serous_ped: Measurement = field(default_factory=Measurement) + hemorrhagic_ped: Measurement = field(default_factory=Measurement) + fibrovascular_ped: Measurement = field(default_factory=Measurement) + drusenoid_ped: Measurement = field(default_factory=Measurement) + + # Друзы + drusen: Measurement = field(default_factory=Measurement) + + # Жидкость под РПЭ + sub_rpe_fluid: Measurement = field(default_factory=Measurement) + + # Состояние зон + ellipsoid_zone: ZoneStatus = field(default_factory=ZoneStatus) + myoid_zone: ZoneStatus = field(default_factory=ZoneStatus) + + # Патологии + nsr_detachment: Measurement = field(default_factory=Measurement) + hyperreflective_material: Measurement = field(default_factory=Measurement) + + @classmethod + def from_query_model(cls, model: QSqlQueryModel, row: int): + def get_date(idx): + val = model.data(model.index(row, idx)) + return val.toPyDate() if hasattr(val, 'toPyDate') else val + + def get_str(idx): + val = model.data(model.index(row, idx)) + return str(val) if val else None + + def get_float(idx): + val = model.data(model.index(row, idx)) + try: + return float(val) if val else None + except (ValueError, TypeError): + return None + + return cls( + visit_date=get_date(3), + disease_duration=get_str(4), + tomograph_type="Топкон", # Установлено значение по умолчанию + areds_criteria=get_str(7), + refraction=get_str(8), + neovascularization_type=get_str(9), + choroidal_center_thickness=get_float(10), + foveal_retinal_thickness=get_float(11), + total_retinal_volume=get_float(14), + average_retinal_volume=get_float(15), + rpe_status=ZoneStatus.from_model(model, row, 16, 17), + cmo_location=get_str(18), + serous_ped=Measurement.from_model(model, row, 19, 20, 21, 22), + hemorrhagic_ped=Measurement.from_model(model, row, 23, 24, 25, 26), + fibrovascular_ped=Measurement.from_model(model, row, 27, 28, 29, 30), + drusenoid_ped=Measurement.from_model(model, row, 31, 32, 33, 34), + drusen=Measurement.from_model(model, row, 35, 36, 37, 38), + sub_rpe_fluid=Measurement( + area=get_float(39), + location=get_str(40) + ), + ellipsoid_zone=ZoneStatus.from_model(model, row, 41, 42), + myoid_zone=ZoneStatus.from_model(model, row, 43, 44), + nsr_detachment=Measurement.from_model(model, row, 45, 46, 47, 48), + hyperreflective_material=Measurement( + location=get_str(49), + area=get_float(50) + ) + ) \ No newline at end of file diff --git a/src/bsmu/macula/version.py b/src/bsmu/macula/version.py index 93b60a1..8411e55 100644 --- a/src/bsmu/macula/version.py +++ b/src/bsmu/macula/version.py @@ -1 +1 @@ -__version__ = '0.5.1' +__version__ = '0.6.1' diff --git a/src/bsmu/macula/widgets/eye_data_widget.py b/src/bsmu/macula/widgets/eye_data_widget.py new file mode 100644 index 0000000..f77440b --- /dev/null +++ b/src/bsmu/macula/widgets/eye_data_widget.py @@ -0,0 +1,120 @@ +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QWidget, QVBoxLayout, QGroupBox, QFormLayout, QLabel, QScrollArea + +from bsmu.macula.records.eye_info_data import PatientExamData + + +class EyeDataWidget(QWidget): + def __init__(self, eye_data: PatientExamData, parent=None): + super().__init__(parent) + self.eye_data = eye_data + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout(self) + scroll_area = self.create_scrollable_area() + layout.addWidget(scroll_area) + self.setLayout(layout) + + def create_block(self, title, fields): + """Создание блока с формой для группы полей""" + group_box = QGroupBox(title) + form_layout = QFormLayout() + + for label_text, field_value in fields: + label = QLabel(label_text) + value = str(field_value) if field_value is not None else "" + value_label = QLabel(value) + value_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + form_layout.addRow(label, value_label) + + group_box.setLayout(form_layout) + return group_box + + def create_scrollable_area(self): + """Создаёт область прокрутки с блоками данных""" + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + + scroll_content = QWidget() + scroll_layout = QVBoxLayout() + + scroll_layout.addWidget(self.create_block("Обследование", [ + ("Дата посещения", self.eye_data.visit_date), + ("Продолжительность заболевания", self.eye_data.disease_duration), + ("Тип томографа ", self.eye_data.tomograph_type), + ("Критерий AREDS", self.eye_data.areds_criteria), + ("Рефракция", self.eye_data.refraction), + ("Тип неоваскуляризации", self.eye_data.neovascularization_type) + ])) + scroll_layout.addWidget(self.create_block("Ретинальные показатели", [ + ("Толщина хориоидеи в центре", self.eye_data.choroidal_center_thickness), + ("Толщина сетчатки в фовеоле", self.eye_data.foveal_retinal_thickness), + ("Общий объем", self.eye_data.total_retinal_volume), + ("Средний объем", self.eye_data.average_retinal_volume) + ])) + scroll_layout.addWidget(self.create_block("", [ + ("Состояние РПЭ", self.eye_data.rpe_status.condition), + ("Локализация дефектов РПЭ", self.eye_data.rpe_status.defect_location), + ("Локализация кистозного макулярного отека", self.eye_data.cmo_location) + ])) + scroll_layout.addWidget(QLabel("Отслойки РПЭ")) + scroll_layout.addWidget(self.create_block("Серозная ОПЭ", [ + ("Локализация", self.eye_data.serous_ped.location), + ("Ширина", self.eye_data.serous_ped.width), + ("Высота", self.eye_data.serous_ped.height), + ("Площадь", self.eye_data.serous_ped.area) + ])) + scroll_layout.addWidget(self.create_block("Геморрагическая ОПЭ", [ + ("Локализация", self.eye_data.hemorrhagic_ped.location), + ("Ширина", self.eye_data.hemorrhagic_ped.width), + ("Высота", self.eye_data.hemorrhagic_ped.height), + ("Площадь", self.eye_data.hemorrhagic_ped.area) + ])) + scroll_layout.addWidget(self.create_block("Фиброваскулярная ОПЭ", [ + ("Локализация", self.eye_data.fibrovascular_ped.location), + ("Ширина", self.eye_data.fibrovascular_ped.width), + ("Высота", self.eye_data.fibrovascular_ped.height), + ("Площадь", self.eye_data.fibrovascular_ped.area) + ])) + scroll_layout.addWidget(self.create_block("Друзеноидная ОПЭ", [ + ("Локализация", self.eye_data.drusenoid_ped.location), + ("Ширина", self.eye_data.drusenoid_ped.width), + ("Высота", self.eye_data.drusenoid_ped.height), + ("Площадь", self.eye_data.drusenoid_ped.area) + ])) + scroll_layout.addWidget(self.create_block("Друзы", [ + ("Локализация", self.eye_data.drusen.location), + ("Ширина", self.eye_data.drusen.width), + ("Высота", self.eye_data.drusen.height), + ("Площадь", self.eye_data.drusen.area) + ])) + scroll_layout.addWidget(self.create_block("Жидкость под РПЭ", [ + ("Пощадь", self.eye_data.sub_rpe_fluid.area), + ("Локализация", self.eye_data.sub_rpe_fluid.location) + ])) + scroll_layout.addWidget(self.create_block("Эллипсоидная зона", [ + ("Состояние", self.eye_data.ellipsoid_zone.condition), + ("Локализация дефектов", self.eye_data.ellipsoid_zone.defect_location) + ])) + scroll_layout.addWidget(self.create_block("Миоидная зона", [ + ("Состояние", self.eye_data.myoid_zone.condition), + ("Локализация дефектов", self.eye_data.myoid_zone.defect_location) + ])) + scroll_layout.addWidget(self.create_block("Отслойка нейросенсорной сетчатки", [ + ("Локализация", self.eye_data.nsr_detachment.location), + ("Ширина", self.eye_data.nsr_detachment.width), + ("Высота", self.eye_data.nsr_detachment.height), + ("Площадь", self.eye_data.nsr_detachment.area) + ])) + scroll_layout.addWidget(self.create_block("Гиперрефлективный материал", [ + ("Локализация", self.eye_data.hyperreflective_material.location), + ("Площадь", self.eye_data.hyperreflective_material.area) + ])) + + # Добавьте остальные блоки по аналогии... + + scroll_content.setLayout(scroll_layout) + scroll_area.setWidget(scroll_content) + + return scroll_area \ No newline at end of file