From 69de852be1721dad0af993ad2a1ca0e2ca29df84 Mon Sep 17 00:00:00 2001 From: Gabriel Studer <gabriel.studer@unibas.ch> Date: Mon, 6 Mar 2023 14:15:42 +0100 Subject: [PATCH] SCHWED-5802 Allow AFDB mmCIF files to be read in OST AFDB v4 files are invalid as there is a mismatch between _citation.id ("primary") and _citation_author.citation_id ("1"). This can now be read by setting fault_tolerant=True. In this case, a warning is logged and no authors are set. --- modules/io/doc/mmcif.rst | 6 +++++- modules/io/pymod/export_mmcif_io.cc | 2 +- modules/io/src/mol/mmcif_info.cc | 11 +++++++++-- modules/io/src/mol/mmcif_info.hh | 3 ++- modules/io/src/mol/mmcif_reader.cc | 3 ++- modules/io/tests/test_io_mmcif.py | 10 ++++++++++ .../mmcif/AF-A0A024L8A3-F1-model_v4.cif.gz | Bin 0 -> 7597 bytes 7 files changed, 29 insertions(+), 6 deletions(-) create mode 100755 modules/io/tests/testfiles/mmcif/AF-A0A024L8A3-F1-model_v4.cif.gz diff --git a/modules/io/doc/mmcif.rst b/modules/io/doc/mmcif.rst index 63bfd8822..c4c361bda 100644 --- a/modules/io/doc/mmcif.rst +++ b/modules/io/doc/mmcif.rst @@ -172,7 +172,7 @@ of the annotation available. :type citation: :class:`MMCifInfoCitation` - .. method:: AddAuthorsToCitation(id, authors) + .. method:: AddAuthorsToCitation(id, authors, fault_tolerant=False) Adds a list of authors to a specific citation. @@ -180,6 +180,10 @@ of the annotation available. :type id: :class:`str` :param authors: List of authors. :type authors: :class:`~ost.StringList` + :param fault_tolerant: Logs a warning if *id* is not found and proceeds + without setting anything if set to True. Raises + otherwise. + :type fault_tolerant: :class:`bool` .. method:: GetCitations() diff --git a/modules/io/pymod/export_mmcif_io.cc b/modules/io/pymod/export_mmcif_io.cc index 954240725..ae6e956fa 100644 --- a/modules/io/pymod/export_mmcif_io.cc +++ b/modules/io/pymod/export_mmcif_io.cc @@ -403,7 +403,7 @@ void export_mmcif_io() .def("GetRFree", &MMCifInfo::GetRFree) .def("SetRWork", &MMCifInfo::SetRWork) .def("GetRWork", &MMCifInfo::GetRWork) - .def("AddAuthorsToCitation", &MMCifInfo::AddAuthorsToCitation) + .def("AddAuthorsToCitation", &MMCifInfo::AddAuthorsToCitation, (arg("id"), arg("list"), arg("fault_tolerant")=false)) .def("AddOperation", &MMCifInfo::AddOperation) .def("GetOperations", make_function(&MMCifInfo::GetOperations, return_value_policy<copy_const_reference>())) diff --git a/modules/io/src/mol/mmcif_info.cc b/modules/io/src/mol/mmcif_info.cc index ce93b6778..576e05912 100644 --- a/modules/io/src/mol/mmcif_info.cc +++ b/modules/io/src/mol/mmcif_info.cc @@ -19,6 +19,7 @@ #include <ost/io/io_exception.hh> #include <ost/io/mol/mmcif_info.hh> +#include <ost/log.hh> namespace ost { namespace io { @@ -76,7 +77,8 @@ String MMCifInfo::GetMMCifEntityIdTr(String cif) const return tr_it->second; } -void MMCifInfo::AddAuthorsToCitation(StringRef id, std::vector<String> list) +void MMCifInfo::AddAuthorsToCitation(StringRef id, std::vector<String> list, + bool fault_tolerant) { // find citation std::vector<MMCifInfoCitation>::iterator cit_it; @@ -88,7 +90,12 @@ void MMCifInfo::AddAuthorsToCitation(StringRef id, std::vector<String> list) } } - throw IOException("No citation for identifier '" + id.str() + "' found."); + if(fault_tolerant) { + LOG_WARNING("No citation for identifier '" + id.str() + "' found. " + "Couldn't set author list."); + } else { + throw IOException("No citation for identifier '" + id.str() + "' found."); + } } void MMCifInfo::AddBioUnit(MMCifInfoBioUnit bu) diff --git a/modules/io/src/mol/mmcif_info.hh b/modules/io/src/mol/mmcif_info.hh index 916954323..351f27bc0 100644 --- a/modules/io/src/mol/mmcif_info.hh +++ b/modules/io/src/mol/mmcif_info.hh @@ -988,7 +988,8 @@ public: /// /// \param id identifier of the citation to be modified. /// \param list list of authors to be added. - void AddAuthorsToCitation(StringRef id, std::vector<String> list); //unit test + void AddAuthorsToCitation(StringRef id, std::vector<String> list, + bool fault_tolerant=false); //unit test /// \brief Get the list of citations stored in an info object. /// diff --git a/modules/io/src/mol/mmcif_reader.cc b/modules/io/src/mol/mmcif_reader.cc index de1dbf8c6..527f68fbe 100644 --- a/modules/io/src/mol/mmcif_reader.cc +++ b/modules/io/src/mol/mmcif_reader.cc @@ -1844,7 +1844,8 @@ void MMCifReader::OnEndData() for (atm_it = authors_map_.begin(); atm_it != authors_map_.end(); ++atm_it) { info_.AddAuthorsToCitation(StringRef(atm_it->first.c_str(), atm_it->first.length()), - atm_it->second.second); + atm_it->second.second, + profile_.fault_tolerant); } bool found; diff --git a/modules/io/tests/test_io_mmcif.py b/modules/io/tests/test_io_mmcif.py index 20162281d..a1b217709 100644 --- a/modules/io/tests/test_io_mmcif.py +++ b/modules/io/tests/test_io_mmcif.py @@ -1,3 +1,4 @@ +import os import unittest import subprocess import ost @@ -312,6 +313,15 @@ class TestMMCifInfo(unittest.TestCase): blinks = info.GetEntityBranchByChain('C') self.assertEqual(len(blinks), 0) + def test_mmcif_fault_tolerant_citation(self): + + p = os.path.join("testfiles", "mmcif", "AF-A0A024L8A3-F1-model_v4.cif.gz") + with self.assertRaises(Exception): + ent, seqres, info = io.LoadMMCIF(p, seqres=True, info=True) + ent, seqres, info = io.LoadMMCIF(p, seqres=True, info=True, + fault_tolerant=True) + + if __name__== '__main__': from ost import testutils testutils.RunTests() diff --git a/modules/io/tests/testfiles/mmcif/AF-A0A024L8A3-F1-model_v4.cif.gz b/modules/io/tests/testfiles/mmcif/AF-A0A024L8A3-F1-model_v4.cif.gz new file mode 100755 index 0000000000000000000000000000000000000000..b3d49495138579e3828572d2105e83197dcbbca2 GIT binary patch literal 7597 zcmW+*byyQ#7bZrGoD3<60Rt4IL%O6!ib|-A?(UEp1Az(1fFIqVbayKuAktj|qXg-a z3BotOf6sZ(dCtA(J@2{CbJ=1@NsT@AtqAUv)cBQzl!QdYv?Y~9`PGE^UEHi~oXvd2 z1gsov@&1Fq@mi9s3;A8G7+~gGZW)Pp)J`q1_}p2?d5G(g*8=^0eqjkc?~^?Y<59K3 z3MCo<Q~b^i)*@iKnpgDOaKbySsIzL)=r;N2x<_`$uG?8Ic!P1Hj|mD~4wdYG&&WI1 zzOsa|OknZL>_6Gy_oHIV+*$d9NrA^M^hdzIr*ZqdzOQ~25VUfT?b0;#M%Ar+r<<<* zZ`r)<ub5#o-OT2oJJjF0z!lTFZMM<*{;gxVC06-bhuWZ#%kodriebgU9ye}Xw|$d7 zw?<9yt?So=Ps7@SbKDea18N-zg|-Vnr((7mRu6wD)T*lt&1IM%2&PtiGZl8O>4PSR zq_3-h5f6nFTomehcmFnwgzWnsKQ^o1_;njD_Ws!#x6F1)|E~K_S(R9VI{`y1_4xX^ zr!;fS_A_YF_F-D*))JcxXwkJk63_3RrCEjPtPq|jG=qQ{z<Nd<bi3r!HPl==utosr zIy7CMu<N&NwkajNa}ZhP<ezm)0|X&w<nNOH7Qo!u0Iq(abOEncp8rQ8mY5lo3zxIG z4thuEnRL|5xY%=;cme`^?T=q!Dz^0f-qdfA-RT6nM|zWZ|BYBw@2}lfD<NUhqVh(u z5|>#<ryMk$7Q-%9K6Le9iGV=;^pV2$HR0m-edCS>Z7Oxo4EjupGLnVB2@x2#JJijB zHH9sA-o0~l$(CO><1h&BOszm><JC3<WFA@&7Hq<jzgo6xa!ZT&oDlRarMmrMUET2V z*|ZwFJJiD7%(Q-lI55pF^Q$>2^U$l>kO9p?I?2RtI`bK)w2wnw^ObKl5S&uO&Hb)s z>Ah}kU;HH#&FX(o#cw(Mu9FYb+rM0h+RijdXO8_cwR_w)bY?}^+ndlaMisT+iil#^ z%q)<~&xx{7S54h=y$)_(l`OXmFsbkx-Zw@tcC*I2MTLgkc8uM6o4)6q|GtYpQo1BP z8GUsqksC8Nk}H_Y=2spREc54-@hgz~?yO9bS;0+W7;|lzWRa`J5bJsl?MxIJCkbNx z$}$GqGj9zVc>-w-@OpOHFiN2sqi}}Vx+s~W<@*c1`ehj?Sw*)xw-95>Ug-8WUHNF; zs(mTMnd&VOu}{tG_!V{M@7%~pDP86#VWb)IMf-%IAJjM44NdBYqsoidr-H;7>Y^sD ze%S~4p$ysT0*_B?1q0Xg*GQjO?4DCC=^d$+5jq9tt_(Oop6_cfLUS@x31p9coe#PE zPj^aY|I4Cj&2?T#2*U4abeA7>sAAw%z#E0oVsXCQ)pF@IRzLsE+KFD!V!Ulq)Ozz$ z{gq~D{`qgtC?5R4D=XrI^<rdmGj*OEPmn~o|4*I|h_U!ysfMWB--Sb0k3*zF#e!;h z0b*hG>$Ho#W2Qk2cPdU}V1~Phk)v9<8)i-aH-YZ`HXhJ!uN=R2wwUIrGbW68S2g_d zb<(Rt-9ag7r_<)@3=oKBBez*DV(o7fH#StpxUc>KbM4SM%QdiY(lADe>~~BU-HvZG zm%d9G{f=y|S6V6j)t3LtS&Z;aWxq<$*4G@^WpJ~6={&oGT7g!ljUW~iSgXLCsYy~h zm-5=qg&nQw{pcKfzou(vAH5+;#m(u!Lgm+ab@pXPiP^LjY4$}u#{}gSm#aG#k9{A| zMH)W;TYSRG!(=BC)z%TU)_7~a;2E$;NRCWjT3`H1HSpH~^@5N~iWn9fAF3Qn{O9yC zBY2_GDE0LnTR_tjMkIn~4pT}aIAnW~&S6{r{M~?|158Ki7gsT4Qe97}C_WFV!KO-- zAfHIa5iE9J<xezk^trwPXqU6R#G4wfn#bCMbNq*MXz)3B=QQEa9v>&<^x(MAjsn=l zxDgIoQ#27k76TrCYN=ny$`rFP`P6!dVf4i-oU8t-Mb4Wp`H;PVbo!|SE@;|PP_BWl zvMOqxw^vr(MQM{~(B*#6yBD2sf>#HhcGdJ>%a{90x9F#bc-*)fVukr;qg+)wt5h-o z(+@4*<T6&PY;q*BrCej;kU@=Nia#x6qHCTtjf#@S?(>r*7=$c1X9<qUaiM}%?F2!b zAM8{lco=_<MLX*~h|Y~+amTJmo8`=6m0oPtm<dQoJ*IKV{u0B>UNmX@weC$Q;$T*^ z<};0Y=aYC&w{J8<Bx6_je4*;T?V_YlU-s3^@I4j{zfp);p_5b%iI`^}p}3O`$^Kw_ zhPD52x3;r#CD3`@dKqKW^cQ<MAXh=WlCP0jH+xS&{T1WHD%-6-n6Q#CX!PxTyFoRB zl;6r}T*18qPa!7vZT=JQA+n}E?7O$|*!bcfRs!qgzXyj2FIc5~OFHAlu*Q6P4Zl4Z z4UPi%1?klc4js0-3|Z}uo?=<gpskuNk9HD=6L|GrN^K&0Du+F|@WX#t(*`pq8&7ew zw=bJNA4!oJDnS^S%qGUcwtWJUpC_$bkd%e)QN-T47yX>Aq4|-5b~C$dpUR|_<x@&a zHTVx;yeoFp<G~0t&B$~bGshpFZp}LS!N71uy*9QZJ@frqB-pmU36W>xkl*6?7s7O( z7*@2{zU4}pliV#O;;nxdx!A_{)aGKyI7Xpn+p~38xHcbhEvXeoKd~A!Y}FO;h7+rR z7h~3Ciga_o@>43KU7~kc2q%jdZ@AKn)b}%c%<|5Z-u&7y0eTTv(0h1&qL=5AtkkgQ zpduOg>L{mPJTQ=fVmx8%*E{o;bO57G`HkC%E?r$?q5KeV-OlDm{l%jJ_<LyW;I5kO zZn*{ivaD+ZOOx;Hf~a>Yi@ZcOyS>KRhw%U)#W`J==ABn#QR^$kQs;_kZ884BO2qWS zNl=|AaX;@d8Qq@`14G|xjQh>M6wI$9ohf1rB<UXevOI~f%zbZQ&F!#OoYmv~n%IGt zsy3;H_d^MIy^oa9Ab8*<v$QEizXw<LLx;DYi+CduRQ+9GcBtC#hS-B=`$Icl6?0Q8 zSl<%@QNjxRobne3Q)&Lm4T9Gx{UKZ|8a|dmiYBy^o-hPv5D0!uRcl;HB~*g~w-_@| zeE#;1pa{;zVl)XH{qSrL3gu6w&JYVj_wauta%A-X5EDE%tU2Y!!e7J}Zx8%=Yff7t zj|3P&1}HqYtyE*nx86N)bn3D#TRDhHMKWw}$=~H1ogIkSaXsi3`@#?LLppn;%X|~% zTCa_n=~$8C`J`Y+NSEVO`_{C?mDxX&KNY(^ZuvDaz<}{9mX){6|5H@|vG3{eH8zB) zJk;xj`;GUfT`j7Hsab})*eFq#=HVTJvfCcj?%v+;XITzyOWo!<-LIHah(7%Ot*rG@ zAS!ChiE83k;~)Z188a*@XQ9%cZcHz@)bCa0T=IoKq`;yJ-gtU*n?fWBC<<8FFRNzn z$QtiBVX(k%xF^CR96U3Rh5pd+^sXO_-?9DCw}TE6dXZ8rcY7A{J&~GvI(%+axm^01 z;!!)gSmgSq6aDbxOn&pPjz;y4QL8cXl%;69cL?>)r=RK+1P$)ryv#j)Y-e~-rMW#Q z(Ea(bo%&_x6}ew^6E>HEZjNH+4hrK&XSpq5fF&$TJLveQpOH46x!ng;{T|JC2<oYz zq-wHs)l4C^2h*gaRZSoHo`iUScWZ`boi5>lAGBJ(lv-Tr#MZyKBBNzkyxFHs!DQ~= z`CgZ`s5i~uHta7fJbJgcOThPpB7eZ-ipPY#YWeuv)+dWgrLRUJL-!9g#G_DOH@pW; z%lZr6gwdA6xl6s;Up{zNdEQ`VW<s?lI!~oCkHG`CjWTxF9wE}?dZWOHKJ~FHKTKC{ zJhq;SGzz3(FVQk-jNY0Pbl=Vvx8zC4{l~&$loxdpF6(Jzf<kVuC`RP#V?sYt>;2wM z9qL6;P6qrzt?1_5rhPa(+}S_!7dWPtV4%unRo}qRq;@+RNQxR-v6r->E*zK$l5|J> zqFAuMu@MQ@-ob5VjDs}RM~s1L-agVNY1du8O^0oM!&)fFpCRsO-kQRM>m8Nbl-qp7 z$RW=v-kt4WWH)tjb0K3BYyCu!?}>|q_Zp?Ip-)iyXq8&M*5#5t73I$O4BLLQx3tc{ znZo=gdGIY6^$qH;uR?@+nsT$=)H+35XPWlH&y!t?2tE0n>5;s<_KE6VVOdXUJH&gI z#+Ttc+H9<O3(Q)@dEbr+RP7sVxlV6F#8_&Iy`}GZEy5@EFWFh-sEY6Q&xz4><yiMv z#}OVOg2AlrmG9ey*k&v`OsMQHQ4Y)NH3$`nSlaH|+{D2#_&oPV98dkkKA3O<>zy{r z7PW!pVfyjKTC!a+dTFR-zjhz5h(}B;?$D-r3sKztsW9q%J1l2|qf>{ZcVMD2t959D zbCk9r;EzOb#`nl$z^Y<gX#*@$0*e<Mm){-}szmG$=jScQ)R@>29a5Rp->p+Wui3U> z8gJOxT^nyKf9FEq*goX6Z=EZ+YD9j&&w@SAzKs8)jZm#vs-OhT6BhXfj8^Meqaw1t zG`hU<(mUwmSc{%5-GszO?8VlHtlGNe6j8%2_TB}}mC@+)OOIPDtYEpLVX!1is$aHo zncw@{V+n(Wa<orcpR^5cW*O~ll26)@w2gdbne1$;Pufo@o9CHj))F|tl(da)W?8Jn ze?DoaQZ{ch%Nisc=Twb(yp~$x=&iD~-IILV0l%zMhF1((Xp4AfwmRSgugXs0n!!G@ z4>b%XhdX^`;y?19ZH2UFk3CPmJ<VGYFfaWU7BF8${;G_|VlZ%cMX&h#`xnWSXKBgp z#k%QC#k^eGuy%OQ^JIE*DS<Rr8JoncvQ6V-Lz%D*4pHl^WE<UIiq1$Cs!6mq&2$-Z zKJrrEuGXN`<gPqEE+)gLc9U>My?;^Acfg43JrleXwq_Gd{7%MxAyHSzKGeX8wEVAc zGCr`PiWpD9goVm&%i~X*{|`GA8Uu}FWJB?c_V<hT(k-7B(J=guqAkTP)u_9RA8EBs zi}a$iDS?&;-)5^aTNf97yt;L&``J`{WyH}$Ir_zA;+BSu+!__Kg}QK;gT*S{Yf(nF z51*&j)MD0H?(H|SC(e4j=xk+^zv)W*AvhV;57u%@HE=t3r1JHzba66p+g|;&(SJit zB`1_JdszNsdM4}82rgRqacFpIEubdretJHcnuSoNoH*MnI`JIE`d|&zfsD|~N~$g2 zSqku=(H=R=srdsW<40S88rmPMnXtI;<myBkp5*}6T5T5@^t+U+YPbAEsDEZ*YbF2l z<um!V3*%<4%A>zG)RPC4xueHu$z5gLy1jW=sznBq5qmxst-u49FFF`{EQ8+0I!W;5 ze)7DV6=NzAHb2*V^0Kl&@Z*E*rJ|AIWe0U8GZ*<8mT!mqH(B0u5h~YwY-sIG#~qdm z4Z*$=cxb_lMzFXFpP?3Ui0ilV_ZJh!ru-b)#9NrDs`1HaUxU`o>8-{WFJrXe0htH! zOZ{dm1@W#Ep%(=!o&j%r`ChzTrzC!0{ygSJp%~M725Mj@{?mnV{bagi9Q~$0a>Ahs zdvJPqY{Z%Op-i~bVTK@8mX$8F)Wzs{k`;L2lUvWxDRh0~o-oQ2p9ZcK?j>&&(bwF1 zv?|_CmIXyRZ)i$-ke(~JFO$B2<RzVo9W!lNCke02RD=VoTwe}DNkIVLVb>SnXg*M% z7sq$bV_D$_g!!2lBnKeu$bkrlXhau>^`H_5i6;wDf=aG!kgg;s8iCJ1eSAKK3g#Oe z2>=Z~SODB+C`RyTfCb+_0}HeeSx*+i9*Vb$h5@R0#824g$XLdFTr!rkNy6@#i$?x# zYlpZ}#Jk^@)`x<iz;{G|%v?g$NA9Oned+{Jd<1b2aGd#hMFz!KI5Z;{vZJ$A`&-;` z?b=h9Lza{N!?7HYBYvP-Uspo-4{$of0D>+u3YjDZa_VKc|HO2jKGb{+QB#;u@;~5l z0flV?SdXymaMPVG$|60m{xu8?B`zn^NP=?c1dWk-P7z{eLM8#gEJX)QJCf`CK0Wy$ z4%dB|)WnTn1~iv%k+!G#y8LMfPOYH`2^Y}7KwY&V`W!ob^+<}hiam>Z8IXAwn!;St zXp3W@*<(mAD?0jSbCno$2oO5Mqd^{U=f&5I03{U2Y34?w+r3LWoRztm(W0JF1IU{` z0}EAC&DKvWa*meXY9-kz5ZFPi3y8Hh;!7Sj|0YrWGe1PlZdx(ojf@qzFO~Huk%%)o z^GX8EsspIbuC0V|*nfI=M5BRuBi_1;f3H!rNqk+{ArT<txE9-EMTdsva+~53>R6t? zT#qOUBd4>O*{mYz!-WEA@U_C$m081Ny2b-RONzvD5d8GxaR5!^Ghh(M2Mw?Sg$uXr zK0Xk*nd7HO!_%kPu>ci6ad-U=`F$fBeSQ@<fsbMlB(8rCx%npL5nL|%BjyYuJTlas zjt?c3ordPOA|5`j2su5<S=ZdFK|WY0?Wpk8iAj#yq<VR*%q(y-${IGP<udn>CzGL< zY&J#A;LS!Rq{diPq++cn5&~>SF!a8sDyTfs0Uh>LO+&x-;^C1KKG4YJUSwJ&hH)rC zm|H;43dX?_=uB3SOPm{0QC0~YX+%w6Lm*pVvLORWlGmAL8l&{0$>T<->NCi$Uf?Kr zat85?>jo3+1nD{UoO}jE^3{$GWlK_X%w2`Sz+=S^nfpp$5NtD}^>`ud<u#Di_v7g$ zcUZK_sQ`UeqU?|Itb58o#J>%3t0{b0q=8obEdusrKTO%p(%Kxou(HJ${)3`QoDP|s z1<3FaBCJU*MViCU?J@0*xeZD|=kmtvop_HD6rVOAmd_E#i+YQ_TV=BHh^%{aLw&>| zyHhGvCw^b0zSxmu?5VPcr!g-n9lllUSZq5s7D`S<JR<X=Kr@(r9_1X=YFsB1o(|7) zwrmin;8(g?Y+HoLzq_vs13l)etqEhO)FJ^o{&GZqHd(7fv9c+OkU2P-6YWkxD=N!@ z7#R<aLLn-8g!x-bd>nC5|B!1Y08bl$E=7@e1JzuSUQ!DcS_c&5yBo{g7O^P+tjtIw zD56T#0B;k6a+~BJ*I^5I3NVv$?oZFE!#-#EYD@AK3K=S!(jb)4Ge#MMF$Z%G{k}$U zQbzdcNIv(%MA7<!H{oa+)I71Jm<5Z9H_jT9!uLDpCQa;~{=d@ter33q!VnW+>ArWA zKmFETE4jW^yr}|$Nrlwt*;qK_spwT|#-&YgeMMFQQ+w|h1Yd(<Q8ZCGb~Am}J<=NU z;_H)b7)Z}eUvDWYOR@>cpkqveYBP~c>D}6((W}NswsgeyoN6)PDvKl^5Kkb*2+(tb z%u5?el9IU^>{}&3g}`4BSSmRmI)Z~^M27%;bT&?g)3TqwtwLdVoXQe<!$R7cfOU~N z<iGw2&>KQf&ob6-TaQ$#d|J4JGot^QOr>}TjLB<~AJ#fr)(Nom{qnRRIDr^rJzEOK z)fGx5$SEKg+33}+9!>E3^9HT|Ksp!0f2#xr^VE~_9_)b~-~!xov~gh+>nDiq*oM{Q zEXoU<ELhA_UT{V0K|md!Q{)(}zyDw33S3L{iUxRUGr;to>T3e*&B${H8o#H(1q9H6 z5h3dfv<E(U3?bj34z<Q4<%MHQ0n{+#bBhN<)i{oA6Hw3g{I1EqBrFVIp_DygEGC>@ zL)C+`0X!Ve7W^bPA9cxw*VtQns{QL1Ll3`hM+_<@wg>;PQgz0C;>ph{{li$Gl?xE# zqm!mswFec3%IE7BNJL{vpAnw4b8ADgB(sZDya|f<yIIixZ=0459#`H9{^EO@P5ytA zi>`6%(M%$JMV67g{E`c4O`MK9fR9egH1r&LL}ve+vx;V_iklletAwcnYJo_5PlI6Q zpD4*~K(ECARh!75ou<XHt|n&Er}s|wNfIPynHA9L#r>TRJujXnRIEDOj}EWqKs(Pg zho$Kl6Rs13)j({~5k;j9T9BV}DGQ2!#O@J;Q!=g?B2H#L%Guv*H~c3kW9B{2af^;y z`&!bZ@~hfECGGkW!KI-xT&+i<bd2GuH2@a^ogiUfB3hv|w1(zc1O%v+@iNpzggClJ ztEH4=nfVmu&LfY7L3qJb&~lPi(TE35TIdq379gOGwF4Z+u8>e#Xh$TRSwl?Y2W%#| zr|hU4f+?F24L^5C^u`(TOOzi-Udg%elr+h;L7wa=xs&ASv;d(1%tP^e`O7-=wRZUF z=k|%m0_%nu(40vXcax2kw-bBzI8C5b!DX22^SU2Sn<tPe!!H5yVD0-w<N6WS#|gxQ zqpRy=L~(=zuY&gEPQj_ESlHUV>ga;^G+6JYUYQ4bo!=OfKI433=may{*T=+99{M}u z3tyv%X_%BdCsnX8nxU2RJxv%rPALX)xb!ql!}vh`TJ08-2mjdSOnUJ=N%38Bgs!B! z9Y+H~`;QomBL7EHfNW)~|0g087lKr<*1=M2ohjleW*K<l*|MydF2st@o7-TITZJqU zeos5~X75kF<=bSGnyhiu4I;wiQeK2?@ubmtD|Z55_JkyJe^g!@fnMSMNGihpSDqau z4Uja@mc`O4VLlU2mZMfFFP?+LmRjv?@;lD&xsMThPLa@f<I>_!_QU14kN6URsZZRc zAxWk>J+39cxyseM^JE1Q+u6e0ruyvR(i+p07hUh+3`-X9lP#e7b5jmcL^Lb9<-uO? zH^$v@d>~P36t%u35#zshJq^xFSv5|tkp*Eag70yDrDR6S(C>vWGP1}0jCcz`O&{W= z34QYN{*Yp<_&uZ`{MfZb9nBD_QulVTu2%}k`9PzZyWqhd7in>l$QW?E%zSrJ5BVDM zBp71)WSUb$nJ1LAM=rn+{I*Fd{oE0QyB$D+^Jq8~=eH2f)qkw>LF2Szt1l3x&2keh zwwB=2@;%>_1lZ2aJek$v==EbD?cFe$pxfH`5ui;CHz3T85dFCbF4D@~z6P#@=)!RV zO9=}W=wU_;AfJ;zi(<3bKMk%EL{emwROHk{1I;_*I#)Ic@i_YY@6p7uTm_7>h9k?@ z$u|3S*B7^~CyP;QaBvDlUZ3N;9t{loO}wl8Us<3D@pZJeF~G5KiqH-EtBx{t0cWfh zvAE$i<qZ|Qk8EivkBUl9Sbkq2YkGTI=Ja2Z&UbE##{1D`>F$J30P!v_@$OWxNAo6} z1yqldXC<2+HJu3UXS+>4)`Yt=(29z3BIVS_4u!*oTD#so&VclDq{;=+{DLvEPsG=v z3^5%&PiAej#U+9r?yXAy<8s#|h05;$pnj3p=%CZ=d5;AE;p?Xb^8}VTU>0bp;O9LE zD)yECN5rrong22w?qEB!^yZlv?w()qq9uyg3x1i}qBG?|re=s4rDLZ5w0MTS-H}EH z!4C$DjKKP42p30=XeH#Mc28XudzPwpdC=)qn0<I39?d;cc$2E#IYFimr3!U)e6K1+ z9X<g#E<5p2)Fw~Wv2(ADHCkF@JPwY^0Gp5{EUSv0SUd~4Qz;qQ*>v95bhJC~aoy(t zX=tFy@sBi%e6abHefvB(qQ=1i1SfzO{P-0D`BLjMsV35p&Wyg5c~wXYyS5!uPqLY3 zK{k+cu!z3M_TtKk;W?I%%%SB5@e_vN<M;zJ4uz}dxvW(?=W%blO|A)gKkRPQum8gx z-8LWPtoqt$bAS$X_Qb&(oiWp_6qL@lc9#iod(*D;@BVQKbwC}@1O*d_gc$Q+7;@bB zvuTX%EqT$DXX|M0{M1rHF<So{M-R0xAAN|jwC6u^t{ETF-6Q>5_s!`)nT=TEH(>x4 zyZ<2ak5LVwx`Dh#w8@;J(E%7Abj?OA4HW$w@`WdQhDZ13qFC#}40KP@3YZ9`HIxi) zr}P{=z2;#-!vskKZjR2VNJEmi^J|{#cCZ;ol4rhAy!Ab~!^6Y7yCcNA2fLA>bi>2@ E9|GN#YXATM literal 0 HcmV?d00001 -- GitLab