From c143c0bd73f67a520de85b6ffafa7fff19306871 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 8 May 2025 17:03:11 +0200 Subject: [PATCH 01/60] Increasing working version to 7.0.0 --- version.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version.properties b/version.properties index df25c4e0b..232aa364b 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,8 @@ #Generated by the Semver Plugin for Gradle -#Thu Feb 27 13:09:58 CET 2025 +#Thu May 08 14:54:26 CEST 2025 version.buildmeta= -version.major=6 -version.minor=1 +version.major=7 +version.minor=0 version.patch=0 version.prerelease= -version.semver=6.1.0 +version.semver=7.0.0 From 4fc97f7880225151e8d3d2bdc0ee072b953b8554 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 8 May 2025 14:22:53 +0200 Subject: [PATCH 02/60] Bumping gradle to 8.14 --- CHANGELOG.md | 3 +++ gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43705 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 6 ++++-- gradlew.bat | 2 ++ 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 757e64f73..6259a579e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed Jenkinsfile [#1315](https://github.com/ie3-institute/PowerSystemDataModel/issues/1315) - Updated readthedocs config [#1317](https://github.com/ie3-institute/PowerSystemDataModel/issues/1317) +### Updates +- Updated gradle to v8.14 + ## [6.0.0] - 2025-02-27 ### Added diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..9bbc975c742b298b441bfb90dbc124400a3751b9 100644 GIT binary patch delta 34877 zcmXuJV_+R@)3u$(Y~1X)v28cDZQE*`9qyPrXx!Mg8{4+s*nWFo&-eX5|IMtKbslRv z=O9}bAZzeZfy=9lI!r-0aXh8xKdlGq)X)o#ON+mC6t7t0WtgR!HN%?__cvdWdtQC< zrFQ;?l@%CxY55`8y(t7?1P_O7(6pv~(~l!kHB;z2evtUsGHzEDL+y4*no%g#AsI~i zJ%SFMv{j__Yaxnn2NtDK+!1XZX`CB}DGMIT{#8(iAk*`?VagyHx&|p8npkmz=-n!f z3D+^yIjP`D&Lfz500rpq#dJE`vM|-N7=`uN0z86BpiMcCOCS^;6CUG4o1I)W{q6Gv z1vZB6+|7An``GNoG7D!xJGJd_Qv(M-kdVdsIJ?CrXFEH^@Ts83}QX}1%P6KQFNz^-=) z<|qo#qmR!Nonr$p*Uu1Jo2c~KLTrvc*Yw%L+`IL}y|kd+t{NCrXaP=7C00CO?=pgp z!fyr#XFfFXO6z2TP5P1W{H_`$PKzUiGtJd!U52%yAJf}~tgXF`1#}@y`cZl9y{J-A zyUA&-X)+^N?W=2Fm_ce2w$C6>YWp7MgXa{7=kwwy9guBx26=MnPpuSt zB4}vo3{qxa+*{^oHxe7;JMNMp>F`iNv>0!MsFtnb+5eEZ$WI z0M9}rA&cgQ^Q8t_ojofiHaKuhvIB{B9I}3`Dsy3vW8ibigX}Kc912|UZ1uhH?RuHU=i&ePe2w%65)nBkHr7Bx5WwMZj%1B53sUEj0bxI( zEbS%WOUw)3-B0`-m0!{mk7Q%={B#7C^Si>C04@P|qm7$Oxn3ki)G_oNQBTh6CN6d_kt@UKx1Ezdo5)J0Gdf@TcW|{ zdz1V?a>zldA7_5*Pjn6kDj|sbUqt-7X z5+oajeC}*6oi~vxZ#Ac&85cYcC$5OKUnYPv$Y~>H@)mnTtALo*>>5&=0QMr5{5?S; zCDF=RI@94n(!~sa`4Y{JLxgcvRqMM&T!}rRd~Kl#_X4Z&85;})o4W*g>?TaAVXSWB zeY#!8qz^hmC6FERsjTnC)1Xu1UPd7_LfuNvuVqF8(}Jfar=T-K9iChEuZi-FH(P%u zzLrjpq|?}8?g1Vnw^&{eqw~QY0f*9c71&*<5#9f5JlhJmG~IuV*8~nEBLr`KrvMjb zlLH&oZ58K?u>1{vAU0CtT>Il<I{Q8#A!lO7#73V&iN13;oV?Hl?N5xDK63)Rp3%5reb&3n5OQ|9H zDpYEI%JQXcrs^o*SCFY~iYf-VM<`7Tl@+kQS3tfR-fyH_JDaz5SYEMU-bTCLQ=JVG ze?ZPcj95Tci|bVvSZk3^enqQ?pIcZn24V=YT{cf-L|P&{-%%^ql$)^Vu~)Ida=h$bZAMQEi$MM|&b zY8;D;aEba_`W^=VdKfttW)h_zjRA&0A^T*tF*%+}TZQCOvFqKUu=xf1Bx@T?&~S(J zopXniA?s%}Q4p9~F(Ty{8wt$l4oHeT(#U6sAu4>Q+~a;}I>0>??v*wfke}0TwPaeE zj3gWtfNlD{jRgy7;S9PS?su5pnobi%Zoe0LVpw%`<)V=yT~Ht_UUXIna4YUa;p=-T4df6^;bz%;@|$F zK;s9#K@9hqZCST!66N0uPB+FT*kq22%ovNkV65?(m3(JG{^ZAU^icN6Vq9&>p&L%Ef1FXR@>BJrHh_Wa6r-M z7u+JprZ^WkBA1!gpa%2kw>7Y#3GFe{95w3vybssyeMdaKN4(9Gy+?H|$R>?1RWr|& zmiD^tle6`{!LarDe6R$;x%Y|8L@dx&ef}{0J6*6}o?)Iy1~n8&n%j^(aRNF$O~IYe z!J}%OK&j%*{2HcCl}>bcBB~&G7P1^{oi|nx0MAP}qI@`Dw`5@}m z_dSrULV|0Cii@pnq_r{wH!;>}Ew^22bFr(psH1-OvDXmZ$4v@}H~QhpnalWvNuI=b{RDO)MyH(-R3WzO5L<{(HzaoI<70 z21zGM?;q&z8~hdY+yw=i-um^zS)iYqjjq9bT{SI(JW{ZVZiK628K!}e`GYeL&+CGj zK7v?Ha$ak5Ax5j%zTDJ#!#T9~=f?a7A@WehA>iR}?j#X#Wxdz>!V;eS+~Fd1CSX8V zN~^b~L_`lGg+-0yIWk6=eh3j4N!c*UrXo=}Sm*kI30HVNf=e6}9o4TZH3+FkzsZ<8 z+Ayxb2cB}7Ge5Su!DUxt!=zK1=3jE7u6L%)jW|_iSYPEz$uC$+)myhOiJc=Mi%!vD zC+n>-I;;V1&2k`Aim4ddTq@wEA{GKTS?Kw3+CmtTe1g5_2_H&U8TMH1kWAnpA9po^jJBQx6E&KB{^lI(3Qqm!3an_2O6o3p_1p%!}u z=1-sDOjTP$x{)ADDy4keL>j+UHK^Q_^`a8vI$r zVo((%s?%&~strG)V8hcaCDtM>AyRte^cmOMbkwb5is9@c>$Xh=OU}?=P!}E0R(Xfh zmGKZ_lAP!EM=7Y;H-{1bj39%R_V^jO-07>5{SoX(swfSzuRo;7<@;)U8x`My4cfbB zPhfKJWX#+sBOMI<{&H;?l)iJ(SjILdcTIFgY$k=fuwg8|r?9#xVBJnt)q{dOHd)w8 zSB;O?OtB8=m)8RH0 ztUADo3u1IbodpXMVgYep#d!5i#nqx@G$73_xpNQUlTeDkH3ucC8UCa<4vCCP9I45j zb)v=@;f}8TF5!%yHV&%ihP?b8IkK{zyL%yqV(y0Jkrfk6Qh`-TiV=84A zBQgr5UXij0e{S~6#XBRvo?c-X6miR8Wa@^^(RgE5BXlm|+ORbxPoXGfjCjr+U02qL zT($V}h3`R%qX5-|o0F);Exni=$}qWc`xiV@TbjKV@kn^LaFF)71;11JB`oU7ZA$u! z2z@(hFoN8;TS~OiRFhN`G~yd}2+nc%IffdIhb~EC-$?!UihWWlK)I?>JWwO5U;A z`NQezQqKHDzTcVW=1J>+2$a@yKV@%IDQL7%+@{mZiqs4RqbxP}#-0_$KNa-InI)UpX-zA5&<(ztwJ z@)!z%#dR?0#v(=8CHa)QqsZUqiTdWh!xo-s*u?WU4t36dM~y!;o&P?i;?wcN+9zZ5 zFLdM3kXE!|Ep)fC-&?Ht6V%uWB75CJgRoC$d)e`#SIG;~|5n^(z(wXdxa+$uP}N>> zP6^AO5eiW^AyA>8t0Cwo)5iLIYrqWkOutzWdT@fH$A*4sL$mA}1B--zcy|wK`mx;G zHtLlmuA;2vG`+AD*ylpPFZ(Dn+x23~a0>`g(qr^g)BTxutzhfysyN)#RF+0qS&){! z)<{USn$50P%`kk4Bzg5?dPtuL2so@_ehkXSw<;&RfX*v$XXrc7?~7&Ex%XbE*dVFc z0^INk43S9IjK$CrhBnyIggDAZb@=VTVMV>g+*G>sSw(d{_#;M>bROLMs`A$o_9#92 zmYY0xoQFrAEGN59xwqPJNuTgMLhUdK)dvTVbG0jO6Mc*@2Fd#h zaLSq29f(E*;60^`W1b%Etiv90FHWh@haK6X&lGRhXlIq?l$i5P5a;H8{h=3KJaE(v z`t9G6wx2ea;pmy8Nx)b+ssUK|$R`xrH-Lzz)whyD5^lzJGw|A6G4U8y$UnSFAX zpf8&0GjR)-M2K{IBvPFZC@9+_o{p-B(QpYpvIdjeX=yLR&W& zf4vW)3hREiQd5t^p;Q1D)gB?@mbJttQy-$(NMydpKWjDNs(7zIxwmm3*C+VoA2o%1 zCN>iU(sCH)wvRF6OhBGfTIEQ97D=5HOi*jy|gPpk^^q}}Kxs{X?{SWe| z&`kF*6)QfMRg(T;)N8IMVU$!cOkjY}=+jTAgOmz+1)uCkubfAh&4-tqOvl`r>6L=E&q3?G}QmH2B*+fLd z3bruLCH`e_rtsA`vMM;$^0Lfw*o6tKLB-#tlAW1^e;E$Z%-&N}vKSPLO^SnA64d5HXGqwhNUNF zxk5FO;0I$6GoDC^IHo9b(_bj`hLGIvc)|iAU8OO(1BXLi!l5$Nv&^8_v(lvXCKQeQ}hN#k2 zG|3;NP~9x;iA#iy{7Er1QU2&7KwHL2mJAj-&A5$&sTW6k)ZCa*XUbw7^2X9E zxED=QE%gZDL@BaQD)hJUzQi6K5SJQYTEZ35Y~`FYxaEXLm<@cd|jd4@0wx zcyX1Hf{1>@xAv26H-JVUxt(6r*hQWQL~5X%n*Kofz!rz&WlB?&DhFj7%H%bk5yW*( z)AjLJy5HShQWIg8icBVyC=vlydBtC8U@?!R?6UKo`6RfeSQ?)(=X6EQ+WxFB0c8XV z)MYP8ZtTfJf67!v#do2e{e!63G7k8XF($b$4^59 zrhHQ%Xgehlrq@-$7FQzqf{~{JT~f;Jbn8upAH39F*QrdQCRhOMK`0ILhae6GgnjoG zecC?qay<01xVZTIfC~6p!TjXv_nz;A-LI#Yrx0}h13Bpa$G$cRwg>ByaSD}Ghj8dR zX@sbQD2tf0_1HM<8W5_Az6wqp&!Pjq@G-o(iBtTD?jZd__5HV6Bs|~#4rOSGYAJfm zJWVYbd&&oCSNpYjK0YNkqW=_x>GbhYA-X3uHtX>|WsFK6W@v&EEAMc9^jU$VLh-s5 zdyWF))~)HZ$GS0L8S*!#r*O&0Gj`qxCZ!hMdBA*^G{XjCFi@u4j}wmVs_2Wm6>~{b zSMwAe^ZC|<9yrc<=xOXcEve^7qy5nY|C6Ielx;sK}v}Ws6z+=wC{|jIBPrIVy z*cQK^h3Jz*#LDKm(=&Bta1EZYNSpgD<0kAvUKKSj39#kCLL5f^Hf=&mcPa7c3e_9E ze`A(#I{CR0G+7XeDQ`Mbap@KLkL6_@dLg4zPOU;bA)Qf@Zi zye|+CevtoF?uA+tIPj0a2})}8VtL6wCVqPAO73KB=HgU@x_oMFwtuFf2;1Y ziNe3ZB5{S!@`A_pa^-dJXz3DS(U{qnpKr~vV@`LSA3M9y@zg@M`n2d0Hg-Q@&j)m- z8{|Y$u$}vR>?kj91RX-? zFn>bLzjhs>6l;mOib-404DVPWpl=IEIX<~$GeTa0>MobN9!&`9xk}cr$8IhtNt-RT zf2|r5unkLkXh96s*+kh*N6pjXiXrbCN4UIB-_O=xv(JKOlOY?3-yXG~{`=NNpwa|T z`S=kObQB9b5@P~r$n1ITNj=A#$sUNJEIE@!u{+od8+qd`G3S%;MI$1o4HL5h4rLCc zvE^_jV4FW^-^+5>%%lL$*rASDC4J07xeYf7AQ*ZJXE+bs$j{C0zr3>;v@x%rxv0FJ z@dFq{i3t4gR*b_h(xs-;Qogc%Wb{KAaUA2ug0Vmi0N`HTLds!IjCp@|>!IzeDa5-^ zSLL??Y@sz{aeQ1>J~i}n>A-nKZZwNq2M$zO4EWa>17HX>N|9p@h zbh8KgSFjSD$^R!|DiT>J8X6ey(_Ff*(#hs9*wm2yU&IP!(7y-${}PVv$dH0hM%`iJ zCb|Cucb88)NTL)^I*|BoaTv3;%m(~?_ks>UdFtK1cz9@V-;>nS%Z+`vH*|Y1Vv<`; zjXat?{?5UovYscBO_Csx4U-qPON(ES2JGHApzSN#QVdFZggRl*tE_adt(9(Vx_9a3 zQ;LMtj)Obf&r>LnXi%ZTBFvj8`8J?yE3~L194-3!NY4zL9E+{WPoI7QoEkh3z6j)fzH&l6F-N?H_p_l_~dFND*Cy)KC68FWa^P`y#vkE+wR`wZaLwGBzR(#P%V`o$7@1cHbE2G*(f_eBD%f{I7mTY* zF`!eE6g*GhO%vxuQu2lTpPFCNej!$ZjTk(DSfhw3j@Ve5^G$B;0qAr9OmY@H{C{9Q zJfd}@lOE}HW_=@X@c+Pifzd81@t-i(Nd|Fza_gshHV=!*G&a}AkC*p7ssOKXR$oDG zPvwsi&DKULNL|DEO8d?d-CRRg0it$eqo-U3YQ|71Pjc$kKC-@5^hE=;M>0QWV1`cu z^(n{DmDhw5wxqkU@nm|pY-62o&{mZX5lkMT!}yFcbSyqR$;O}DKEh9oTIDn+ zL$&w6hDiU3+gI`lx5WXZR-gw0suYq5!@EVBUaxDs*@S0Q0*7Mwd#05xfEq+Epd2jh8jHiu)dv{M7N`ivNNbM*l zit3%rH8_14{)7;hQmFbUjyCty`t~ifFM!&-gLZRh%!atW_00<>4FQT4Sn5Uk4Udu~ zAvabtV+XiFlvSJT%9VcFFv#wP7a@`GR^Z;x?6TfQ)Slf+Pw}bbobM^!$L{d33+upw zoTVHuq3}-DBoBSQyHwWR8<~2ei--7Yc{vcu}8R!dzN#liPsP9O5_8)>uQu%70 zz=}4co4V8N^c05(JH!G25bBO$#yWCRx~?yDf-B+@kjc~Ubd8ojNHW;{MSh(^&q4hW zY;M+`XsFXl_si;_u2QeeDa6)L_k>2zQPv|%LGeR;i);WmQ{^&CBV z2_>2iB6uY!{5QYpI2Jk;dXy_tC;r7o13_(LcD1k%CaKQ#>MBq3&Sa2*e zI#6F1QxeO62%4+EK}yYz{*-e81Cj&=3;oS1T8k$ByRf~OY}f{g_VVAs4HTJ5gQvT; zsl7sjhrzWbo!|GR=kAW)Gda_r%03o-n$K4!27f0ry^h9tE_rS*h(F(M@P44$AWsnq zq#py(a&}fbYVMwX=w-xs405!qaL8TYHUX2%mfOC!lgahy3rCq>6gAXMA8zOj#GsD5 z%wcC;+t8@*EF_Wmjo;n7+X@sToZVHQ=TxRq;;yqQy3eU;QS@Q-vQ%JbWQFQ=xN%KxKVUIPCTg)>eXP>GP4Sx= zU3z5xD`2Sw@(3h}x9Ub0#(W6N1^!OU^~yknf$QZCKZGasEJjDMGKSB}pFjJW&dEBF zj#Uu^5RGEg>qGapV0a1|>P$Z)_Mi)ToWUDJCy4nT?KgYi3|j0zk22h<5*+@eQF-HJ zyj~l2=V?NpqHIjI8O%eNDd_QFe+jrX6D#dr+%7 zv&ph+JTF))a?w0kOcw`>j_IjswyL#iGq|22w$-PXDf8;()3&)$Ei|cRe5N^^A?~my zJ1zdC76jGvO>;Dgax~?Wwgf3s6l!{qY;^PFgeDBY_x<@Cmoj;C0hT?MWU@LSdPeVf z`p;1YbEd^^zvPugX`j+%{djIJQ}j<>x3!hu*JRk^_Dx_k4+QL7lO8OsIqOVuugn3uz2hupd0d*C5F4{-YBn!^|-{syQ?UaZX5 zF+@z{B3G}Dj375K1G%L>^VXKI$5jA#y2BB#2GHgW7orHTqSK^MeLZ5Y51C}Y!f2ea zRf=q4jNbXy=b9f!+vxDsy^=3y6SQA8;#o(__HB}JcjRPtB)B4?M1(rOo2b|ZytB{* zm|3T+${2z?-n{}O3P$dJTK^o+ftoRM#+}jR)*{dz%-vVy2EIYfh0FLLe6E zYt&+1tq=LIl==)WV9(YSIi$hsMyNS*{O}F6V|k)W+LikLjb1D)kq%%mcURk~|3QL2RRZbR@P;Rk%u!{d(n$(#uAJ`Y_FJDEmbgvh z(HC7jq3e3ocym<9&;Pb+F4^>J?Z2sJ3jd!+HbBD!f9FL9r*lvN*Jkm&A=Y%D(;K9c z@Jhl)#ua$a%_t}3+KgJwo*}vdIv|=`H0=M-C(|+Mbp@n$h2L-S{4lAT*Bk#h%DEIe zzg*+%e!t(Ff+#X(<@b7fa=0mXQdoFsDGWR6(-9;B~~G^L#PhkcT`L{5XuuG)QXG)(+CCa^rS>>0|5xTj=6gvr9&uNY$ZI1p4IG z)i$ykbaadW^gQbaDb3np9=)vVn9N}~=YCb)K`1w2+4+oop)Lt|BP{C}&40O1^OJTW zhr7i4SUYTyiwE1yvRj6>@pD>=w*eqCDR@?7_dA-Mf@ouCZ0PB&ID^cKAIhuc%zy`7{R|IF-7B?u_oL(Lc^V~X4} zd5J*@AXk}ywO6)pdqvC(NBQSqgSX0B6**m-(InF_Jten`ct;#_I~9ULa6x0A6bY0+ zH3l=6^;-Y@b35ytMqo@BX}x4;m|OeZvNNe((g~m%?Hv5b^&|qXfc{Vi=qzaZ5s<_* z)-;sL@ZMyvg*;bDsy*3Sz%rrRC=cE(Icl=I2T7R}f1c>l;1^9sL_C^TM3e?sqjRLY zP-bPJ!aAusEn_0VIAtW2rO3F^F!vAfmAid&z{<5&k&5EQN4BF6;42`Ra>#D`nTI(2t`OO6h{U^Q|fTImm|d(fB-f5I=}A zTjI1(#5DgxYq+j6e)4ucKoIwbL3q3DVFYr zN6KCuk4_(z1A3#JTVYDT`!yxc+xndnZ^WXRj$3)aiBr9&^cWRTx?52*mEhMk(4{mW zZX7n!kk9StwP!1RIeh9;U3zByv09!f+3yk zDVI5KSDDK%N-@qqj$n7svXNC(q&0>WiM=al5qf}d`_0v^l6V_~p%hB*H^s&yX2IVF z5L4o{BwJ^#95X&JRxu+&uis_sXm=>w!Vmt-qYG4;km>t@_OV!2+W66Mx*+;B&jeXLNkts3 zCn_{DXxq6xt;m*3dPN?Q^g-Ac36+%k2|_T5o5Ayzb^Nh@Yy9uy)DTVpR0UE;^bpqs zRX5#2!0fQ&bJ@}5Gp#IWby&~RI^GPO#c?Fwt>473?Z>?|ie3VD_X2ouCR->vSPb|T zN?_!eh?r}>2i?d1_R_DK8HBKuDjZBwSgR0GpMD;*OgQOq2&rf+hiuDnY2)3YiwOZiW#AB3s&v6 z3^2ZSNS)z0Vmkqkd{q15Mk|7+n0OImvs#=-(#P6bP>Wl+`WT{iU+TbkQ{iqaSP|4aH3OAfinI2%1hWxH{qd@;gy&zJ=|IluB z@s5xreENf!S2g_G{qNS)ZJ|kl!8)b_Kgwc=`)_a{vIbF1FW5& zCw0Vr(EXsP^CmKX0)6wp_}TG8>V&<}fRjZQ+k8~^#7Zvnt>#$1P~7DAU=1JE3uydB zU3fdgXnR1D)yCMEZRFZhovm-%dA3>9 zXiJS2h-RaW;itowzZwn=j6QG`;%ZE?80^~DyA5{UI&Zd^FF0f!#@ak@U>39YPP;>^ za*p|=y{$&A)P|%C5t@}Gw6KgQ>SA;6VE@MnmN=NpzWlrA$o%g*ia|)}AM08Bf##=+ zAt$0DmFpYe69{h_T1E{S5~AcoTed25*Rd&=Npa@hU@Q6kAFCCbW}||JizRp}DKm{K zgL`1k_qv?OSn+ys{e&>W!H4Xws_sUq?iDMeSy65wE^}@nEaT6xCPT_vqaKs&Zy^(% z#ROxXka3W3+?yZvz1Ok>vzz*~@yuPmos5#Ltl^hzpjNo|0pIs#0kCgk5>SjIMXMM* z_No#``}|WTzAd-@mVg*5q7S_wG%MhgZ1FLeliVr3on0Y|05`IcVOZOGmqoR9WFe_? zHx6es$zbXXWLh}V^CoBXIC5+VvRDr^;ax+`U5?0QxaOwq6k zKmG04{Dw6b6YTS%V?#S$_-F9&XjSaUS$?>Gx@QqL%J9z@10;5J^`AA7i>w;cm|Cyh z(ABc_r=wpMq4B7EX_2S*PZoS&lo|T288ihZgIw!@Q3n}1(;{#imM zWcSRIbDvSLxPHYqwh_Y$Kb(*MM%V-}D>&@m2!r}RCQC1@095?nxg=TWKpidoz$NNp zYS2cN5jhG+myw(tGaeqhU#5a%GgN(j#>z?;;F;tDF?_E>ZtsWetw#`l0jC=XfR6c5AWezg%(`Hl_%N~q!n)2}KZrld z1tfA0N0Flfn_%@0=HctOJkVpQGju z)3MgIa!e_3%)?K^X52R+TU2#W+L4OvYf!GnXV^z4DzRqSuRe9q7|Tkm&r&~(ep&U0 z{3A6-k(~=b^e!}g@}s$|WQVh_L0%vE(cvCR z*l%5Spfk}eC%5jrtGO=7+LJnUEKs`{m#D2Q0!NQ`}z<3<#?KY-1G9+`Vi#WyxpgT7aPA9I)sEPt! zHlS?W%!srhu@F`j@!O)};UXehQPO>(xh7!fZwwu@ehq4!?3M7Puw#6TS3;NL4;uEc zg>_qh3wk;?TEqR&;@X3^%HlSQ9HG)OKpy+lNQ*H!@ygaEm6qmm_DqnAY}K$~v_*w! zkbGz`woRGu$w`TxcNC?tsPS!rGo_%07SPtipmDlCh7zN}?1zpkA@)V%)aPPO*5?XK z^w?Uay8#`6LFhRBL>7PYl((d#H`@)0-6!UmOrnt{2@MS*2le0PPs`O_`@dYZVNjv> z#WzLArlP$xda2zVl3)lLC;t7K#2j zyUkj@rAGVdWu1`H>!D`|fAaEG98Lt(FMfU6yAo)DVIKA$x&+OFVbOH^46i||p%y$23>r29c%wop*9FQt-!FSO3 zP*g*T(udq6RLy~*uVeJ zsoIqIl1IKGuEPG7s*-UqNivfi03az5210-$0}zVifnnVUlnfRF0$C0SjfYpOfy?RW zal1~1vaV>HqP*R0I?I5rb&(t9=x*FO6e4#H0;)2i?(*OF-nkCn!%*Eb|5QY_cLl~7 zR4IPg_D@KSlC1bs1Q_;i$63O9~GSQL_^GSx(^{TJzHGs;*exyxu6W z)zMt^_cn{!3-u&JPpoBsPlQQw2PQdzq;BXgg&An(wLzwiXlJ#=tpe8r`m%QmozqU) z#@;{#u6EXC1(go0gP>T%GV+sN_!Ja^0hTPcmSN_!Oy^)1KYK&oSs$y=3&g%2Sp68L zJpJxNVIe;P@&tLd{ZvtI3A0d6+`N=bE4}bI5^NkSMbhnBeE_>Gd^due~ zJam)qFx3#avgAJlTv#$FfwUQ$eJA?#7KgMaN7UwX?c5$w@WB4%dqX8_?3^9&mP>tH zj*(ms>H@rvY(isI_a7@30j~BaS;f*R+)NRq92#^CpD#-o?UDEWoG0bfLb+*2Y@lytWCK{zulP%M3vZ2M2_b2&T0rWs&4YUUg1>1TI0N2>A z^%+=E9%JV;hTW;dNH}t%*MH|zfr@gE`yL#Ud_DIP(_yasXW~kVm?1Qa)1IWjx|pVo zp+Hs-D^3zF?^;U6&ek#52zj&)oC^BI!q9`}ZpNFE!fzh29J}9|8DDE{In+dueQJ<2 zdA2dqk&51AiMQ@*2BD5@pW&M^WVbUg>H_3ImcOVvi=bCmp_;;C6x~}#7rac+u3*o)FvCBPs{jwVaY8a_mv;}O9Pu46C*>16f1CD zkVzVjacLK$Dk;2>ONSrScYaq6f(!TbEl_&M6?w?SN`RrMB`cP} zr%Sa&7^e-}s%2o>AUkU`LqleIWK;QfezwA-M|hS=__3vK*15lmBDwM16NC4N|4gQ1 zIkx8T_dI0bDIVDJmNUAUnIR=ZlPDkr?C`e+96VE&%ifnF<_?TaaD1dN^1=?$#oYL) zIt=spv=jZ_JdJQX->?lTfcMihAA)GsOn@q3$3;b^YfPyyY!QQ&lBLiH8XILG{=7BHE=s_t|#(UK1i#@UPy#QzL2&4nnFfeD@4PId7_68ysl9Yj^4SV_%<{Hs= zNXq2{4%Bz>yOwqOui|_|^c0Z^sWjGHXNXS+vEZM%cQS^*;hm7l{}+C|;-4HH{tG|Q zVg7|5vQW@i5b*Hu5D*aHE$pvgNjV-M--ZLpN>gBALo7*d>(9A0_COJ^0SO#QMgb$k z;>g9AMfygXxdkU&viEm`fJjLOb>$A)$W{0acCp_!*4U_G*J9!WuYyX~dH#+TTpQ%J ze-&@X9J`;p9=rDZmR>har-YuE1KhdCoc6bSvIn8Cp=8{C+15rDMIIZ8`-!Uo6^rIW zOiJ6n$(^z&xCK+Sn7HT=sEixAg-eD_xXo5r;i+LzR#!_61WQw3tA$1f%4aM}EZG-y zN|?q1nX{zOdI(g4Y!ME0^cJ|e)EE@z(H(vjL>6GrK5DU;iE{j|F3Z5<`gM zT!WwZ5aEw&a55rz%#WReGc3Yvbo4NhHNTk*TUV?P!X>-)j_z!@E{96FW_E!aM50qh zTX#B$d~dI{+3&6@s2$vuMI@-!zT#suG<{#309!{;Mb(s zHQm)v+_+Zhg~_-qnFiw!*%fs5Z&s7fu7~09K;n%IC&62o-ur?NRS{P=LM+ge=>UuB zO!2KSyPFzwO!Rb*!o7LoPJ1@QzFMSRI)L=gxPk{G{Jyf#ap!M0)?qknwR3Px-;gTp z1@!`u1~mEl3c+ZK>_Aj+z&B;N-udE0tRw^UQsx&aa%GQ`qJut%um_W{#Y%hygM`8* z(>Se_41MJa5@Lhhzne$>FKz5x)Y@2K+<$6*h4Ud(WETWKp}8K<*QzYsVjlTY+kfI` z?7g-q3HCdUwBBd4^Kg$e$BBE%c&-G$tD*2=*_5-a%pN3}{Q99sxg;&cJkMz7ei@$| zM+=3Jy-@7+F9lt-^+)dAOD~K*py^29-H10Y6rn-C69nU9jB9W7Iw)92is-SYjUj=d zlw7~wZU0o^@$qOjNq0jSAP0viu>&phRgHGV>{owZ^zmgu`rrhweQgbCAEER&O}9V? z9fY!mT|_6=SY)ouirlEkr;*`qsybg&akN(*%5RjCG|0pZz3YN z!`7704Z!jQZL#6$C5x2?=d>R6nlNIaA%!I!?shGYPoDYR6p7XQ>ifOf4t>V3(!C+; z(5A$xU}#v!ufaYw9kYX<3_zJ%a*7A(;yw=-Ue1G^1(<^KG%5zet{yl{tPxmANSYU9 zLApuz$erERqq0n8pY+TD`vNXLBB>_=(V6UTs6=Bf-x4o-aA-)&IP*~F8f#jAfG&8Z zemUks#PVUNCdZ4&lKAQ-wc7N$7Mj)xtDzO@w(gi&3)Cwy!f#RCUGwbNRx=y*zv zT2>h*G>e{*&Ae>I+(*q;qq6AL2i(8K7qCqAdOdD3wkp2u_jr`TDBqstvhEipXAzas zRSQ=0DHYs4UN_)u^`QbBhTW}{8DJ2{k|fn_tpa=1<+bM+R^*CR-0J5jBpaN)WrmmT zJXChxskx9=Dfc&2gn&h&V##9c^3}wQc$16V(uE(U#r^R2{8=5h^?(1Mj|%FMzme){ z=O*m)>{eR^O;5xZk++6yDM4bg)<5xCDzoH?VtjnJKzaR@kMSX<7{Gz?A@?YmyrN`2 zx}a`R|859=q}i!XQVwoQP^;jYGQmUo7GAsA}? z82gcbahCru7c8Sn4wNN32e(iMC#$wZ=jJ;gav*ycv-v^eD&&!1oMiU$f{;1g<&D|; zaHT44sZ-|H0114y!S}d&t*^%`s*OY6Kdd;1QYz{=3iPPUD`Ng`@I{oL#+ur`J2xnp z`V@3JV6@xB?WHFV%_Q_RVsbu`y6v^mKQe?wkQs2qIEhIO1AljC&1_0p!gAS-Y!}K` zA?L|oAj4+J*qohe!s>nT_-%%9TKiy)EcrIFDqTQS8%kf)94JggQ=dybX0N4iN%lq4 zC>h0E&!UgkI}(YyL#oel@pslfpF~a7!wv45{?Q&*O)ljQe|#FQwnM=?Fm zL(UV9PL`rbG1X=tZGy}_2@Njc&EZBQDWI8Zz(6$yU@2p;hX>5m9}Z!|_m9WFW83eN z+hp3h@5JNV`5$qONFQH6sVG@?sHWs4NaYzn)nMbEg!JFgE~d6;D4Rc{{|H@@@~iy) z6!{hYg)84&5mpgx0Tbbyqswb|N)K0R`{T?FrWr}8VK?9Q4N&(=FnMaMj$YR;Hkk*M zZ%JHT4Xi}s9kjgaL!h#n;uh8of!fbkeL`^PBf%#c+~Dkhdt7k}dL!M=pLd9Y7nJT) z`cr(>fRfWw&*}DNr~o6XF88c17RWH@>Qrlzm%LNF9FKBk?0Ih1s&AIz^f~2v;fS-$ zj^D95B8H4-f?) z9o!FH_(v3_sE}NcV{T*ZeD;0xuLBCpjp!TBAao4n2Lv$by2&bfH<*ddb#mSHven~o z?QzQRONFWQ_Qr{I{e#4%jO~xWO2<hkGpXihuy{DV}1`C zjVa0Uov-=i{HWN!O5f_AAQdiRlAKuucbx5h;I_J z>I_2X1UxhS`hNhHKxw~v^%2wM@+Zul;dc2hFK5s{;K6%fEX(jZfy@t3O9u$8Q2LaU zA6Q#|3w#`9wLfR}F|(8HE1Q%qrDaK5yJ@lsEs(ZbQkqxWw41av4Q2bFOm-*9(%qe~ zJKHv>cm*n;*9%@1EpokzV0@q;wwpkNB5FZJQPis_zP(<>*Y$-8O7H)h*-f&^rqti< zukGx7-#O9z;ySGH||=0_xhSXEp|vx$7{khvHqI+nwXIqN+dNi zVWdMTBd%jTqbGGOt7CIe%Z6fudhAd(m&(?J`?X|Nudf*z2&J^4P(sk?Yie2@TXPv; zGwX`@{kdck3)w*}v>LB^dLWV3^-Ll?fYrl#CX2JMzOLbthIOI1ez@k13Ne$~W8^Y_ zF@19)sWUA%G6RhR87-dF8;@kPp&>ofxW#(iW50E2iL^{kruo-thqcC}mL6!_(RZC5 zGi7o!IaAnYS{U3HncVL&1rr-;uVR`vx!RW0vRRo_Cf|T=?#vh_h=9d*!=_OathH%m z^;j;GFozqb!))-9m*%KcL35dwo*hR@ELSvS<~^-?{BRH~x}*vjT4VKfSwjXO1S5JtS1$pMDoKfzKViZV@w2WxBS5|vid zrA(DG_ho7VOQvCadwpmlj7oiH~} z6K}#Rz0^XjDm7D^t=64dMo*i6Ug{78nrX95v|CH*UfOD}!CvnD4cBRzn3AY}j{t7l}2!l*%;-aeJ~(tcQ9OD2tfBfaTEY2!$G z$B=M%cn!ltuAze-z+8*B0fqWtH=B4U2U?*)BL)A9Lu3U&_dR@SWD*g+6IMgzzK0Z8_OgL`l&4E3~!(}3O;Wv#<6vJOD3ZYBL@Ek z+SRgx7p4^@+ARihq?Bb4yoqjB>CJS@OkG+|5TBw^ncf2BO;Xr@s$~Zuu1vQftJ_x1 zwhr5@!ciinkX_mkj(aP;O*qNF&LD(snf?s|SPFqlEecNMw#`T;?PLxjchWmlx`Y0m z$sa5aWBcs8RJxtsEoxC@2G<3U_o#F$y_c!!wSr-JtKM&9>~QYM^%eGIx|?ZB@GMSi zV{e!aF+;fpe(q6!>3#Gc#iVH2uG7>rTAxU6|H-5z#G7ekgj7=%)LB@EdOk?^R?r9N zLq#ej`!d~+Y=-utTR&=A;f>H8p^sG1hv}oJ6KQL?w4M~a$4eil2L#+FnCf3sU-qNN z)J$;xApA9@4fpAI&zL(39$q#XgPl*&!zw*QpJtLmA%#wVGKF6AxR!nhSja~*jfwy` zSDini(ilAot%O4Ru4z6{r_g8clG02R*Q}Qw7u?j*DU^n6t}k0~@9JP@*=+q;dQw1t z4w=_Tmq@$!9817!ifR*_qF)^Q1v)KM_7u~ae;!|^FCv>2*cE=!l7WO52hV|*QZBws zez`Uge4=;oAI=%FDdQRx-8^V`6XH)051jv7( zNj1_fg*498TF!I+S#G~W&kJt9ivnSBE10!-eF52PIqHHa=WwU?L{`LK+)F>OOWY5U zstXvQ0|Md4#s1LZr=^J5k;#aF`>9Gl6Q#2vW~5DjG@{w<`mmRNE*h#k=zo~bn=VRg zE|H9j`uj^19|XX!RC-agCT`Jxr%^*gWyPO`3?%(6{Z5ehU*r$dus6N*2hqs9NPmQ} z&?6u%7S-#eKhsBqW?r(i4mA!XbrZeAUv2aL4V)w~TbP4Z{(vE0p}z|&{R1)@>29OY z7kKG^jL`5y5Q64gbc*KaNXNY_iJsyic9gcHR_T=4Rp?wMnyTpqVRC1Kmt|H|cC$w) z6pFt5T)bmOHkfQL*o&&bbC_OtZa6Z}Lqdp5E69ZcdnYgO@O-W;HqNC0GFPcwEpjzC zD}3H8IZ?z4V}Ph*3=pI+h6cw_ZhBi;NYk@_mi>}k&Py4i#T|^%qN;`1#!TVNCT`HZyao=2g-d4S-HFn)hA$Hkm>VvfQaaHP3}{I!;5&|g z#`J=<)-f%%Sq-2N22#1CnShH2?AD_};jqfHjp+vJwUgUwT=zAlEaVR$=GX{}G?H!w2dLz3JZrRn+9_cvP+tab@;MN^o9bRrhYsZ_o zb)s=@5RG$#)i`szJ!2N^GYr=}rxXBxrElgfA~v>y?DR7g-Ub_kte!sX<%kW4*=0fD z{3#<1?_gRMEFHsU89n$)3>dtNDOg4^lMW_GY(*F)k?450eFb1g|J0zraN3!*)BMoO zSMeT|d--ZKgJsT(7y|?1fW4yV?6vvZukt=VAZFg9h(NgDL6Pp78Iwy*84`tmYmbhj znEgcq#eML4k!Dtw)yMSgWS^<49Ai{IHypn|f$Cb4kER{fX2Ik#nw^k%kP{xDW0F~1 z2B{r`SklnqGAGMBV>zlaqa&G%8U2WnIkY>G(hZSLxYNr+e7%PaMuT}Ccs&d$W?H2# zIE$?1dVV%Jr*euhF|7%fliId_(S|a(owo9h3Uv7V`DKth(^(TEsm!l0onR&$PBRBZ zK~D8qj`qfxE;Y@;tP|g)@{Npf>cCkUK8rERZkF&;IO!&p-@rGc1&Jp_YuT5xo5i`) zZi4t2zeSkkRv4*K;oFf8Fu9tYc1Pvqx7p7@4>qCY*ri4+Yh`+5Zu=)YSsPxVL@|$q*(3H-48alCI&jwrfww&%s%e8#ev8a7P*h}0|E!rjyu?C zk%7G)RQY54km#PC6u%x8EfjLW{Hf+^)v~BrCq+ItI1gLw+_hs{N84_N$EHDA_f-6- z4LJ_T8xlh{_G9+i4MnPed8ce00RxTt8)XMIa&4#uW zzNqrk{3Y8ftScPUkCKtKaIeG9@K;ol`KvH$Lo#+q;jh7(sY7v$@m_w;&ij}@DiY}O zGw39Y4BC%x+3Og8I?kV@xGR@7kte6L5#Pa#)Mn(8ajP|mWpsF4V92^_3&e}m0{uoN zAk-cZ1_&sOabq61Zt2S!$(*U%mVLpxROIig{JiKpl(d#ML{_#M>}_8D5&u}!=AXDo z{F&Ff$wBaP#lCy%!jJZNKGs6)`Dbmesq{Tky{(=9f^6&XiOdI|mekwDj zlLgkDLR-?v>Q{>Ey5#U=cEIV@h8W$fcJ;6PHZZ`w7vG*6lj zw~`hVXUxJ^2P-%ts8Ud)ADsO>DJaznbPOv?VX=lnOPthl>DVCJa=XJ9_EMyJVIg1^ zGSP~E*J#ZPxk+k}8igJ(<@n0ngv-(z1*4wzJ)%oD2MtKNsSM?PGbm3zE2H;|JJCj) z0uH@QYEr2}T3d2sQ3@qX>yacA>BGh$B%t+WM$Fl-mP>{*X@hjRDupEsNv@cPMXz*) z2#6|a6H~`z>P(6+XS#JqZmTs=RC8ck%dS9wB3)dbS~>$OS7cW>VRftuz+b;#UKpon6})a;EUfFu_^+IY#?WUTv4PearC5?Fscqh7Z}L{_9Y~L zgzsTmb@ppTgoAOUm;(_+y(lr#RZR7T>Kd3F^6UyF)H*rvS|bt;!g#f@4Y>|Wam^vc-a#f*eCO7oToXgT?htcP`bZXLbu7 z=pu5FY?W8U94Yw6l1}7#3BM|cl!cY9Jk85fb)FXI>7r;PPb({H^VE1;exYuRE_;MF zFhxeFa?dz5N4x6sv}u&u>m#e`itk(SZ(C)gvO7<^MyWSXSKEIh?N~ zB+d00)kUL@%2v6>aDdx|SLtQ-+5(aK=}R=)luy=jb&jnl2s zuydSlkA_ar+w=6!QMzlCj*rv(qG4Ca?;NG~KSK90h24JlBlIz*<9yoh62Cvm^aMzU zM${o;Xf^pvh3q=l$}*JUyMKuZCSCXCA=* z*R1^pu|K~#Pv2}3fYku~whdbCa$alw`h1?gCyH8K^Kp;6MLH)9O5^U$g^rO3J z5rBVU0lP=2Vw`>!9i{(16#^O{!wRJKD|!0GajFuu#P1?+^FsyNVUK`+@>oze`(5Mo zV$|#DIse!^Kuv^v_R1F@sd1Wv}feZbAC${ zzwD@1gfz1A+JdRA?N9ri(U3TDWo1n0iRbP)!F6Jx;W+j9;egFyS7i+A(XiX%VYTxn z;S=`DrOpr0dBW}R=E(C}FoUQWA$^?JM}53ulrKMJ|J*2kKFn=@dwkq6#+^9pG*yex zf=Djl_}!47LO$L;#@(~*&a+lrpdvyu6cw*^KHfRXJ!2e&3}V6WDp}!u(Qe3Cc|D@3 zC>?$@jPf;k){Z-#8s}IvT0hRqqN5xi<$)7?sB4^401wrl;4CaL#zzj0@(ttshG-We zZ=7!gNmtz{zd1C2%C`VM+I@m=6ZB~l820g7^ZfQ`lYEbG?74n-wXJhuJ0IUs+*2Ww zJVJB)Zb!9jStb+(nK6E6p6?1PK7Q{QzdsuG`0?`tdA={t9~tM5!H=9xN}fMit$?Rb z&0n79Ph0LKKWMvISQhqEPVguQLA6#MQ2nlduxA8rf|WI-xU#3szqWe>;0a(DTJOZB~6i2Ttd)hMT_R4dE`*OI-!_ZH*C(*C9qrEZH}9s^Az@FNgU7e6 zloA-{=c59DxBj4yzb8VEe^A8x;VJIsFv9GoRs6G*kAHqlTkGPm?3bUS-oola*Sqea zt>gTQs1;u?)`Npz<@tA(BmFtr{S+-lq=UvQ_`86fJ~k%t2&vosa`y-?M2hN$ea}3! zeS|%J`80i}E-yLZKG1^X0fu$_D^FnDw?(b5UQJ3>ES`u~C_l!wP^HN|`S~e!F z#L1u@%1f)UTM>;oe9|R7KIu}dufvLrl~p~Aw~c%9Qp=}=-mK;Ajyiy~ts0ZI2$juX zp1V(f6?F{b_@qwDI6u!z5uem8tn4XK`KnM+TN7x0^`KAMX{SY>v}+P}0>Cp1z;*%Q zlXkBfmG+#P!f`z~juttdCdt0yx`hnPYfe!WI)=H5SGtxK({c(*ea;7+C*^0QxO2>T z+Il|Y{H}PqtK5s-M~U34+^enUT6frbZgg*dww{~ao$f(ABkmp6bGQ2%>)GcXw4QHv zACp`0Jm$XBf`6y`F7cFGFQ|_^zz4CzdyiUGJJkiJWI(bk`_9a(0PskEpn_NzoVAUcQnyrM;l$>*hxzqgS6CZA>>9dx-XMa{0mw9z$8SGe9w zn_Lf4i@S;Gfd{H}O(scKrycgSqW!_6&EPWa&fCW$&p$cj~v_-Go8k@t6OP*Qwc94tvyya8J7_i zSkRWok$f46`*Ts1hik&{O=T)GTNle}?3p zp6i5yc>)u{rnAa_;Dbz`*5vHQtfxAT!Lb_VqefursK^e7bMCaH6$zPf1+^OLSiM5x z+Ks3=-h%XU66Qk#3u~lEa|~i30bk9b3lH6!QAHvaVKHkvj+}3>H>zk7P#rtHO2-MT zpbkp}=H@-YF{CrInzJq(Is6Ei$m@>o^(9c=i;3GS^D56dlXcL#GK$B4?L( zC+tYlF;^K*uZ|UI?@kw}JbX$h_yk=@BN#Ljl#vT5C&M*I%%K10#Su2o%g`1E8j4*j zKB?ghoGEbZQEpOj7FnBKc!nLN0G!PU*^X6XV4`D7!ZD)?R#W86INj^=gJ!QHD;=`c zG@@j|8mujULI=*JJKkehk!0LFi{fB}DP>CYCCqsUu(tCFDe?$Zu%42xj|U=z2<7=w zi4OTw=+bZjE~H}&5db^nMR)obgOogUj4cr(ksuXgl2#6q2_|~@c7^i?E#GBUV39Go zsMgVIEN*JLJWHmgUyH6M_!nlNp?a|QWCQ#NFMQDW8lf;sm1?$FR z!6o=K>$^028dA#gc-)ZU6?{g+<%|PvBNQ5U92pSeTlG17p4VMLIWX211y|B}SdK|y zv?+;yD#lpbni(fMuELj!@kLxs4jnqL;2KH_s;}+lW=F?Yu&fx@;yMDym>l>j=JLP| z6vv1i4x6NCdcHfKB0%?BN$jrZYx-uUBm(MaRxj3>Fn7BzOyNMv8`tY?PdsB2gh!K{5 z@(_8O)p}a8r^k$&q1C1#>(#?_PT9HESYI*&C)w#ovb8Q_aLy71kL5WiSxA1O;c+}6 zP`Gx@O5YL{PYTqIF3gc}*i!VghDWi7ap>T-v`LxypK92JpV1W|DWNv%{B%6WA=`zY zliFa!PSD6NxEa`mUy_S0b}|yGirG$oRS$zq72S#6DgqtK*%v73^JHo^F%@^5EXx-lFpeGx5+Vw!0ni$YBb1yq_^<4 zMm6f4Y=ukX6DKyQ{;Pm%ZO6fCl`}^>-^JgH@Hf0Swl+$+3jRq3Id+@fPt}6n0HX%w z%E)Wb2l$tU_wjFXuiuJ=?EZv`|4^i;A$ANaMqoWX*SD5lBi>H5cAM^eL z6t!+EmN|1((8FNb=q?HrwRH2Y#TrQ269ka+@d2L0J zYY&`u$+}#9;ioa z)kV3e(8Lrmm8uDumUGSM66bWYx%W>OUQx-LrjIFPBs6L`4m&?n6SHK0KRrJ&Kc))$ z^7P1Afu(uUXx(9Reym{9TrK93Y%z}&EE$t0l;3o%6>%&9c;irPMAS;~YcauqK$lG{WVIyN zG26|4+3SkMvb_-0YFCbbYG0jeRI(4lg*B3(nK?tzL{C{Fhf%=4 zx1!2QkUdrOoU=kzR4?RQgDU)49Wr1v(MT`I934x?KtayLvYgJa_3WI9Qwg?4ceG~X zV}^3pP#0g&LN9A-<{3`glhJN7zJ?=2xKv0@A4L|0lS}wL1`ySMGnC$9lF~~|QhK=o zaMAiQOraO|3gT*MzlZ3o+Q9nt-hv&dsM~>Q^*d1M+kqM0!X213ggN(t|4LAex#@j{ z+es%$cVAaKg86~A+CfZ9VZjLM0<~R3sF&=*6pk-#rhh4%IE1Bxs7&G1t!S!Cp=B!? zXio+GDg!C397bDz;H*KM6KLNJ&wzVk-Tmk!A?s2wQV4a{1_JA8HLaM|K8P9q0@~&; z9K@`E-&3DLZ|5MQe#PCadYX%TQo35MZiQCw^A@CVk+(1fXB&!#aj{<=Kr8c?1^nuh zr0c*tUUdYQ2mIO)KKpQUvAbC>*UO9Vz-+Htt}hPwCrG1zi@lnczP`|Tg)RmTyz15b zs#kpgUlvGzTraQ{$MM(K1RkM~_%*Ws8ypa?)>XQ72;0fcbSzT1Z5VfU4jg!z?DGs_ zAccE;US$~fvSEYd#sFULEHCohj_16}lh{))R|Wiv6sK^2QyAjtK9H5T)31(5tzOlu z`7%f0ORrpin6r}3fdVpuU4iwy(b*l4^9ccQqZiH7r8DBG#A|>{N?Jl zk2|v|K))GM*gZLkAT*v1_zU=eOaDBKzub?1r0`*X=|?FJwr2n@NS6zJWx_>%iS`ju z5b*58`+0_LrGuhfloIplEMI9Kz-0PW zvY=ys=%d0nEb3FDk%B;+>SOBLjXB7y+ZC(6D1%EU>Tv!!_~rl-SA;yiIOweELIdJi?eOb79Rq>oY4$8-;# zmGouolk_$0m-J0)ADDhfwU;Q>SWVIiRKA#hR*E^2R*MrQJz1=lG%EVUtKt-Kk+@3I ztHrgFUN5#wdb1do^dYfV(!Jt&u^$jGikBq6U%bWCb&cyr_XM$A(jw8~+U~kl@=Te( z&2^{bnKD1%8k9U!=7(GlN}eh6J6(@Ro+xt@?bQ|6y?y&`$0%Oo?}wxGR{Klz0Nn(+NB`ppt-B;7kJGPPnlS1@z=Eq<5wV zR}u){02Ox;sD1=ZEJrbctS-WsAflM)T82rkHJI$W041&M%LYJOKqvn8>I&WVJ`e z@>#4mHnuhzUW>Zd%6?zt$4SI~lcxhlC4TO|$3j~w-G4Q7M%K!ZiRsf{m&+`_ zEmNcWDpuKnz~ahZga7dAl|W%-^~!;R$uf$lI4EIk3?ryIC}TXYW(0;0`IS)TrpP}t zglbN4Rm~aBg2TZCuXEfjpuhoC)~>H#Ftz@S>Dn`9pMU{c7+4fO0Z>Z^2t=Mc0&4*P z0OtV!08mQ<1d~V*7L%EKFMkPm8^?8iLjVN0f)0|RWazNhlxTrCNF5O=L$(|qvP}`9 z6jDcE$(EPEf?NsMWp)>mXxB>G$Z3wYX%eT2l*V%1)^uAZjamt$qeSWzyLHo~Y15=< z+Qx3$rdOKYhok&&0FWRF%4wrdA7*Ff&CHwk{`bE(eC0czzD`8jMSo7v#dGI|cRk)Z zs-;iqW~MdKn$EVyTGLj3!pLc^VVUu~mC-S7>p5L>bWDzGPCPxXr%ySBywjSH8!T(g4QQ%tWV0x-GTxc>x`MRw2YvQwFLXi(-2*! zpH1fqj&WM*)ss%^jy-O~~=Jod&rs3`p z^lQh*xx>$V^%w2Z&j!JV31wR!8-t%AmCUa;)Y-AU<8!|LS2%021Y5tmW3yZsi6 zH<#N!hAI1YOn-O#a+>1^Y7Vzo?Ij0y2kCaYgRP(n3RWNMr&c&bKWjLyBMtUYkTz4B zLYwF=K`m0W;2OEkJ}Z|4-hg4pPhmj~dVa#4Ok$m&rpk#@lE-jhgrW+yQw*XxjPPMN zp)uTkZ2rB2)Iptm9_-aTw@Z(0YjS%(ZC7XqyKkA{^nV*Rl(6i{Anhz^*#)h&3?SVS zPA&|N-F%x}bT_Y02wE{;M?c*o$Zt4%`65BuLv73GUb;`vqYp@vs~HH{#%O^rt!`;^ zwx}6PcU04I)wE^0nqjJ%ISH|nPKNGusC&;&prdD0*HW{FnNjt#TH4J`s@rDeCOZPu zGcS}&{(tsUA6${O?7Rk>-W^^Hh+{QwxL7Jkd+C0K`so2dTfRpG`DsAVrtljgQiju@ zLi;Ew$mLtxrwweRuSZebVg~sWWptaT7 z4S$#u1s7ZBTHa52W{3I8m+)pOWYR>19WXa<84{8gUtj=V_*gGP(WQby4xL6c6(%y8 z3!VL#8W`a1&e9}n@)*R^Im^+5^aGq99C`xc8L2Ne1WWY>>Fx9mmi@ts)>Sv|Ef~2B zXN7kvbe@6II43cH)FLy+yI?xkdQd-GT7R<$v9kgDZhDVGKTPlCRF1mA9S_ov&;gF& zAH@(u#l-zKg!>k+E-Qjf-cLWyx_m%Td}$9YvGPN_@+qVd*Q)5cI$TrLpP-Mh>_<6k zysd!BC`cEXVf*Q0Y(UgdE^PYo5;;FDXeF@IGwN8mf~#|e4$?Ec!zTJEQCEM2VSjC; zWf`Vg*;)ahW;Gxob7z~`W~NXn)s)F=lj^v3T31JP-BevIkI)8>oH5+-jyAK;GP8!A zSKV>V#gDFTsa`xXt|1Uc3i&PSgl%D=JEwjW^F5vD1UeDg2OE5$hxnCFVvbUDpIEl_O19mVOmP_8bVz-kCsYEtX_1Ovb zj+KS444hDHKJfNHwq&hQ29#QGU>;3PSjf!&) zYr_T8HS#)YF@1v9`RQjDr1yF0XiA~y=y{YGCGep{s6iwTA*ge*SZSH9K;{Gc1^NWT z@{>XOdHMwf#oVVr5e4%x1I%+r&CEE*Qu8V$tmu5mm?%|OR}{L++~wCzm$RIp(7a-4 zuUW|Jw)8G^n5G$)e{tS^RevIWx`v3t^JKqe>w9y09=jp{Kg*@dXXrZU#?;Tc<%xwM zJewbXg?^RAe+_wMk=A>m=A@r~0~#Z6hmh`q^b!Z`=jde+%aR2&hxQ>`<7bXmDk+!% ze+$*7qh)2_^In4P`ktr>O8z!|UZGd$clcz~c=h>Hr~z=--z_oAmw!Nq6({r-vRRJz z0|mD#FZ{ls+p66(fA$X)`U?9cH0RlBfikrIP@yl=AE9!T32=5+P-i$<+jN!7%+FG| z&!5nrvTOegUa57UpZ*+hJA>p2ga0MxsK21E^Uo8!3b{#gdjViLw zDj?{%qL2b=fc}>G8GrHM04YZSz|%^HpkOH)4w1W41*h( zbOQ8mmEBsPEo@ObLg z93$OR0O5mpOMj_muJWzicd5+~DdKi<2U`M<%O>D6UC5#6I_&6n&lq+LidLWk)0^OY z9*xW4fM}}_(4tNKVhgr%baxmv1}d_H<;08!&5{N0g2W)&MMM!{5rt{6{~60ZbqGnt zDu5ToKv2X*M+0=~M6SR&<)ddMykRaD#Wt~>_t=3wq<=D6rYsQ@J4;ibrnTWEV_xiH znY-c4F?oiIdnZc;p4g2750m%IdkG@6bOz!c03W3^!@e}MkjzV?@Z_6Ck0S09y;xv4 zTzT4dVFJ}bQ1pW-F|*f4{BIQzPD0Kdvk|QP{?*Mzf6Q4J5u5wBBE`9VlR!DpSj`QxGz*C1KwY`uOsHURS@Wb04YUIC8;j5AVHYM92El2AI3|7!eaOO$$wm{yC zc6}sue43iB(dyLTG_^#o(%R@%3dOF{`pXhN4YYwamKKQzu%sUCvS_48cOEU$mW!m! zP=9=IitdXRXsou|$KQ-uyjWqQ}X6V7eYqT$w6p?A#KSdvb6cFIOR4q2L zNNghFd6ACRq1M@i@lB~zGSZZqriY;H1%C=h<@t9;uhDT<@L}{HO(kEVmC@_oXQ(0S z**-;H@pAPMql=DME;|u{PV`eSkr1cw8-cy+VdH~Tho_^5PQzI5hn1hk=oGB~D*W}B#^ZpzM3Zs;1Bsf0H=O>b*lMV|>Id?7De>`bbw{(os| ziidojmii(+J_T#jhg$0EF0t9a77uxgbgoE0g!SjKewv>2bop9*@$1i0N4&+iqmgc& zo1yom5?K6WxbL!%ch%M+eefu@$Iyq5p7+5aUyAWQ7g9q-`pFAWDVi$MB{=)pq@RtF zI-c-)A|u}Dh%Yu$A0KJ@nUJ?+p?~L6u+PukkXqb;1zKnw?ZnMCAU$*2j^CZL_F4f6 zAMEu3*y|O1H*on~MrSW(JZQTj(qC~jzsPRd?74SC6t~&Ho{dB|Y=>iK=<-GKd0seQ z2i;$T8Bdj+^cwz8-F(Mj1Sh?ABUYrpy39W}5TOdE+*bM#6<z)Ddox>o2N5DqtOG!qxx|%NBqc+6Fj^Fz(uu%!QGdXaA8r=)rLCl^ zE*&i&6g$x@0yt?#tSE}ciVo|C*xX<);bC`*gjXbdQe-WHg1wsXvs(d>ud+wQMn*g0 zivOoLF2tQhvAJ2?b)qO@SH#w$c$56?E{a6L*BFNL_ZP*zUEYT7Kts0@^2Hfeo@y3{rp4hK(U3pni(e5(n#Egj z{R-^BgMlcUDgtvJJ9-)Hy>pP4vE5+TX7MmA3PKQ#&Ef<;Z z3EAhC`=6xCvd=B|IeNLzE%#rd&&xiy-2Xa#L-x7l{_7|Jxz8>7!Xp~FFI(=%M7Qj7 z%l))?O6pmPiz6nW|1H4kBUC4nix*$<2{av@xW z8pXsPUVs;6JVT3+(1xAt?9Q3@Iqyu)%%8u%egjy8DR6vr^rrerZ%S*Q{Fc6`FJH6}@8{p6nQo%F$e3uUKnO zSQ}Q)_}#>HIS{p_QQ;x^w&N3pj&F1Hkiv+)I9^?SyjnF{bf|wGg%C(Lf+V!)h2xUI zd=T2E9mcN1L$QF^5g2*u_)h#xV5qoL+7?I^OWPS_a6JtT*$mPcAHy(mJmUtoz)Z1zp0^RJ zebf|pVGWIsQB0nO8D@fneP+6d6PT}AA2UVLt7UKlb7PprygKtn-5>!^V1XRwIr zG!}4+mn=`WBk<_rS~lAZls_hOj; zGnnAs;L$9uaRbuj_dhXN_<^afP)`ndO!qW}o+exVj;Uj$zv1Tc32vVWmrHP`CoJ`Z zxvp@$E4=rv{Dp%8tK5(97c5fP{T{ZAA#Omvi%lqOVetgT%V6phEDiQ6oM7cL#+QIm z<(v8kP)i30>q=X}6r zk(Ww~N);x^iv)>V)F>R%WhPu8Gn7lW${nB1g?2dLWg6t73{<@%o=iq^d`ejx{msu; zS`%=Y2!BRo(WJ^CT4hqAYqXBuA|4G-hEb5mu9WW%-NT3U(UDppMSsn9l$6&h9?gmEM$I+<+-sY>_TijW)x$|nBi1h z)8fAA*r|$B5Pu|>!V=sQq%3nUWt4@n=2a_RY`n-VPb6b*DOKTa%2XKnv9S?j^a|O^ z%)WoIYFQ-k$~-kfM`4#tTL@{|C6cZS=}|0_XNE5iXHo^R9{V{2#-J}cRcVM@rX?8S zjx421k{2wI-jLjNg-qX(4!wL+c*$)WrJ}VISa*F}M;|US1T2Ra7|u70n*8gwmk?87`Wa3d zmg9*C-c^D7FhJOiT&KBLrcyM-bquPcf@@-PQTVOpl8DM3LQ;XI7}^i1G^D9jrY|J- z9m#O+knhZ%oB&2J8piv$%+PsMui*-VMr@rE_kaBeK16#MW5`goHVLT3`>0J6An!!!qN!5A#Eh8;<}j}mcj#PFH!u)CTJEtO zSbxBxx|St!BoZ)Wj&b~-P8eeez$}_PZ;AQ|KROTh@U@zUZx}8# zz!$2vZ&t+AeM7ivvNU|RPyVLP+^CvXL2ZKX8TzNBbYyg+EbORaI;o@X!Bjf6RAnERF=+$>eOC%OUDW- zw7m}IbH1s5hd4b+YnHm4rL8(wt>lGVQtp9EI7tLmKVlO?^f3HDr`HIQ2KX&e!|5l` zo}>HOHhOZo>=xeKMqh4rD49!aAzH&bHN3Zt!QAaFkn!*fe84c9e1VS`9%Gz7u75G) z=4$w~bFzk+$2+f6^xYAzVKz4&sNsuWcm7KB28KxbB`IpiEkE7)Bk>&HKFdBuC`stA zwy~1i2G1o{I*lz9YgnyeZDgR{}rT%7+Bt3;T z+QP(koWLXcCK8kM1ls-qP)i30T?r=oZ}tNK0QLrx(G?t%tCCTFTcB1zlqZ!0#k7Kf zkdSS=y&hcen!76`8u=i82484mW8w=xfFH^@+q=`!9=6HN?9Tr;yF0V{>-UeJ0FZ%A z0-r7~^SKXVk(SPwS{9eZQbn8-OIociE7X)VHCfZj4Ci&GFlsOiR;iIJRaxoGXw(dG zxk43#&53m>S)=uTq|9>^v)ObhvxHhb=kS$=qTqy4rO7l7nJURDW4f$LID5`?1J}a& z-2B3PE?H*h;zu740{(*5&`a#OtS|ymO_x%VPRj~QUFfu4XL{-O9v0OB=uyFEst^tz2VT!z4g<2#lRmMJ`j5 zZM7xZ*AM>%2rvSpe(=Ig+{%mm`qu9D$$nuwfAVtg)wU1D1@Oa- z0qBDX0)tL}srdd3AKVr|u!4652w2`d0fsD36d(v8?%fw448z=eKw!vV=GK+cg<@B0 z$2aAJ0j^IF7?!T;tpbe1;%>zpHr&Lcv2JbrpgXly(as#!?0ARvZ(9Tyw9dPLBI6nn zUO(iIoc8&R_JI|#ma!w&AcT?E9qq-QVS__Pcf=Ea+u?_rK zX*`?w+8~YR^5P4}7sOkF9^v<)Wd+*~+BRU@A=_f}TNYc7Hi#bHH2iMhXaTblw9&-j z;qmcz7z^KOLL_{r36tEL;@)&98f?OhrwP%oz<(i#LEKIdh93L_^e1MUFzdwUAZf=# zX!!zWeTi=n`C^CXA?1cg9Q>gxKI!0TcYM;pGp_iegD<(`iw>T3#itznkvl%+;5k=( z+QA>Y9v3?#|5p?&G^NcjljeZ~g^f18y^% zJ9)Cd^>|=NijQzL5oimxJIZx~ ze9?Ss^Ty`ZaDtBpPPoAsJW(yH$N4T<;S2#yPeoF?lu&qNOqVhlu1EGea_2aYXH89a zp^|@L(Gh7>iYStriu4X0;c?T2YBH74HPSR?ZZItAvUReitVH^z=C?2`C}=rO7dV=- z77=68sE%uDQcf{6cFi77hpm&o07Yne+0~cxt zd5_*)sP&)@HC}ize=e%9#0xj(imzo}crbrYe63*c7RTYjDhiU1%Z6##t_Qui5BGbp z8h+wH(WFEnJTC%R=pic)GR)Vxl-NNqUE8ZG40R2ST?P81rl{~1FV5^e_8Pg(x$FW_6 z(mpMLKFJ(*W5>({#DW*QoCKbj>CJyx?{us_MShE|Mu(*hn_8mTv>ROv%chy0TJ@sG zvER$E`JN~loQ0D;f|Gu7Wz6bozzKCPos?s8CQ8kPJJs7yy@Vnhlrv7zVopqhG;I`3 zKjYvJ7U3Q84o~47P9z6EG=+Dj6AqqAR72W5+#J*NkpVf)wXA6$(M~T?7#4pzGDBrU zrkr3p#=R|)ud>4j>mb%X;#lOggUgWlJKjV=@*U0pX+Y^LM!$sbuI0$ zUt`oayK%Cl!#hQF;YI3SNlkxGOJ@1KaiDAZtx*2Fyo^^ocnPmE1pj}B4Ginrm^4J~ z!|BtndvF48(8e#Q6kA$3D>1Y1{L2vI$jLoLnr{z9x5`b*_v|~n-PWSHe8uW%yH#T< z`cH`UT(r|W(5-Vj%8|7vO#W9m+B*z5ke-^**-@8c=OH|!7f-wORAEh#sBvXTFf)U7#tJDY3v?icgP z5r0$X9X=7bAnoptzJsaNFMPYwu0@(H=-#2y^^5sqOvKwIyH!-zb;(QKk@PfQq47+I z7dUz*`sd9-{~xVfmQi)DcNc~pIQ@N2x8V=pmJijQ9B*g2o!7c0XTHMwhT@a7G|pze zhb(Hg8$Pg4-Pb%XEM%2lmFL%#%g!#5&lEY|J4viB!XvukTvKM^nL{7F zBd*qRt0zs}UjMM`Ylw+!t$vI8$EXaZr7@FzZ^KJ z_&c!IQYE_S`o=ru_YQ@ZKji&&ZnM{^Z>u`yh=sjdC-m_6F0GpS=ZmKvT=9Ok+Nne) z@pl4~9{T_KK4GfVuecoVUB`;Q{oQ%u&XHZYMO=Ztmo%+onJwR@T@t(aqe;8={`+}H zo<2xqoONV{grz|;(uixW85nF(^k1FaI4?pLd`Bo7149yUu*DWdzDyEglEi#dS@2zt zKvzr$UKxRE$}t6qs-F2KvWP45(9LR72B~rfy9jYT8v}y{ij9{h`!5KQjR9TO1+>c< z=wo9P`NtX{Q#2;OSfDBkz7`7DIXwqdX@sIGS{vdbpM_e|$QSt7qo^vJJaJ((EBG3W z$sZSrGDjPLbSVNa_TdM)QWkuwJeu;rW2}Sfm!PF6G*p;ir0B{<{sUU9M>#WqH4lTN!~PgB@D;`rIdQ#hRw z?T|`wO^O=zovKDMVjuZHAeratT0Q-HK<95;BTTtc%A5Bo>Z{jfiz& z$W5u4#(O_eLYQDY_i&xqzVd#y&cR>MOQU@-w1GN((w{b+PM;=Y3ndBGVv|>|_=ZIC zB^E2+XVovHYl%!I#}4)Pma4)hM2Ly6E;&R5LmOnMf-Qz43>#K*j*LSWoYxxIR5Csm zuHXA8{`YgmqApC|BgY0wGwj-im6rmS^jrAbN8^PEIHj1WH#AVVuUA2HXj&Vm*QD^# zWX8+sR14XM!@6HrfzFpcC$ZXlhjA{{oq5cs&VRBUX2VwX$fdjO~`3n~1})#Bxr5Vh%KwFov=k zW;Jy5qsvC$lw>?*BsoPIo}YgJN>u)C^4Abbjx$NW@n5S8aN_T0BeAXWjz#dQ=3v*# zRQrjH1%R&krxBrfITop};aQdE=ZRgLN%n%+^y5BOs|pO6lg|I3prX{gSgQuRK%177 zlE#t+nHbT~VSO995imTaX&SCB&pgp`Izkg}-NV zI%~Z42T+^_9-gw;yOI&!oZf=H(Cot~)w4^gX&q(zg`7ekm4un&?FuaJQKIrLF$<_% zR;ok9K%L!NlTYgW8?uhX&TS?ojtu~oLm(`7iY<5Ci@V)7+gRHbb!o0OipVh)`vKW) zp9OVLDkaP@Sn!ZRa zpfwY36ct~JlEsS7_Dr%e0UL8^zRSsSv3K)+n$b@Xq9*^-p|AFj(*#}L-%5Z}D@Zl%y2gokn7l;Zr z3CK}pP8BDR1$L~R{R^BwKH~@v9m;O_$00a5MMXTe!u0FG^=2=_f-XZR!DQeQ`5S_$ zO>mOUF8Y-Wfl3P|Mk-VDsBp`X&=kMQl<>nt9$C)^A<4v@xtW>qn@`Z)`|gCedb?$A z^S(N0{?3!oy|^tx0p&<-D62OWo$gVhEodpMi;O#DM7P>i6bnTf$_=~8)PdQ+^h30pu>DfM=LQT20!&5)= zGdR6}f=YHb45NFG9?dd44$Dm~B6k3w1%E%atidmZ`Kaw4q&8yb+5=wqe`pXWH0J%);cCo710p3&(EMuAI{aKjT^Z!u)Eq~b?HpnrSE9ftF4Ibs#HFpuPR zyT$g5JIX12nSw?q!}IY^iHMikUh8V)gjx{JN@8Am6<$2Mz^mHY*_n$LNj)%w6Vs2|Kwpq;J=(VFf`y)>|;A@J@8mL zpw=k%oRd`%OdUL*1^Bd27^<|sYM9NqMxOfyc56FSDcG3u;oJKCAOsBvw)JlyBt5jT zQZ;fkKI1}9MJMtnCEG?ZUph^R-lV{%Av1S91fH#pacM-EI@93$Z)d@UUxu6ruJMHVl=>YjT8reRi0SjW8t!4qJkSw2EWvi_K%!>35@JDfw9#W$~G@9?4ubk&}M9<~>f3`r6~|Hun&D&#w^ zZ2xrK!I3O(3uNXz*JhWWdgESs3jPCOS_W_J;0ggAduavgNUuLi`PfS*0$=1$q$C-# z>ca0l=Pm+p9&+rJQNFKvb%8vn0!qW9SGnIO&tjv!kv980`FquGKanhc(YAwQTGx)(9c1fRnojjxST~<*=y|?=9V1w`t~7Ag$5h)P#FwB7FM=E`e^youj?Nh^d}|GOC7mPW z_H&16WtD5M9H)i@@=Vzo^f`%yIQZ-qGuCko?CP8h^B$X|UkaKazJe>9C00F82u$Iz zFOjPU5)>;*KBg9UezT$OL$aW(Ogut^COwjSO2!@-ZbW#lHVfb_k?7DlEGcbl^tn{p z#+go${sx^TPB3R5272wadT(x2lACj6Y4~LktAm z<+#pEqlksdo%9?Q29%rP9C+LM*WZM-N-e*wX85OOu}J7Zrt%9iGjxN358Fy5GGaNA zlr-b*b{4zqiK)A~_jjEnJhRaVOdID52{6I%oS^X6)EYS(>ZE6NKd-S?F}lIJNYkBz zX=;apb)xyAi#nMFCj#Ex($CGiR?oF|gei))16?8E-mB*}o2=$UtMDZxq+&Q?liP(n z&Ni8pBpgnCai7%!7$wG2n4{^JeW)f-h&_$4648~!d7<~p8apf5f~7e0n$lV_qbrLM zH6T|df(D0@=>WA5f5yN)2BIZFqObOK5I*vhD*2~PZSt*83>fM))aLjXIEokDF;KGw zZ_75?2$lhYW)I_!@r8QpYKr4p27lOeG~ESg#8)LE@pH;oozO*hv19;A7iT#2eow_h z8?gZtDstc~s|f{hFXH|~d~zQ~z_94FB&hp$n~Uv_DB!2y<6&VqZs>-fmUU^yuJGdJ zNCHP?2Q+FZr?J{^_M3`92rOWnrL2vymWZ&0dYxz>Kv&GXWgwxTKz)<+J43r&!q}II z1DmfLl8nu-xGa?TgsrX45d}j{QAC!m8iO1JU=|Pb8D@9FE-V0hJEA?F)srec5$GqD z8(`^KQozt$N;6ts8^+R_uiy|d8MO=#Jvd3z_#2aHXjF94XkEdq3myI_UvT|r>1&LP zU*Mm7Fk}T$qbutLyH`@m{L57Mlkq!hAMe>2-o(8*axogLh^b!!{|amH_{Hrdu!4kWol?jSB%l2>w;Jry$!mf_nbz9_B1#8bWJwL@w!No42F zZ!YAr(^WO;wuxHb`%ZD(qKIOW&)L%j)eAUf-WERo1D?D~FV`np( z5x$@RPj8}2Rbm<>mRjfuPFJ`nN>>ltyp;oE9#K9IU>+pE$;Cq!IYr!NXvc_-MDFXBXW=Z9LZM(k9}OKqEKn5 zMk4%l_POO{UM$2M+YvQV#N~$?Ycqe>LbTz9ur0(-Wp!^8a^GDh7h{U~8h980RG|9E z6RPnEU0ccY1fEIdJfnZ?3Nl4X0Ag>*m6>|oajhbexf9~a8(K`2Ys~o)z{jnuOj93V zg4L4K@x2Dewt5Bok=03M@JIhBSWy2hwxcxRv7ukj`8uYPGrMdH0q!`qHJ^xDQ_bLG ze*?ZCvMv^t`JI7rlqLPEo^WJ0b^>d@C~mI!Zv)-ljBg#u;uvw%ZXMqZsz8Mxdtvbh zbK^eGn90ynsgjzKUOl)O`l3#-uY%L?tj;+Edgz+awV132>9Z-?mj*}u ziM4~P{Pc$s;}v&zYF)Te5J7W2!$o`EH|~F3NfA2NjF&~?@K5S*f_mv2@wT};{Sj`b z%#^~iJN17>qQ6aej~{ubsrhkBAD`C(j7{y)+hU@!^SU03F0Vu6vU3+>!lN@MLR}42 zLOtGS+@f@~=id z8&aK=-2+Pz*y)te)kF3xgyS?qgp@L;G(tM1&#!4p&Z$yX2<+lj>VWT1tiO4`_h^}* zQ@WGd`H9t~sH>+NT2d{O5(~BeYjG#5=s&k0J)iACkpC8u;rFz@_E-w@s0bAs_;b>+ zeR6?5n@}4wjy}GSL@%#%!-~chg|$Q=CE38#Hj0u5P4^Y-V?j(=38#%L#%l4={T(Rq z=x*H|^!EG)+e-leqrbec5?(g)@Op(cHsVg4*>F$Xb=BheCE*5LdSmdwZ-MSJs@@i{5t){y; zxAVyon;`>Rns;YH^`c&M3QdxzNaJl(Byct8a9v38fkXaJ_<=8oe=(6%mZ}CJAQ}2r z#oHZ)q;H0pGydy~@02e)oeVW*rQaD_OLr+)29*|p(gAHd<9*JxBnu0W61lNr+cO_= zX$B`VmPwyz9?FV9j3-@v0D7Z1Z}O;#KZ!@Gm7ZeKORcLQsPN8= zAZRd8VWqow?b1Kp8!AiYk8acC$>6xHuUZWkNk~?EqKsUr2$iixV=zYwM9laPwn)(W z7b-$PlwKh6n5^&Rs$#s&98P1ch#7FGNN6yU!Nwzcesp2Ylw~C1F@G^YA!PF|a$MJ+ z{!r?468ju$sWQLL=o~SYP|CBJ7(3`;c^t;TL4ScL$Pvv>N+5iugRLdmL zaD(CzY&3J+N)7MS)Jw`U8u*IevtEAUKN4~AiL82B$4Bl5oK#No3jGEW-o4`>c%G#8 z!h<$iX*efTk1lnM-d*7Db6h_94Y@IcQg@UJ1-g76_d9@vHWB%F55WG&!4DAy{K)Xv zz~7iiiq(J#G*Jdb2F>RKFnc3y>bIwlQ_Jhzoc4h(EOVm|0C}@X1v`lf-*wuaH5_H)kg%$_&tAkc`-Mk_04t+f0A_7=y20O8`7#X)4WDMOUpG*Z~n ziH5Zevf@*c28LS>z60h(QH92FxJHOKTj&>ep>z##ag+Tm*{QU<#Sk`f3)1y<#hgNV zkGRx3`qggo)?FK!Vd`6U+lA@MVk3QlsjDj#M*^!8JsEqK;p+%l%NyiKg#EX^3GBuk zlh2;u`5~mtZgY!005*{*dmF!OsrxVg*Rpvf{ieqF1ZPV6Mm4vb&^x06M8jn4XO#a* zXJhi$qNRT@M;;!sLq`lbqmcnAsSvSakQ{XcfmP-CU5_ini_P>t3m1P+(5I3tq028F zE8xAnu-M!FQ{&(q8oC{RXMCqw5&ri5tvt$=P|_J!+#m6Iz;U2BaX7}7%E%i{`jgjM^OfP1@K6wN+iSJ-2z7%MfLBS2$+zC|(5j4tu zq@N1d5n}UyXF>Bz{_%qT2O=&{@hkb|g++>5oZPMe%j~Ee^;OCr)Y7u{V4m&Qf@%WD zEUKEu%teX>pmF5DMIP1!>pm1D);32{D-N5>U4W*9kTO|z(Tb#n-@+j!vWj-S8aRy<(xvQm zwZ-#hyB%RQf|G(r&oI7iZhf^pG13lCEWA>mk}rI8IFlm%*!~#7;2xQps>NS2$f@g2 z1EoM!1ML(HjM)=bp>Z>u=jEM5{Ir>yFJ{m8hLv-$1jxB4a{4HNUhk+Rj5-H8}G za~r&Uoh}bQzyC)f6#o3mEkwFNhaD8_~{CW03Dv2Tbl4{ zAFamTS$i&ZYWmae1aCxVNIKrj+u4g3%D96}iqw8~HBu+gFA&*oRP5Z`MikjjDgYjq zkf0&#_Xj->@bJ>!}JGl=t1|~ zGIx9!u63fRtm^?=^0z=^H2SZA43p1deVixbphteFyrqycaRq6DLy2$x4nxgB;-Dug zzoN<>vK7~UxLPDR{wE0ps6mN9MKC>dWM{~@#F)ne0*ExL**#VrA^|@km1xCtF`2N( ze{G#meS3J5(rIs2)mwi>518)j5=wQ+Q`|O{br)MyktYd}-u+5QYQmrBU2ckYE7#Z$ z>MgHjknqi-2`)(Z+pJ?ah4UMg*D%PFgHFMnKg?{GSZZ*f3V+g@129FH@79v%&$&v32_So*G$-3SIp6 zYTlLgF2}s>)U;QtdWf5P&xikI0p1eg2{G!w0+xXNuYf%n#X#fou8}EYvAw$zmrjK&OZkS!$REMr$*aG zyPPjsYd_SXp#Vt9NGI*R;-*4~Gz)&7!zq>hh7)i?8PzCAAv(pNcUGlPNf^OXS$=bx(V#ji2eMF6q{U@ z9?ldp%YEsl;)d%}_Qs81OX>!2>kyChh!-n0Xd@2C1cI2qkRk&b4)(?@KY|?%qMoYb zEi7l}n$O`v+T31;YZF(;FEwj`I8Dz*9fbKrE)8#&?joolVY~3YbZuJwfRt4-kCOM; zcm34HXKH>;a?joGLqjIBG|B??@rS`LSU(l!vxSyfKmGa^x5&S$gvrsrlVT0@Yw#bP z-3#zdbm1;n!DpT@>AnxkZ4llVa;h^fj?R3uN5?-F)SLb}a%TBE=HM5_U*{K=ddu;L7kJ## zqyyGh;WY5rpvMm)$*xZHv!CUlc{zU8huQp`KmQT*yq*ugOu_#Kt-kRa+ODx`Va(;{ zLMO*lsSV`U%+u>-R9GmwqgWulP#>jO9|V60TBE z5ONjntHY2V_MmDJHr3CyuL5X%IlQKbDRch~>EBrwAM? zvOJj&z#NzlWa*K*VEZgjP#cAQ-HRG&mC)aqyjY19GP$U zSKm`d_gXzrLE_^a!9R<~vT9n;>{y3F`!rB%M5psN(yv*%*}F{akxIj9`XBf6jg8a| z^a*Bnpt%;w7P)rXQ8ZkhEt)_RlV=QxL5Ub(IPe9H%T>phrx_UNUT(Tx_Ku09G2}!K($6 zk&bmp@^oUdf8qZpAqrEe`R@M|WEk$lzm$X=&;cRF7^D#Nd;~}a8z$(h7q%A88yb=# zVd1n3r|vPZuhe!9QR*ZtnjELX5i*NoXH%d1E1O1wmebT~HX0F~DbFxk=J^<v|BCiebRdAHYXxOo$YS#BHYecz?S6CX@AcF_k;#_IF+JIV*5|%lV=Y;Ql?=b^ zt}1qN)~qaKnz~KZRf9Aa7U5S&Opz~;SF2ojOSD3HP8WYTbvlEyYK~);#wr+UO8_Sl z$-Yx3B~JYU!uChjzf0v1TKYAtsRkH`QZeF8Q$_`7iPJ79{8V(jbX4T=-LF59vw>au zY6LS|t!~Zz>*ops1&9o5w z3lQx+lhgdg^4d0r-%q!s(A$J%XYhUx~)v|ptx_cU#?44pnz*s$G%3=wh_01 z5l7f$uM;P6oqhM8F|$4h0me5--syUE%vI)HuhLv@kL`s1eP@buw&}80Umf5QOXBlP zAY(8r9}paD1p*&Bir^3<@3Cc4Mr>EpoDHghr{U$hcD8$^OZ6bZS{UYhl_*Otp}Be} z-P^9U7tc!@aodKCp{~TV6o}?M9xG$hN$Kr>|7e~E4mJK>_yjrqF@Kk1;fHw1PP`UI z1Aoa$7yGRMrUVO0M9$rM;=Glzi>SO8!lqon9E_1^0b)CsR0%Nv-$st+be?a*qJkqI zUNaqi*6Y^E>qlHH+*M=aj?)y2r>RGkG?X;Rv!7JG6Uz=^g7B`jEKEvgUq)s3Fw|zFMdak((XwlUaSRN4hGMrH zn2xFaLH!t8txnTiQW;qUWd^m#<3zgCp(=5~i~xw9lU{R~o1qSo#Sh1_4W5(^hL%O9 zOauMH!uGL}u?hV!4V~#?F-<;)X<)4B$u1F4 zf=%}>{b#f`$Ixo^Du_42V6Wir?Muh`(!izQSV9Y3d-MCQT|9bs zIlCtJP7*;A%^1-=u(Laj97hG}uP6Hq0+DzAjB^|$CG(?e_adMTiO&^_9WwrW4H!ju zWEYrjLw<{fSyh-yiPOP{O;c|453fxkp`E;k&)d^wYK=ipbD_kG$u*Ro!kQJOppV5* zP4o#ab%r@RITbag_zHMKF5$z8fJd1L+D8G@m^`*H->XyF$E{x;d;A+T`A zR!1#O!ed)ai|TF054f1+K6 zTDH=fps}vL7=Yl3_R)o948I{CP*`f1v{E~-xX#PaLvb?#qQRElOF-pVuL>d8_�{ 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b1c..ca025c83a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf133..faf93008b 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. diff --git a/gradlew.bat b/gradlew.bat index 25da30dbd..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From a467115d06ebe9b5cddef0231b710a89e4483554 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 8 May 2025 14:24:33 +0200 Subject: [PATCH 03/60] Preparing changelog for release --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6259a579e..064b97b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +## [7.0.0] - 2025-05-08 + ### Added - Implemented GitHub Actions for automatic code integration. [#1237](https://github.com/ie3-institute/PowerSystemDataModel/issues/1237) - Added `CopyBuilders` to `Line-/Transformer2W-/Tranformer3WTypeInput` [#1275](https://github.com/ie3-institute/PowerSystemDataModel/issues/1275) @@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Updates - Updated gradle to v8.14 +- Updated PSU to 3.1.0 ## [6.0.0] - 2025-02-27 @@ -354,7 +357,8 @@ coordinates or multiple exactly equal coordinates possible - CsvDataSource now stops trying to get an operator for empty operator uuid field in entities - CsvDataSource now parsing multiple geoJson strings correctly -[Unreleased/Snapshot]: https://github.com/ie3-institute/powersystemdatamodel/compare/6.0.0...HEAD +[Unreleased/Snapshot]: https://github.com/ie3-institute/powersystemdatamodel/compare/7.0.0...HEAD +[7.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/6.0.0...7.0.0 [6.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.1.0...6.0.0 [5.1.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.0.1...5.1.0 [5.0.1]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.0.0...5.0.1 From a61df5b8cb5e8bc4602db7c2cb53bacf46d948e5 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 8 May 2025 14:42:15 +0200 Subject: [PATCH 04/60] Updating mavenCentralPublish.gradle --- gradle/scripts/mavenCentralPublish.gradle | 37 +++++++++++------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/gradle/scripts/mavenCentralPublish.gradle b/gradle/scripts/mavenCentralPublish.gradle index 8133361b0..bc0c3d510 100644 --- a/gradle/scripts/mavenCentralPublish.gradle +++ b/gradle/scripts/mavenCentralPublish.gradle @@ -1,29 +1,25 @@ /* Maven publish - start */ -task sourcesJar(type: Jar) { + +tasks.register("sourcesJar", Jar) { archiveClassifier.set("sources") from sourceSets.main.allJava } -task javadocJar(type: Jar, dependsOn: javadoc) { +tasks.register("javadocJar", Jar) { + dependsOn tasks.named("javadoc", Javadoc) archiveClassifier.set("javadoc") - from javadoc.destinationDir + from { tasks.named("javadoc", Javadoc).get().destinationDir } } - if (project.hasProperty('user') && project.hasProperty('password') && project.hasProperty('deployVersion')) { // snapshot version differs from normal version String versionString = project.getProperty('deployVersion') - signing { - required { !versionString.endsWith('SNAPSHOT') } - if (required) - sign(publishing.publications) - } publishing { publications { - mavenJava(MavenPublication) { + create("mavenJava", MavenPublication) { versionMapping { // resolves dynamic versioning to current version number @@ -66,9 +62,9 @@ if (project.hasProperty('user') && project.hasProperty('password') && project.ha } removeTestDependenciesFromPom(pom) - groupId group - artifactId 'PowerSystemDataModel' - version versionString + groupId = group + artifactId = 'PowerSystemDataModel' + version = versionString from components.java artifact sourcesJar @@ -86,18 +82,21 @@ if (project.hasProperty('user') && project.hasProperty('password') && project.ha } } } + signing { + useInMemoryPgpKeys( + findProperty('signingKey') as String, + findProperty('signingPassword') as String + ) + sign publications.mavenJava + } } - - model { - tasks.generatePomFileForMavenJavaPublication { - destination = file("$rootDir/generated-pom.xml") - } + tasks.named("generatePomFileForMavenJavaPublication") { + destination = layout.buildDirectory.file("generated-pom.xml").get().asFile } } def removeTestDependenciesFromPom(pom) { - pom.withXml { def root = asNode() // eliminate test-scoped dependencies (no need in maven central POMs) From 1a4eb8df4301e24eecea0c62b0cbb2b57875db81 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 8 May 2025 14:47:00 +0200 Subject: [PATCH 05/60] spotless --- gradle/scripts/mavenCentralPublish.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/scripts/mavenCentralPublish.gradle b/gradle/scripts/mavenCentralPublish.gradle index bc0c3d510..ccdebe9a9 100644 --- a/gradle/scripts/mavenCentralPublish.gradle +++ b/gradle/scripts/mavenCentralPublish.gradle @@ -84,9 +84,9 @@ if (project.hasProperty('user') && project.hasProperty('password') && project.ha } signing { useInMemoryPgpKeys( - findProperty('signingKey') as String, - findProperty('signingPassword') as String - ) + findProperty('signingKey') as String, + findProperty('signingPassword') as String + ) sign publications.mavenJava } } From cc4a9e84b30d20772d0830e8142903718649a1a4 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 8 May 2025 17:04:05 +0200 Subject: [PATCH 06/60] Increasing working version to 7.1.0 --- version.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/version.properties b/version.properties index 232aa364b..6840ab3c7 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,8 @@ #Generated by the Semver Plugin for Gradle -#Thu May 08 14:54:26 CEST 2025 +#Thu May 08 17:03:55 CEST 2025 version.buildmeta= version.major=7 -version.minor=0 +version.minor=1 version.patch=0 version.prerelease= -version.semver=7.0.0 +version.semver=7.1.0 From 6bb0e9cf9259092a6657fba95d5c3d2bab62f500 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 04:27:48 +0000 Subject: [PATCH 07/60] Bump com.couchbase.client:java-client from 3.8.0 to 3.8.1 Bumps [com.couchbase.client:java-client](https://github.com/couchbase/couchbase-jvm-clients) from 3.8.0 to 3.8.1. - [Commits](https://github.com/couchbase/couchbase-jvm-clients/compare/core-io-3.8.0...core-io-3.8.1) --- updated-dependencies: - dependency-name: com.couchbase.client:java-client dependency-version: 3.8.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c7ac9e64d..139c1bc2d 100644 --- a/build.gradle +++ b/build.gradle @@ -98,7 +98,7 @@ dependencies { // Databases implementation 'org.influxdb:influxdb-java:2.25' - implementation 'com.couchbase.client:java-client:3.8.0' + implementation 'com.couchbase.client:java-client:3.8.1' runtimeOnly 'org.postgresql:postgresql:42.7.5' // postgresql jdbc driver required during runtime implementation 'commons-io:commons-io:2.19.0' // I/O functionalities From 48921747c58d72d901aee46bf70ddb5d7fafdd37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 06:15:23 +0000 Subject: [PATCH 08/60] Bump org.sonarqube from 6.1.0.5360 to 6.2.0.5505 (#1324) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 139c1bc2d..5d5b84d1d 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin - id "org.sonarqube" version "6.1.0.5360" // sonarqube + id "org.sonarqube" version "6.2.0.5505" // sonarqube id 'net.thauvin.erik.gradle.semver' version '1.0.4' // semantic versioning id "com.github.johnrengelman.shadow" version "8.1.1" // fat jar } From c5d99d93c3dbd79c0b7d1e6fe4be2ac7a7a9fa33 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 20 May 2025 13:00:26 +0200 Subject: [PATCH 09/60] Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` --- CHANGELOG.md | 3 ++ .../ie3/datamodel/io/processor/Processor.java | 3 ++ .../result/ResultEntityProcessorTest.groovy | 32 +++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 064b97b1b..ec783414e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Fixed +- Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` [#1325](https://github.com/ie3-institute/PowerSystemDataModel/issues/1325) + ## [7.0.0] - 2025-05-08 ### Added diff --git a/src/main/java/edu/ie3/datamodel/io/processor/Processor.java b/src/main/java/edu/ie3/datamodel/io/processor/Processor.java index 194edd8f7..a97e20232 100644 --- a/src/main/java/edu/ie3/datamodel/io/processor/Processor.java +++ b/src/main/java/edu/ie3/datamodel/io/processor/Processor.java @@ -15,6 +15,7 @@ import edu.ie3.datamodel.models.input.connector.SwitchInput; import edu.ie3.datamodel.models.input.system.characteristic.CharacteristicInput; import edu.ie3.datamodel.models.profile.LoadProfile; +import edu.ie3.datamodel.models.result.CongestionResult; import edu.ie3.datamodel.models.voltagelevels.VoltageLevel; import edu.ie3.datamodel.utils.Try; import edu.ie3.datamodel.utils.Try.*; @@ -293,6 +294,8 @@ protected String processMethodResult(Object methodReturnObject, Method method, S "ReactivePowerCharacteristic", "CharacteristicInput" -> resultStringBuilder.append( ((CharacteristicInput) methodReturnObject).serialize()); + case "InputModelType" -> resultStringBuilder.append( + ((CongestionResult.InputModelType) methodReturnObject).type); default -> throw new EntityProcessorException( "Unable to process value for attribute/field '" + fieldName diff --git a/src/test/groovy/edu/ie3/datamodel/io/processor/result/ResultEntityProcessorTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/processor/result/ResultEntityProcessorTest.groovy index 320dc8db7..7a8dbf297 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/processor/result/ResultEntityProcessorTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/processor/result/ResultEntityProcessorTest.groovy @@ -7,6 +7,7 @@ package edu.ie3.datamodel.io.processor.result import edu.ie3.datamodel.exceptions.EntityProcessorException import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.datamodel.models.result.CongestionResult import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.datamodel.models.result.ResultEntity import edu.ie3.datamodel.models.result.connector.LineResult @@ -277,6 +278,37 @@ class ResultEntityProcessorTest extends Specification { validProcessedElement == expectedResults } + def "A ResultEntityProcessor should serialize a CongestionResult correctly"() { + given: + def resultProcessor = new ResultEntityProcessor(CongestionResult) + + def validResult = new CongestionResult( + ZonedDateTime.parse("2020-01-30T17:26:44Z"), + inputModel, + CongestionResult.InputModelType.LINE, + 3, + Quantities.getQuantity(110, Units.PERCENT), + Quantities.getQuantity(0, Units.PERCENT), + Quantities.getQuantity(100, Units.PERCENT), + ) + + def expectedResults = [ + inputModel: '22bea5fc-2cb2-4c61-beb9-b476e0107f52', + max : '100.0', + min : '0.0', + subgrid : '3', + time : '2020-01-30T17:26:44Z', + type : 'line', + value : '110.0' + ] + + when: + def validProcessedElement = resultProcessor.handleEntity(validResult) + + then: + validProcessedElement == expectedResults + } + def "A ResultEntityProcessor should throw an EntityProcessorException when it receives an entity result that is not eligible"() { given: From dba1737aedfa148362d945c40920a3384047cfac Mon Sep 17 00:00:00 2001 From: Philipp Schmelter Date: Wed, 21 May 2025 17:48:28 +0200 Subject: [PATCH 10/60] dependabot-auto-merge.yml --- .github/CODEOWNERS | 5 +++++ .github/workflows/dependabot-auto-merge.yml | 13 +++++++++++++ CHANGELOG.md | 3 +++ 3 files changed, 21 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..2605b5d32 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# Reviewers for Dependabot PRs +build.gradle @sebastian-peter @danielfeismann @staudtMarius + +# Reviewers for CI/CD related PRs +.github/workflows/ @sebastian-peter @PhilippSchmelter diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index aa7c41713..5ed781fd6 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -16,6 +16,19 @@ jobs: with: github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Check Snapshot + if: contains(steps.metadata.outputs.new-version, 'snap') + run: | + echo "::error::Snapshot versions are not allowed – workflow stopped." + exit 1 + + - name: Approve the PR + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Enable auto-merge for Dependabot PRs if: steps.metadata.outputs.update-type == 'version-update:semver-patch' run: gh pr merge --auto --merge "$PR_URL" diff --git a/CHANGELOG.md b/CHANGELOG.md index 064b97b1b..2f4b8956a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Changed +- Updated dependabot workflow and added CODEOWNERS [#]() + ## [7.0.0] - 2025-05-08 ### Added From 54037a1ac58d63c60579f4b120afb1f6a8ac9555 Mon Sep 17 00:00:00 2001 From: Philipp Schmelter Date: Wed, 21 May 2025 17:49:21 +0200 Subject: [PATCH 11/60] dependabot-auto-merge.yml fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f4b8956a..dd930a42b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] ### Changed -- Updated dependabot workflow and added CODEOWNERS [#]() +- Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) ## [7.0.0] - 2025-05-08 From ebfc31353c19e36c3d0a35674bbb30a2fd338e7c Mon Sep 17 00:00:00 2001 From: Philipp Schmelter Date: Wed, 21 May 2025 18:03:04 +0200 Subject: [PATCH 12/60] dependabot.yml --- .github/dependabot.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 467a2f6b8..4dd23974a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,12 +7,6 @@ updates: time: "04:00" open-pull-requests-limit: 10 target-branch: dev - reviewers: - - t-ober - - staudtMarius - - sebastian-peter - - danielfeismann - - jo-bao - package-ecosystem: pip directory: "/docs/readthedocs" From b03e85d147cf8c2109e5b2daa4cd2fda4e2ae17b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 08:30:30 +0000 Subject: [PATCH 13/60] Bump com.github.spotbugs from 6.1.11 to 6.1.12 Bumps com.github.spotbugs from 6.1.11 to 6.1.12. --- updated-dependencies: - dependency-name: com.github.spotbugs dependency-version: 6.1.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5d5b84d1d..c16c4c8a2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'signing' id 'pmd' // code check, working on source code id 'com.diffplug.spotless' version '7.0.3' //code format - id 'com.github.spotbugs' version '6.1.11' // code check, working on byte code + id 'com.github.spotbugs' version '6.1.12' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin From b3054298c518a39a93b5326758e38690d1e55343 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 22 May 2025 11:58:17 +0200 Subject: [PATCH 14/60] Fixed em fields in input models. --- CHANGELOG.md | 1 + docs/readthedocs/models/input/em.md | 2 +- .../models/input/participant/bm.md | 2 +- .../models/input/participant/chp.md | 2 +- .../models/input/participant/ev.md | 2 +- .../models/input/participant/evcs.md | 2 +- .../models/input/participant/fixedfeedin.md | 2 +- .../models/input/participant/hp.md | 2 +- .../models/input/participant/load.md | 2 +- .../models/input/participant/pv.md | 2 +- .../models/input/participant/storage.md | 2 +- .../models/input/participant/wec.md | 2 +- .../factory/input/EmAssetInputEntityData.java | 24 ++++++------ .../{participant => }/EmInputFactory.java | 10 ++--- .../input/participant/BmInputFactory.java | 2 +- .../input/participant/ChpInputFactory.java | 2 +- .../input/participant/EvInputFactory.java | 2 +- .../input/participant/EvcsInputFactory.java | 2 +- .../participant/FixedFeedInInputFactory.java | 2 +- .../input/participant/HpInputFactory.java | 2 +- .../input/participant/LoadInputFactory.java | 2 +- .../input/participant/PvInputFactory.java | 2 +- .../participant/StorageInputFactory.java | 2 +- .../SystemParticipantEntityData.java | 36 +++++++++--------- .../SystemParticipantInputEntityFactory.java | 5 ++- .../SystemParticipantTypedEntityData.java | 4 +- .../input/participant/WecInputFactory.java | 2 +- .../io/source/EnergyManagementSource.java | 13 ++++--- .../io/source/SystemParticipantSource.java | 2 +- .../EmInputFactoryTest.groovy | 3 +- .../FixedFeedInInputFactoryTest.groovy | 11 +++--- .../source/EnergyManagementSourceTest.groovy | 38 +++++++++---------- .../source/SystemParticipantSourceTest.groovy | 4 +- .../io/source/csv/CsvDataSourceTest.groovy | 2 +- .../io/source/csv/_participants/bm_input.csv | 2 +- .../io/source/csv/_participants/chp_input.csv | 2 +- .../cylindrical_storage_input.csv | 2 +- .../io/source/csv/_participants/em_input.csv | 2 +- .../io/source/csv/_participants/ev_input.csv | 2 +- .../source/csv/_participants/evcs_input.csv | 2 +- .../csv/_participants/fixed_feed_in_input.csv | 2 +- .../io/source/csv/_participants/hp_input.csv | 2 +- .../source/csv/_participants/load_input.csv | 2 +- .../io/source/csv/_participants/pv_input.csv | 2 +- .../csv/_participants/storage_input.csv | 2 +- .../io/source/csv/_participants/wec_input.csv | 2 +- 46 files changed, 110 insertions(+), 109 deletions(-) rename src/main/java/edu/ie3/datamodel/io/factory/input/{participant => }/EmInputFactory.java (78%) rename src/test/groovy/edu/ie3/datamodel/io/factory/input/{participant => }/EmInputFactoryTest.groovy (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d4cc181..8ce3dfdc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` [#1325](https://github.com/ie3-institute/PowerSystemDataModel/issues/1325) +- -Fixed em fields in input models [#1331](https://github.com/ie3-institute/PowerSystemDataModel/issues/1331) ### Changed - Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) diff --git a/docs/readthedocs/models/input/em.md b/docs/readthedocs/models/input/em.md index cbb562109..1e875e09f 100644 --- a/docs/readthedocs/models/input/em.md +++ b/docs/readthedocs/models/input/em.md @@ -37,7 +37,7 @@ Participants are connected to an EM each via their `em` field. - - String representation (e.g. name) of a control strategy - * - parentEm + * - controllingEm - - Reference to a superior Energy Management Unit that is controlling this EM. Field can be empty or missing, if this EM itself is not controlled. diff --git a/docs/readthedocs/models/input/participant/bm.md b/docs/readthedocs/models/input/participant/bm.md index f75407f12..57af584c8 100644 --- a/docs/readthedocs/models/input/participant/bm.md +++ b/docs/readthedocs/models/input/participant/bm.md @@ -107,7 +107,7 @@ Model of a biomass power plant. - € / MWh - Fixed feed in tariff - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/chp.md b/docs/readthedocs/models/input/participant/chp.md index 9be96519d..ed1898998 100644 --- a/docs/readthedocs/models/input/participant/chp.md +++ b/docs/readthedocs/models/input/participant/chp.md @@ -114,7 +114,7 @@ Combined heat and power plant. - Whether to adapt output based on (volatile) market price or not - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/ev.md b/docs/readthedocs/models/input/participant/ev.md index b9669eed6..21c191200 100644 --- a/docs/readthedocs/models/input/participant/ev.md +++ b/docs/readthedocs/models/input/participant/ev.md @@ -97,7 +97,7 @@ Model of an electric vehicle, that is occasionally connected to the grid via an - - - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/evcs.md b/docs/readthedocs/models/input/participant/evcs.md index 83d3218a7..7f187be02 100644 --- a/docs/readthedocs/models/input/participant/evcs.md +++ b/docs/readthedocs/models/input/participant/evcs.md @@ -63,7 +63,7 @@ station and has some limitations outlined below. - Boolean - Vehicle to grid (V2G) support, true if the charging station supports feed in. - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/fixedfeedin.md b/docs/readthedocs/models/input/participant/fixedfeedin.md index c83570a04..3c17a23e9 100644 --- a/docs/readthedocs/models/input/participant/fixedfeedin.md +++ b/docs/readthedocs/models/input/participant/fixedfeedin.md @@ -49,7 +49,7 @@ model can be derived. - - Rated power factor - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/hp.md b/docs/readthedocs/models/input/participant/hp.md index 2559c79c5..61c577916 100644 --- a/docs/readthedocs/models/input/participant/hp.md +++ b/docs/readthedocs/models/input/participant/hp.md @@ -93,7 +93,7 @@ Model of a heat pump. - - - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/load.md b/docs/readthedocs/models/input/participant/load.md index 4ad7f4667..f774f9b87 100644 --- a/docs/readthedocs/models/input/participant/load.md +++ b/docs/readthedocs/models/input/participant/load.md @@ -56,7 +56,7 @@ Model of (mainly) domestic loads. - - Rated power factor - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/pv.md b/docs/readthedocs/models/input/participant/pv.md index f91f1801b..db954e852 100644 --- a/docs/readthedocs/models/input/participant/pv.md +++ b/docs/readthedocs/models/input/participant/pv.md @@ -76,7 +76,7 @@ Detailed model of a photovoltaic power plant. - - Rated power factor - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/storage.md b/docs/readthedocs/models/input/participant/storage.md index 51c25f359..952995c5d 100644 --- a/docs/readthedocs/models/input/participant/storage.md +++ b/docs/readthedocs/models/input/participant/storage.md @@ -107,7 +107,7 @@ Model of an ideal electrical battery energy storage. - Foreseen operation strategy of the storage. Eligible input: *"market"*, *"grid"*, *"self"* - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/docs/readthedocs/models/input/participant/wec.md b/docs/readthedocs/models/input/participant/wec.md index b7d62498c..9ea86fdde 100644 --- a/docs/readthedocs/models/input/participant/wec.md +++ b/docs/readthedocs/models/input/participant/wec.md @@ -106,7 +106,7 @@ Model of a wind energy converter. - Whether to adapt output based on (volatile) market price or not - * - em + * - controllingEm - - UUID reference to an [Energy Management Unit](#em_model) that is controlling this system participant. Field can be empty or missing, if this participant diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/EmAssetInputEntityData.java b/src/main/java/edu/ie3/datamodel/io/factory/input/EmAssetInputEntityData.java index d001c405e..a51835568 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/EmAssetInputEntityData.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/EmAssetInputEntityData.java @@ -19,23 +19,23 @@ */ public class EmAssetInputEntityData extends AssetInputEntityData { - private final EmInput emUnit; + private final EmInput controllingEm; public EmAssetInputEntityData( Map fieldsToAttributes, Class entityClass, - EmInput emUnit) { + EmInput controllingEm) { super(fieldsToAttributes, entityClass); - this.emUnit = emUnit; + this.controllingEm = controllingEm; } public EmAssetInputEntityData( Map fieldsToAttributes, Class entityClass, OperatorInput operator, - EmInput emUnit) { + EmInput controllingEm) { super(fieldsToAttributes, entityClass, operator); - this.emUnit = emUnit; + this.controllingEm = controllingEm; } /** @@ -43,15 +43,15 @@ public EmAssetInputEntityData( * object and given em unit * * @param entityData The entity data object to use attributes of - * @param emUnit The em input to use + * @param controllingEm The controlling em input to use */ - public EmAssetInputEntityData(AssetInputEntityData entityData, EmInput emUnit) { + public EmAssetInputEntityData(AssetInputEntityData entityData, EmInput controllingEm) { super(entityData, entityData.getOperatorInput()); - this.emUnit = emUnit; + this.controllingEm = controllingEm; } - public EmInput getEmUnit() { - return emUnit; + public EmInput getControllingEm() { + return controllingEm; } @Override @@ -60,11 +60,11 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; EmAssetInputEntityData that = (EmAssetInputEntityData) o; - return Objects.equals(emUnit, that.emUnit); + return Objects.equals(controllingEm, that.controllingEm); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), emUnit); + return Objects.hash(super.hashCode(), controllingEm); } } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EmInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/EmInputFactory.java similarity index 78% rename from src/main/java/edu/ie3/datamodel/io/factory/input/participant/EmInputFactory.java rename to src/main/java/edu/ie3/datamodel/io/factory/input/EmInputFactory.java index 3c4590d6e..4e2535f70 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EmInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/EmInputFactory.java @@ -3,10 +3,8 @@ * Institute of Energy Systems, Energy Efficiency and Energy Economics, * Research group Distribution grid planning and operation */ -package edu.ie3.datamodel.io.factory.input.participant; +package edu.ie3.datamodel.io.factory.input; -import edu.ie3.datamodel.io.factory.input.AssetInputEntityFactory; -import edu.ie3.datamodel.io.factory.input.EmAssetInputEntityData; import edu.ie3.datamodel.models.OperationTime; import edu.ie3.datamodel.models.input.EmInput; import edu.ie3.datamodel.models.input.OperatorInput; @@ -19,7 +17,7 @@ public class EmInputFactory extends AssetInputEntityFactory> getFields(Class entityClass) { List> fields = new ArrayList<>(super.getFields(entityClass)); List> withEm = - fields.stream().map(f -> (Set) expandSet(f, PARENT_EM)).toList(); + fields.stream().map(f -> (Set) expandSet(f, CONTROLLING_EM)).toList(); fields.addAll(withEm); @@ -51,7 +49,7 @@ protected EmInput buildModel( OperationTime operationTime) { String controlStrategy = data.getField(CONTROL_STRATEGY); - EmInput parentEm = data.getEmUnit(); + EmInput parentEm = data.getControllingEm(); return new EmInput(uuid, id, operator, operationTime, controlStrategy, parentEm); } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/BmInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/BmInputFactory.java index 8a1c58b9e..dd17b0fb0 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/BmInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/BmInputFactory.java @@ -42,7 +42,7 @@ protected BmInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); final BmTypeInput typeInput = data.getTypeInput(); final boolean marketReaction = data.getBoolean(MARKET_REACTION); final boolean costControlled = data.getBoolean(COST_CONTROLLED); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/ChpInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/ChpInputFactory.java index d398f5a59..db7bb3cdf 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/ChpInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/ChpInputFactory.java @@ -35,7 +35,7 @@ protected ChpInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); final boolean marketReaction = data.getBoolean(MARKET_REACTION); return new ChpInput( diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvInputFactory.java index b2d492ce5..a24abad06 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvInputFactory.java @@ -36,7 +36,7 @@ protected EvInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); return new EvInput( uuid, id, operator, operationTime, node, qCharacteristics, em, data.getTypeInput()); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java index a0bc9e9c0..46b75a9f9 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/EvcsInputFactory.java @@ -54,7 +54,7 @@ protected EvcsInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); final ChargingPointType type; try { type = ChargingPointTypeUtils.parse(data.getField(TYPE)); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactory.java index f0b6e28a9..257c40974 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactory.java @@ -40,7 +40,7 @@ protected FixedFeedInInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); final ComparableQuantity sRated = data.getQuantity(S_RATED, StandardUnits.S_RATED); final double cosPhiRated = data.getDouble(COSPHI_RATED); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/HpInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/HpInputFactory.java index b01ede298..d075d64a3 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/HpInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/HpInputFactory.java @@ -34,7 +34,7 @@ protected HpInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); return new HpInput( uuid, diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java index c66cec08f..71af7c515 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/LoadInputFactory.java @@ -58,7 +58,7 @@ protected LoadInput buildModel( id); loadProfile = LoadProfile.DefaultLoadProfiles.NO_LOAD_PROFILE; } - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); final ComparableQuantity eConsAnnual = data.getQuantity(E_CONS_ANNUAL, StandardUnits.ENERGY_IN); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/PvInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/PvInputFactory.java index 6cb13b79f..70f7253fc 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/PvInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/PvInputFactory.java @@ -50,7 +50,7 @@ protected PvInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); final double albedo = data.getDouble(ALBEDO); final ComparableQuantity azimuth = data.getQuantity(AZIMUTH, StandardUnits.AZIMUTH); final ComparableQuantity etaConv = diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/StorageInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/StorageInputFactory.java index aea43ba85..4b9149525 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/StorageInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/StorageInputFactory.java @@ -36,7 +36,7 @@ protected StorageInput buildModel( ReactivePowerCharacteristic qCharacteristics, OperatorInput operator, OperationTime operationTime) { - final EmInput em = data.getEm().orElse(null); + final EmInput em = data.getControllingEm().orElse(null); final StorageTypeInput typeInput = data.getTypeInput(); return new StorageInput( uuid, id, operator, operationTime, node, qCharacteristics, em, typeInput); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantEntityData.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantEntityData.java index 971832be4..5304124f2 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantEntityData.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantEntityData.java @@ -22,7 +22,7 @@ public class SystemParticipantEntityData extends NodeAssetInputEntityData { /** Energy management unit that is managing the system participant. Can be null. */ - private final EmInput em; + private final EmInput controllingEm; /** * Creates a new SystemParticipantEntityData object for an operated, always on system participant @@ -31,20 +31,20 @@ public class SystemParticipantEntityData extends NodeAssetInputEntityData { * @param fieldsToAttributes attribute map: field name to value * @param entityClass class of the entity to be created with this data * @param node input node - * @param em The energy management unit that is managing the system participant. Null, if the - * system participant is not managed. + * @param controllingEm The energy management unit that is managing the system participant. Null, + * if the system participant is not managed. */ public SystemParticipantEntityData( Map fieldsToAttributes, Class entityClass, NodeInput node, - EmInput em) { + EmInput controllingEm) { super(fieldsToAttributes, entityClass, node); - this.em = em; + this.controllingEm = controllingEm; } - public Optional getEm() { - return Optional.ofNullable(em); + public Optional getControllingEm() { + return Optional.ofNullable(controllingEm); } /** @@ -54,17 +54,17 @@ public Optional getEm() { * @param entityClass class of the entity to be created with this data * @param operator operator input * @param node input node - * @param em The energy management unit that is managing the system participant. Null, if the - * system participant is not managed. + * @param controllingEm The energy management unit that is managing the system participant. Null, + * if the system participant is not managed. */ public SystemParticipantEntityData( Map fieldsToAttributes, Class entityClass, OperatorInput operator, NodeInput node, - EmInput em) { + EmInput controllingEm) { super(fieldsToAttributes, entityClass, operator, node); - this.em = em; + this.controllingEm = controllingEm; } /** @@ -72,20 +72,20 @@ public SystemParticipantEntityData( * NodeAssetInputEntityData} object and given energy management unit * * @param nodeAssetInputEntityData The node asset entity data object to use attributes of - * @param em The energy management unit that is managing the system participant. Null, if the - * system participant is not managed. + * @param controllingEm The energy management unit that is managing the system participant. Null, + * if the system participant is not managed. */ public SystemParticipantEntityData( - NodeAssetInputEntityData nodeAssetInputEntityData, EmInput em) { + NodeAssetInputEntityData nodeAssetInputEntityData, EmInput controllingEm) { super(nodeAssetInputEntityData, nodeAssetInputEntityData.getNode()); - this.em = em; + this.controllingEm = controllingEm; } @Override public String toString() { return "SystemParticipantEntityData{" + "em=" - + getEm().map(EmInput::toString).orElse("") + + getControllingEm().map(EmInput::toString).orElse("") + ", node=" + getNode().getUuid() + ", operatorInput=" @@ -103,11 +103,11 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; SystemParticipantEntityData that = (SystemParticipantEntityData) o; - return getEm().equals(that.getEm()); + return getControllingEm().equals(that.getControllingEm()); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), getEm()); + return Objects.hash(super.hashCode(), getControllingEm()); } } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantInputEntityFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantInputEntityFactory.java index 298bdfe92..d3a1939c6 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantInputEntityFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantInputEntityFactory.java @@ -32,7 +32,7 @@ public abstract class SystemParticipantInputEntityFactory< private static final String Q_CHARACTERISTICS = "qCharacteristics"; - public static final String EM = "em"; + public static final String CONTROLLING_EM = "controllingEm"; protected SystemParticipantInputEntityFactory(Class... allowedClasses) { super(allowedClasses); @@ -43,7 +43,8 @@ protected List> getFields(Class entityClass) { List> fields = new ArrayList<>(super.getFields(entityClass)); for (Set set : fields) set.add(Q_CHARACTERISTICS); - List> withEm = fields.stream().map(f -> (Set) expandSet(f, EM)).toList(); + List> withEm = + fields.stream().map(f -> (Set) expandSet(f, CONTROLLING_EM)).toList(); fields.addAll(withEm); diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantTypedEntityData.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantTypedEntityData.java index ab0bbe4b3..a57d814ed 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantTypedEntityData.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/SystemParticipantTypedEntityData.java @@ -80,7 +80,7 @@ public SystemParticipantTypedEntityData( */ public SystemParticipantTypedEntityData( SystemParticipantEntityData systemParticipantEntityData, T typeInput) { - super(systemParticipantEntityData, systemParticipantEntityData.getEm().orElse(null)); + super(systemParticipantEntityData, systemParticipantEntityData.getControllingEm().orElse(null)); this.typeInput = typeInput; } @@ -94,7 +94,7 @@ public String toString() { + "typeInput=" + typeInput.getUuid() + ", em=" - + getEm().map(EmInput::toString).orElse("") + + getControllingEm().map(EmInput::toString).orElse("") + ", node=" + getNode().getUuid() + ", operatorInput=" diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/WecInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/WecInputFactory.java index 5d8fbca71..a1724fcac 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/participant/WecInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/participant/WecInputFactory.java @@ -38,7 +38,7 @@ protected WecInput buildModel( OperatorInput operator, OperationTime operationTime) { WecTypeInput typeInput = data.getTypeInput(); - EmInput em = data.getEm().orElse(null); + EmInput em = data.getControllingEm().orElse(null); final boolean marketReaction = data.getBoolean(MARKET_REACTION); return new WecInput( diff --git a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java index 8a5cce317..5cea8a531 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java @@ -5,13 +5,13 @@ */ package edu.ie3.datamodel.io.source; -import static edu.ie3.datamodel.io.factory.input.participant.EmInputFactory.PARENT_EM; +import static edu.ie3.datamodel.io.factory.input.EmInputFactory.CONTROLLING_EM; import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.factory.input.AssetInputEntityData; import edu.ie3.datamodel.io.factory.input.EmAssetInputEntityData; -import edu.ie3.datamodel.io.factory.input.participant.EmInputFactory; +import edu.ie3.datamodel.io.factory.input.EmInputFactory; import edu.ie3.datamodel.models.input.EmInput; import edu.ie3.datamodel.models.input.OperatorInput; import edu.ie3.datamodel.utils.Try; @@ -96,7 +96,8 @@ private static Map createEmInputs( dataTry .map( data -> - data.containsKey(PARENT_EM) && !data.getField(PARENT_EM).isBlank()) + data.containsKey(CONTROLLING_EM) + && !data.getField(CONTROLLING_EM).isBlank()) .getOrElse(() -> true))); List> rootEmsEntityData = split.get(false); @@ -129,7 +130,7 @@ private static Map createEmInputs( data -> { // we already filtered out those entities that do not have a parent, // so the field should exist - String uuidString = data.getField(PARENT_EM); + String uuidString = data.getField(CONTROLLING_EM); return Try.of( () -> UUID.fromString(uuidString), IllegalArgumentException.class) @@ -138,7 +139,7 @@ private static Map createEmInputs( new SourceException( String.format( "Exception while trying to parse UUID of field \"%s\" with value \"%s\"", - PARENT_EM, uuidString), + CONTROLLING_EM, uuidString), iae)) // failed UUID parses are filtered out at this point. We save // the parsed UUID with the asset data @@ -199,7 +200,7 @@ private static Map createHierarchicalEmInputs( /** * Helper data record that holds an {@link AssetInputEntityData} and the UUID successfully parsed - * from {@link EmInputFactory#PARENT_EM} field + * from {@link EmInputFactory#CONTROLLING_EM} field */ private record AssetDataAndValidParentUuid(AssetInputEntityData entityData, UUID parentEm) {} } diff --git a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java index 3df433dac..48aba751e 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java @@ -64,7 +64,7 @@ public class SystemParticipantSource extends AssetEntitySource { .andThen(enrich(NODE, nodes, NodeAssetInputEntityData::new)) .andThen( enrichWithDefault( - SystemParticipantInputEntityFactory.EM, + SystemParticipantInputEntityFactory.CONTROLLING_EM, emUnits, null, SystemParticipantEntityData::new)) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EmInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy similarity index 97% rename from src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EmInputFactoryTest.groovy rename to src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy index 6a0734f07..113fc739e 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/EmInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy @@ -3,10 +3,9 @@ * Institute of Energy Systems, Energy Efficiency and Energy Economics, * Research group Distribution grid planning and operation */ -package edu.ie3.datamodel.io.factory.input.participant +package edu.ie3.datamodel.io.factory.input import edu.ie3.datamodel.exceptions.FactoryException -import edu.ie3.datamodel.io.factory.input.EmAssetInputEntityData import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.input.OperatorInput import edu.ie3.datamodel.utils.Try diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy index d94ff5b9e..f43e60e6e 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy @@ -8,6 +8,7 @@ package edu.ie3.datamodel.io.factory.input.participant import static edu.ie3.util.quantities.PowerSystemUnits.PU import edu.ie3.datamodel.exceptions.FactoryException +import edu.ie3.datamodel.exceptions.ValidationException import edu.ie3.datamodel.models.StandardUnits import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.input.NodeInput @@ -81,7 +82,7 @@ class FixedFeedInInputFactoryTest extends Specification implements FactoryTestHe def actualFields = FixedFeedInInputFactory.newSet("uuid", "id", "s_rated", "cosphi_rated") when: - Try input = inputFactory.validate(actualFields, FixedFeedInInput) + Try input = inputFactory.validate(actualFields, FixedFeedInInput) then: input.failure @@ -91,9 +92,9 @@ class FixedFeedInInputFactoryTest extends Specification implements FactoryTestHe "1: [cosPhiRated, id, operatesFrom, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_from, q_characteristics, s_rated, uuid]\n" + "2: [cosPhiRated, id, operatesUntil, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_until, q_characteristics, s_rated, uuid]\n" + "3: [cosPhiRated, id, operatesFrom, operatesUntil, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_from, operates_until, q_characteristics, s_rated, uuid]\n" + - "4: [cosPhiRated, em, id, qCharacteristics, sRated, uuid] or [cos_phi_rated, em, id, q_characteristics, s_rated, uuid]\n" + - "5: [cosPhiRated, em, id, operatesFrom, qCharacteristics, sRated, uuid] or [cos_phi_rated, em, id, operates_from, q_characteristics, s_rated, uuid]\n" + - "6: [cosPhiRated, em, id, operatesUntil, qCharacteristics, sRated, uuid] or [cos_phi_rated, em, id, operates_until, q_characteristics, s_rated, uuid]\n" + - "7: [cosPhiRated, em, id, operatesFrom, operatesUntil, qCharacteristics, sRated, uuid] or [cos_phi_rated, em, id, operates_from, operates_until, q_characteristics, s_rated, uuid]\n" + "4: [controllingEm, cosPhiRated, id, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, q_characteristics, s_rated, uuid]\n" + + "5: [controllingEm, cosPhiRated, id, operatesFrom, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, q_characteristics, s_rated, uuid]\n" + + "6: [controllingEm, cosPhiRated, id, operatesUntil, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_until, q_characteristics, s_rated, uuid]\n" + + "7: [controllingEm, cosPhiRated, id, operatesFrom, operatesUntil, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, operates_until, q_characteristics, s_rated, uuid]\n" } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy index 288c6be0a..2cdac0018 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy @@ -24,35 +24,35 @@ class EnergyManagementSourceTest extends Specification { new AssetInputEntityData( ["uuid": "0-0-0-0-0", "id": "root", - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-1", "id": "child 1", - "parentem" : "0-0-0-0-0", + "controllingem" : "0-0-0-0-0", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-11", "id": "child 1-1", - "parentem" : "0-0-0-0-1", + "controllingem" : "0-0-0-0-1", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-2", "id": "child 2", - "parentem" : "0-0-0-0-0", + "controllingem" : "0-0-0-0-0", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-21", "id": "child 2-1", - "parentem" : "0-0-0-0-2", + "controllingem" : "0-0-0-0-2", "controlstrategy" : ""], EmInput ), @@ -101,21 +101,21 @@ class EnergyManagementSourceTest extends Specification { new AssetInputEntityData( ["uuid": "0-0-0-0-1", "id": "em 1", - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-2", "id": "em 2", - "parentem" : "", + "controllingem" : "", "controlstrategy" : "strat_b"], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-3", "id": "em 3", - "parentem" : "", + "controllingem" : "", "controlstrategy" : "other"], EmInput ), @@ -152,14 +152,14 @@ class EnergyManagementSourceTest extends Specification { new Try.Success(new AssetInputEntityData( ["uuid": "0-0-0-0-1", "id": "em 1", - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput )), new Try.Success(new AssetInputEntityData( ["uuid": "0-0-0-0-2", "id": "em 2", - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput )), @@ -180,14 +180,14 @@ class EnergyManagementSourceTest extends Specification { new AssetInputEntityData( ["uuid": "0-0-0-0-1", "id": "em 1", - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-2", "id": "em 2", - "parentem" : "not-a-uuid", + "controllingem" : "not-a-uuid", "controlstrategy" : ""], EmInput ), @@ -198,7 +198,7 @@ class EnergyManagementSourceTest extends Specification { then: def exc = thrown(SourceException) - exc.cause.message.contains("Exception while trying to parse UUID of field \"parentem\" with value \"not-a-uuid\"") + exc.cause.message.contains("Exception while trying to parse UUID of field \"controllingEm\" with value \"not-a-uuid\"") } def "An EnergyManagementSource should fail if the factory fails for one EM"() { @@ -207,13 +207,13 @@ class EnergyManagementSourceTest extends Specification { new AssetInputEntityData( ["uuid": "0-0-0-0-1", "id": "em 1", - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-2", // id is missing - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput ), @@ -234,14 +234,14 @@ class EnergyManagementSourceTest extends Specification { new AssetInputEntityData( ["uuid": "0-0-0-0-1", "id": "em 1", - "parentem" : "", + "controllingem" : "", "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-2", "id": "em 2", - "parentem" : "1-2-3-4-5", // does not exist + "controllingem" : "1-2-3-4-5", // does not exist "controlstrategy" : ""], EmInput ), @@ -261,14 +261,14 @@ class EnergyManagementSourceTest extends Specification { new AssetInputEntityData( ["uuid": "0-0-0-0-1", "id": "em 1", - "parentem" : "1-2-3-4-5", // does not exist + "controllingem" : "1-2-3-4-5", // does not exist "controlstrategy" : ""], EmInput ), new AssetInputEntityData( ["uuid": "0-0-0-0-2", "id": "em 2", - "parentem" : "1-2-3-4-5", // does not exist + "controllingem" : "1-2-3-4-5", // does not exist "controlstrategy" : ""], EmInput ), diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy index aeb9f8829..845594901 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy @@ -21,7 +21,7 @@ class SystemParticipantSourceTest extends Specification { def "An SystemParticipantSource participantEnricher should work as expected"() { given: - def entityData = new ConnectorInputEntityData(["operators": "", "node": sptd.participantNode.uuid.toString(), "em": sptd.emInput.uuid.toString()], LineInput, GridTestData.nodeA, GridTestData.nodeB) + def entityData = new ConnectorInputEntityData(["operators": "", "node": sptd.participantNode.uuid.toString(), "controllingEm": sptd.emInput.uuid.toString()], LineInput, GridTestData.nodeA, GridTestData.nodeB) def operators = map([OperatorInput.NO_OPERATOR_ASSIGNED]) def nodes = map([sptd.participantNode]) def emUnits = map([sptd.emInput]) @@ -33,7 +33,7 @@ class SystemParticipantSourceTest extends Specification { actual.success actual.data.get().operatorInput == OperatorInput.NO_OPERATOR_ASSIGNED actual.data.get().node == sptd.participantNode - actual.data.get().em == Optional.of(sptd.emInput) + actual.data.get().controllingEm == Optional.of(sptd.emInput) } def "An SystemParticipantSource can enrich SystemParticipantEntityData with SystemParticipantTypeInput correctly"() { diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy index 9e4a8a614..83c5108c3 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy @@ -123,7 +123,7 @@ class CsvDataSourceTest extends Specification implements CsvTestDataMeta { "q_characteristics", "e_cons_annual", "operator", - "em" + "controlling_em" ] as Set } diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/bm_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/bm_input.csv index 9e690815a..86d29a1f5 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/bm_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/bm_input.csv @@ -1,2 +1,2 @@ -uuid,cost_controlled,feed_in_tariff,id,market_reaction,node,operates_from,operates_until,operator,q_characteristics,type,em +uuid,cost_controlled,feed_in_tariff,id,market_reaction,node,operates_from,operates_until,operator,q_characteristics,type,controlling_em d06e5bb7-a3c7-4749-bdd1-4581ff2f6f4d,false,10.0,test_bmInput,false,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"qV:{(0.90,-0.30),(0.95,0.00),(1.05,0.00),(1.10,0.30)}",5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/chp_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/chp_input.csv index 9f8be0765..e41e2d5c1 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/chp_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/chp_input.csv @@ -1,2 +1,2 @@ -uuid,id,market_reaction,node,operates_from,operates_until,operator,q_characteristics,thermal_bus,thermal_storage,type,em +uuid,id,market_reaction,node,operates_from,operates_until,operator,q_characteristics,thermal_bus,thermal_storage,type,controlling_em 9981b4d7-5a8e-4909-9602-e2e7ef4fca5c,test_chpInput,false,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiFixed:{(0.00,0.95)}",0d95d7f2-49fb-4d49-8636-383a5220384e,8851813b-3a7d-4fee-874b-4df9d724e4b3,5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/cylindrical_storage_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/cylindrical_storage_input.csv index 141286dbb..c3531deb5 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/cylindrical_storage_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/cylindrical_storage_input.csv @@ -1,2 +1,2 @@ -uuid,c,id,inlet_temp,operates_from,operates_until,operator,return_temp,storage_volume_lvl,p_thermal_max,thermal_bus,em +uuid,c,id,inlet_temp,operates_from,operates_until,operator,return_temp,storage_volume_lvl,p_thermal_max,thermal_bus,controlling_em 8851813b-3a7d-4fee-874b-4df9d724e4b3,1.0,test_cylindricalThermalStorage,110.0,,,f15105c4-a2de-4ab8-a621-4bc98e372d92,80.0,1.039154027,20.0,0d95d7f2-49fb-4d49-8636-383a5220384e,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/em_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/em_input.csv index 14d20cb3e..f8e8263a2 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/em_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/em_input.csv @@ -1,3 +1,3 @@ -uuid,control_strategy,id,operates_from,operates_until,operator,parent_em +uuid,control_strategy,id,operates_from,operates_until,operator,controlling_em 977157f4-25e5-4c72-bf34-440edc778792,self_optimization,test_emInput,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,897bfc17-8e54-43d0-8d98-740786fd94dd 897bfc17-8e54-43d0-8d98-740786fd94dd,self_optimization,test_parentEmInput,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510, \ No newline at end of file diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/ev_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/ev_input.csv index dfb07f22d..634773452 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/ev_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/ev_input.csv @@ -1,2 +1,2 @@ -uuid,id,node,operates_from,operates_until,operator,q_characteristics,type,em +uuid,id,node,operates_from,operates_until,operator,q_characteristics,type,controlling_em a17be20f-c7a7-471d-8ffe-015487c9d022,test_evInput,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiFixed:{(0.00,0.95)}",5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv index d5579f7cd..0fd01d91f 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/evcs_input.csv @@ -1,2 +1,2 @@ -uuid,id,operator,operates_from,operates_until,node,q_characteristics,cos_phi_rated,type,charging_points,location_type,v2g_support,em +uuid,id,operator,operates_from,operates_until,node,q_characteristics,cos_phi_rated,type,charging_points,location_type,v2g_support,controlling_em 798028b5-caff-4da7-bcd9-1750fdd8742c,test_csInput,8f9682df-0744-4b58-a122-f0dc730f6510,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,4ca90220-74c2-4369-9afa-a18bf068840d,"cosPhiFixed:{(0.00,0.95)}",0.95,hhs,4,HOME,false,977157f4-25e5-4c72-bf34-440edc778792 \ No newline at end of file diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/fixed_feed_in_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/fixed_feed_in_input.csv index 5d25bd18f..a9be543de 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/fixed_feed_in_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/fixed_feed_in_input.csv @@ -1,2 +1,2 @@ -uuid,cos_phi_rated,id,node,operates_from,operates_until,operator,q_characteristics,s_rated,em +uuid,cos_phi_rated,id,node,operates_from,operates_until,operator,q_characteristics,s_rated,controlling_em 717af017-cc69-406f-b452-e022d7fb516a,0.95,test_fixedFeedInInput,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiFixed:{(0.00,0.95)}",25.0,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/hp_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/hp_input.csv index 9ed79f8ea..5ea7947c1 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/hp_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/hp_input.csv @@ -1,2 +1,2 @@ -uuid,id,node,operates_from,operates_until,operator,q_characteristics,thermal_bus,type,em +uuid,id,node,operates_from,operates_until,operator,q_characteristics,thermal_bus,type,controlling_em 798028b5-caff-4da7-bcd9-1750fdd8742b,test_hpInput,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiFixed:{(0.00,0.95)}",0d95d7f2-49fb-4d49-8636-383a5220384e,5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/load_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/load_input.csv index 4a2d97e37..1825a5ed6 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/load_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/load_input.csv @@ -1,2 +1,2 @@ -uuid,cos_phi_rated,e_cons_annual,id,node,operates_from,operates_until,operator,q_characteristics,s_rated,load_profile,em +uuid,cos_phi_rated,e_cons_annual,id,node,operates_from,operates_until,operator,q_characteristics,s_rated,load_profile,controlling_em eaf77f7e-9001-479f-94ca-7fb657766f5f,0.95,4000.0,test_loadInput,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiFixed:{(0.00,0.95)}",25.0,h0,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/pv_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/pv_input.csv index 37e6b8666..3bdf961e3 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/pv_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/pv_input.csv @@ -1,2 +1,2 @@ -uuid,albedo,azimuth,cos_phi_rated,eta_conv,elevation_angle,id,k_g,k_t,market_reaction,node,operates_from,operates_until,operator,q_characteristics,s_rated,em +uuid,albedo,azimuth,cos_phi_rated,eta_conv,elevation_angle,id,k_g,k_t,market_reaction,node,operates_from,operates_until,operator,q_characteristics,s_rated,controlling_em d56f15b7-8293-4b98-b5bd-58f6273ce229,0.20000000298023224,-8.926613807678223,0.95,98.0,41.01871871948242,test_pvInput,0.8999999761581421,1.0,false,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiFixed:{(0.00,0.95)}",25.0,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/storage_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/storage_input.csv index 158e8bbae..d21595f60 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/storage_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/storage_input.csv @@ -1,2 +1,2 @@ -uuid,id,node,operates_from,operates_until,operator,q_characteristics,type,em +uuid,id,node,operates_from,operates_until,operator,q_characteristics,type,controlling_em 06b58276-8350-40fb-86c0-2414aa4a0452,test_storageInput,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiFixed:{(0.00,0.95)}",5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,977157f4-25e5-4c72-bf34-440edc778792 diff --git a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/wec_input.csv b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/wec_input.csv index 3305e2e25..0e717f7a2 100644 --- a/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/wec_input.csv +++ b/src/test/resources/edu/ie3/datamodel/io/source/csv/_participants/wec_input.csv @@ -1,2 +1,2 @@ -uuid,id,market_reaction,node,operates_from,operates_until,operator,q_characteristics,type,em +uuid,id,market_reaction,node,operates_from,operates_until,operator,q_characteristics,type,controlling_em ee7e2e37-a5ad-4def-a832-26a317567ca1,test_wecInput,false,4ca90220-74c2-4369-9afa-a18bf068840d,2020-03-24T15:11:31Z,2020-03-25T15:11:31Z,8f9682df-0744-4b58-a122-f0dc730f6510,"cosPhiP:{(0.00,1.00),(0.90,1.00),(1.20,-0.30)}",5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,977157f4-25e5-4c72-bf34-440edc778792 From b6f96fcb34636b9236e356d3165507b5ee653069 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Fri, 23 May 2025 13:15:07 +0200 Subject: [PATCH 15/60] =?UTF-8?q?Extend=20azimuth=20angle=20range=20to=20[?= =?UTF-8?q?-180=C2=B0,=20180=C2=B0]=20for=20PV=20inputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + docs/readthedocs/models/input/participant/pv.md | 3 +-- .../validation/SystemParticipantValidationUtils.java | 10 +++++----- .../SystemParticipantValidationUtilsTest.groovy | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d4cc181..c8617d055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) +- Extend azimuth angle range to [-180°, 180°] for PV inputs [#1330](https://github.com/ie3-institute/PowerSystemDataModel/issues/1330) ## [7.0.0] - 2025-05-08 diff --git a/docs/readthedocs/models/input/participant/pv.md b/docs/readthedocs/models/input/participant/pv.md index f91f1801b..c8b4bcb47 100644 --- a/docs/readthedocs/models/input/participant/pv.md +++ b/docs/readthedocs/models/input/participant/pv.md @@ -44,8 +44,7 @@ Detailed model of a photovoltaic power plant. * - azimuth - ° - - Inclination in a compass direction - South = 0°, West = 90°, East = -90° + - South = 0°, West = 90°, East = -90°, North = +/- 180° * - etaConv - % diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java index 790bca28b..35930dc11 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java @@ -393,7 +393,7 @@ private static List> checkLoad(LoadInput loadI *
    *
  • its rated apparent power is not negative *
  • its albedo value of the plant's surrounding is between 0 and 1 - *
  • its inclination in a compass direction (azimuth) is between -90° and 90° + *
  • its inclination in a compass direction (azimuth) is between -180° and 180° *
  • its efficiency of the asset's inverter (etaConv) is between 0% and 100% *
  • its tilted inclination from horizontal (elevation angle) is between 0° and 90° *
  • its rated power factor is between 0 and 1 @@ -431,17 +431,17 @@ private static void checkAlbedo(PvInput pvInput) throws InvalidEntityException { } /** - * Check if azimuth angle of pvInput is between -90° and 90° + * Check if azimuth angle of pvInput is between -180° and 180° * * @param pvInput PvInput to validate */ private static void checkAzimuth(PvInput pvInput) throws InvalidEntityException { - if (pvInput.getAzimuth().isLessThan(Quantities.getQuantity(-90d, AZIMUTH)) - || pvInput.getAzimuth().isGreaterThan(Quantities.getQuantity(90d, AZIMUTH))) + if (pvInput.getAzimuth().isLessThan(Quantities.getQuantity(-180d, AZIMUTH)) + || pvInput.getAzimuth().isGreaterThan(Quantities.getQuantity(180d, AZIMUTH))) throw new InvalidEntityException( "Azimuth angle of " + pvInput.getClass().getSimpleName() - + " must be between -90° (east) and 90° (west)", + + " must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", pvInput); } diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy index c89d7d005..add28c7b1 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy @@ -365,7 +365,7 @@ class SystemParticipantValidationUtilsTest extends Specification { invalidPV || expectedSize || expectedException SystemParticipantTestData.pvInput.copy().sRated(Quantities.getQuantity(-25d, ACTIVE_POWER_IN)).build() || 1 || new InvalidEntityException("The following quantities have to be zero or positive: -25 kVA", invalidPV) SystemParticipantTestData.pvInput.copy().albedo(2).build() || 1 || new InvalidEntityException("Albedo of the plant's surrounding of PvInput must be between 0 and 1", invalidPV) - SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-100d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between -90° (east) and 90° (west)", invalidPV) + SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-181d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between -90° (east) and 90° (west)", invalidPV) SystemParticipantTestData.pvInput.copy().etaConv(Quantities.getQuantity(110d, EFFICIENCY)).build() || 1 || new InvalidEntityException("Efficiency of the converter of PvInput must be between 0% and 100%", invalidPV) SystemParticipantTestData.pvInput.copy().elevationAngle(Quantities.getQuantity(100d, SOLAR_ELEVATION_ANGLE)).build() || 1 || new InvalidEntityException("Tilted inclination from horizontal of PvInput must be between 0° and 90°", invalidPV) SystemParticipantTestData.pvInput.copy().cosPhiRated(2).build() || 1 || new InvalidEntityException("Rated power factor of PvInput must be between 0 and 1", invalidPV) From ce011c37697f78797a9bb461427a56d6c591aed6 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Fri, 23 May 2025 13:32:47 +0200 Subject: [PATCH 16/60] Fix failed test --- .../validation/SystemParticipantValidationUtilsTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy index add28c7b1..6a23a88d1 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy @@ -365,7 +365,7 @@ class SystemParticipantValidationUtilsTest extends Specification { invalidPV || expectedSize || expectedException SystemParticipantTestData.pvInput.copy().sRated(Quantities.getQuantity(-25d, ACTIVE_POWER_IN)).build() || 1 || new InvalidEntityException("The following quantities have to be zero or positive: -25 kVA", invalidPV) SystemParticipantTestData.pvInput.copy().albedo(2).build() || 1 || new InvalidEntityException("Albedo of the plant's surrounding of PvInput must be between 0 and 1", invalidPV) - SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-181d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between -90° (east) and 90° (west)", invalidPV) + SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-181d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", invalidPV) SystemParticipantTestData.pvInput.copy().etaConv(Quantities.getQuantity(110d, EFFICIENCY)).build() || 1 || new InvalidEntityException("Efficiency of the converter of PvInput must be between 0% and 100%", invalidPV) SystemParticipantTestData.pvInput.copy().elevationAngle(Quantities.getQuantity(100d, SOLAR_ELEVATION_ANGLE)).build() || 1 || new InvalidEntityException("Tilted inclination from horizontal of PvInput must be between 0° and 90°", invalidPV) SystemParticipantTestData.pvInput.copy().cosPhiRated(2).build() || 1 || new InvalidEntityException("Rated power factor of PvInput must be between 0 and 1", invalidPV) From 317376aae8286284423aca506b7e999bdff09ab0 Mon Sep 17 00:00:00 2001 From: Pierre <155652256+pierrepetersmeier@users.noreply.github.com> Date: Mon, 26 May 2025 10:37:38 +0200 Subject: [PATCH 17/60] Update SystemParticipantValidationUtils.java --- .../utils/validation/SystemParticipantValidationUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java index 35930dc11..42e73d552 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java @@ -441,7 +441,7 @@ private static void checkAzimuth(PvInput pvInput) throws InvalidEntityException throw new InvalidEntityException( "Azimuth angle of " + pvInput.getClass().getSimpleName() - + " must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", + + " must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", pvInput); } From 83a2cbcec3dc840bb757438b8d1684770d583315 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 04:33:00 +0000 Subject: [PATCH 18/60] Bump org.apache.groovy:groovy from 4.0.26 to 4.0.27 Bumps [org.apache.groovy:groovy](https://github.com/apache/groovy) from 4.0.26 to 4.0.27. - [Commits](https://github.com/apache/groovy/commits) --- updated-dependencies: - dependency-name: org.apache.groovy:groovy dependency-version: 4.0.27 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c16c4c8a2..16c606662 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ ext { //version (changing these should be considered thoroughly!) javaVersion = JavaVersion.VERSION_17 groovyVersion = "4.0" - groovyBinaryVersion = "4.0.26" + groovyBinaryVersion = "4.0.27" junitVersion = '1.12.0' testcontainersVersion = '1.21.0' From 3baf62b7537558f1b175da905cafeb4d92712015 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 04:14:44 +0000 Subject: [PATCH 19/60] Bump com.diffplug.spotless from 7.0.3 to 7.0.4 Bumps com.diffplug.spotless from 7.0.3 to 7.0.4. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-version: 7.0.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 16c606662..183d7f5f2 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id 'maven-publish' id 'signing' id 'pmd' // code check, working on source code - id 'com.diffplug.spotless' version '7.0.3' //code format + id 'com.diffplug.spotless' version '7.0.4' //code format id 'com.github.spotbugs' version '6.1.12' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation From 762c1203ac6da42443f81606a013d14ca7990d00 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Wed, 28 May 2025 15:24:09 +0200 Subject: [PATCH 20/60] Fix correct expected exception message in PV validation test --- .../validation/SystemParticipantValidationUtilsTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy index 6a23a88d1..125eec5c7 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy @@ -365,7 +365,7 @@ class SystemParticipantValidationUtilsTest extends Specification { invalidPV || expectedSize || expectedException SystemParticipantTestData.pvInput.copy().sRated(Quantities.getQuantity(-25d, ACTIVE_POWER_IN)).build() || 1 || new InvalidEntityException("The following quantities have to be zero or positive: -25 kVA", invalidPV) SystemParticipantTestData.pvInput.copy().albedo(2).build() || 1 || new InvalidEntityException("Albedo of the plant's surrounding of PvInput must be between 0 and 1", invalidPV) - SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-181d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", invalidPV) + SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-181d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", invalidPV) SystemParticipantTestData.pvInput.copy().etaConv(Quantities.getQuantity(110d, EFFICIENCY)).build() || 1 || new InvalidEntityException("Efficiency of the converter of PvInput must be between 0% and 100%", invalidPV) SystemParticipantTestData.pvInput.copy().elevationAngle(Quantities.getQuantity(100d, SOLAR_ELEVATION_ANGLE)).build() || 1 || new InvalidEntityException("Tilted inclination from horizontal of PvInput must be between 0° and 90°", invalidPV) SystemParticipantTestData.pvInput.copy().cosPhiRated(2).build() || 1 || new InvalidEntityException("Rated power factor of PvInput must be between 0 and 1", invalidPV) From 2275c73e08123b4ed6008270fc9dc13213c54a0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 04:42:55 +0000 Subject: [PATCH 21/60] Bump org.postgresql:postgresql from 42.7.5 to 42.7.6 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.5 to 42.7.6. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.5...REL42.7.6) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 183d7f5f2..03c64ba5f 100644 --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ dependencies { // Databases implementation 'org.influxdb:influxdb-java:2.25' implementation 'com.couchbase.client:java-client:3.8.1' - runtimeOnly 'org.postgresql:postgresql:42.7.5' // postgresql jdbc driver required during runtime + runtimeOnly 'org.postgresql:postgresql:42.7.6' // postgresql jdbc driver required during runtime implementation 'commons-io:commons-io:2.19.0' // I/O functionalities implementation 'commons-codec:commons-codec:1.18.0' // needed by commons-compress From 7b17b56035837347645b8707299835e7a2840e03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 04:54:07 +0000 Subject: [PATCH 22/60] Bump com.github.spotbugs from 6.1.12 to 6.1.13 Bumps com.github.spotbugs from 6.1.12 to 6.1.13. --- updated-dependencies: - dependency-name: com.github.spotbugs dependency-version: 6.1.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 03c64ba5f..d1c5b6aa1 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'signing' id 'pmd' // code check, working on source code id 'com.diffplug.spotless' version '7.0.4' //code format - id 'com.github.spotbugs' version '6.1.12' // code check, working on byte code + id 'com.github.spotbugs' version '6.1.13' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin From abd6dca9a078cc2bdf4aa33eb4c2f93063955888 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Thu, 29 May 2025 22:01:19 +0200 Subject: [PATCH 23/60] Correct azimuth error message --- .../utils/validation/SystemParticipantValidationUtils.java | 2 +- .../validation/SystemParticipantValidationUtilsTest.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java index 42e73d552..c68db81f1 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtils.java @@ -441,7 +441,7 @@ private static void checkAzimuth(PvInput pvInput) throws InvalidEntityException throw new InvalidEntityException( "Azimuth angle of " + pvInput.getClass().getSimpleName() - + " must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", + + " must be between -180° and 180°", pvInput); } diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy index 125eec5c7..0992c426b 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy @@ -365,7 +365,7 @@ class SystemParticipantValidationUtilsTest extends Specification { invalidPV || expectedSize || expectedException SystemParticipantTestData.pvInput.copy().sRated(Quantities.getQuantity(-25d, ACTIVE_POWER_IN)).build() || 1 || new InvalidEntityException("The following quantities have to be zero or positive: -25 kVA", invalidPV) SystemParticipantTestData.pvInput.copy().albedo(2).build() || 1 || new InvalidEntityException("Albedo of the plant's surrounding of PvInput must be between 0 and 1", invalidPV) - SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-181d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between 0° (south), 90° (west), -90° (east), +/- 180° (north)", invalidPV) + SystemParticipantTestData.pvInput.copy().azimuth(Quantities.getQuantity(-181d, AZIMUTH)).build() || 1 || new InvalidEntityException("Azimuth angle of PvInput must be between -180° and 180°", invalidPV) SystemParticipantTestData.pvInput.copy().etaConv(Quantities.getQuantity(110d, EFFICIENCY)).build() || 1 || new InvalidEntityException("Efficiency of the converter of PvInput must be between 0% and 100%", invalidPV) SystemParticipantTestData.pvInput.copy().elevationAngle(Quantities.getQuantity(100d, SOLAR_ELEVATION_ANGLE)).build() || 1 || new InvalidEntityException("Tilted inclination from horizontal of PvInput must be between 0° and 90°", invalidPV) SystemParticipantTestData.pvInput.copy().cosPhiRated(2).build() || 1 || new InvalidEntityException("Rated power factor of PvInput must be between 0 and 1", invalidPV) From 88e9af5163dd7dc0eded7d1477d1e0fd3b95fe26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 04:17:39 +0000 Subject: [PATCH 24/60] Bump testcontainersVersion from 1.21.0 to 1.21.1 Bumps `testcontainersVersion` from 1.21.0 to 1.21.1. Updates `org.testcontainers:testcontainers` from 1.21.0 to 1.21.1 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.0...1.21.1) Updates `org.testcontainers:spock` from 1.21.0 to 1.21.1 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.0...1.21.1) Updates `org.testcontainers:influxdb` from 1.21.0 to 1.21.1 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.0...1.21.1) Updates `org.testcontainers:postgresql` from 1.21.0 to 1.21.1 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.0...1.21.1) Updates `org.testcontainers:couchbase` from 1.21.0 to 1.21.1 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.0...1.21.1) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers dependency-version: 1.21.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:spock dependency-version: 1.21.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:influxdb dependency-version: 1.21.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:postgresql dependency-version: 1.21.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:couchbase dependency-version: 1.21.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d1c5b6aa1..04faa6439 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ ext { groovyBinaryVersion = "4.0.27" junitVersion = '1.12.0' - testcontainersVersion = '1.21.0' + testcontainersVersion = '1.21.1' scriptsLocation = 'gradle' + File.separator + 'scripts' + File.separator //location of script plugins } From 1e295b4e9051565d7407e89c7e6824d1dbba27fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 04:27:00 +0000 Subject: [PATCH 25/60] Bump com.couchbase.client:java-client from 3.8.1 to 3.8.2 Bumps [com.couchbase.client:java-client](https://github.com/couchbase/couchbase-jvm-clients) from 3.8.1 to 3.8.2. - [Commits](https://github.com/couchbase/couchbase-jvm-clients/compare/core-io-3.8.1...core-io-3.8.2) --- updated-dependencies: - dependency-name: com.couchbase.client:java-client dependency-version: 3.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 04faa6439..013c9d414 100644 --- a/build.gradle +++ b/build.gradle @@ -98,7 +98,7 @@ dependencies { // Databases implementation 'org.influxdb:influxdb-java:2.25' - implementation 'com.couchbase.client:java-client:3.8.1' + implementation 'com.couchbase.client:java-client:3.8.2' runtimeOnly 'org.postgresql:postgresql:42.7.6' // postgresql jdbc driver required during runtime implementation 'commons-io:commons-io:2.19.0' // I/O functionalities From 571f498a3346cf6766f04d3eb02a381f6cd7a7e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 07:15:15 +0000 Subject: [PATCH 26/60] Bump com.github.spotbugs from 6.1.13 to 6.2.0 (#1344) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 013c9d414..5013be8bf 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'signing' id 'pmd' // code check, working on source code id 'com.diffplug.spotless' version '7.0.4' //code format - id 'com.github.spotbugs' version '6.1.13' // code check, working on byte code + id 'com.github.spotbugs' version '6.2.0' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin From c995d5ef534e6a317d9bf75039200e01c070953a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 04:41:13 +0000 Subject: [PATCH 27/60] Bump org.postgresql:postgresql from 42.7.6 to 42.7.7 Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.6 to 42.7.7. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.6...REL42.7.7) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5013be8bf..5eba3de45 100644 --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ dependencies { // Databases implementation 'org.influxdb:influxdb-java:2.25' implementation 'com.couchbase.client:java-client:3.8.2' - runtimeOnly 'org.postgresql:postgresql:42.7.6' // postgresql jdbc driver required during runtime + runtimeOnly 'org.postgresql:postgresql:42.7.7' // postgresql jdbc driver required during runtime implementation 'commons-io:commons-io:2.19.0' // I/O functionalities implementation 'commons-codec:commons-codec:1.18.0' // needed by commons-compress From 9911f7d8429386a695feeecba546a5ce91a8c941 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 17 Jun 2025 10:30:35 +0200 Subject: [PATCH 28/60] Sonarqube: new parameter name --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 053b6e14d..b39d4516f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: ./gradlew sonar \ -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} \ -Dsonar.host.url=${{ vars.SONAR_HOST_URL }} \ - -Dsonar.login=${{ secrets.SONAR_TOKEN }} \ + -Dsonar.token=${{ secrets.SONAR_TOKEN }} \ -Dsonar.qualitygate.wait=true #Deployment From 70ac05402b003d2d6066751ee6c6e4e77759be14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:37:42 +0000 Subject: [PATCH 29/60] Bump org.apache.logging.log4j:log4j-bom from 2.24.3 to 2.25.0 Bumps [org.apache.logging.log4j:log4j-bom](https://github.com/apache/logging-log4j2) from 2.24.3 to 2.25.0. - [Release notes](https://github.com/apache/logging-log4j2/releases) - [Changelog](https://github.com/apache/logging-log4j2/blob/2.x/RELEASE-NOTES.adoc) - [Commits](https://github.com/apache/logging-log4j2/compare/rel/2.24.3...rel/2.25.0) --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-bom dependency-version: 2.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5eba3de45..5a18b9b0f 100644 --- a/build.gradle +++ b/build.gradle @@ -91,7 +91,7 @@ dependencies { testImplementation "org.testcontainers:couchbase:$testcontainersVersion" // logging - implementation platform('org.apache.logging.log4j:log4j-bom:2.24.3') + implementation platform('org.apache.logging.log4j:log4j-bom:2.25.0') implementation 'org.apache.logging.log4j:log4j-api' // log4j implementation 'org.apache.logging.log4j:log4j-core' // log4j implementation 'org.apache.logging.log4j:log4j-slf4j-impl' // log4j -> slf4j From 731fe6117129e43cea35b411a4d41ba30d961296 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 17 Jun 2025 11:43:03 +0200 Subject: [PATCH 30/60] Token delimiter --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b39d4516f..07dc94b71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: ./gradlew sonar \ -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} \ -Dsonar.host.url=${{ vars.SONAR_HOST_URL }} \ - -Dsonar.token=${{ secrets.SONAR_TOKEN }} \ + -Dsonar.token="${{ secrets.SONAR_TOKEN }}" \ -Dsonar.qualitygate.wait=true #Deployment From 4372c2d9a7d955bf206ad30d54e4ad2a839743f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 08:21:13 +0000 Subject: [PATCH 31/60] Bump net.bytebuddy:byte-buddy from 1.17.5 to 1.17.6 Bumps [net.bytebuddy:byte-buddy](https://github.com/raphw/byte-buddy) from 1.17.5 to 1.17.6. - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.17.5...byte-buddy-1.17.6) --- updated-dependencies: - dependency-name: net.bytebuddy:byte-buddy dependency-version: 1.17.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5a18b9b0f..cde4043ec 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" testImplementation "org.spockframework:spock-core:2.3-groovy-$groovyVersion" testImplementation 'org.objenesis:objenesis:3.4' // Mock creation with constructor parameters - testImplementation 'net.bytebuddy:byte-buddy:1.17.5' // Mocks of classes + testImplementation 'net.bytebuddy:byte-buddy:1.17.6' // Mocks of classes // testcontainers (docker framework for testing) testImplementation "org.testcontainers:testcontainers:$testcontainersVersion" From 53d4a92046a2fad47a12356c2328a0f986d57fd5 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 24 Jun 2025 18:38:02 +0200 Subject: [PATCH 32/60] test sonar token --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07dc94b71..aa7abdbb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,11 +75,13 @@ jobs: run: ./gradlew javadoc - name: SonarQube + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_2025 }} run: | ./gradlew sonar \ -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} \ -Dsonar.host.url=${{ vars.SONAR_HOST_URL }} \ - -Dsonar.token="${{ secrets.SONAR_TOKEN }}" \ + -Dsonar.token="${{ secrets.SONAR_TOKEN_2025 }}" \ -Dsonar.qualitygate.wait=true #Deployment From 16025973559713fbf008500406e205eb44e4a969 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:46:15 +0000 Subject: [PATCH 33/60] Bump testcontainersVersion from 1.21.1 to 1.21.2 Bumps `testcontainersVersion` from 1.21.1 to 1.21.2. Updates `org.testcontainers:testcontainers` from 1.21.1 to 1.21.2 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.1...1.21.2) Updates `org.testcontainers:spock` from 1.21.1 to 1.21.2 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.1...1.21.2) Updates `org.testcontainers:influxdb` from 1.21.1 to 1.21.2 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.1...1.21.2) Updates `org.testcontainers:postgresql` from 1.21.1 to 1.21.2 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.1...1.21.2) Updates `org.testcontainers:couchbase` from 1.21.1 to 1.21.2 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.1...1.21.2) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers dependency-version: 1.21.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:spock dependency-version: 1.21.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:influxdb dependency-version: 1.21.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:postgresql dependency-version: 1.21.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:couchbase dependency-version: 1.21.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cde4043ec..432ce561c 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ ext { groovyBinaryVersion = "4.0.27" junitVersion = '1.12.0' - testcontainersVersion = '1.21.1' + testcontainersVersion = '1.21.2' scriptsLocation = 'gradle' + File.separator + 'scripts' + File.separator //location of script plugins } From 550ab1da51bd71b68d74b6075823a84b7f6973b4 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 24 Jun 2025 18:57:38 +0200 Subject: [PATCH 34/60] Revert "test sonar token" This reverts commit 53d4a92046a2fad47a12356c2328a0f986d57fd5. --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa7abdbb6..07dc94b71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,13 +75,11 @@ jobs: run: ./gradlew javadoc - name: SonarQube - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_2025 }} run: | ./gradlew sonar \ -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} \ -Dsonar.host.url=${{ vars.SONAR_HOST_URL }} \ - -Dsonar.token="${{ secrets.SONAR_TOKEN_2025 }}" \ + -Dsonar.token="${{ secrets.SONAR_TOKEN }}" \ -Dsonar.qualitygate.wait=true #Deployment From 95c517bf72eb02ff283ccde19535943481b91a1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 11:33:59 +0000 Subject: [PATCH 35/60] Bump testcontainersVersion from 1.21.2 to 1.21.3 Bumps `testcontainersVersion` from 1.21.2 to 1.21.3. Updates `org.testcontainers:testcontainers` from 1.21.2 to 1.21.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.2...1.21.3) Updates `org.testcontainers:spock` from 1.21.2 to 1.21.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.2...1.21.3) Updates `org.testcontainers:influxdb` from 1.21.2 to 1.21.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.2...1.21.3) Updates `org.testcontainers:postgresql` from 1.21.2 to 1.21.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.2...1.21.3) Updates `org.testcontainers:couchbase` from 1.21.2 to 1.21.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.2...1.21.3) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers dependency-version: 1.21.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:spock dependency-version: 1.21.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:influxdb dependency-version: 1.21.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:postgresql dependency-version: 1.21.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:couchbase dependency-version: 1.21.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 432ce561c..1a9706035 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ ext { groovyBinaryVersion = "4.0.27" junitVersion = '1.12.0' - testcontainersVersion = '1.21.2' + testcontainersVersion = '1.21.3' scriptsLocation = 'gradle' + File.separator + 'scripts' + File.separator //location of script plugins } From 55373589ad819a28e95148ad26cac52b55be0691 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 11:40:46 +0000 Subject: [PATCH 36/60] Bump com.github.spotbugs from 6.2.0 to 6.2.1 Bumps com.github.spotbugs from 6.2.0 to 6.2.1. --- updated-dependencies: - dependency-name: com.github.spotbugs dependency-version: 6.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1a9706035..c70398ce3 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'signing' id 'pmd' // code check, working on source code id 'com.diffplug.spotless' version '7.0.4' //code format - id 'com.github.spotbugs' version '6.2.0' // code check, working on byte code + id 'com.github.spotbugs' version '6.2.1' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin From e9f21ae22e029f4cabd0866cbc0cab397a18e3ee Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 2 Jul 2025 15:55:19 +0200 Subject: [PATCH 37/60] Improved error messages when reading and validating an invalid grid --- CHANGELOG.md | 1 + .../io/source/csv/CsvDataSource.java | 58 +++++++++++------ .../io/source/csv/CsvIdCoordinateSource.java | 2 +- .../io/source/csv/CsvWeatherSource.java | 10 +-- .../java/edu/ie3/datamodel/utils/Try.java | 14 ++++ .../GridContainerValidationUtils.java | 44 ++++++------- .../validation/UniquenessValidationUtils.java | 65 ++++++++++++------- 7 files changed, 123 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7593a97d..7353a7834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) - Extend azimuth angle range to [-180°, 180°] for PV inputs [#1330](https://github.com/ie3-institute/PowerSystemDataModel/issues/1330) +- Improved error messages when reading and validating an invalid grid [#1354](https://github.com/ie3-institute/PowerSystemDataModel/issues/1354) ## [7.0.0] - 2025-05-08 diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java index 78aa77f75..2e8dc614d 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java @@ -79,8 +79,19 @@ public Optional> getSourceFields(Class entityClass */ public Optional> getSourceFields(Path filePath) throws SourceException { try (BufferedReader reader = connector.initReader(filePath)) { - return Optional.of( - Arrays.stream(parseCsvRow(reader.readLine(), csvSep)).collect(Collectors.toSet())); + String line = reader.readLine(); + String[] headline = parseCsvRow(line, csvSep); + + if (headline.length <= 1) { + throw new SourceException( + "The given file has less than two columns! (Used separator '" + + csvSep + + "' on headline '" + + line + + "')"); + } + + return Optional.of(Arrays.stream(headline).collect(Collectors.toSet())); } catch (FileNotFoundException e) { // A file not existing can be acceptable in many cases, and is handled elsewhere. log.debug("The source for the given entity couldn't be found! Cause: {}", e.getMessage()); @@ -208,36 +219,40 @@ protected Set getTimeSeriesFilePaths(Pattern pattern) { */ protected Map buildFieldsToAttributes( final String csvRow, final String[] headline) throws SourceException { - - TreeMap insensitiveFieldsToAttributes = - new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - + // parse row String[] fieldVals = parseCsvRow(csvRow, csvSep); - insensitiveFieldsToAttributes.putAll( - IntStream.range(0, Math.min(fieldVals.length, headline.length)) - .boxed() - .collect( - Collectors.toMap( - k -> StringUtils.snakeCaseToCamelCase(headline[k]), v -> fieldVals[v]))); + // check if the number row elements matched the number of headline elements if (fieldVals.length != headline.length) { + String headlineElements = "['" + String.join("', '", headline) + "']"; + String parsedRow = "['" + String.join("', '", fieldVals) + "']"; + throw new SourceException( "The size of the headline (" + headline.length + ") does not fit to the size of the attribute fields (" + fieldVals.length + ").\nHeadline: " - + String.join(", ", headline) - + "\nRow: " - + csvRow.trim() + + headlineElements + + "\nParsed row: " + + parsedRow + ".\nPlease check:" - + "\n - is the csv separator in the file matching the separator provided in the constructor ('" + + "\n - is the csv separator in the row matching the provided separator '" + csvSep - + "')" + + "'" + "\n - does the number of columns match the number of headline fields " + "\n - are you using a valid RFC 4180 formatted csv row?"); } + TreeMap insensitiveFieldsToAttributes = + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + insensitiveFieldsToAttributes.putAll( + IntStream.range(0, Math.min(fieldVals.length, headline.length)) + .boxed() + .collect( + Collectors.toMap( + k -> StringUtils.snakeCaseToCamelCase(headline[k]), v -> fieldVals[v]))); + if (insensitiveFieldsToAttributes.size() != fieldVals.length) { throw new SourceException( "There might be duplicate headline elements.\nHeadline: " @@ -298,7 +313,9 @@ protected Try>, SourceException> buildStreamWithField // is wanted to avoid a lock on the file), but this causes a closing of the stream as well. // As we still want to consume the data at other places, we start a new stream instead of // returning the original one - return csvRowFieldValueMapping(reader, headline); + return csvRowFieldValueMapping(reader, headline, filePath.getFileName()) + .transformF( + e -> new SourceException("The file '" + filePath + "' could not be parsed.", e)); } catch (FileNotFoundException e) { if (allowFileNotExisting) { log.warn("Unable to find file '{}': {}", filePath, e.getMessage()); @@ -324,10 +341,11 @@ private Try getFilePath(Class entityCla * * @param reader for the file * @param headline of the file + * @param fileName the name of the file, that is read * @return a list of mapping */ protected Try>, SourceException> csvRowFieldValueMapping( - BufferedReader reader, String[] headline) { + BufferedReader reader, String[] headline, Path fileName) { return Try.scanStream( reader .lines() @@ -337,7 +355,7 @@ protected Try>, SourceException> csvRowFieldValueMapp Try.of( () -> buildFieldsToAttributes(csvRow, headline), SourceException.class)), - "Map") + fileName.toString()) .transform( stream -> stream.filter(map -> !map.isEmpty()), e -> new SourceException("Parsing csv row failed.", e)); diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java index 199a4925d..26038ee55 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java @@ -209,7 +209,7 @@ private Collection getCoordinatesInBoundingBox( // is wanted to avoid a lock on the file), but this causes a closing of the stream as well. // As we still want to consume the data at other places, we start a new stream instead of // returning the original one - return dataSource.csvRowFieldValueMapping(reader, headline); + return dataSource.csvRowFieldValueMapping(reader, headline, filePath.getFileName()); } catch (IOException e) { return Failure.of( new SourceException("Cannot read the file for coordinate id to coordinate mapping.", e)); diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java index ab565db80..803fd6c26 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java @@ -177,9 +177,11 @@ private Map> readWeatherTimeSeries( this::buildWeatherValue; /* Reading in weather time series */ for (CsvIndividualTimeSeriesMetaInformation data : weatherMetaInformation) { + Path path = data.getFullFilePath(); + // we need a reader for each file - try (BufferedReader reader = connector.initReader(data.getFullFilePath())) { - buildStreamWithFieldsToAttributesMap(reader) + try (BufferedReader reader = connector.initReader(path)) { + buildStreamWithFieldsToAttributesMap(reader, path.getFileName()) .getOrThrow() .map(fieldToValueFunction) .flatMap(Optional::stream) @@ -227,7 +229,7 @@ private Map> readWeatherTimeSeries( } private Try>, SourceException> buildStreamWithFieldsToAttributesMap( - BufferedReader bufferedReader) throws ValidationException { + BufferedReader bufferedReader, Path fileName) throws ValidationException { Class entityClass = TimeBasedValue.class; try (BufferedReader reader = bufferedReader) { @@ -240,7 +242,7 @@ private Try>, SourceException> buildStreamWithFieldsT // is wanted to avoid a lock on the file), but this causes a closing of the stream as well. // As we still want to consume the data at other places, we start a new stream instead of // returning the original one - return dataSource.csvRowFieldValueMapping(reader, headline); + return dataSource.csvRowFieldValueMapping(reader, headline, fileName); } catch (IOException e) { return Failure.of( new SourceException( diff --git a/src/main/java/edu/ie3/datamodel/utils/Try.java b/src/main/java/edu/ie3/datamodel/utils/Try.java index ffe006407..7ff103cc8 100644 --- a/src/main/java/edu/ie3/datamodel/utils/Try.java +++ b/src/main/java/edu/ie3/datamodel/utils/Try.java @@ -67,6 +67,20 @@ public static Try ofVoid( } } + /** + * Method to create multiple {@link Try} object easily. + * + * @param suppliers that either return no data or throw an exception + * @param clazz class of the exception + * @return a collection of try objects + * @param type of exception that could be thrown + */ + @SafeVarargs + public static Collection> ofVoids( + Class clazz, VoidSupplier... suppliers) { + return Arrays.stream(suppliers).map(supplier -> ofVoid(supplier, clazz)).toList(); + } + /** * Method to create a {@link Try} object easily. * diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java index 7d9e9b26b..471c83567 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java @@ -99,10 +99,15 @@ private GridContainerValidationUtils() { /* sanity check to ensure uniqueness */ List> exceptions = new ArrayList<>(); - exceptions.add( - Try.ofVoid( - () -> checkAssetUniqueness(rawGridElements.allEntitiesAsList()), - DuplicateEntitiesException.class)); + exceptions.addAll( + Try.ofVoids( + DuplicateEntitiesException.class, + () -> checkAssetUniqueness(rawGridElements.getNodes()), + () -> checkAssetUniqueness(rawGridElements.getLines()), + () -> checkAssetUniqueness(rawGridElements.getSwitches()), + () -> checkAssetUniqueness(rawGridElements.getTransformer2Ws()), + () -> checkAssetUniqueness(rawGridElements.getTransformer3Ws()), + () -> checkAssetUniqueness(rawGridElements.getMeasurementUnits()))); /* Checking nodes */ Set nodes = rawGridElements.getNodes(); @@ -287,10 +292,6 @@ protected static Try checkConnectivity( /* sanity check to ensure uniqueness */ List> exceptions = new ArrayList<>(); - exceptions.add( - Try.ofVoid( - () -> checkAssetUniqueness(systemParticipants.allEntitiesAsList()), - DuplicateEntitiesException.class)); exceptions.addAll(checkSystemParticipants(systemParticipants.getBmPlants(), nodes)); exceptions.addAll(checkSystemParticipants(systemParticipants.getChpPlants(), nodes)); @@ -306,8 +307,8 @@ protected static Try checkConnectivity( } /** - * Checks the validity of specific system participant. Moreover, it checks, if the systems are - * connected to a node that is not in the provided set + * Checks the validity and uniqueness of specific system participant. Moreover, it checks, if the + * systems are connected to a node that is not in the provided set * * @param participants a set of specific system participants * @param nodes Set of already known nodes @@ -316,18 +317,17 @@ protected static Try checkConnectivity( */ protected static List> checkSystemParticipants( Set participants, Set nodes) { - return participants.stream() - .map( - entity -> { - List> exceptions = new ArrayList<>(); - - exceptions.add(checkNodeAvailability(entity, nodes)); - exceptions.addAll(SystemParticipantValidationUtils.check(entity)); - - return exceptions; - }) - .flatMap(List::stream) - .toList(); + List> exceptions = new ArrayList<>(); + exceptions.add( + Try.ofVoid(() -> checkAssetUniqueness(participants), DuplicateEntitiesException.class)); + + participants.forEach( + participant -> { + exceptions.add(checkNodeAvailability(participant, nodes)); + exceptions.addAll(SystemParticipantValidationUtils.check(participant)); + }); + + return exceptions; } /** diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/UniquenessValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/UniquenessValidationUtils.java index cdf980fad..031ee0aad 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/UniquenessValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/UniquenessValidationUtils.java @@ -24,10 +24,11 @@ public class UniquenessValidationUtils extends ValidationUtils { // default field set supplier - protected static final FieldSetSupplier uuidFieldSupplier = + protected static final FieldSetSupplier uuidFieldSupplier = entity -> Set.of(entity.getUuid()); - protected static final FieldSetSupplier idFieldSupplier = e -> Set.of(e.getId()); - protected static final FieldSetSupplier resultFieldSupplier = + protected static final FieldSetSupplier idFieldSupplier = + e -> Set.of(e.getId()); + protected static final FieldSetSupplier resultFieldSupplier = entity -> Set.of(entity.getTime(), entity.getInputModel()); protected static final FieldSetSupplier mappingFieldSupplier = entity -> Set.of(entity.getAsset()); @@ -44,9 +45,10 @@ public class UniquenessValidationUtils extends ValidationUtils { * @param entities to be checked * @throws DuplicateEntitiesException if uniqueness is violated */ - public static void checkUniqueEntities(Collection entities) + @SuppressWarnings("unchecked") + public static void checkUniqueEntities(Collection entities) throws DuplicateEntitiesException { - checkUniqueness(entities, uuidFieldSupplier).getOrThrow(); + checkUniqueness(entities, (FieldSetSupplier) uuidFieldSupplier).getOrThrow(); } /** @@ -55,13 +57,14 @@ public static void checkUniqueEntities(Collection entiti * @param entities to be checked * @throws DuplicateEntitiesException if uniqueness is violated */ - public static void checkAssetUniqueness(Collection entities) + @SuppressWarnings("unchecked") + public static void checkAssetUniqueness(Collection entities) throws DuplicateEntitiesException { List exceptions = Try.getExceptions( Try.ofVoid(() -> checkUniqueEntities(entities), DuplicateEntitiesException.class), - checkUniqueness(entities, idFieldSupplier)); + checkUniqueness(entities, (FieldSetSupplier) idFieldSupplier)); if (!exceptions.isEmpty()) { throw new DuplicateEntitiesException("AssetInput", exceptions); @@ -74,9 +77,10 @@ public static void checkAssetUniqueness(Collection entitie * @param entities to be checked * @throws DuplicateEntitiesException if uniqueness is violated */ - public static void checkResultUniqueness(Collection entities) + @SuppressWarnings("unchecked") + public static void checkResultUniqueness(Collection entities) throws DuplicateEntitiesException { - checkUniqueness(entities, resultFieldSupplier).getOrThrow(); + checkUniqueness(entities, (FieldSetSupplier) resultFieldSupplier).getOrThrow(); } /** @@ -122,33 +126,46 @@ public static void checkWeatherUniqueness(Collection Try checkUniqueness( Collection entities, FieldSetSupplier supplier) { + Optional option = entities.stream().findAny().map(e -> e.getClass().getSimpleName()); + if (option.isPresent()) { + return checkUniqueness(entities, supplier, option.get()); + } else { + return Try.Success.empty(); + } + } + + /** + * Checking the uniqueness for a given {@link Entity}. + * + * @param entities to be checked + * @param supplier for the field set + * @param entityName name of the class of the entity + * @return a try object + * @param type of entity + */ + private static Try checkUniqueness( + Collection entities, FieldSetSupplier supplier, String entityName) { if (entities.size() < 2) { return Success.empty(); } - return entities.stream() - .findAny() - .map( - entity -> { - List> elements = entities.stream().map(supplier::getFieldSets).toList(); - Set> uniqueElements = new HashSet<>(elements); - - return Try.ofVoid( - elements.size() != uniqueElements.size(), - () -> buildDuplicationException(entity.getClass(), elements)); - }) - .orElse(Success.empty()); + List> elements = entities.stream().map(supplier::getFieldSets).toList(); + Set> uniqueElements = new HashSet<>(elements); + + return Try.ofVoid( + elements.size() != uniqueElements.size(), + () -> buildDuplicationException(entityName, elements)); } /** * Method for building a {@link DuplicateEntitiesException}. * - * @param entityClass class of the entity + * @param entityClass name of the class of the entity * @param notUniqueElements list of not unique elements * @return a {@link DuplicateEntitiesException} */ protected static DuplicateEntitiesException buildDuplicationException( - Class entityClass, List> notUniqueElements) { + String entityClass, List> notUniqueElements) { String fieldName = notUniqueElements.get(0).stream() .map(f -> f.getClass().getSimpleName()) @@ -167,7 +184,7 @@ protected static DuplicateEntitiesException buildDuplicationException( return new DuplicateEntitiesException( "'" - + entityClass.getSimpleName() + + entityClass + "' entities with duplicated " + fieldName + " key, but different field " From 28756ae54ed1ca83de7e3f9bf45b4c0ca3cfe6bf Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 3 Jul 2025 14:16:09 +0200 Subject: [PATCH 38/60] Improving the messages and the format of exceptions. --- .../DuplicateEntitiesException.java | 4 +- .../exceptions/FailedValidationException.java | 3 +- .../exceptions/InvalidEntityException.java | 5 +- .../datamodel/exceptions/SourceException.java | 2 +- .../io/processor/ProcessorProvider.java | 36 +++++------ .../ie3/datamodel/io/source/EntitySource.java | 21 +++---- .../datamodel/io/source/GraphicSource.java | 9 ++- .../datamodel/io/source/RawGridSource.java | 23 ++++--- .../io/source/ResultEntitySource.java | 4 +- .../io/source/SystemParticipantSource.java | 28 ++++----- .../datamodel/io/source/ThermalSource.java | 4 +- .../io/source/TimeSeriesMappingSource.java | 8 ++- .../ie3/datamodel/io/source/TypeSource.java | 4 +- .../datamodel/io/source/WeatherSource.java | 5 +- .../io/source/csv/CsvDataSource.java | 31 +++++---- .../io/source/csv/CsvIdCoordinateSource.java | 3 +- .../csv/CsvJointGridContainerSource.java | 3 +- .../io/source/csv/CsvLoadProfileSource.java | 4 +- .../io/source/csv/CsvTimeSeriesSource.java | 6 +- .../io/source/csv/CsvWeatherSource.java | 2 +- .../ie3/datamodel/utils/ExceptionUtils.java | 63 +++++++------------ .../java/edu/ie3/datamodel/utils/Try.java | 36 ++++++----- .../source/EnergyManagementSourceTest.groovy | 8 +-- .../io/source/EntitySourceTest.groovy | 3 +- .../source/TimeSeriesMappingSourceTest.groovy | 11 ++-- .../io/source/csv/CsvDataSourceTest.groovy | 23 +++++-- .../io/source/csv/CsvGraphicSourceTest.groovy | 10 +-- .../io/source/csv/CsvRawGridSourceTest.groovy | 7 ++- .../csv/CsvSystemParticipantSourceTest.groovy | 25 ++++++-- .../edu/ie3/datamodel/utils/TryTest.groovy | 9 +-- ...ystemParticipantValidationUtilsTest.groovy | 42 +++++-------- .../UniquenessValidationUtilsTest.groovy | 5 +- .../validation/ValidationUtilsTest.groovy | 6 +- 33 files changed, 222 insertions(+), 231 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java b/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java index f2a46bce0..eee2942f1 100644 --- a/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java +++ b/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java @@ -19,7 +19,7 @@ public DuplicateEntitiesException( this( "The following exception(s) occurred while checking the uniqueness of '" + entityName - + "' entities: " - + ExceptionUtils.getMessages(exceptions)); + + "' entities: \n" + + ExceptionUtils.combineExceptions(exceptions)); } } diff --git a/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java b/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java index b1cc6386b..4b10acc06 100644 --- a/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java +++ b/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java @@ -23,7 +23,6 @@ public FailedValidationException(String message) { /** @param exceptions List of exceptions, which must not be empty */ public FailedValidationException(List exceptions) { - super( - "Validation failed due to: \n" + ExceptionUtils.getMessages(exceptions), exceptions.get(0)); + super("Validation failed due to: " + ExceptionUtils.combineExceptions(exceptions)); } } diff --git a/src/main/java/edu/ie3/datamodel/exceptions/InvalidEntityException.java b/src/main/java/edu/ie3/datamodel/exceptions/InvalidEntityException.java index 744e08c25..162acd0b1 100644 --- a/src/main/java/edu/ie3/datamodel/exceptions/InvalidEntityException.java +++ b/src/main/java/edu/ie3/datamodel/exceptions/InvalidEntityException.java @@ -13,13 +13,12 @@ public class InvalidEntityException extends ValidationException { private static final long serialVersionUID = 809496087520306374L; public InvalidEntityException(String faultDescription, UniqueEntity invalidEntity) { - super("Entity is invalid because of: \n" + faultDescription + " [" + invalidEntity + "]"); + super("Entity is invalid because of: " + faultDescription + " [" + invalidEntity + "]"); } public InvalidEntityException( String faultDescription, Throwable cause, UniqueEntity invalidEntity) { - super( - "Entity is invalid because of: \n" + faultDescription + " [" + invalidEntity + "]", cause); + super("Entity is invalid because of: " + faultDescription + " [" + invalidEntity + "]", cause); } public InvalidEntityException(String message, Throwable cause) { diff --git a/src/main/java/edu/ie3/datamodel/exceptions/SourceException.java b/src/main/java/edu/ie3/datamodel/exceptions/SourceException.java index 2a5a19030..623dc801b 100644 --- a/src/main/java/edu/ie3/datamodel/exceptions/SourceException.java +++ b/src/main/java/edu/ie3/datamodel/exceptions/SourceException.java @@ -32,6 +32,6 @@ public SourceException(final String message) { } public SourceException(String message, List exceptions) { - super(message + " " + ExceptionUtils.getMessages(exceptions), exceptions.get(0)); + super(message + "\n " + ExceptionUtils.combineExceptions(exceptions)); } } diff --git a/src/main/java/edu/ie3/datamodel/io/processor/ProcessorProvider.java b/src/main/java/edu/ie3/datamodel/io/processor/ProcessorProvider.java index f1858c44b..9cb9ee5a0 100644 --- a/src/main/java/edu/ie3/datamodel/io/processor/ProcessorProvider.java +++ b/src/main/java/edu/ie3/datamodel/io/processor/ProcessorProvider.java @@ -6,7 +6,6 @@ package edu.ie3.datamodel.io.processor; import edu.ie3.datamodel.exceptions.EntityProcessorException; -import edu.ie3.datamodel.exceptions.FailureException; import edu.ie3.datamodel.exceptions.ProcessorProviderException; import edu.ie3.datamodel.io.processor.input.InputEntityProcessor; import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor; @@ -313,25 +312,22 @@ public static Collection> allResultEntityProce Value, Value>> allTimeSeriesProcessors() throws EntityProcessorException { - try { - return Try.scanStream( - TimeSeriesProcessor.eligibleKeys.stream() - .map( - key -> - Try.of( - () -> - new TimeSeriesProcessor<>( - (Class, Value, Value>>) - key.getTimeSeriesClass(), - (Class>) key.getEntryClass(), - (Class) key.getValueClass()), - EntityProcessorException.class)), - "list of processors") - .getOrThrow() - .collect(Collectors.toMap(TimeSeriesProcessor::getRegisteredKey, Function.identity())); - } catch (FailureException e) { - throw new EntityProcessorException(e.getCause()); - } + return Try.scanStream( + TimeSeriesProcessor.eligibleKeys.stream() + .map( + key -> + Try.of( + () -> + new TimeSeriesProcessor<>( + (Class, Value, Value>>) + key.getTimeSeriesClass(), + (Class>) key.getEntryClass(), + (Class) key.getValueClass()), + EntityProcessorException.class)), + "time series processors", + EntityProcessorException::new) + .getOrThrow() + .collect(Collectors.toMap(TimeSeriesProcessor::getRegisteredKey, Function.identity())); } @SuppressWarnings("unchecked cast") diff --git a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java index be0cdea31..726a35279 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java @@ -204,14 +204,12 @@ protected static Stream getEntities( * @return a stream of the entity data wrapped in a {@link Try} */ protected static Stream> buildEntityData( - Class entityClass, DataSource dataSource) { - return Try.of(() -> dataSource.getSourceData(entityClass), SourceException.class) - .convert( - data -> - data.map( - fieldsToAttributes -> - new Try.Success<>(new EntityData(fieldsToAttributes, entityClass))), - exception -> Stream.of(Failure.of(exception))); + Class entityClass, DataSource dataSource) throws SourceException { + Stream> dataStream = + Try.of(() -> dataSource.getSourceData(entityClass), SourceException.class).getOrThrow(); + + return dataStream.map( + fieldsToAttributes -> new Try.Success<>(new EntityData(fieldsToAttributes, entityClass))); } /** @@ -227,7 +225,8 @@ protected static Stream> buildEntityData( protected static Stream> buildEntityData( Class entityClass, DataSource dataSource, - WrappedFunction converter) { + WrappedFunction converter) + throws SourceException { return buildEntityData(entityClass, dataSource).map(converter); } @@ -356,9 +355,7 @@ Function, R> enrichFunction( */ protected static Stream unpack( Stream> inputStream, Class clazz) throws SourceException { - return Try.scanStream(inputStream, clazz.getSimpleName()) - .transformF(SourceException::new) - .getOrThrow(); + return Try.scanStream(inputStream, clazz.getSimpleName(), SourceException::new).getOrThrow(); } /** diff --git a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java index 339f3f638..d92336aa2 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java @@ -59,8 +59,8 @@ public void validate() throws ValidationException { Stream.of( validate(NodeGraphicInput.class, dataSource, nodeGraphicInputFactory), validate(LineGraphicInput.class, dataSource, lineGraphicInputFactory)), - "Validation") - .transformF(FailedValidationException::new) + "Validation", + FailedValidationException::new) .getOrThrow(); } @@ -95,12 +95,11 @@ public GraphicElements getGraphicElements(Map nodes, Map, SourceException> lineGraphics = Try.of(() -> getLineGraphicInput(lines), SourceException.class); - List exceptions = Try.getExceptions(List.of(nodeGraphics, lineGraphics)); + List exceptions = Try.getExceptions(nodeGraphics, lineGraphics); if (!exceptions.isEmpty()) { throw new GraphicSourceException( - exceptions.size() + " error(s) occurred while initializing graphic elements. ", - exceptions); + "Some exception(s) occurred while initializing graphic elements. ", exceptions); } else { // if everything is fine, return a GraphicElements instance // getOrThrow should not throw an exception in this context, because all exception are diff --git a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java index 46d9432b5..3d5f5e2ef 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java @@ -5,11 +5,19 @@ */ package edu.ie3.datamodel.io.source; -import edu.ie3.datamodel.exceptions.*; +import edu.ie3.datamodel.exceptions.FailedValidationException; +import edu.ie3.datamodel.exceptions.RawGridException; +import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.input.*; -import edu.ie3.datamodel.models.input.*; -import edu.ie3.datamodel.models.input.connector.*; +import edu.ie3.datamodel.models.input.MeasurementUnitInput; +import edu.ie3.datamodel.models.input.NodeInput; +import edu.ie3.datamodel.models.input.OperatorInput; +import edu.ie3.datamodel.models.input.connector.LineInput; +import edu.ie3.datamodel.models.input.connector.SwitchInput; +import edu.ie3.datamodel.models.input.connector.Transformer2WInput; +import edu.ie3.datamodel.models.input.connector.Transformer3WInput; import edu.ie3.datamodel.models.input.connector.type.LineTypeInput; import edu.ie3.datamodel.models.input.connector.type.Transformer2WTypeInput; import edu.ie3.datamodel.models.input.connector.type.Transformer3WTypeInput; @@ -62,8 +70,8 @@ public void validate() throws ValidationException { validate(Transformer3WInput.class, dataSource, transformer3WInputFactory), validate(SwitchInput.class, dataSource, switchInputFactory), validate(MeasurementUnitInput.class, dataSource, measurementUnitInputFactory)), - "Validation") - .transformF(FailedValidationException::new) + "Validation", + FailedValidationException::new) .getOrThrow(); } @@ -146,12 +154,11 @@ public RawGridElements getGridData( Try.of(() -> getMeasurementUnits(operators, nodes), SourceException.class); List exceptions = - Try.getExceptions( - List.of(transformer2WInputs, transformer3WInputs, switches, measurementUnits)); + Try.getExceptions(transformer2WInputs, transformer3WInputs, switches, measurementUnits); if (!exceptions.isEmpty()) { throw new RawGridException( - exceptions.size() + " error(s) occurred while initializing raw grid. ", exceptions); + "Some exception(s) occurred while initializing raw grid.", exceptions); } else { /* build and return the grid if it is not empty */ // getOrThrow should not throw an exception in this context, because all exception are diff --git a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java index d1889c4bd..1e555cc56 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java @@ -105,9 +105,7 @@ public void validate() throws ValidationException { validate(FlexOptionsResult.class, dataSource, flexOptionsResultFactory), validate(CongestionResult.class, dataSource, congestionResultFactory))); - Try.scanCollection(participantResults, Void.class) - .transformF(FailedValidationException::new) - .getOrThrow(); + Try.scanCollection(participantResults, Void.class, FailedValidationException::new).getOrThrow(); } /** diff --git a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java index 48aba751e..dc240862a 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java @@ -110,8 +110,8 @@ public void validate() throws ValidationException { validate(StorageInput.class, dataSource, storageInputFactory), validate(WecInput.class, dataSource, wecInputFactory), validate(EvcsInput.class, dataSource, evcsInputFactory)), - "Validation") - .transformF(FailedValidationException::new) + "Validation", + FailedValidationException::new) .getOrThrow(); } @@ -212,22 +212,20 @@ public SystemParticipants getSystemParticipants( List exceptions = Try.getExceptions( - List.of( - fixedFeedInInputs, - pvInputs, - loads, - bmInputs, - storages, - wecInputs, - evs, - evcs, - chpInputs, - hpInputs)); + fixedFeedInInputs, + pvInputs, + loads, + bmInputs, + storages, + wecInputs, + evs, + evcs, + chpInputs, + hpInputs); if (!exceptions.isEmpty()) { throw new SystemParticipantsException( - exceptions.size() + " error(s) occurred while initializing system participants. ", - exceptions); + "Some exception(s) occurred while initializing system participants.", exceptions); } else { // if everything is fine, return a system participants container // getOrThrow should not throw an exception in this context, because all exception are diff --git a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java index f8d73a0a7..bc955ff82 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java @@ -65,8 +65,8 @@ public void validate() throws ValidationException { dataSource, domesticHotWaterStorageInputFactory), validate(ThermalHouseInput.class, dataSource, thermalHouseInputFactory)), - "Validation") - .transformF(FailedValidationException::new) + "Validation", + FailedValidationException::new) .getOrThrow(); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java index 15e5b63e4..eeb956b56 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java @@ -38,10 +38,12 @@ public void validate() throws ValidationException { * @return That mapping */ public Map getMapping() throws SourceException { - return Try.scanStream(getMappingSourceData().map(this::createMappingEntry), "MappingEntry") - .transform( - s -> s.collect(Collectors.toMap(MappingEntry::getAsset, MappingEntry::getTimeSeries)), + return Try.scanStream( + getMappingSourceData().map(this::createMappingEntry), + "MappingEntry", SourceException::new) + .transformS( + s -> s.collect(Collectors.toMap(MappingEntry::getAsset, MappingEntry::getTimeSeries))) .getOrThrow(); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java b/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java index 16b2a9f4f..67f3ecd0b 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java @@ -74,9 +74,7 @@ public void validate() throws ValidationException { validate(Transformer2WTypeInput.class, dataSource, transformer2WTypeInputFactory), validate(Transformer3WTypeInput.class, dataSource, transformer3WTypeInputFactory))); - Try.scanCollection(participantResults, Void.class) - .transformF(FailedValidationException::new) - .getOrThrow(); + Try.scanCollection(participantResults, Void.class, FailedValidationException::new).getOrThrow(); } /** diff --git a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java index a7a056d85..94fdbd733 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java @@ -170,8 +170,9 @@ protected List> buildTimeBasedValues( return factory.get( Try.from(data, () -> new SourceException("Missing data in: " + data))); }), - "TimeBasedValue") - .transform(Stream::toList, SourceException::new) + "TimeBasedValue", + SourceException::new) + .transformS(Stream::toList) .getOrThrow(); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java index 2e8dc614d..6879b57eb 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java @@ -232,22 +232,22 @@ protected Map buildFieldsToAttributes( + headline.length + ") does not fit to the size of the attribute fields (" + fieldVals.length - + ").\nHeadline: " + + ").\n Headline fields: " + headlineElements - + "\nParsed row: " + + "\n Row values: " + parsedRow - + ".\nPlease check:" - + "\n - is the csv separator in the row matching the provided separator '" + + ".\n Please check:" + + "\n - is the csv separator in the row matching the provided separator '" + csvSep + "'" - + "\n - does the number of columns match the number of headline fields " - + "\n - are you using a valid RFC 4180 formatted csv row?"); + + "\n - does the number of columns match the number of headline fields " + + "\n - are you using a valid RFC 4180 formatted csv row?"); } TreeMap insensitiveFieldsToAttributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); insensitiveFieldsToAttributes.putAll( - IntStream.range(0, Math.min(fieldVals.length, headline.length)) + IntStream.range(0, headline.length) .boxed() .collect( Collectors.toMap( @@ -255,9 +255,9 @@ protected Map buildFieldsToAttributes( if (insensitiveFieldsToAttributes.size() != fieldVals.length) { throw new SourceException( - "There might be duplicate headline elements.\nHeadline: " - + String.join(", ", headline) - + ".\nPlease keep in mind that headlines are case-insensitive and underscores from snake case are ignored."); + "There might be duplicate headline elements.\nHeadline fields: ['" + + String.join("', '", headline) + + "'].\nPlease keep in mind that headlines are case-insensitive and underscores from snake case are ignored."); } return insensitiveFieldsToAttributes; @@ -313,9 +313,7 @@ protected Try>, SourceException> buildStreamWithField // is wanted to avoid a lock on the file), but this causes a closing of the stream as well. // As we still want to consume the data at other places, we start a new stream instead of // returning the original one - return csvRowFieldValueMapping(reader, headline, filePath.getFileName()) - .transformF( - e -> new SourceException("The file '" + filePath + "' could not be parsed.", e)); + return csvRowFieldValueMapping(reader, headline, filePath.getFileName()); } catch (FileNotFoundException e) { if (allowFileNotExisting) { log.warn("Unable to find file '{}': {}", filePath, e.getMessage()); @@ -355,9 +353,8 @@ protected Try>, SourceException> csvRowFieldValueMapp Try.of( () -> buildFieldsToAttributes(csvRow, headline), SourceException.class)), - fileName.toString()) - .transform( - stream -> stream.filter(map -> !map.isEmpty()), - e -> new SourceException("Parsing csv row failed.", e)); + fileName.toString(), + SourceException::new) + .transformS(stream -> stream.filter(map -> !map.isEmpty())); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java index 26038ee55..59c29d28c 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java @@ -77,8 +77,7 @@ private Map setupIdToCoordinateMap() throws SourceException { fieldToValues -> new SimpleFactoryData(fieldToValues, IdCoordinateInput.class)) .map(factory::get)) - .flatMap( - s -> Try.scanStream(s, "Pair").transformF(SourceException::new)) + .flatMap(s -> Try.scanStream(s, "Pair", SourceException::new)) .getOrThrow() .toList(); diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvJointGridContainerSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvJointGridContainerSource.java index 2fd49c0f1..5796454a8 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvJointGridContainerSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvJointGridContainerSource.java @@ -89,8 +89,7 @@ public static JointGridContainer read( Try.getExceptions(rawGridElements, systemParticipants, graphicElements); if (!exceptions.isEmpty()) { - throw new SourceException( - exceptions.size() + " error(s) occurred while reading sources. ", exceptions); + throw new SourceException("Some exception(s) occurred while reading the grid.", exceptions); } else { // getOrThrow should not throw an exception in this context, because all exception are // filtered and thrown before diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java index f3b271617..e17d07211 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvLoadProfileSource.java @@ -116,8 +116,8 @@ protected LoadProfileTimeSeries buildLoadProfileTimeSeries( .buildStreamWithFieldsToAttributesMap(filePath, false) .flatMap( stream -> - Try.scanStream(stream.map(fieldToValueFunction), "LoadProfileEntry") - .transformF(SourceException::new)) + Try.scanStream( + stream.map(fieldToValueFunction), "LoadProfileEntry", SourceException::new)) .getOrThrow() .collect(Collectors.toSet()); diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java index 4b5acd0ba..7b110711b 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java @@ -166,8 +166,10 @@ protected IndividualTimeSeries buildIndividualTimeSeries( .buildStreamWithFieldsToAttributesMap(filePath, false) .flatMap( stream -> - Try.scanStream(stream.map(fieldToValueFunction), "TimeBasedValue") - .transformF(SourceException::new)); + Try.scanStream( + stream.map(fieldToValueFunction), + "TimeBasedValue", + SourceException::new)); return new IndividualTimeSeries<>( timeSeriesUuid, new HashSet<>(timeBasedValues.getOrThrow().toList())); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java index 803fd6c26..241442725 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java @@ -224,7 +224,7 @@ private Map> readWeatherTimeSeries( if (exceptions.isEmpty()) { return weatherTimeSeries; } else { - throw new SourceException("Due to: " + ExceptionUtils.getMessages(exceptions)); + throw new SourceException("Due to: " + ExceptionUtils.combineExceptions(exceptions)); } } diff --git a/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java b/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java index df8f03947..f700958bf 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java @@ -5,11 +5,8 @@ */ package edu.ie3.datamodel.utils; -import edu.ie3.datamodel.models.Entity; -import java.util.Arrays; -import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; +import java.util.function.Function; public class ExceptionUtils { private ExceptionUtils() { @@ -22,45 +19,29 @@ private ExceptionUtils() { * @param exceptions list of exceptions * @return str containing the messages */ - public static String getMessages(List exceptions) { - return exceptions.stream() - .map(Throwable::getMessage) - .reduce("", (a, b) -> a + "\n " + b) - .replaceFirst("\n ", ""); - } + public static String combineExceptions(List exceptions) { + String messageSeparator = "\n "; - /** - * Creates a string containing multiple exception messages. - * - * @param exceptions list of exceptions - * @return str containing the messages - */ - public static String getFullMessages(List exceptions) { - return exceptions.stream() - .map(e -> e.getMessage() + printStackTrace(e.getStackTrace())) - .reduce("", (a, b) -> a + "\n " + b) - .replaceFirst("\n ", ""); - } + // function to convert an exception into a string + Function converter = + e -> { + String clazz = e.getClass().getName(); + String message = e.getMessage(); + Throwable cause = e.getCause(); - /** - * Combines multiple {@link Entity} into a string. - * - * @param entities to be combined - * @return a string - */ - public static String combine(Collection entities) { - return "{" + entities.stream().map(Entity::toString).collect(Collectors.joining(", ")) + "}"; - } + String res = clazz + ": " + message; - /** - * Method for combining {@link StackTraceElement}s. - * - * @param elements to be combined - * @return a string - */ - public static String printStackTrace(StackTraceElement... elements) { - return Arrays.stream(elements) - .map(StackTraceElement::toString) - .collect(Collectors.joining("\n ")); + if (cause != null) { + res += " Caused by: " + cause.getClass().getName() + ": " + cause.getMessage(); + } + + return res; + }; + + String messages = + exceptions.stream().map(converter).reduce("", (a, b) -> a + messageSeparator + b); + + // some formating + return messages.replaceAll("\n", "\n ").replaceFirst(messageSeparator, ""); } } diff --git a/src/main/java/edu/ie3/datamodel/utils/Try.java b/src/main/java/edu/ie3/datamodel/utils/Try.java index 7ff103cc8..9becdda01 100644 --- a/src/main/java/edu/ie3/datamodel/utils/Try.java +++ b/src/main/java/edu/ie3/datamodel/utils/Try.java @@ -7,7 +7,6 @@ import static java.util.stream.Collectors.partitioningBy; -import edu.ie3.datamodel.exceptions.FailureException; import edu.ie3.datamodel.exceptions.TryException; import java.util.*; import java.util.function.BiFunction; @@ -35,7 +34,7 @@ public static Try of(TrySupplier supplier, return new Success<>(supplier.get()); } catch (Exception e) { // this is necessary because we only want to catch exceptions that are of type E - if (e.getClass().isAssignableFrom(clazz)) { + if (clazz.isAssignableFrom(e.getClass())) { return new Failure<>((E) e); } else { throw new TryException("Wrongly caught exception: ", e); @@ -133,8 +132,7 @@ public static Try from( * @param tries collection of {@link Try} objects * @return a list of {@link Exception}'s */ - public static List getExceptions( - Collection> tries) { + public static List getExceptions(Collection> tries) { return getExceptions(tries.stream()); } @@ -144,7 +142,7 @@ public static List getExceptions( * @param tries stream of {@link Try} objects * @return a list of {@link Exception}'s */ - public static List getExceptions(Stream> tries) { + public static List getExceptions(Stream> tries) { return tries.filter(Try::isFailure).map(t -> ((Failure) t).get()).toList(); } @@ -163,13 +161,15 @@ public static List getExceptions(Try * Method to scan a collection of {@link Try} objects for {@link Failure}'s. * * @param c collection of {@link Try} objects - * @param typeOfData type of data + * @param typeOfData information added to exception to help identify the place, that needs to be + * fixed + * @param exceptionBuilder function to build the failure message * @return a {@link Success} if no {@link Failure}'s are found in the collection * @param type of data */ - public static Try, FailureException> scanCollection( - Collection> c, Class typeOfData) { - return scanStream(c.stream(), typeOfData.getSimpleName()) + public static Try, RE> scanCollection( + Collection> c, Class typeOfData, Function exceptionBuilder) { + return scanStream(c.stream(), typeOfData.getSimpleName(), exceptionBuilder) .transformS(stream -> stream.collect(Collectors.toSet())); } @@ -177,11 +177,14 @@ public static Try, FailureException> scanCollect * Method to scan a stream of {@link Try} objects for {@link Failure}'s. * * @param stream of {@link Try} objects + * @param typeOfData information added to exception to help identify the place, that needs to be + * fixed + * @param exceptionBuilder function to build the failure message * @return a {@link Success} if no {@link Failure}'s are found in the stream * @param type of data */ - public static Try, FailureException> scanStream( - Stream> stream, String typeOfData) { + public static Try, RE> scanStream( + Stream> stream, String typeOfData, Function exceptionBuilder) { Map>> map = stream.collect(partitioningBy(Try::isSuccess)); List> successes = map.get(true); @@ -191,16 +194,15 @@ public static Try, FailureException> scanStre assert successes != null && failures != null; if (!failures.isEmpty()) { - E first = ((Failure) failures.get(0)).exception; + List exceptions = failures.stream().map(f -> ((Failure) f).exception).toList(); return new Failure<>( - new FailureException( - failures.size() + exceptionBuilder.apply( + exceptions.size() + " exception(s) occurred within \"" + typeOfData - + "\" data, one is: " - + first, - first.getCause())); + + "\" data: \n " + + ExceptionUtils.combineExceptions(exceptions))); } else { return new Success<>(successes.stream().map(t -> ((Success) t).data)); } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy index 2cdac0018..3fb52f9f5 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy @@ -171,7 +171,7 @@ class EnergyManagementSourceTest extends Specification { then: def exc = thrown(SourceException) - exc.cause.message.contains("test failure abc") + exc.message.contains("test failure abc") } def "An EnergyManagementSource should fail if a parent EM UUID is malformed"() { @@ -198,7 +198,7 @@ class EnergyManagementSourceTest extends Specification { then: def exc = thrown(SourceException) - exc.cause.message.contains("Exception while trying to parse UUID of field \"controllingEm\" with value \"not-a-uuid\"") + exc.message.contains("Exception while trying to parse UUID of field \"controllingEm\" with value \"not-a-uuid\"") } def "An EnergyManagementSource should fail if the factory fails for one EM"() { @@ -224,8 +224,8 @@ class EnergyManagementSourceTest extends Specification { then: def exc = thrown(SourceException) - exc.cause.message.contains("An error occurred when creating instance of EmInput") - exc.cause.cause.class == FactoryException + exc.message == "1 exception(s) occurred within \"EmInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of EmInput.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Field \"id\" not found in EntityData" } def "An EnergyManagementSource should fail if a parent em is not provided"() { diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy index 0c46d0c9a..801faeb4d 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy @@ -48,7 +48,8 @@ class EntitySourceTest extends Specification { then: SourceException ex = thrown() - ex.message == "edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within \"OperatorInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of OperatorInput.class." + ex.message == "1 exception(s) occurred within \"OperatorInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of OperatorInput.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Field \"id\" not found in EntityData" } def "An EntitySource can build EntityData correctly"() { diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy index 9b5da47e2..8d7f475ed 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy @@ -98,8 +98,7 @@ class TimeSeriesMappingSourceTest extends Specification { then: def ex = thrown(SourceException) - ex.cause.class == FailureException - ex.cause.message.startsWith("1 exception(s) occurred within \"MappingEntry\" data") + ex.message.startsWith("1 exception(s) occurred within \"MappingEntry\" data") } def "should throw SourceException for invalid timeSeries"(){ @@ -110,8 +109,7 @@ class TimeSeriesMappingSourceTest extends Specification { then: def ex = thrown(SourceException) - ex.cause.class == FailureException - ex.cause.message.startsWith("1 exception(s) occurred within \"MappingEntry\" data") + ex.message.startsWith("1 exception(s) occurred within \"MappingEntry\" data") } def "should throw SourceException for invalid timeSeries and asset"(){ @@ -122,7 +120,8 @@ class TimeSeriesMappingSourceTest extends Specification { then: def ex = thrown(SourceException) - ex.cause.class == FailureException - ex.cause.message.startsWith("2 exception(s) occurred within \"MappingEntry\" data") + ex.message == "2 exception(s) occurred within \"MappingEntry\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of MappingEntry.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Exception while trying to parse UUID of field \"asset\" with value \"invalidAsset\"\n" + + " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of MappingEntry.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Exception while trying to parse UUID of field \"asset\" with value \"invalidAsset2\"" } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy index 83c5108c3..754c62fa8 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvDataSourceTest.groovy @@ -127,6 +127,18 @@ class CsvDataSourceTest extends Specification implements CsvTestDataMeta { ] as Set } + def "A CsvDataSource should throw an exception when retrieving column names from a valid CSV file using the wrong separator"() { + given: + DummyCsvSource source = new DummyCsvSource(";", participantsFolderPath, fileNamingStrategy) + + when: + source.getSourceFields(LoadInput) + + then: + SourceException ex = thrown(SourceException) + ex.message == "The given file has less than two columns! (Used separator ';' on headline 'uuid,cos_phi_rated,e_cons_annual,id,node,operates_from,operates_until,operator,q_characteristics,s_rated,load_profile,controlling_em')" + } + def "A CsvDataSource should return an empty result when retrieving column names for a non-existing CSV file"() { given: def path = Path.of("this/path/does-not-exist") @@ -322,16 +334,15 @@ class CsvDataSourceTest extends Specification implements CsvTestDataMeta { then: def exception = thrown(SourceException) - exception.getMessage().startsWith("The size of the headline (8) does not fit to the size of the attribute fields") + exception.getMessage() == expectedMessage where: - invalidCsvRow || explaination - "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8;25.0;100.0;0.95;98.0;test_bmTypeInput;50.0;25.0" || "wrong separator" - "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,25.0,100.0,0.95,98.0,test_bmTypeInput" || "too little columns" - "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,25.0,100.0,0.95,98.0,test_bmTypeInput,,,," || "too many columns" + invalidCsvRow || explaination || expectedMessage + "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8;25.0;100.0;0.95;98.0;test_bmTypeInput;50.0;25.0" || "wrong separator" || "The size of the headline (8) does not fit to the size of the attribute fields (1).\n Headline fields: ['uuid', 'active_power_gradient', 'capex', 'cosphi_rated', 'eta_conv', 'id', 'opex', 's_rated']\n Row values: ['5ebd8f7e-dedb-4017-bb86-6373c4b68eb8;25.0;100.0;0.95;98.0;test_bmTypeInput;50.0;25.0'].\n Please check:\n - is the csv separator in the row matching the provided separator ','\n - does the number of columns match the number of headline fields \n - are you using a valid RFC 4180 formatted csv row?" + "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,25.0,100.0,0.95,98.0,test_bmTypeInput" || "too little columns" || "The size of the headline (8) does not fit to the size of the attribute fields (6).\n Headline fields: ['uuid', 'active_power_gradient', 'capex', 'cosphi_rated', 'eta_conv', 'id', 'opex', 's_rated']\n Row values: ['5ebd8f7e-dedb-4017-bb86-6373c4b68eb8', '25.0', '100.0', '0.95', '98.0', 'test_bmTypeInput'].\n Please check:\n - is the csv separator in the row matching the provided separator ','\n - does the number of columns match the number of headline fields \n - are you using a valid RFC 4180 formatted csv row?" + "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8,25.0,100.0,0.95,98.0,test_bmTypeInput,,,," || "too many columns" || "The size of the headline (8) does not fit to the size of the attribute fields (10).\n Headline fields: ['uuid', 'active_power_gradient', 'capex', 'cosphi_rated', 'eta_conv', 'id', 'opex', 's_rated']\n Row values: ['5ebd8f7e-dedb-4017-bb86-6373c4b68eb8', '25.0', '100.0', '0.95', '98.0', 'test_bmTypeInput', '', '', '', ''].\n Please check:\n - is the csv separator in the row matching the provided separator ','\n - does the number of columns match the number of headline fields \n - are you using a valid RFC 4180 formatted csv row?" } - def "A CsvDataSource should throw an exception if there are duplicate headlines"() { given: def invalidHeadline = [ diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy index 9a7b15fc5..b218968df 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy @@ -70,7 +70,9 @@ class CsvGraphicSourceTest extends Specification implements CsvTestDataMeta { Exception ex = graphicElements.exception.get() ex.class == GraphicSourceException - ex.message.startsWith("1 error(s) occurred while initializing graphic elements. edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within \"LineGraphicInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not provided.") + ex.message == "Some exception(s) occurred while initializing graphic elements. \n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"LineGraphicInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not provided." } @@ -143,8 +145,7 @@ class CsvGraphicSourceTest extends Specification implements CsvTestDataMeta { then: def e = thrown(SourceException) - e.cause.class == FailureException - e.cause.message.startsWith(expectedFailures + " exception(s) occurred") + e.message.startsWith(expectedFailures + " exception(s) occurred") where: nodeCollection || expectedFailures @@ -167,8 +168,7 @@ class CsvGraphicSourceTest extends Specification implements CsvTestDataMeta { then: def e = thrown(SourceException) - e.cause.class == FailureException - e.cause.message.startsWith(expectedFailures + " exception(s) occurred") + e.message.startsWith(expectedFailures + " exception(s) occurred") where: lineCollection || expectedFailures diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy index b3ad25998..282f1d16b 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy @@ -268,7 +268,8 @@ class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { then: "the optional is empty" actual == null SourceException ex = thrown() - ex.message == "edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within \"NodeInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of NodeInput.class." + ex.message == "1 exception(s) occurred within \"NodeInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of NodeInput.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Exception while trying to parse UUID of field \"uuid\" with value \"bd837a25-58f3-44ac-aa90-c6b6e3 cd91b2\"" } def "The CsvRawGridSource returns an empty grid, if the RawGridElements contain no single element"() { @@ -308,6 +309,8 @@ class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { Exception ex = rawGridElements.exception.get() ex.class == SourceException - ex.message.startsWith("edu.ie3.datamodel.exceptions.FailureException: 2 exception(s) occurred within \"LineInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid ") + ex.message == "2 exception(s) occurred within \"LineInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid bd837a25-58f3-44ac-aa90-c6b6e3cd91b2 was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid bd837a25-58f3-44ac-aa90-c6b6e3cd91b2 was not provided." } } \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy index 67316ab30..1e8285318 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy @@ -76,10 +76,27 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat Exception ex = systemParticipants.exception.get() ex.class == SystemParticipantsException - ex.message.startsWith("10 error(s) occurred while initializing system participants. " + - "edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within " + - "\"FixedFeedInInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: " + - "edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.") + ex.message == "Some exception(s) occurred while initializing system participants.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"FixedFeedInInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"PvInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"LoadInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"BmInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"StorageInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"WecInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"EvInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"EvcsInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"ChpInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"HpInput\" data: \n" + + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided." } def "A SystemParticipantSource with csv input should return data from valid input file as expected"() { diff --git a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy index b7d636a15..8ecd40bec 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy @@ -233,17 +233,18 @@ class TryTest extends Specification { given: Set> set = Set.of( new Try.Success<>("one"), - new Try.Failure<>(new Exception("exception")), + new Try.Failure<>(new Exception("exception", new SourceException("source exception"))), new Try.Success<>("two"), new Try.Success<>("three") ) when: - Try, Exception> scan = Try.scanCollection(set, String) + Try, Exception> scan = Try.scanCollection(set, String, Exception::new) then: scan.failure - scan.exception.get().message == "1 exception(s) occurred within \"String\" data, one is: java.lang.Exception: exception" + scan.exception.get().message == "1 exception(s) occurred within \"String\" data: \n" + + " java.lang.Exception: exception Caused by: edu.ie3.datamodel.exceptions.SourceException: source exception" } def "A scan for exceptions should work as expected when no failures are included"() { @@ -255,7 +256,7 @@ class TryTest extends Specification { ) when: - Try, Exception> scan = Try.scanCollection(set, String) + Try, Exception> scan = Try.scanCollection(set, String, Exception::new) then: scan.success diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy index 0992c426b..a8c90b766 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/SystemParticipantValidationUtilsTest.groovy @@ -99,10 +99,8 @@ class SystemParticipantValidationUtilsTest extends Specification { SystemParticipantValidationUtils.check(invalidType) then: - Throwable topEx = thrown() - Throwable ex = topEx.cause - ex.class == expectedException.class - ex.message == expectedException.message + Throwable ex = thrown() + ex.message.contains(expectedException.message) where: invalidType || expectedException @@ -142,10 +140,8 @@ class SystemParticipantValidationUtilsTest extends Specification { ValidationUtils.check(invalidBmType) then: - Throwable topEx = thrown() - Throwable ex = topEx.cause - ex.class == expectedException.class - ex.message == expectedException.message + Throwable ex = thrown() + ex.message.contains(expectedException.message) where: invalidBmType || expectedException @@ -184,10 +180,8 @@ class SystemParticipantValidationUtilsTest extends Specification { SystemParticipantValidationUtils.check(invalidChpType) then: - Throwable topEx = thrown() - Throwable ex = topEx.cause - ex.class == expectedException.class - ex.message == expectedException.message + Throwable ex = thrown() + ex.message.contains(expectedException.message) where: invalidChpType || expectedException @@ -228,10 +222,8 @@ class SystemParticipantValidationUtilsTest extends Specification { SystemParticipantValidationUtils.check(invalidEvType) then: - Throwable topEx = thrown() - Throwable ex = topEx.cause - ex.class == expectedException.class - ex.message == expectedException.message + Throwable ex = thrown() + ex.message.contains(expectedException.message) where: invalidEvType || expectedException @@ -298,10 +290,8 @@ class SystemParticipantValidationUtilsTest extends Specification { SystemParticipantValidationUtils.check(invalidHpType) then: - Throwable topEx = thrown() - Throwable ex = topEx.cause - ex.class == expectedException.class - ex.message == expectedException.message + Throwable ex = thrown() + ex.message.contains(expectedException.message) where: invalidHpType || expectedException @@ -402,10 +392,8 @@ class SystemParticipantValidationUtilsTest extends Specification { SystemParticipantValidationUtils.check(invalidStorageType) then: - Throwable topEx = thrown() - Throwable ex = topEx.cause - ex.class == expectedException.class - ex.message == expectedException.message + Throwable ex = thrown() + ex.message.contains(expectedException.message) where: invalidStorageType || expectedException @@ -445,10 +433,8 @@ class SystemParticipantValidationUtilsTest extends Specification { SystemParticipantValidationUtils.check(invalidWecType) then: - Throwable topEx = thrown() - Throwable ex = topEx.cause - ex.class == expectedException.class - ex.message == expectedException.message + Throwable ex = thrown() + ex.message.contains(expectedException.message) where: invalidWecType || expectedException diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy index 82066c52c..4fb703f60 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy @@ -97,9 +97,8 @@ class UniquenessValidationUtilsTest extends Specification { then: DuplicateEntitiesException de = thrown() - de.message == "The following exception(s) occurred while checking the uniqueness of 'AssetInput' entities: " + - "'DummyAssetInput' entities with duplicated String key, but different field values found! " + - "Affected primary keys: [first]" + de.message == "The following exception(s) occurred while checking the uniqueness of 'AssetInput' entities: \n" + + " edu.ie3.datamodel.exceptions.DuplicateEntitiesException: 'DummyAssetInput' entities with duplicated String key, but different field values found! Affected primary keys: [first]" } def "Checking if result entities are unique"() { diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/ValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/ValidationUtilsTest.groovy index 43d3b518f..74bf0a1b4 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/ValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/ValidationUtilsTest.groovy @@ -86,7 +86,7 @@ class ValidationUtilsTest extends Specification { then: InvalidEntityException ex = thrown() - ex.message == "Entity is invalid because of: \nThe following quantities have to be zero or positive: -1 µS/km [LineTypeInput{uuid=3bed3eb3-9790-4874-89b5-a5434d408088, id=lineType_AtoB, b=-1 µS/km, g=0.0 µS/km, r=0.437 Ω/km, x=0.356 Ω/km, iMax=300 A, vRated=20 kV}]" + ex.message == "Entity is invalid because of: The following quantities have to be zero or positive: -1 µS/km [LineTypeInput{uuid=3bed3eb3-9790-4874-89b5-a5434d408088, id=lineType_AtoB, b=-1 µS/km, g=0.0 µS/km, r=0.437 Ω/km, x=0.356 Ω/km, iMax=300 A, vRated=20 kV}]" } def "The check for zero or negative entities should work as expected"() { @@ -123,7 +123,7 @@ class ValidationUtilsTest extends Specification { then: InvalidEntityException ex = thrown() - ex.message == "Entity is invalid because of: \nThe following quantities have to be positive: 0.0 µS/km [LineTypeInput{uuid=3bed3eb3-9790-4874-89b5-a5434d408088, id=lineType_AtoB, b=0.0 µS/km, g=0.0 µS/km, r=0.437 Ω/km, x=0.356 Ω/km, iMax=300 A, vRated=20 kV}]" + ex.message == "Entity is invalid because of: The following quantities have to be positive: 0.0 µS/km [LineTypeInput{uuid=3bed3eb3-9790-4874-89b5-a5434d408088, id=lineType_AtoB, b=0.0 µS/km, g=0.0 µS/km, r=0.437 Ω/km, x=0.356 Ω/km, iMax=300 A, vRated=20 kV}]" } def "Checking an asset type input without an id leads to an exception"() { @@ -136,6 +136,6 @@ class ValidationUtilsTest extends Specification { then: exceptions.size() == 1 def e = exceptions.get(0).exception.get() - e.message.startsWith("Entity is invalid because of: \nNo ID assigned [AssetTypeInput") + e.message.startsWith("Entity is invalid because of: No ID assigned [AssetTypeInput") } } From 4bbe628022ac4790cc8e53ce5ac5135c5d753cd9 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Thu, 3 Jul 2025 18:28:33 +0200 Subject: [PATCH 39/60] Extend Validation to EnergyManagement Systems --- CHANGELOG.md | 3 + .../EnergyManagementValidationUtils.java | 58 +++++++++++++++++++ .../utils/validation/ValidationUtils.java | 19 +++--- .../validation/EmValidationUtilsTest.groovy | 41 +++++++++++++ .../edu/ie3/test/common/GridTestData.groovy | 10 ++++ 5 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java create mode 100644 src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index a7593a97d..479009ac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Added +- Extend Validation to EnergyManagement Systems. [#1356](https://github.com/ie3-institute/PowerSystemDataModel/issues/1356) + ### Fixed - Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` [#1325](https://github.com/ie3-institute/PowerSystemDataModel/issues/1325) - -Fixed em fields in input models [#1331](https://github.com/ie3-institute/PowerSystemDataModel/issues/1331) diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java new file mode 100644 index 000000000..661c7659f --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java @@ -0,0 +1,58 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.utils.validation; + +import edu.ie3.datamodel.exceptions.InvalidEntityException; +import edu.ie3.datamodel.exceptions.ValidationException; +import edu.ie3.datamodel.models.input.EmInput; +import edu.ie3.datamodel.utils.Try; +import java.util.ArrayList; +import java.util.List; + +public class EnergyManagementValidationUtils extends ValidationUtils { + + /** Private Constructor as this class is not meant to be instantiated */ + private EnergyManagementValidationUtils() { + throw new IllegalStateException("Don't try and instantiate a Utility class."); + } + + /** + * Validates a energy management unit if: + * + *
      + *
    • its control strategy is not null + *
    • its control strategy matches the supported strategies + *
    + * + * A "distribution" method, that forwards the check request to specific implementations to fulfill + * the checking task, based on the class of the given object. + * + * @param energyManagement EmInput to validate + * @return a list of try objects either containing an {@link ValidationException} or an empty + * Success + */ + protected static List> check(EmInput energyManagement) { + List> exceptions = new ArrayList<>(); + + exceptions.add( + Try.ofVoid( + energyManagement.getControlStrategy() == null, + () -> + new InvalidEntityException( + "No control strategy of energy management defined for", energyManagement))); + + exceptions.add( + Try.ofVoid( + !(energyManagement.getControlStrategy().equals("PRIORITIZED") + || energyManagement.getControlStrategy().equals("PROPORTIONAL")), + () -> + new InvalidEntityException( + "Control strategy of energy management system must be one of the following: PRIORITIZED, PROPORTIONAL.", + energyManagement))); + + return exceptions; + } +} diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java index 80006953b..6d664aba1 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java @@ -5,12 +5,12 @@ */ package edu.ie3.datamodel.utils.validation; -import edu.ie3.datamodel.exceptions.*; +import edu.ie3.datamodel.exceptions.FailedValidationException; +import edu.ie3.datamodel.exceptions.InvalidEntityException; +import edu.ie3.datamodel.exceptions.UnsafeEntityException; +import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.models.UniqueEntity; -import edu.ie3.datamodel.models.input.AssetInput; -import edu.ie3.datamodel.models.input.AssetTypeInput; -import edu.ie3.datamodel.models.input.MeasurementUnitInput; -import edu.ie3.datamodel.models.input.NodeInput; +import edu.ie3.datamodel.models.input.*; import edu.ie3.datamodel.models.input.connector.ConnectorInput; import edu.ie3.datamodel.models.input.connector.type.LineTypeInput; import edu.ie3.datamodel.models.input.connector.type.Transformer2WTypeInput; @@ -22,8 +22,11 @@ import edu.ie3.datamodel.models.input.system.type.SystemParticipantTypeInput; import edu.ie3.datamodel.models.input.thermal.ThermalUnitInput; import edu.ie3.datamodel.utils.Try; -import edu.ie3.datamodel.utils.Try.*; -import java.util.*; +import edu.ie3.datamodel.utils.Try.Failure; +import edu.ie3.datamodel.utils.Try.Success; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.measure.Quantity; @@ -157,6 +160,8 @@ else if (ThermalUnitInput.class.isAssignableFrom(assetInput.getClass())) exceptions.addAll(ThermalValidationUtils.check((ThermalUnitInput) assetInput)); else if (ThermalGrid.class.isAssignableFrom(assetInput.getClass())) exceptions.addAll(ThermalValidationUtils.check((ThermalUnitInput) assetInput)); + else if (EmInput.class.isAssignableFrom(assetInput.getClass())) + exceptions.addAll(EnergyManagementValidationUtils.check((EmInput) assetInput)); else { logNotImplemented(assetInput); } diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy new file mode 100644 index 000000000..b4615a66d --- /dev/null +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy @@ -0,0 +1,41 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.datamodel.utils.validation + +import edu.ie3.datamodel.exceptions.InvalidEntityException +import edu.ie3.datamodel.exceptions.ValidationException +import edu.ie3.datamodel.utils.Try +import edu.ie3.test.common.GridTestData +import spock.lang.Specification + +class EmValidationUtilsTest extends Specification { + + def "Smoke Test: Correct energy management system throws no exception"() { + given: + def em = GridTestData.energyManagementInput + + when: + List> tries = EnergyManagementValidationUtils.check(em) + + then: + tries.every { it.success } + } + + def "The check method recognizes all potential errors for an energy management input"() { + when: + List> exceptions = EnergyManagementValidationUtils.check(invalidEm).stream().filter { it -> it.failure }.toList() + + then: + exceptions.size() == expectedSize + Exception ex = exceptions.get(0).exception.get() + ex.class == expectedException.class + ex.message == expectedException.message + + where: + invalidEm || expectedSize || expectedException + GridTestData.energyManagementInput.copy().controlStrategy("invalid").build() || 1 || new InvalidEntityException("Control strategy of energy management system must be one of the following: PRIORITIZED, PROPORTIONAL.", invalidEm) + } +} diff --git a/src/test/groovy/edu/ie3/test/common/GridTestData.groovy b/src/test/groovy/edu/ie3/test/common/GridTestData.groovy index 2ca15217c..87c13de89 100644 --- a/src/test/groovy/edu/ie3/test/common/GridTestData.groovy +++ b/src/test/groovy/edu/ie3/test/common/GridTestData.groovy @@ -9,6 +9,7 @@ import static edu.ie3.datamodel.models.StandardUnits.* import static edu.ie3.util.quantities.PowerSystemUnits.* import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.input.MeasurementUnitInput import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.OperatorInput @@ -387,4 +388,13 @@ class GridTestData { true, true ) + + public static final EmInput energyManagementInput = new EmInput( + UUID.fromString("4bef6955-5e31-4283-8920-5e4cae267a23"), + "test_energyManagement", + profBroccoli, + defaultOperationTime, + "PRIORITIZED", + null, + ) } From 9088286ff844a9b25d26fea46f0d6493aa31ea2c Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 4 Jul 2025 12:31:02 +0200 Subject: [PATCH 40/60] remove validation for emInput.controllStrategy --- .../validation/EnergyManagementValidationUtils.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java index 661c7659f..762c91f13 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java @@ -24,7 +24,6 @@ private EnergyManagementValidationUtils() { * *
      *
    • its control strategy is not null - *
    • its control strategy matches the supported strategies *
    * * A "distribution" method, that forwards the check request to specific implementations to fulfill @@ -44,14 +43,6 @@ private EnergyManagementValidationUtils() { new InvalidEntityException( "No control strategy of energy management defined for", energyManagement))); - exceptions.add( - Try.ofVoid( - !(energyManagement.getControlStrategy().equals("PRIORITIZED") - || energyManagement.getControlStrategy().equals("PROPORTIONAL")), - () -> - new InvalidEntityException( - "Control strategy of energy management system must be one of the following: PRIORITIZED, PROPORTIONAL.", - energyManagement))); return exceptions; } From 9f9034db5f19c7a73aec6d222e549d9f64b5323a Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 4 Jul 2025 12:31:16 +0200 Subject: [PATCH 41/60] fmt --- .../utils/validation/EnergyManagementValidationUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java index 762c91f13..9b2894cca 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java @@ -43,7 +43,6 @@ private EnergyManagementValidationUtils() { new InvalidEntityException( "No control strategy of energy management defined for", energyManagement))); - return exceptions; } } From 6afd362048167b963f6a261512e97ca6a6830335 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 4 Jul 2025 12:35:35 +0200 Subject: [PATCH 42/60] adapted test --- .../ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy index b4615a66d..ed5139031 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy @@ -36,6 +36,6 @@ class EmValidationUtilsTest extends Specification { where: invalidEm || expectedSize || expectedException - GridTestData.energyManagementInput.copy().controlStrategy("invalid").build() || 1 || new InvalidEntityException("Control strategy of energy management system must be one of the following: PRIORITIZED, PROPORTIONAL.", invalidEm) + GridTestData.energyManagementInput.copy().controlStrategy(null).build() || 1 || new InvalidEntityException("No control strategy of energy management defined for", invalidEm) } } From 44494c1d72c098b276cf35b4e6ea137f4b4e2dda Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 4 Jul 2025 12:51:27 +0200 Subject: [PATCH 43/60] Fixed valid fields for `EmInput` --- CHANGELOG.md | 3 +- .../input/AssetInputEntityFactory.java | 26 ++++++- .../io/factory/input/EmInputFactory.java | 19 +---- .../input/AssetInputEntityFactoryTest.groovy | 6 +- .../factory/input/EmInputFactoryTest.groovy | 73 +++++++++++++++++++ .../FixedFeedInInputFactoryTest.groovy | 16 +++- 6 files changed, 117 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7593a97d..a176bc37f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` [#1325](https://github.com/ie3-institute/PowerSystemDataModel/issues/1325) -- -Fixed em fields in input models [#1331](https://github.com/ie3-institute/PowerSystemDataModel/issues/1331) +- Fixed em fields in input models [#1331](https://github.com/ie3-institute/PowerSystemDataModel/issues/1331) +- Fixed valid fields for `EmInput` [#1360](https://github.com/ie3-institute/PowerSystemDataModel/issues/1360) ### Changed - Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java index b9a104137..9670eca99 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java @@ -24,8 +24,9 @@ public abstract class AssetInputEntityFactory extends UniqueEntityFactory { - private static final String OPERATES_FROM = "operatesFrom"; - private static final String OPERATES_UNTIL = "operatesUntil"; + protected static final String OPERATOR = "operator"; + protected static final String OPERATES_FROM = "operatesFrom"; + protected static final String OPERATES_UNTIL = "operatesUntil"; @SafeVarargs protected AssetInputEntityFactory(Class... allowedClasses) { @@ -48,14 +49,33 @@ protected List> getFields(Class entityClass) { Set constructorParamsUntil = expandSet(constructorParamsMin, OPERATES_UNTIL); Set constructorParamsBoth = expandSet(constructorParamsFrom, OPERATES_UNTIL); + // with operator field + Set constructorParamsOperator = expandSet(constructorParamsMin, OPERATOR); + Set constructorParamsOperatorFrom = expandSet(constructorParamsFrom, OPERATOR); + Set constructorParamsOperatorUntil = expandSet(constructorParamsUntil, OPERATOR); + Set constructorParamsOperatorBoth = expandSet(constructorParamsBoth, OPERATOR); + final String[] additionalFields = getAdditionalFields(); constructorParamsMin = expandSet(constructorParamsMin, additionalFields); constructorParamsFrom = expandSet(constructorParamsFrom, additionalFields); constructorParamsUntil = expandSet(constructorParamsUntil, additionalFields); constructorParamsBoth = expandSet(constructorParamsBoth, additionalFields); + + constructorParamsOperator = expandSet(constructorParamsOperator, additionalFields); + constructorParamsOperatorFrom = expandSet(constructorParamsOperatorFrom, additionalFields); + constructorParamsOperatorUntil = expandSet(constructorParamsOperatorUntil, additionalFields); + constructorParamsOperatorBoth = expandSet(constructorParamsOperatorBoth, additionalFields); + return Arrays.asList( - constructorParamsMin, constructorParamsFrom, constructorParamsUntil, constructorParamsBoth); + constructorParamsMin, + constructorParamsFrom, + constructorParamsUntil, + constructorParamsBoth, + constructorParamsOperator, + constructorParamsOperatorFrom, + constructorParamsOperatorUntil, + constructorParamsOperatorBoth); } /** diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/EmInputFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/EmInputFactory.java index 4e2535f70..85e15822d 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/EmInputFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/EmInputFactory.java @@ -8,10 +8,7 @@ import edu.ie3.datamodel.models.OperationTime; import edu.ie3.datamodel.models.input.EmInput; import edu.ie3.datamodel.models.input.OperatorInput; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; public class EmInputFactory extends AssetInputEntityFactory { @@ -23,21 +20,9 @@ public EmInputFactory() { super(EmInput.class); } - @Override - protected List> getFields(Class entityClass) { - List> fields = new ArrayList<>(super.getFields(entityClass)); - - List> withEm = - fields.stream().map(f -> (Set) expandSet(f, CONTROLLING_EM)).toList(); - - fields.addAll(withEm); - - return fields; - } - @Override protected String[] getAdditionalFields() { - return new String[] {CONTROL_STRATEGY}; + return new String[] {CONTROL_STRATEGY, CONTROLLING_EM}; } @Override diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactoryTest.groovy index f6fa90645..a468dfc4e 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactoryTest.groovy @@ -289,7 +289,11 @@ class AssetInputEntityFactoryTest extends Specification implements FactoryTestHe "0: [id, uuid] or [id, uuid]\n" + "1: [id, operatesFrom, uuid] or [id, operates_from, uuid]\n" + "2: [id, operatesUntil, uuid] or [id, operates_until, uuid]\n" + - "3: [id, operatesFrom, operatesUntil, uuid] or [id, operates_from, operates_until, uuid]\n" + "3: [id, operatesFrom, operatesUntil, uuid] or [id, operates_from, operates_until, uuid]\n" + + "4: [id, operator, uuid] or [id, operator, uuid]\n" + + "5: [id, operatesFrom, operator, uuid] or [id, operates_from, operator, uuid]\n" + + "6: [id, operatesUntil, operator, uuid] or [id, operates_until, operator, uuid]\n" + + "7: [id, operatesFrom, operatesUntil, operator, uuid] or [id, operates_from, operates_until, operator, uuid]\n" } private static class TestAssetInput extends AssetInput { diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy index 113fc739e..1c302161d 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy @@ -24,6 +24,79 @@ class EmInputFactoryTest extends Specification { inputFactory.supportedClasses == expectedClasses } + def "An EmInputFactory should return the valid fields correctly"() { + given: + def inputFactory = new EmInputFactory() + def validCombinations = [ + [ + "uuid", + "id", + "controlStrategy", + "controllingEm" + ] as Set, + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operatesFrom" + ] as Set, + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operatesUntil" + ] as Set, + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operatesFrom", + "operatesUntil" + ] as Set, + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operator" + ] as Set, + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operator", + "operatesFrom" + ] as Set, + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operator", + "operatesUntil" + ] as Set, + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operator", + "operatesFrom", + "operatesUntil" + ] as Set, + ] + + when: + def fieldCombinations = inputFactory.getFields(EmInput) + + then: + fieldCombinations == validCombinations + } + def "A EmInputFactory should parse a valid EmInput with parent EM correctly"() { given: def inputFactory = new EmInputFactory() diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy index f43e60e6e..3f4eeb84a 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/participant/FixedFeedInInputFactoryTest.groovy @@ -92,9 +92,17 @@ class FixedFeedInInputFactoryTest extends Specification implements FactoryTestHe "1: [cosPhiRated, id, operatesFrom, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_from, q_characteristics, s_rated, uuid]\n" + "2: [cosPhiRated, id, operatesUntil, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_until, q_characteristics, s_rated, uuid]\n" + "3: [cosPhiRated, id, operatesFrom, operatesUntil, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_from, operates_until, q_characteristics, s_rated, uuid]\n" + - "4: [controllingEm, cosPhiRated, id, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, q_characteristics, s_rated, uuid]\n" + - "5: [controllingEm, cosPhiRated, id, operatesFrom, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, q_characteristics, s_rated, uuid]\n" + - "6: [controllingEm, cosPhiRated, id, operatesUntil, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_until, q_characteristics, s_rated, uuid]\n" + - "7: [controllingEm, cosPhiRated, id, operatesFrom, operatesUntil, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, operates_until, q_characteristics, s_rated, uuid]\n" + "4: [cosPhiRated, id, operator, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operator, q_characteristics, s_rated, uuid]\n" + + "5: [cosPhiRated, id, operatesFrom, operator, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_from, operator, q_characteristics, s_rated, uuid]\n" + + "6: [cosPhiRated, id, operatesUntil, operator, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_until, operator, q_characteristics, s_rated, uuid]\n" + + "7: [cosPhiRated, id, operatesFrom, operatesUntil, operator, qCharacteristics, sRated, uuid] or [cos_phi_rated, id, operates_from, operates_until, operator, q_characteristics, s_rated, uuid]\n" + + "8: [controllingEm, cosPhiRated, id, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, q_characteristics, s_rated, uuid]\n" + + "9: [controllingEm, cosPhiRated, id, operatesFrom, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, q_characteristics, s_rated, uuid]\n" + + "10: [controllingEm, cosPhiRated, id, operatesUntil, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_until, q_characteristics, s_rated, uuid]\n" + + "11: [controllingEm, cosPhiRated, id, operatesFrom, operatesUntil, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, operates_until, q_characteristics, s_rated, uuid]\n" + + "12: [controllingEm, cosPhiRated, id, operator, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operator, q_characteristics, s_rated, uuid]\n" + + "13: [controllingEm, cosPhiRated, id, operatesFrom, operator, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, operator, q_characteristics, s_rated, uuid]\n" + + "14: [controllingEm, cosPhiRated, id, operatesUntil, operator, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_until, operator, q_characteristics, s_rated, uuid]\n" + + "15: [controllingEm, cosPhiRated, id, operatesFrom, operatesUntil, operator, qCharacteristics, sRated, uuid] or [controlling_em, cos_phi_rated, id, operates_from, operates_until, operator, q_characteristics, s_rated, uuid]\n" } } From 48fb0ba7bcf02126b800afe27ff2fc7dbbf0db2d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 7 Jul 2025 11:47:40 +0200 Subject: [PATCH 44/60] Improving test. --- .../factory/input/EmInputFactoryTest.groovy | 86 +++++++------------ 1 file changed, 30 insertions(+), 56 deletions(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy index 1c302161d..9a10787e3 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy @@ -12,6 +12,7 @@ import edu.ie3.datamodel.utils.Try import spock.lang.Specification import java.time.ZonedDateTime +import java.util.stream.Collectors class EmInputFactoryTest extends Specification { @@ -27,67 +28,40 @@ class EmInputFactoryTest extends Specification { def "An EmInputFactory should return the valid fields correctly"() { given: def inputFactory = new EmInputFactory() - def validCombinations = [ - [ - "uuid", - "id", - "controlStrategy", - "controllingEm" - ] as Set, - [ - "uuid", - "id", - "controlStrategy", - "controllingEm", - "operatesFrom" - ] as Set, - [ - "uuid", - "id", - "controlStrategy", - "controllingEm", - "operatesUntil" - ] as Set, + + def requiredFields = [ + "uuid", + "id", + "controlStrategy", + "controllingEm" + ] as Set + + def operationCombinations = [ + ["operatesFrom"], + ["operatesUntil"], [ - "uuid", - "id", - "controlStrategy", - "controllingEm", "operatesFrom", "operatesUntil" - ] as Set, - [ - "uuid", - "id", - "controlStrategy", - "controllingEm", - "operator" - ] as Set, + ], + ["operator"], + ["operatesFrom", "operator"], + ["operatesUntil", "operator"], [ - "uuid", - "id", - "controlStrategy", - "controllingEm", - "operator", - "operatesFrom" - ] as Set, - [ - "uuid", - "id", - "controlStrategy", - "controllingEm", - "operator", - "operatesUntil" - ] as Set, - [ - "uuid", - "id", - "controlStrategy", - "controllingEm", - "operator", "operatesFrom", - "operatesUntil" - ] as Set, + "operatesUntil", + "operator" + ] + ] + + def validCombinations = [ + requiredFields, + requiredFields + operationCombinations.get(0), + requiredFields + operationCombinations.get(1), + requiredFields + operationCombinations.get(2), + requiredFields + operationCombinations.get(3), + requiredFields + operationCombinations.get(4), + requiredFields + operationCombinations.get(5), + requiredFields + operationCombinations.get(6), ] when: From 9d4528b31a41c9ad06102c4594bdd7cac4330f37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 05:53:34 +0000 Subject: [PATCH 45/60] Bump com.diffplug.spotless from 7.0.4 to 7.1.0 (#1364) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c70398ce3..dbc1fd406 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id 'maven-publish' id 'signing' id 'pmd' // code check, working on source code - id 'com.diffplug.spotless' version '7.0.4' //code format + id 'com.diffplug.spotless' version '7.1.0' //code format id 'com.github.spotbugs' version '6.2.1' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation From d8451f5583205f484bfc2ea0c6a5b7ab6fcf67f9 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 8 Jul 2025 19:31:54 +0200 Subject: [PATCH 46/60] proposal for testing fields of EmInputFactory --- .../factory/input/EmInputFactoryTest.groovy | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy index 9a10787e3..5cc32ca9d 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/factory/input/EmInputFactoryTest.groovy @@ -28,41 +28,68 @@ class EmInputFactoryTest extends Specification { def "An EmInputFactory should return the valid fields correctly"() { given: def inputFactory = new EmInputFactory() - - def requiredFields = [ - "uuid", - "id", - "controlStrategy", - "controllingEm" - ] as Set - - def operationCombinations = [ - ["operatesFrom"], - ["operatesUntil"], + def validCombinations = [ [ - "operatesFrom", + "uuid", + "id", + "controlStrategy", + "controllingEm" + ], + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operatesFrom" + ], + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", "operatesUntil" ], - ["operator"], - ["operatesFrom", "operator"], - ["operatesUntil", "operator"], [ + "uuid", + "id", + "controlStrategy", + "controllingEm", "operatesFrom", - "operatesUntil", + "operatesUntil" + ], + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", "operator" + ], + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operator", + "operatesFrom" + ], + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operator", + "operatesUntil" + ], + [ + "uuid", + "id", + "controlStrategy", + "controllingEm", + "operator", + "operatesFrom", + "operatesUntil" ] - ] - - def validCombinations = [ - requiredFields, - requiredFields + operationCombinations.get(0), - requiredFields + operationCombinations.get(1), - requiredFields + operationCombinations.get(2), - requiredFields + operationCombinations.get(3), - requiredFields + operationCombinations.get(4), - requiredFields + operationCombinations.get(5), - requiredFields + operationCombinations.get(6), - ] + ].collect { it as Set } when: def fieldCombinations = inputFactory.getFields(EmInput) From 39e1178340c076e428d9fd895f17843f6467c8db Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 9 Jul 2025 09:05:41 +0200 Subject: [PATCH 47/60] Implementing reviewer's comments. --- src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java | 5 ++++- .../edu/ie3/datamodel/io/source/SystemParticipantSource.java | 5 ++++- .../io/source/csv/CsvSystemParticipantSourceTest.groovy | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java index 3d5f5e2ef..a6dc0ec3f 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java @@ -158,7 +158,10 @@ public RawGridElements getGridData( if (!exceptions.isEmpty()) { throw new RawGridException( - "Some exception(s) occurred while initializing raw grid.", exceptions); + "Exception(s) occurred in " + + exceptions.size() + + " input files while initializing raw grid.", + exceptions); } else { /* build and return the grid if it is not empty */ // getOrThrow should not throw an exception in this context, because all exception are diff --git a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java index dc240862a..00dfd4ce6 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java @@ -225,7 +225,10 @@ public SystemParticipants getSystemParticipants( if (!exceptions.isEmpty()) { throw new SystemParticipantsException( - "Some exception(s) occurred while initializing system participants.", exceptions); + "Exception(s) occurred in " + + exceptions.size() + + " input files while initializing system participants.", + exceptions); } else { // if everything is fine, return a system participants container // getOrThrow should not throw an exception in this context, because all exception are diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy index 1e8285318..b3d750a49 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy @@ -76,7 +76,7 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat Exception ex = systemParticipants.exception.get() ex.class == SystemParticipantsException - ex.message == "Some exception(s) occurred while initializing system participants.\n" + + ex.message == "Exception(s) occurred in 10 input files while initializing system participants.\n" + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"FixedFeedInInput\" data: \n" + " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"PvInput\" data: \n" + From 50e8afad195b6ef2dec6be4362d33353e722e62b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 9 Jul 2025 10:14:47 +0200 Subject: [PATCH 48/60] Fix sonarqube issues. --- src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java | 2 +- src/main/java/edu/ie3/datamodel/utils/Try.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java b/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java index f700958bf..e6dddb67d 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java @@ -42,6 +42,6 @@ public static String combineExceptions(List exceptions) { exceptions.stream().map(converter).reduce("", (a, b) -> a + messageSeparator + b); // some formating - return messages.replaceAll("\n", "\n ").replaceFirst(messageSeparator, ""); + return messages.replace("\n", "\n ").replaceFirst(messageSeparator, ""); } } diff --git a/src/main/java/edu/ie3/datamodel/utils/Try.java b/src/main/java/edu/ie3/datamodel/utils/Try.java index 9becdda01..c03d0ea5e 100644 --- a/src/main/java/edu/ie3/datamodel/utils/Try.java +++ b/src/main/java/edu/ie3/datamodel/utils/Try.java @@ -167,8 +167,8 @@ public static List getExceptions(Try * @return a {@link Success} if no {@link Failure}'s are found in the collection * @param type of data */ - public static Try, RE> scanCollection( - Collection> c, Class typeOfData, Function exceptionBuilder) { + public static Try, R> scanCollection( + Collection> c, Class typeOfData, Function exceptionBuilder) { return scanStream(c.stream(), typeOfData.getSimpleName(), exceptionBuilder) .transformS(stream -> stream.collect(Collectors.toSet())); } @@ -183,8 +183,8 @@ public static Try, RE> sca * @return a {@link Success} if no {@link Failure}'s are found in the stream * @param type of data */ - public static Try, RE> scanStream( - Stream> stream, String typeOfData, Function exceptionBuilder) { + public static Try, R> scanStream( + Stream> stream, String typeOfData, Function exceptionBuilder) { Map>> map = stream.collect(partitioningBy(Try::isSuccess)); List> successes = map.get(true); From e85ae3aaf7ba726c32792872cce57db7a1850d48 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 9 Jul 2025 10:30:04 +0200 Subject: [PATCH 49/60] Improving the exceptions thrown by methods in `UniquenessValidationUtils`. --- .../exceptions/DuplicateEntitiesException.java | 2 +- .../ie3/datamodel/utils/ExceptionUtils.java | 18 ++++++++++++++++++ .../UniquenessValidationUtilsTest.groovy | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java b/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java index eee2942f1..21fd3adbe 100644 --- a/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java +++ b/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java @@ -20,6 +20,6 @@ public DuplicateEntitiesException( "The following exception(s) occurred while checking the uniqueness of '" + entityName + "' entities: \n" - + ExceptionUtils.combineExceptions(exceptions)); + + ExceptionUtils.combineMessages(exceptions)); } } diff --git a/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java b/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java index e6dddb67d..b3d2add80 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ExceptionUtils.java @@ -13,6 +13,24 @@ private ExceptionUtils() { throw new IllegalStateException("Utility classes cannot be instantiated"); } + /** + * Creates a string containing multiple exception messages. + * + * @param exceptions list of exceptions + * @return str containing the messages + */ + public static String combineMessages(List exceptions) { + String messageSeparator = "\n "; + + String messages = + exceptions.stream() + .map(Throwable::getMessage) + .reduce("", (a, b) -> a + messageSeparator + b); + + // some formating + return messages.replace("\n", "\n ").replaceFirst(messageSeparator, ""); + } + /** * Creates a string containing multiple exception messages. * diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy index 4fb703f60..92169b0bf 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/UniquenessValidationUtilsTest.groovy @@ -98,7 +98,7 @@ class UniquenessValidationUtilsTest extends Specification { then: DuplicateEntitiesException de = thrown() de.message == "The following exception(s) occurred while checking the uniqueness of 'AssetInput' entities: \n" + - " edu.ie3.datamodel.exceptions.DuplicateEntitiesException: 'DummyAssetInput' entities with duplicated String key, but different field values found! Affected primary keys: [first]" + " 'DummyAssetInput' entities with duplicated String key, but different field values found! Affected primary keys: [first]" } def "Checking if result entities are unique"() { From 66a9db794e5337fdaabcbde816d3b73dc33be18a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 9 Jul 2025 14:10:34 +0200 Subject: [PATCH 50/60] Changed `SubgridContainer` to represent galvanically seperated grids --- CHANGELOG.md | 4 +- .../models/input/grid/gridcontainer.md | 13 -- .../ie3/datamodel/utils/ContainerUtils.java | 121 +----------- .../EnergyManagementValidationUtils.java | 48 ----- .../GridContainerValidationUtils.java | 20 +- .../utils/validation/ValidationUtils.java | 19 +- .../datamodel/utils/ContainerUtilsTest.groovy | 175 +----------------- .../validation/EmValidationUtilsTest.groovy | 41 ---- .../edu/ie3/test/common/GridTestData.groovy | 10 - 9 files changed, 18 insertions(+), 433 deletions(-) delete mode 100644 src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java delete mode 100644 src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b8252909..7f5fdde36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] -### Added -- Extend Validation to EnergyManagement Systems. [#1356](https://github.com/ie3-institute/PowerSystemDataModel/issues/1356) - ### Fixed - Fixed handling of `CongestionResult.InputModelType` in `EntityProcessor` [#1325](https://github.com/ie3-institute/PowerSystemDataModel/issues/1325) - Fixed em fields in input models [#1331](https://github.com/ie3-institute/PowerSystemDataModel/issues/1331) @@ -17,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) - Extend azimuth angle range to [-180°, 180°] for PV inputs [#1330](https://github.com/ie3-institute/PowerSystemDataModel/issues/1330) +- Changed `SubgridContainer` to represent galvanically seperated grids [#1226](https://github.com/ie3-institute/PowerSystemDataModel/issues/1226) ## [7.0.0] - 2025-05-08 diff --git a/docs/readthedocs/models/input/grid/gridcontainer.md b/docs/readthedocs/models/input/grid/gridcontainer.md index 24f8e66c4..d6411b318 100644 --- a/docs/readthedocs/models/input/grid/gridcontainer.md +++ b/docs/readthedocs/models/input/grid/gridcontainer.md @@ -20,19 +20,6 @@ Why predominant? As of convention, the `SubGridContainers` hold also reference to the transformers leading to higher sub grids and their higher voltage coupling point. -![Sub grid boundary definition for transformers with upstream switchgear](../../../_static/figures/transformerWithSwitchGear.png) - -Let's shed a more detailed light on the boundaries of a sub grid as of our definition. -This especially is important, if the switchgear of the transformer is modeled in detail. -We defined, that all nodes in upstream direction of the transformer, that are connected by switches *only* (therefore -are within the switchgear) are counted towards the inferior sub grid structure (here "2"), although they belong to a -different voltage level. -This decision is taken, because we assume, that the interest to operate on the given switchgear will most likely be -placed in the inferior grid structure. - -The "real" coupling node A is not comprised in the sub grids node collection, but obviously has reference through the -switch between nodes A and B. - A synoptic overview of both classes' attributes is given here: ## Attributes, Units and Remarks diff --git a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java index 5033221a2..522dfeaa4 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java @@ -426,41 +426,11 @@ public static GraphicElements filterForSubnet(GraphicElements input, int subnet) */ public static VoltageLevel determinePredominantVoltLvl(RawGridElements rawGrid, int subnet) throws InvalidGridException { - /* Exclude all nodes, that are at the high voltage side of the transformer */ - Set gridNodes = new HashSet<>(rawGrid.getNodes()); - gridNodes.removeAll( - /* Remove all nodes, that are upstream of transformers, this comprises all those, that are connected by - * switches */ - rawGrid.getTransformer2Ws().stream() - .flatMap( - transformer -> - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGrid) - .stream()) - .collect(Collectors.toSet())); - gridNodes.removeAll( - rawGrid.getTransformer3Ws().stream() - .flatMap( - transformer -> { - if (transformer.getNodeA().getSubnet() == subnet) - return Stream.of(transformer.getNodeB(), transformer.getNodeC()); - else if (transformer.getNodeB().getSubnet() == subnet) - return Stream.concat( - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGrid) - .stream(), - Stream.of(transformer.getNodeC(), transformer.getNodeInternal())); - else - return Stream.concat( - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGrid) - .stream(), - Stream.of(transformer.getNodeB(), transformer.getNodeInternal())); - }) - .collect(Collectors.toSet())); - /* Build a mapping, which voltage level appears how often */ Map voltageLevelCount = - gridNodes.stream() - .map(NodeInput::getVoltLvl) - .collect(Collectors.groupingBy(voltLvl -> voltLvl, Collectors.counting())); + rawGrid.getNodes().stream() + .filter(n -> n.getSubnet() == subnet) + .collect(Collectors.groupingBy(NodeInput::getVoltLvl, Collectors.counting())); /* At this point only one voltage level should be apparent */ int amountOfVoltLvl = voltageLevelCount.size(); @@ -677,14 +647,8 @@ private static TransformerSubGridContainers getSubGridContainers( RawGridElements rawGridElements, Map subGrids) throws TopologyException { - /* Get the sub grid container at port A - travel upstream as long as nodes are connected - * _only_ by switches */ - NodeInput topNode = traverseAlongSwitchChain(transformer.getNodeA(), rawGridElements).getLast(); - if (Objects.isNull(topNode)) - throw new TopologyException( - "Cannot find most upstream node of transformer '" + transformer + "'"); - - SubGridContainer containerA = subGrids.get(topNode.getSubnet()); + /* Get the sub grid container at port A */ + SubGridContainer containerA = subGrids.get(transformer.getNodeA().getSubnet()); /* Get the sub grid container at port B */ SubGridContainer containerB = subGrids.get(transformer.getNodeB().getSubnet()); @@ -696,81 +660,6 @@ private static TransformerSubGridContainers getSubGridContainers( } else return new TransformerSubGridContainers(containerA, containerB); } - /** - * Traversing along a chain of switches and return the traveled nodes. The end thereby is defined - * by a node, that either is a dead end or is connected to any other type of connector (e.g. - * lines, transformers) and therefore leads to other parts of a "real" grid. If the starting node - * is not part of any switch, the starting node is returned. - * - * @param startNode Node that is meant to be the start of the switch chain - * @param rawGridElements Elements of the pure grid structure. - * @return The end node of the switch chain - */ - public static LinkedList traverseAlongSwitchChain( - NodeInput startNode, RawGridElements rawGridElements) { - Set possibleJunctions = - Stream.concat( - Stream.concat( - rawGridElements.getLines().parallelStream(), - rawGridElements.getTransformer2Ws().parallelStream()), - rawGridElements.getTransformer3Ws().parallelStream()) - .flatMap(connector -> connector.allNodes().parallelStream()) - .collect(Collectors.toSet()); - return traverseAlongSwitchChain(startNode, rawGridElements.getSwitches(), possibleJunctions); - } - - /** - * Traversing along a chain of switches and return the traveled nodes. The end thereby is defined - * by a node, that either is a dead end or part of the provided node set. If the starting node is - * not part of any switch, the starting node is returned. - * - * @param startNode Node that is meant to be the start of the switch chain - * @param switches Set of available switches - * @param possibleJunctions Set of nodes that denote possible junctions to "real" grid - * @return The end node of the switch chain - */ - private static LinkedList traverseAlongSwitchChain( - NodeInput startNode, Set switches, Set possibleJunctions) { - LinkedList traveledNodes = new LinkedList<>(); - traveledNodes.addFirst(startNode); - - /* Get the switch, that is connected to the starting node and determine the next node */ - List nextSwitches = - switches.stream().filter(switcher -> switcher.allNodes().contains(startNode)).toList(); - switch (nextSwitches.size()) { - case 0: - /* No further switch found -> Return the starting node */ - break; - case 1: - /* One next switch has been found -> Travel in this direction */ - SwitchInput nextSwitch = nextSwitches.get(0); - Optional candidateNodes = - nextSwitch.allNodes().stream().filter(node -> node != startNode).findFirst(); - NodeInput nextNode = - candidateNodes.orElseThrow( - () -> - new IllegalArgumentException( - "There is no further node available at switch " + nextSwitch)); - if (possibleJunctions.contains(nextNode)) { - /* This is a junction, leading to another Connector than a switch */ - traveledNodes.addLast(nextNode); - } else { - /* Add the traveled nodes to the nodes to be excluded, to avoid endless loops in cyclic switch topologies */ - HashSet newNodesToExclude = new HashSet<>(possibleJunctions); - newNodesToExclude.add(nextNode); - HashSet newSwitches = new HashSet<>(switches); - newSwitches.remove(nextSwitch); - traveledNodes.addAll(traverseAlongSwitchChain(nextNode, newSwitches, newNodesToExclude)); - } - break; - default: - throw new IllegalArgumentException( - "Cannot traverse along switch chain, as there is a junction included at node " - + startNode); - } - return traveledNodes; - } - /** * Combines a given collection of sub grid containers to a joint model. If the single models do * not fit together, exceptions are thrown. diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java deleted file mode 100644 index 9b2894cca..000000000 --- a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * © 2021. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation -*/ -package edu.ie3.datamodel.utils.validation; - -import edu.ie3.datamodel.exceptions.InvalidEntityException; -import edu.ie3.datamodel.exceptions.ValidationException; -import edu.ie3.datamodel.models.input.EmInput; -import edu.ie3.datamodel.utils.Try; -import java.util.ArrayList; -import java.util.List; - -public class EnergyManagementValidationUtils extends ValidationUtils { - - /** Private Constructor as this class is not meant to be instantiated */ - private EnergyManagementValidationUtils() { - throw new IllegalStateException("Don't try and instantiate a Utility class."); - } - - /** - * Validates a energy management unit if: - * - *
      - *
    • its control strategy is not null - *
    - * - * A "distribution" method, that forwards the check request to specific implementations to fulfill - * the checking task, based on the class of the given object. - * - * @param energyManagement EmInput to validate - * @return a list of try objects either containing an {@link ValidationException} or an empty - * Success - */ - protected static List> check(EmInput energyManagement) { - List> exceptions = new ArrayList<>(); - - exceptions.add( - Try.ofVoid( - energyManagement.getControlStrategy() == null, - () -> - new InvalidEntityException( - "No control strategy of energy management defined for", energyManagement))); - - return exceptions; - } -} diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java index 7d9e9b26b..344e1a7cb 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java @@ -20,7 +20,6 @@ import edu.ie3.datamodel.models.input.container.*; import edu.ie3.datamodel.models.input.graphics.GraphicInput; import edu.ie3.datamodel.models.input.system.SystemParticipantInput; -import edu.ie3.datamodel.utils.ContainerUtils; import edu.ie3.datamodel.utils.Try; import edu.ie3.datamodel.utils.Try.Failure; import edu.ie3.datamodel.utils.Try.Success; @@ -28,7 +27,6 @@ import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.jgrapht.Graph; import org.jgrapht.alg.connectivity.ConnectivityInspector; import org.jgrapht.graph.DefaultEdge; @@ -135,26 +133,12 @@ private GridContainerValidationUtils() { exceptions.addAll(ConnectorValidationUtils.check(transformer)); }); - /* Checking switches - * Because of the fact, that a transformer with switch gear in "upstream" direction has its corresponding node in - * upper grid connected to a switch, instead of to the transformer directly: Collect all nodes at the end of the - * upstream switch chain and add them to the set of allowed nodes */ - HashSet validSwitchNodes = new HashSet<>(nodes); - validSwitchNodes.addAll( - Stream.of(rawGridElements.getTransformer2Ws(), rawGridElements.getTransformer2Ws()) - .flatMap(Set::stream) - .parallel() - .map( - transformer -> - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGridElements) - .getLast()) - .toList()); - + /* Checking switches */ rawGridElements .getSwitches() .forEach( switcher -> { - exceptions.add(checkNodeAvailability(switcher, validSwitchNodes)); + exceptions.add(checkNodeAvailability(switcher, nodes)); exceptions.addAll(ConnectorValidationUtils.check(switcher)); }); diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java index 6d664aba1..80006953b 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/ValidationUtils.java @@ -5,12 +5,12 @@ */ package edu.ie3.datamodel.utils.validation; -import edu.ie3.datamodel.exceptions.FailedValidationException; -import edu.ie3.datamodel.exceptions.InvalidEntityException; -import edu.ie3.datamodel.exceptions.UnsafeEntityException; -import edu.ie3.datamodel.exceptions.ValidationException; +import edu.ie3.datamodel.exceptions.*; import edu.ie3.datamodel.models.UniqueEntity; -import edu.ie3.datamodel.models.input.*; +import edu.ie3.datamodel.models.input.AssetInput; +import edu.ie3.datamodel.models.input.AssetTypeInput; +import edu.ie3.datamodel.models.input.MeasurementUnitInput; +import edu.ie3.datamodel.models.input.NodeInput; import edu.ie3.datamodel.models.input.connector.ConnectorInput; import edu.ie3.datamodel.models.input.connector.type.LineTypeInput; import edu.ie3.datamodel.models.input.connector.type.Transformer2WTypeInput; @@ -22,11 +22,8 @@ import edu.ie3.datamodel.models.input.system.type.SystemParticipantTypeInput; import edu.ie3.datamodel.models.input.thermal.ThermalUnitInput; import edu.ie3.datamodel.utils.Try; -import edu.ie3.datamodel.utils.Try.Failure; -import edu.ie3.datamodel.utils.Try.Success; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import edu.ie3.datamodel.utils.Try.*; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.measure.Quantity; @@ -160,8 +157,6 @@ else if (ThermalUnitInput.class.isAssignableFrom(assetInput.getClass())) exceptions.addAll(ThermalValidationUtils.check((ThermalUnitInput) assetInput)); else if (ThermalGrid.class.isAssignableFrom(assetInput.getClass())) exceptions.addAll(ThermalValidationUtils.check((ThermalUnitInput) assetInput)); - else if (EmInput.class.isAssignableFrom(assetInput.getClass())) - exceptions.addAll(EnergyManagementValidationUtils.check((EmInput) assetInput)); else { logNotImplemented(assetInput); } diff --git a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy index c07ff0d54..240312437 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy @@ -553,175 +553,6 @@ class ContainerUtilsTest extends Specification { * - filtering of system participants can be tested * - filtering of graphic elements can be tested */ - def "Traversing along a simple switch chain returns the correct list of traveled nodes"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - def nodeD = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchCD = Mock(SwitchInput) - switchCD.getNodeA() >> nodeC - switchCD.getNodeB() >> nodeD - switchCD.allNodes() >> List.of(nodeC, nodeD) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchCD) - - def possibleJunctions = new HashSet() - - def expected = new LinkedList() - expected.addFirst(nodeA) - expected.addLast(nodeB) - expected.addLast(nodeC) - expected.addLast(nodeD) - - when: - def actual = ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - actual == expected - } - - def "Traversing along a switch chain with intermediate junction returns the correct list of traveled nodes"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - def nodeD = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchCD = Mock(SwitchInput) - switchCD.getNodeA() >> nodeC - switchCD.getNodeB() >> nodeD - switchCD.allNodes() >> List.of(nodeC, nodeD) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchCD) - - def possibleJunctions = new HashSet() - possibleJunctions.add(nodeC) - - def expected = new LinkedList() - expected.addFirst(nodeA) - expected.addLast(nodeB) - expected.addLast(nodeC) - - when: - def actual = ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - actual == expected - } - - def "Traversing along a non existing switch chain returns the correct list of traveled nodes"() { - given: - def nodeA = Mock(NodeInput) - - def switches = new HashSet() - - def possibleJunctions = new HashSet() - - def expected = new LinkedList() - expected.addFirst(nodeA) - - when: - def actual = ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - actual == expected - } - - def "Traversing along a cyclic switch chain throws an exception"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchCA = Mock(SwitchInput) - switchCA.getNodeA() >> nodeC - switchCA.getNodeB() >> nodeA - switchCA.allNodes() >> List.of(nodeC, nodeA) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchCA) - - def possibleJunctions = new HashSet() - - when: - ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - IllegalArgumentException ex = thrown() - ex.message == "Cannot traverse along switch chain, as there is a junction included at node Mock for type " + - "'NodeInput' named 'nodeA'" - } - - def "Traversing along a switch chain with switch junction throws an exception"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - def nodeD = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchBD = Mock(SwitchInput) - switchBD.getNodeA() >> nodeB - switchBD.getNodeB() >> nodeD - switchBD.allNodes() >> List.of(nodeB, nodeD) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchBD) - - def possibleJunctions = new HashSet() - - when: - ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - IllegalArgumentException ex = thrown() - ex.message == "Cannot traverse along switch chain, as there is a junction included at node Mock for type " + - "'NodeInput' named 'nodeB'" - } - def "Determining the surrounding sub grid containers of a two winding transformer w/o switchgear works fine"() { given: def nodeD = Mock(NodeInput) @@ -762,13 +593,13 @@ class ContainerUtilsTest extends Specification { nodeA.getSubnet() >> 1 def nodeB = Mock(NodeInput) nodeB.getUuid() >> UUID.fromString("8361b082-9d4c-4c54-97d0-2df9ac35333c") - nodeB.getSubnet() >> 2 + nodeB.getSubnet() >> 1 def nodeC = Mock(NodeInput) nodeC.getUuid() >> UUID.fromString("b9e4f16b-0317-4794-9f53-339db45a2092") - nodeC.getSubnet() >> 2 + nodeC.getSubnet() >> 1 def nodeD = Mock(NodeInput) nodeD.getUuid() >> UUID.fromString("ae4869d5-3551-4cce-a101-d61629716c4f") - nodeD.getSubnet() >> 2 + nodeD.getSubnet() >> 1 def nodeE = Mock(NodeInput) nodeE.getUuid() >> UUID.fromString("5d4107b2-385b-40fe-a668-19414bf45d9d") nodeE.getSubnet() >> 2 diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy deleted file mode 100644 index ed5139031..000000000 --- a/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy +++ /dev/null @@ -1,41 +0,0 @@ -/* - * © 2025. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ -package edu.ie3.datamodel.utils.validation - -import edu.ie3.datamodel.exceptions.InvalidEntityException -import edu.ie3.datamodel.exceptions.ValidationException -import edu.ie3.datamodel.utils.Try -import edu.ie3.test.common.GridTestData -import spock.lang.Specification - -class EmValidationUtilsTest extends Specification { - - def "Smoke Test: Correct energy management system throws no exception"() { - given: - def em = GridTestData.energyManagementInput - - when: - List> tries = EnergyManagementValidationUtils.check(em) - - then: - tries.every { it.success } - } - - def "The check method recognizes all potential errors for an energy management input"() { - when: - List> exceptions = EnergyManagementValidationUtils.check(invalidEm).stream().filter { it -> it.failure }.toList() - - then: - exceptions.size() == expectedSize - Exception ex = exceptions.get(0).exception.get() - ex.class == expectedException.class - ex.message == expectedException.message - - where: - invalidEm || expectedSize || expectedException - GridTestData.energyManagementInput.copy().controlStrategy(null).build() || 1 || new InvalidEntityException("No control strategy of energy management defined for", invalidEm) - } -} diff --git a/src/test/groovy/edu/ie3/test/common/GridTestData.groovy b/src/test/groovy/edu/ie3/test/common/GridTestData.groovy index 87c13de89..2ca15217c 100644 --- a/src/test/groovy/edu/ie3/test/common/GridTestData.groovy +++ b/src/test/groovy/edu/ie3/test/common/GridTestData.groovy @@ -9,7 +9,6 @@ import static edu.ie3.datamodel.models.StandardUnits.* import static edu.ie3.util.quantities.PowerSystemUnits.* import edu.ie3.datamodel.models.OperationTime -import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.input.MeasurementUnitInput import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.OperatorInput @@ -388,13 +387,4 @@ class GridTestData { true, true ) - - public static final EmInput energyManagementInput = new EmInput( - UUID.fromString("4bef6955-5e31-4283-8920-5e4cae267a23"), - "test_energyManagement", - profBroccoli, - defaultOperationTime, - "PRIORITIZED", - null, - ) } From 077a760e5a26c02afd72cdd9b6bb06dc64c4e9a0 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 9 Jul 2025 15:22:34 +0200 Subject: [PATCH 51/60] Removing exception class information from combined exceptions. --- .../DuplicateEntitiesException.java | 2 +- .../exceptions/FailedValidationException.java | 2 +- .../edu/ie3/datamodel/io/factory/Factory.java | 2 +- .../ie3/datamodel/io/source/EntitySource.java | 26 ++++++------ .../datamodel/io/source/GraphicSource.java | 5 ++- .../datamodel/io/source/RawGridSource.java | 2 +- .../io/source/SystemParticipantSource.java | 2 +- .../ie3/datamodel/utils/ExceptionUtils.java | 29 +++---------- .../source/EnergyManagementSourceTest.groovy | 2 +- .../io/source/EntitySourceTest.groovy | 8 ++-- .../source/TimeSeriesMappingSourceTest.groovy | 4 +- .../io/source/csv/CsvGraphicSourceTest.groovy | 6 +-- .../io/source/csv/CsvRawGridSourceTest.groovy | 6 +-- .../csv/CsvSystemParticipantSourceTest.groovy | 42 +++++++++---------- .../edu/ie3/datamodel/utils/TryTest.groovy | 2 +- 15 files changed, 62 insertions(+), 78 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java b/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java index 21fd3adbe..eee2942f1 100644 --- a/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java +++ b/src/main/java/edu/ie3/datamodel/exceptions/DuplicateEntitiesException.java @@ -20,6 +20,6 @@ public DuplicateEntitiesException( "The following exception(s) occurred while checking the uniqueness of '" + entityName + "' entities: \n" - + ExceptionUtils.combineMessages(exceptions)); + + ExceptionUtils.combineExceptions(exceptions)); } } diff --git a/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java b/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java index 4b10acc06..25e1aa4de 100644 --- a/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java +++ b/src/main/java/edu/ie3/datamodel/exceptions/FailedValidationException.java @@ -23,6 +23,6 @@ public FailedValidationException(String message) { /** @param exceptions List of exceptions, which must not be empty */ public FailedValidationException(List exceptions) { - super("Validation failed due to: " + ExceptionUtils.combineExceptions(exceptions)); + super("Validation failed due to:\n " + ExceptionUtils.combineExceptions(exceptions)); } } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/Factory.java b/src/main/java/edu/ie3/datamodel/io/factory/Factory.java index 9daff0c2d..c5da1cd14 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/Factory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/Factory.java @@ -73,7 +73,7 @@ public Try get(D data) { * {@link Failure} */ public Try get(Try data) { - return data.transformF(FactoryException::new).flatMap(this::get); + return data.transformF(e -> new FactoryException(e.getMessage(), e)).flatMap(this::get); } /** diff --git a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java index 726a35279..0c4d9c1d6 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java @@ -205,11 +205,11 @@ protected static Stream getEntities( */ protected static Stream> buildEntityData( Class entityClass, DataSource dataSource) throws SourceException { - Stream> dataStream = - Try.of(() -> dataSource.getSourceData(entityClass), SourceException.class).getOrThrow(); - - return dataStream.map( - fieldsToAttributes -> new Try.Success<>(new EntityData(fieldsToAttributes, entityClass))); + return dataSource + .getSourceData(entityClass) + .map( + fieldsToAttributes -> + new Try.Success<>(new EntityData(fieldsToAttributes, entityClass))); } /** @@ -373,16 +373,14 @@ protected static Try extractFuncti return entityData.flatMap( data -> Try.of(() -> data.getUUID(fieldName), FactoryException.class) + .flatMap(entityUuid -> extractFunction(entityUuid, entities)) .transformF( exception -> new SourceException( - "Extracting UUID field " + "Extracting UUID for field '" + fieldName - + " from entity data " - + entityData - + " failed.", - exception)) - .flatMap(entityUuid -> extractFunction(entityUuid, entities))); + + "' failed. Caused by: " + + exception.getMessage()))); } /** @@ -393,13 +391,13 @@ protected static Try extractFuncti * @return a try of the {@link Entity} * @param type of entity */ - protected static Try extractFunction(UUID uuid, Map entityMap) { + protected static Try extractFunction(UUID uuid, Map entityMap) { return Optional.ofNullable(entityMap.get(uuid)) // We either find a matching entity for given UUID, thus return a success - .map(entity -> Try.of(() -> entity, SourceException.class)) + .map(entity -> Try.of(() -> entity, FactoryException.class)) // ... or find no matching entity, returning a failure. .orElse( - new Failure<>(new SourceException("Entity with uuid " + uuid + " was not provided."))); + new Failure<>(new FactoryException("Entity with uuid " + uuid + " was not provided."))); } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= diff --git a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java index d92336aa2..904e0a42d 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java @@ -99,7 +99,10 @@ public GraphicElements getGraphicElements(Map nodes, Map exceptions) { - String messageSeparator = "\n "; - - String messages = - exceptions.stream() - .map(Throwable::getMessage) - .reduce("", (a, b) -> a + messageSeparator + b); - - // some formating - return messages.replace("\n", "\n ").replaceFirst(messageSeparator, ""); - } - /** * Creates a string containing multiple exception messages. * @@ -43,17 +25,18 @@ public static String combineExceptions(List exceptions) { // function to convert an exception into a string Function converter = e -> { - String clazz = e.getClass().getName(); String message = e.getMessage(); Throwable cause = e.getCause(); - String res = clazz + ": " + message; - if (cause != null) { - res += " Caused by: " + cause.getClass().getName() + ": " + cause.getMessage(); + String causeMessage = cause.getMessage(); + + if (!message.equalsIgnoreCase(causeMessage)) { + message += " Caused by: " + cause.getMessage(); + } } - return res; + return message; }; String messages = diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy index 3fb52f9f5..86d5edf77 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/EnergyManagementSourceTest.groovy @@ -225,7 +225,7 @@ class EnergyManagementSourceTest extends Specification { then: def exc = thrown(SourceException) exc.message == "1 exception(s) occurred within \"EmInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of EmInput.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Field \"id\" not found in EntityData" + " An error occurred when creating instance of EmInput.class. Caused by: Field \"id\" not found in EntityData" } def "An EnergyManagementSource should fail if a parent em is not provided"() { diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy index 801faeb4d..1061376f4 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy @@ -49,7 +49,7 @@ class EntitySourceTest extends Specification { then: SourceException ex = thrown() ex.message == "1 exception(s) occurred within \"OperatorInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of OperatorInput.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Field \"id\" not found in EntityData" + " An error occurred when creating instance of OperatorInput.class. Caused by: Field \"id\" not found in EntityData" } def "An EntitySource can build EntityData correctly"() { @@ -169,12 +169,12 @@ class EntitySourceTest extends Specification { then: actual.failure actual.exception.get().class == SourceException - actual.exception.get().message.contains(expectedMessage) + actual.exception.get().message == expectedMessage where: fieldsToAttributes | entityMap | expectedMessage - ["operator": "no uuid"] | map([OperatorInput.NO_OPERATOR_ASSIGNED]) | "Extracting UUID field operator from entity data" - ["operator": GridTestData.profBroccoli.uuid.toString()] | map([OperatorInput.NO_OPERATOR_ASSIGNED]) | "Entity with uuid f15105c4-a2de-4ab8-a621-4bc98e372d92 was not provided." + ["operator": "no uuid"] | map([OperatorInput.NO_OPERATOR_ASSIGNED]) | "Extracting UUID for field 'operator' failed. Caused by: Exception while trying to parse UUID of field \"operator\" with value \"no uuid\"" + ["operator": GridTestData.profBroccoli.uuid.toString()] | map([OperatorInput.NO_OPERATOR_ASSIGNED]) | "Extracting UUID for field 'operator' failed. Caused by: Entity with uuid f15105c4-a2de-4ab8-a621-4bc98e372d92 was not provided." } def "An EntitySource returns a failure if a given map does not contain the given uuid"() { diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy index 8d7f475ed..d706fffc8 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/TimeSeriesMappingSourceTest.groovy @@ -121,7 +121,7 @@ class TimeSeriesMappingSourceTest extends Specification { then: def ex = thrown(SourceException) ex.message == "2 exception(s) occurred within \"MappingEntry\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of MappingEntry.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Exception while trying to parse UUID of field \"asset\" with value \"invalidAsset\"\n" + - " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of MappingEntry.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Exception while trying to parse UUID of field \"asset\" with value \"invalidAsset2\"" + " An error occurred when creating instance of MappingEntry.class. Caused by: Exception while trying to parse UUID of field \"asset\" with value \"invalidAsset\"\n" + + " An error occurred when creating instance of MappingEntry.class. Caused by: Exception while trying to parse UUID of field \"asset\" with value \"invalidAsset2\"" } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy index b218968df..24c33f1e7 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy @@ -70,9 +70,9 @@ class CsvGraphicSourceTest extends Specification implements CsvTestDataMeta { Exception ex = graphicElements.exception.get() ex.class == GraphicSourceException - ex.message == "Some exception(s) occurred while initializing graphic elements. \n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"LineGraphicInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not provided." + ex.message == "Exception(s) occurred in 1 input file(s) while initializing graphic elements. \n" + + " 1 exception(s) occurred within \"LineGraphicInput\" data: \n" + + " Extracting UUID for field 'line' failed. Caused by: Entity with uuid 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not provided." } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy index 282f1d16b..6c62c4338 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy @@ -269,7 +269,7 @@ class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { actual == null SourceException ex = thrown() ex.message == "1 exception(s) occurred within \"NodeInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of NodeInput.class. Caused by: edu.ie3.datamodel.exceptions.FactoryException: Exception while trying to parse UUID of field \"uuid\" with value \"bd837a25-58f3-44ac-aa90-c6b6e3 cd91b2\"" + " An error occurred when creating instance of NodeInput.class. Caused by: Exception while trying to parse UUID of field \"uuid\" with value \"bd837a25-58f3-44ac-aa90-c6b6e3 cd91b2\"" } def "The CsvRawGridSource returns an empty grid, if the RawGridElements contain no single element"() { @@ -310,7 +310,7 @@ class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { Exception ex = rawGridElements.exception.get() ex.class == SourceException ex.message == "2 exception(s) occurred within \"LineInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid bd837a25-58f3-44ac-aa90-c6b6e3cd91b2 was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid bd837a25-58f3-44ac-aa90-c6b6e3cd91b2 was not provided." + " Extracting UUID for field 'nodeA' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " Extracting UUID for field 'nodeA' failed. Caused by: Entity with uuid bd837a25-58f3-44ac-aa90-c6b6e3cd91b2 was not provided." } } \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy index b3d750a49..293ae514b 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy @@ -76,27 +76,27 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat Exception ex = systemParticipants.exception.get() ex.class == SystemParticipantsException - ex.message == "Exception(s) occurred in 10 input files while initializing system participants.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"FixedFeedInInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"PvInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"LoadInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"BmInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"StorageInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"WecInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"EvInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"EvcsInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"ChpInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + - " edu.ie3.datamodel.exceptions.SourceException: 1 exception(s) occurred within \"HpInput\" data: \n" + - " edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided. Caused by: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided." + ex.message == "Exception(s) occurred in 10 input file(s) while initializing system participants.\n" + + " 1 exception(s) occurred within \"FixedFeedInInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"PvInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"LoadInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"BmInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"StorageInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"WecInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"EvInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"EvcsInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"ChpInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.\n" + + " 1 exception(s) occurred within \"HpInput\" data: \n" + + " Extracting UUID for field 'node' failed. Caused by: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided." } def "A SystemParticipantSource with csv input should return data from valid input file as expected"() { diff --git a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy index 8ecd40bec..59909cb14 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy @@ -244,7 +244,7 @@ class TryTest extends Specification { then: scan.failure scan.exception.get().message == "1 exception(s) occurred within \"String\" data: \n" + - " java.lang.Exception: exception Caused by: edu.ie3.datamodel.exceptions.SourceException: source exception" + " exception Caused by: source exception" } def "A scan for exceptions should work as expected when no failures are included"() { From 40eef0b3314318123274bd6c8e46ce156f8b2e0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 04:35:21 +0000 Subject: [PATCH 52/60] Bump com.github.spotbugs from 6.2.1 to 6.2.2 Bumps com.github.spotbugs from 6.2.1 to 6.2.2. --- updated-dependencies: - dependency-name: com.github.spotbugs dependency-version: 6.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dbc1fd406..40a650e4c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'signing' id 'pmd' // code check, working on source code id 'com.diffplug.spotless' version '7.1.0' //code format - id 'com.github.spotbugs' version '6.2.1' // code check, working on byte code + id 'com.github.spotbugs' version '6.2.2' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin From 37c9e08b754c73b8f67cc0e596d843de7ba0eac6 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 11 Jul 2025 12:53:49 +0200 Subject: [PATCH 53/60] Including reviewer's comments. --- .../figures/transformerWithSwitchGear.png | Bin 19186 -> 16091 bytes .../figures/transformerWithSwitchGear.tex | 12 ++-- .../models/input/grid/gridcontainer.md | 11 ++++ .../datamodel/utils/ContainerUtilsTest.groovy | 62 ------------------ 4 files changed, 17 insertions(+), 68 deletions(-) diff --git a/docs/readthedocs/_static/figures/transformerWithSwitchGear.png b/docs/readthedocs/_static/figures/transformerWithSwitchGear.png index 310ccf99f4d3b223570b7ac18c9f976c8b58d5c8..0ad8468fcbbf479dbad0f149185423b2e2a4602c 100644 GIT binary patch literal 16091 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%0A(C6HEo-(|O)=g8k8Kq}wkRph5kbxT$52 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 Uwp1&97dahyX=SNWNz)Ji17~9Zm;e9( literal 19186 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 Xb93-%@auoac0o=?`FWAF>AU{}+M8cI diff --git a/docs/readthedocs/_static/figures/transformerWithSwitchGear.tex b/docs/readthedocs/_static/figures/transformerWithSwitchGear.tex index 8ca4ed98a..ac2d44160 100644 --- a/docs/readthedocs/_static/figures/transformerWithSwitchGear.tex +++ b/docs/readthedocs/_static/figures/transformerWithSwitchGear.tex @@ -159,9 +159,9 @@ \begin{tikzpicture} % === Anschlüsse === \node[circle, draw = tuGreen, fill = tuGreen, inner sep = 0, minimum height = 1.5mm] (port_a) at (0,0){}; - \node[circle, draw = tuOrange, fill = tuOrange, inner sep = 0, minimum height = 1.5mm] (port_b) at (15mm,0){}; - \node[circle, draw = tuOrange, fill = tuOrange, inner sep = 0, minimum height = 1.5mm] (port_c) at (30mm,0){}; - \node[circle, draw = tuOrange, fill = tuOrange, inner sep = 0, minimum height = 1.5mm] (port_d) at (45mm,0){}; + \node[circle, draw = tuOrange, fill = tuGreen, inner sep = 0, minimum height = 1.5mm] (port_b) at (15mm,0){}; + \node[circle, draw = tuOrange, fill = tuGreen, inner sep = 0, minimum height = 1.5mm] (port_c) at (30mm,0){}; + \node[circle, draw = tuOrange, fill = tuGreen, inner sep = 0, minimum height = 1.5mm] (port_d) at (45mm,0){}; \node[circle, draw = tuOrange, fill = tuOrange, inner sep = 0, minimum height = 1.5mm] (port_e) at (62.5mm,0){}; \draw (port_a.west) -- ++(-3mm, -1mm) -- ++(0mm, -2mm) edge[densely dotted] ++(0mm, -1.8mm); @@ -176,9 +176,9 @@ \draw (port_d.east) -- (winding_a.west) (winding_b.east) -- (port_e.west); \node[tuGreen, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_a.south) {A \\ \SI{110}{\kV} \\ 1}; - \node[tuOrange, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_b.south) {B \\ \SI{110}{\kV} \\ 2}; - \node[tuOrange, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_c.south) {C \\ \SI{110}{\kV} \\ 2}; - \node[tuOrange, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_d.south) {D \\ \SI{110}{\kV} \\ 2}; + \node[tuOrange, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_b.south) {B \\ \SI{110}{\kV} \\ 1}; + \node[tuOrange, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_c.south) {C \\ \SI{110}{\kV} \\ 1}; + \node[tuOrange, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_d.south) {D \\ \SI{110}{\kV} \\ 1}; \node[tuOrange, anchor = north, inner sep = 2mm, text width = 10mm, align = center] at (port_e.south) {E \\ \SI{10}{\kV} \\ 2}; \end{tikzpicture} \end{document} \ No newline at end of file diff --git a/docs/readthedocs/models/input/grid/gridcontainer.md b/docs/readthedocs/models/input/grid/gridcontainer.md index d6411b318..2ee0f95fe 100644 --- a/docs/readthedocs/models/input/grid/gridcontainer.md +++ b/docs/readthedocs/models/input/grid/gridcontainer.md @@ -20,6 +20,17 @@ Why predominant? As of convention, the `SubGridContainers` hold also reference to the transformers leading to higher sub grids and their higher voltage coupling point. +![Sub grid boundary definition for transformers with upstream switchgear](../../../_static/figures/transformerWithSwitchGear.png) + +Let's shed a more detailed light on the boundaries of a sub grid as of our definition. +This especially is important, if the switchgear of the transformer is modeled in detail. +We defined, that all nodes in upstream direction of the transformer, including those, which are connected by switches *only* +(therefore are within the switchgear) are counted towards the superior sub grid structure (here "1"), because they belong +to a different voltage level. +If a switchgear should be operated by the inferior grid, one can set the operator, that is used in the inferior grid, for +the switchgear. This can be necessary, if we assume, that the interest to operate on the given switchgear will most likely +be placed in the inferior grid structure. + A synoptic overview of both classes' attributes is given here: ## Attributes, Units and Remarks diff --git a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy index 240312437..4dba21666 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy @@ -585,66 +585,4 @@ class ContainerUtilsTest extends Specification { then: actual == expected } - - def "Determining the surrounding sub grid containers of a two winding transformer w/ switchgear works fine"() { - given: - def nodeA = Mock(NodeInput) - nodeA.getUuid() >> UUID.fromString("a37b2501-70c5-479f-92f9-d5b0e4628b2b") - nodeA.getSubnet() >> 1 - def nodeB = Mock(NodeInput) - nodeB.getUuid() >> UUID.fromString("8361b082-9d4c-4c54-97d0-2df9ac35333c") - nodeB.getSubnet() >> 1 - def nodeC = Mock(NodeInput) - nodeC.getUuid() >> UUID.fromString("b9e4f16b-0317-4794-9f53-339db45a2092") - nodeC.getSubnet() >> 1 - def nodeD = Mock(NodeInput) - nodeD.getUuid() >> UUID.fromString("ae4869d5-3551-4cce-a101-d61629716c4f") - nodeD.getSubnet() >> 1 - def nodeE = Mock(NodeInput) - nodeE.getUuid() >> UUID.fromString("5d4107b2-385b-40fe-a668-19414bf45d9d") - nodeE.getSubnet() >> 2 - - def transformer = Mock(Transformer2WInput) - transformer.getUuid() >> UUID.fromString("ddcdd72a-5f97-4bef-913b-d32d31216e27") - transformer.getNodeA() >> nodeD - transformer.getNodeB() >> nodeE - transformer.allNodes() >> List.of(nodeD, nodeE) - - def switchAB = Mock(SwitchInput) - switchAB.getUuid() >> UUID.fromString("5fcb8705-1436-4fbe-97b3-d2dcaf6a783b") - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getUuid() >> UUID.fromString("4ca81b0b-e06d-408e-a991-de140f4e229b") - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchCD = Mock(SwitchInput) - switchCD.getUuid() >> UUID.fromString("92ce075e-9e3b-4ee6-89b6-19e6372fba01") - switchCD.allNodes() >> List.of(nodeC, nodeD) - - def rawGridElements = new RawGridElements([ - nodeA, - nodeB, - nodeC, - nodeD, - nodeE, - transformer, - switchAB, - switchBC, - switchCD - ]) - - def subGrid1 = Mock(SubGridContainer) - def subGrid2 = Mock(SubGridContainer) - def subGridMapping = [ - 1: subGrid1, - 2: subGrid2 - ] - - def expected = new ContainerUtils.TransformerSubGridContainers(subGrid1, subGrid2) - - when: - def actual = ContainerUtils.getSubGridContainers(transformer, rawGridElements, subGridMapping) - - then: - actual == expected - } } From fac745005c7f6df255c4da7fe58d80e5f58264e4 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 11 Jul 2025 12:56:25 +0200 Subject: [PATCH 54/60] fix merge issues --- .../EnergyManagementValidationUtils.java | 48 +++++++++++++++++++ .../validation/EmValidationUtilsTest.groovy | 41 ++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java create mode 100644 src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java new file mode 100644 index 000000000..9b2894cca --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/utils/validation/EnergyManagementValidationUtils.java @@ -0,0 +1,48 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.utils.validation; + +import edu.ie3.datamodel.exceptions.InvalidEntityException; +import edu.ie3.datamodel.exceptions.ValidationException; +import edu.ie3.datamodel.models.input.EmInput; +import edu.ie3.datamodel.utils.Try; +import java.util.ArrayList; +import java.util.List; + +public class EnergyManagementValidationUtils extends ValidationUtils { + + /** Private Constructor as this class is not meant to be instantiated */ + private EnergyManagementValidationUtils() { + throw new IllegalStateException("Don't try and instantiate a Utility class."); + } + + /** + * Validates a energy management unit if: + * + *
      + *
    • its control strategy is not null + *
    + * + * A "distribution" method, that forwards the check request to specific implementations to fulfill + * the checking task, based on the class of the given object. + * + * @param energyManagement EmInput to validate + * @return a list of try objects either containing an {@link ValidationException} or an empty + * Success + */ + protected static List> check(EmInput energyManagement) { + List> exceptions = new ArrayList<>(); + + exceptions.add( + Try.ofVoid( + energyManagement.getControlStrategy() == null, + () -> + new InvalidEntityException( + "No control strategy of energy management defined for", energyManagement))); + + return exceptions; + } +} diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy new file mode 100644 index 000000000..ed5139031 --- /dev/null +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/EmValidationUtilsTest.groovy @@ -0,0 +1,41 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.datamodel.utils.validation + +import edu.ie3.datamodel.exceptions.InvalidEntityException +import edu.ie3.datamodel.exceptions.ValidationException +import edu.ie3.datamodel.utils.Try +import edu.ie3.test.common.GridTestData +import spock.lang.Specification + +class EmValidationUtilsTest extends Specification { + + def "Smoke Test: Correct energy management system throws no exception"() { + given: + def em = GridTestData.energyManagementInput + + when: + List> tries = EnergyManagementValidationUtils.check(em) + + then: + tries.every { it.success } + } + + def "The check method recognizes all potential errors for an energy management input"() { + when: + List> exceptions = EnergyManagementValidationUtils.check(invalidEm).stream().filter { it -> it.failure }.toList() + + then: + exceptions.size() == expectedSize + Exception ex = exceptions.get(0).exception.get() + ex.class == expectedException.class + ex.message == expectedException.message + + where: + invalidEm || expectedSize || expectedException + GridTestData.energyManagementInput.copy().controlStrategy(null).build() || 1 || new InvalidEntityException("No control strategy of energy management defined for", invalidEm) + } +} From 324986725aecbf35230409c4b2d4858d733c6bac Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 11 Jul 2025 13:04:47 +0200 Subject: [PATCH 55/60] fixing merge issue. --- .../groovy/edu/ie3/test/common/GridTestData.groovy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/groovy/edu/ie3/test/common/GridTestData.groovy b/src/test/groovy/edu/ie3/test/common/GridTestData.groovy index 2ca15217c..87c13de89 100644 --- a/src/test/groovy/edu/ie3/test/common/GridTestData.groovy +++ b/src/test/groovy/edu/ie3/test/common/GridTestData.groovy @@ -9,6 +9,7 @@ import static edu.ie3.datamodel.models.StandardUnits.* import static edu.ie3.util.quantities.PowerSystemUnits.* import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.EmInput import edu.ie3.datamodel.models.input.MeasurementUnitInput import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.OperatorInput @@ -387,4 +388,13 @@ class GridTestData { true, true ) + + public static final EmInput energyManagementInput = new EmInput( + UUID.fromString("4bef6955-5e31-4283-8920-5e4cae267a23"), + "test_energyManagement", + profBroccoli, + defaultOperationTime, + "PRIORITIZED", + null, + ) } From 829e37bbf05451bab08b188cd2c34a6e07855b9a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 11 Jul 2025 14:48:24 +0200 Subject: [PATCH 56/60] Including reviewer's comments. --- docs/readthedocs/models/input/grid/gridcontainer.md | 5 ++--- .../groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/readthedocs/models/input/grid/gridcontainer.md b/docs/readthedocs/models/input/grid/gridcontainer.md index 2ee0f95fe..9c8eebd44 100644 --- a/docs/readthedocs/models/input/grid/gridcontainer.md +++ b/docs/readthedocs/models/input/grid/gridcontainer.md @@ -24,9 +24,8 @@ and their higher voltage coupling point. Let's shed a more detailed light on the boundaries of a sub grid as of our definition. This especially is important, if the switchgear of the transformer is modeled in detail. -We defined, that all nodes in upstream direction of the transformer, including those, which are connected by switches *only* -(therefore are within the switchgear) are counted towards the superior sub grid structure (here "1"), because they belong -to a different voltage level. +We defined, that all nodes in upstream direction of the transformer, including those, which are within the switchgear are +counted towards the superior sub grid structure (here "1"), because they belong to a different voltage level. If a switchgear should be operated by the inferior grid, one can set the operator, that is used in the inferior grid, for the switchgear. This can be necessary, if we assume, that the interest to operate on the given switchgear will most likely be placed in the inferior grid structure. diff --git a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy index 4dba21666..660603fe9 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy @@ -553,7 +553,7 @@ class ContainerUtilsTest extends Specification { * - filtering of system participants can be tested * - filtering of graphic elements can be tested */ - def "Determining the surrounding sub grid containers of a two winding transformer w/o switchgear works fine"() { + def "Determining the surrounding sub grid containers of a two winding transformer works fine"() { given: def nodeD = Mock(NodeInput) nodeD.getUuid() >> UUID.fromString("ae4869d5-3551-4cce-a101-d61629716c4f") From 4c9b597ca16d73abf050448d259d83fcadc2dcbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:54:02 +0000 Subject: [PATCH 57/60] Bump org.apache.logging.log4j:log4j-bom from 2.25.0 to 2.25.1 Bumps [org.apache.logging.log4j:log4j-bom](https://github.com/apache/logging-log4j2) from 2.25.0 to 2.25.1. - [Release notes](https://github.com/apache/logging-log4j2/releases) - [Changelog](https://github.com/apache/logging-log4j2/blob/2.x/RELEASE-NOTES.adoc) - [Commits](https://github.com/apache/logging-log4j2/compare/rel/2.25.0...rel/2.25.1) --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-bom dependency-version: 2.25.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 40a650e4c..f14c49fff 100644 --- a/build.gradle +++ b/build.gradle @@ -91,7 +91,7 @@ dependencies { testImplementation "org.testcontainers:couchbase:$testcontainersVersion" // logging - implementation platform('org.apache.logging.log4j:log4j-bom:2.25.0') + implementation platform('org.apache.logging.log4j:log4j-bom:2.25.1') implementation 'org.apache.logging.log4j:log4j-api' // log4j implementation 'org.apache.logging.log4j:log4j-core' // log4j implementation 'org.apache.logging.log4j:log4j-slf4j-impl' // log4j -> slf4j From 838de0d81e6fdc37e0e8826ca04e818bbf8cf405 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:05:15 +0000 Subject: [PATCH 58/60] Bump com.couchbase.client:java-client from 3.8.2 to 3.8.3 Bumps [com.couchbase.client:java-client](https://github.com/couchbase/couchbase-jvm-clients) from 3.8.2 to 3.8.3. - [Commits](https://github.com/couchbase/couchbase-jvm-clients/compare/core-io-3.8.2...core-io-3.8.3) --- updated-dependencies: - dependency-name: com.couchbase.client:java-client dependency-version: 3.8.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f14c49fff..d8752419e 100644 --- a/build.gradle +++ b/build.gradle @@ -98,7 +98,7 @@ dependencies { // Databases implementation 'org.influxdb:influxdb-java:2.25' - implementation 'com.couchbase.client:java-client:3.8.2' + implementation 'com.couchbase.client:java-client:3.8.3' runtimeOnly 'org.postgresql:postgresql:42.7.7' // postgresql jdbc driver required during runtime implementation 'commons-io:commons-io:2.19.0' // I/O functionalities From a0671173facffe2acf928178d00d92eb74c58de7 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Thu, 17 Jul 2025 15:00:14 +0200 Subject: [PATCH 59/60] changelog and version.properties --- CHANGELOG.md | 8 +++++++- version.properties | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 211241031..2a7cc55c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] -## [7.0.0] - 2025-05-08 +### Added + +### Fixed + +### Changed + +## [8.0.0] - 2025-07-17 ### Added - Extend Validation to EnergyManagement Systems. [#1356](https://github.com/ie3-institute/PowerSystemDataModel/issues/1356) diff --git a/version.properties b/version.properties index 508a6d490..414a5f2fc 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,8 @@ #Generated by the Semver Plugin for Gradle -#Thu May 08 17:03:55 CEST 2025 +#Thu Jul 17 14:58:32 CEST 2025 version.buildmeta= -version.major=7 +version.major=8 version.minor=0 version.patch=0 version.prerelease= -version.semver=7.0.0 +version.semver=8.0.0 From 6065b1f8947661be320e42093330cab82d1d1bcd Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Thu, 17 Jul 2025 15:02:12 +0200 Subject: [PATCH 60/60] changelog 2 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7cc55c0..1d07c6166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -379,7 +379,8 @@ coordinates or multiple exactly equal coordinates possible - CsvDataSource now stops trying to get an operator for empty operator uuid field in entities - CsvDataSource now parsing multiple geoJson strings correctly -[Unreleased/Snapshot]: https://github.com/ie3-institute/powersystemdatamodel/compare/7.0.0...HEAD +[Unreleased/Snapshot]: +[8.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/7.0.0...8.0.0 [7.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/6.0.0...7.0.0 [6.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.1.0...6.0.0 [5.1.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.0.1...5.1.0