From e52c6b1e843af758c56da580a781d69ee790705d Mon Sep 17 00:00:00 2001 From: Davifek <178534466+Davifek@users.noreply.github.com> Date: Mon, 18 May 2026 20:40:56 +0200 Subject: [PATCH] feat: add benchmark leakage audit assistant --- benchmark-leakage-audit-assistant/README.md | 37 +++ benchmark-leakage-audit-assistant/demo.gif | Bin 0 -> 61140 bytes benchmark-leakage-audit-assistant/demo.js | 84 +++++ benchmark-leakage-audit-assistant/index.js | 305 ++++++++++++++++++ .../requirement-map.md | 29 ++ benchmark-leakage-audit-assistant/test.js | 169 ++++++++++ 6 files changed, 624 insertions(+) create mode 100644 benchmark-leakage-audit-assistant/README.md create mode 100644 benchmark-leakage-audit-assistant/demo.gif create mode 100644 benchmark-leakage-audit-assistant/demo.js create mode 100644 benchmark-leakage-audit-assistant/index.js create mode 100644 benchmark-leakage-audit-assistant/requirement-map.md create mode 100644 benchmark-leakage-audit-assistant/test.js diff --git a/benchmark-leakage-audit-assistant/README.md b/benchmark-leakage-audit-assistant/README.md new file mode 100644 index 0000000..d4f759a --- /dev/null +++ b/benchmark-leakage-audit-assistant/README.md @@ -0,0 +1,37 @@ +# Benchmark Leakage Audit Assistant + +This self-contained module adds an AI research assistant slice for pre-release benchmark hygiene. It helps reviewers catch evaluation leakage before a paper, model, or scientific benchmark result is published. + +## What It Checks + +- Train/test overlap by record ID or normalized content fingerprint +- Benchmark contamination in the training corpus +- Final holdout or test set use during model selection +- Missing split provenance such as deterministic method, seed, or manifest hash +- Missing reproducibility packet evidence such as lockfiles, manifests, code archive, and preregistration + +## Run It + +```bash +node benchmark-leakage-audit-assistant/test.js +node benchmark-leakage-audit-assistant/demo.js +``` + +The module uses only Node.js standard library APIs. + +## Public API + +```js +const { auditBenchmarkLeakage } = require("./index.js"); + +const audit = auditBenchmarkLeakage(project); +console.log(audit.summary.releaseDecision); +console.log(audit.findings); +console.log(audit.reviewerPacket.tasks); +``` + +The audit returns a release decision of `pass`, `needs-remediation`, or `block`, plus reviewer-ready findings with evidence and remediation tasks. + +## Demo + +The included `demo.gif` shows the module blocking a release candidate with train/test overlap, benchmark contamination, held-out tuning, weak split provenance, and missing reproducibility artifacts. diff --git a/benchmark-leakage-audit-assistant/demo.gif b/benchmark-leakage-audit-assistant/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..c021b1368f3e56bdf7af17ea31d9b25cf28f42cf GIT binary patch literal 61140 zcmZ_VWmHsQ7YF)b24;pHN{}2nq`MtLK#)dCkdzV-5s^}c4oL~=76FN&yE{a>V+a9} z7)qMCyzhJK!@XTy}iD^xw^W(xVpKx zyga|WKEJs5cX4%oetvd-b#{JvcJ}Y|-{r}_i<7g9latfqv-6{qv*Y6v%<&oK`1IiD z^bm7&fH~Q}ufya0gTuYUmLE>6xaPtGiiP0sxqpB@>X9T}S$ z`87HCYkF{WdT?Z-Z+N12c%pxBw0CH{XK=iGaI9}&q`QBlvwyUs@4kL^_6)Z5jI?+4 zw|Dlnb@sP(4YhXkwY2p#wf8o(^*6S3);D$3H22grcGlLn)-<$M)wfmEwNzF&Rn#<> zRW+7Z)|aB{N-CRwl-GVQZ!9jW{{ExtTWNh^QAI&fdH(mZyuz~V?^U@4KQh0eGV)5Z za*H!_i!yVHQgchwvI@Uu|47X&_?n)dm|mEelKmw)D=H~BCLtp_J~bjHIU+haG%hPN zDkbc5VsJz}GBPD7EG{4<78w%rF)Z0XDB3$X;bXvOAODCC0dbzlxObmoKKO-t`UJoA zi*ob*?BN~c{sH;c6Y1g=;rKqp#of>8jklw#x1D>Cty_S#tG~Uom)&bmTSpH|=a1%& zzAqentnAz^Y~PyLc^TPyn%TIq+x&fDKfTRGj-80an>+)QZsbYd2Xfs+(z}eqqc#imcE6izLlol zOATFfbsYJ;Q#;t=1W{a&i$SD z{sR1GnE;?z02ZHK4VBXw1f}3H8$jiDhrw9YGt?^cdOy>MI4uoS=J&_Kl_Ke%Ruv2; zaOszr4OV>{{wioQl<~B>@MpT@yWOS1>hHg@5kUkD>NQ2<`DzI~=0i2blizjn)ic#= zOQwGqS34~a)&7{Ru({l^tF`4m1s64x{1t+PB+m$hN*HVE?U+T*EUSqhb@P(myJ8>QS20oRf8?f3myr2pk|{j3_#!k5ItOL zkLB&24l0?r&VLIv5@+L3d;Q>#oeHo-El$>C0l@AraXF`xVW>4fDFUE$i}suKz+L+a zsRVSS5aaN0<%)6-6{&t{b1Z)1M{58D0GF!u61@utbqd-36erM|-c5o4?`e2g6_doV z!8}wYRkVNd;JrS7$_<8|#<2nD$4C_}bQF8=@M?5arEvFWe{(87>*yfsLhyk&?%}DKnGh>3&j=y5o8;( z@c;rZq&K?+3&5?r9ul9XQ`yhayw`epuz1)Gx*R}t!>~aWZ8ukhD8$yzks^>aM`4s5 zfKVu{9N-ytRysA)VMh@^5G4Q*(ctFN;U)$34{`4?|H>87ihzXI@Fbp$($KQ~V*dNU zvWvQl(&PQiix103NfshGVzVGQO^YcOdo$sFpwu;K&4k|PoQ0FhpMO{IpJHWU!9Mxb zJmD1d@AzRwfnInD8%qEd05_7N-p@_8t#{21Ie-w0_$7f9#eELy+5o@;ayleh1TAO? zuBBq|0dsL$tK9r-7aJjA@>d&$64B_tmB0Aob_&k<$HZ8XUJ49Cq#iPBp(3a6fQL5; zqW}Qz<*#1~)S{<|u|4@!V(peD=9S|=AIDCoEwVo<9L^!6;h^5f*tt&&Sjk&#PWJBO zo(+b;jOoTp-g1APjdUQHV#%k0d|JmMZ830HEDP=~?01f;aCk?~+h-)cM#xavs!DbM zS`mH&Q2fYd74so(9kg==i2$JR90xXN_2Xg%IO=o6JT@4Lk@tVeFWaFm1FTdO03wmpgQJ)z=7~=flDNA zgi2)RlbRm(-*Jk4d&b0&pn^Fmp*+-Tnu+nQ@_eXSgc>uGHY}J?OS*eA@#1Ib zvI2aG2q6S@C%j}+3OybNDy-mi2X|5cEa2e_Xap8!l!pESph0eO6*avBKa;=dW|E(X zXBX!0d4spc0ceqexd9Owi-bD}U1Ob9A<GZr}ceqse?GM%7B!qI)xN=&evPNl1n8=RlEG6BNyyk-wd%D|3H=ZWB#RQhM)Pqbu_nOecN++ zHFZ59v$qjKyhY&UvhNBV%uK%V4h(pMW~R?2pdoQww!pXDST)MUw?jd2DXI>IgWPBX zfHgj$u#z1P#&UE>H3Xp3lcnMdyL}Mc4jAL0c;vT6BthK6q6H1%i?RYZ&@dG|3sK10 z(xafj?d6dV-@VX@`);1o{qXmnRZ(u(-p4NZ^QiLdxkbZZqQ(ItMR$%(kpMoOyi(x2 zFrew?RS-&T#1>pFx+wGf!K^X1`y5wL0e8gkyoo9gYl)|b4cK@+s@l)Sa)4m*YV>D2 zA_phOHbV2zMe#fJ8#RW(+o{lpapcNw59R5Hn@3UZqf{ENUY#4foagm-t;H?(4PG7Vx1eOSqdF zSW^eBByfcnQ_h)7@ljqw_5880mV0n|#*$Z>1o5{l*S0d5cT6xfa`sBtN5bG0E&w9n zB=hW}K*RnyZbLAh-XL*<;uw~Tshj-;4CIZm2Y6K}&hU8x;>?-fx63G;uGF*-t71GL z$mOF^FYnDcxDR4;j$OExh8ovcOVbrd)YYlU2%j_k>K@|ti7(Up&Pe*qly3%=!$z;n zSY6SoTbAq#nEUiIL0$H@_9yM2vGgHx3v`pz1u%duAR~f5zOnv}6x!%9`UqY%kL^5M zmw2R^pv8v$SD+sEiS5lHiK@<}ypBr{?2hS|_9Mj$p}WA8?2Gk}ublso0**;H@7%W7 zz)xV(NVOwQvd0vFY;o8DD+>e<5ciMtHb*4gR>4gLR+8^yW)H(}K#B#*vAB~c1Aysb zFl3Gyi&`Mw+rZ^&W^Us^U}xY?pZ^^v3^4a*(#T=ptKUmiYfu1T!6s5LUkgtGp8^gV zsrq=jg`YugyMgjvoc(Bb{s#8I4T~T>9*5xNvX;Kn@!Pigv`&nVHVWDf!V?OP7K5pT z2OC2HrfM*=248EnP@|NP7hFu{G-ebSN6p`Q4#vT%ULs%|ywWKn)qkY-UyC}F-f`vA zL@bO;^{bh*5p&5qFQ`H|!an@i&42V&AONGC(GZyJV34Z@PF03hq;A^Nfy1A zE{c?GkWgi@=!Fv%puuwq0ML8V7!jN=Iwa?4;*ThJNMZ2Q_Gh9%{9O!5ZH_dDj-2j= z=fnqo{ZM_w3(}ZwKvo>dF@`9Wl?lQoXi=mC5`rFgJ|_cN)H!_S`4-i|71iSq1+0u- zzKC9%d)9nTx&sB6y^39d#(>#ihv=teaMC|;e4B7J3{6zGanzJOO#KXs&#a3ZNGfOx ztcR0sz@Q+@H|fEoTE{rq$ha6Cz?0t^SRLdGz)L)#cq$W_bRghE2l1&6Nh}um#TO0} zF-ed}O#q-0+NKi3BNC*gV7&9@fQVSopO2{UMA6MJ)fXIYPo(k3Uh8kO#de!lNIH<= zlazp1m{2B@I}UEDAt16(SRJA#5=_c*@5JIvemBIP$FYw)s1y`J6uu|HyOLhdCm$ar z+bDnaG?8;2lySqCmsVn8#rQ!IlL|JzrU3ys@Nlq@nEf&SIv6157MLFFZmrA&Lh;aY z!8`<+(;Jl{C~UGjjM zvi2{tj-+6la0s^`fE5Maij(^LKI9HRXC@U^a^j0$36}omM&||)MnKQeEZLW&yD+G5 z-2Ki_G1VyTyB(Mg2B3i3w$;PfXnk(n@SnKh{2jziMLBS>6)0cfs6-aXlNTtN7N|;V zOP1sV$8+!qa-lpK_SEr*I^Se`GOT&%gSirId6RdYqdkaB?K_gTNKb*3{!~(+gE%m)DDsN(0xL+M9S6h%01;@YUSmwroPWY|6B3#;0s9t!$yGY-yov z<*E!#TfQz`zG+&%GN$FNN*WxZW&o@8NF^9 zqidazX`On}I^)|qm)^S2+`6>bx^mr$#?rN|H&+ztv~Bse?WDKuHn;69wjErzVd&bA zW!g_)wEy#MzesPtYHq(-Y`?p12heu_Wjk=qI>3G%(2Nd(mJWbyHPKB6jJ}gxwv*DV z^MPL{O-3heODFwOC!=gT$xSDVY!{nZ7l&ULS4J04OBe4_7o4|xOQua&wp*L7ThOdc zG^1PExkjA6TCSxVLEj^N-7TQeqv+S8X4WI=*Ig9Xqjl5s=yea@qh1}eUc;731NvSK zzh1MYUh4E-9>TttvVFGBDC?*yd$T^LmcGsAUcvM}XZn73`U*F*s`s+}UM+neWUG8L z`ado8ex$DoxakkR=?S{23^N;u#M1XfELBFg48+NH$42$K(hnv(_a>B9ruq$Ll(wcv zRc0>@<~?i8b*}s-J5;pS^8H!mkBp)6sFpI`%F3Ie8s3&_qRM)+;ii%1#ywPP%W#Kt zbNdLYn|`E^sJXWkHRv}oQra{eh5EHLGV!cw+_^7F_UD{m%d{D4G2`b-M&q(9YVGFd zrflN|J!;!*ba$!Y&rQXC%P7XL;c%(qg#On*`i8TmK25)0H?qx_r4`tvUqF|7oahQX zxiKhlJ;bGgFmsHgtd3Z-g6wvTQnQYNuY%fqoOZwVA#nvm>o`2RmTAA7m0^N|ua@0> zg0po(Gks!qWJ0j4fxq>a5Y~KB_;!N##fUzdbQg|ubcJ_^0Sjml59w5nqlwP1fMrM= z;E$pLbU`kOF zP>LpX-zPnTk$klT9yd+O@l}}ZPh0ZM97^NAlbgGPPoEM%7trNyIV8tE1l6z#4kZ4z z4(VtduK3Sd5%bD?1j$_-_%XwjlKB*+#`Hf^=v@vD@by9w@pP!>0`6<@9R{k80pe%? z(K)2mInV?I@!c39F{T`WAlZgN%`kW=aip#2NnG^I0qtxE3>pvzEsG;vzyOY#<}(>8 z>Y^8$elE6Q_sausp<{9@0Z8y!8bLO?JW-Gca|QT{CXKxyJRVA4V-+xxHlv8J|q4!DoIx5gPkb@y2)`N0=OX54^ zB4!tQH3m9J1D+xAuYayemi3AKIxq&c4OUIGKsc8H?6fp||fEXh@0Fd@z za1SGa`8p(LC}KYpphI)ZE@n&gC1e~xde%YgYY8|Q`u&n*3pWn813A-x+{8spxnXmN z&(Opl5EWp{ZLjieKP`aM=$0$Vc4FI>%iWfK^i-+Vbjo=^h z&w&r{1hWx=Ce?*k{E8#+EC2mAdL1_oPyDW2|K)F^mAzji6%%(Gfteduw7?lH02D@Q zhc2f@lm2sqtie`sEdkpd1Q;K};jvAY+fA!y7y>%tZ4^KPO_~_@o1L*@&XTmj4bK$; z(AFXC69n79h%P(ut{I6};=l%Q{5u`!fzQ#y_9KSF^63Mpu^@OLZW$K~JEE{S(zhfX zw**@W67LIw!dyuyO7Lo62S*FX0!$}aE&#Z|G25GCb*5uhi{s3@Bc0V#TuV?kn$$oC zgll;!C~z``B%w&dt4FOHWuLuZs?h#*s;3R8)mo6qK9N~Hc@TR<`|F62sX`7uXJL=qVZ)k#W}H;T7->y$fuwy=ABrN0WX*m(O&Nq*eTeWs{D$0v2J!?Zk&E zZvet*()XD1){d19ft!O!KyNHyMv&le7v}^`@bTdKFpYQ#o3q4;B=K_FxwZtIl8_d| zD<-sW51el2{BH&i%ZIfA2NgGaE`QbJuow$0e%9Tz|7~#_DS$Z$fJ1nsN6fpa-Hiyv zwY~V!jhaOTECv!sZ8=+tyOM*m5Mlitbq>T}7Rv{ti0%Ndn@%`v%}g8u;)}ZuMFQ&l zSd4{x+(uSqEd$b~=yxnFDzbUoFMLJfk{*{lAOGp#(4AcnFf+$TPvhY*o=?nq`Fi}+ zK9(b^Ma=P7&76XPxBI6xP46lV0=;WOmv4x%>aq>ZNPj_w;#9w(o89CRH`!h;|i-Whp|Njj z!)l1cNPNsdoDu_Q||yJ>RL+`xdIU-M^uw9Yl3|^HBtYZF zWc_WUf7;sY-?2(bbl8=H8f~wOYq<7^n0@$3Hrw|M_L=6*(X+wkM9~u~@rp}3pjxa) zYv;?uhw=yVqzn|p#5T3~SQ;P(TsW<$!0_bLu5va6nsbex%n0A6PpJ9#-8eL>QkAod zxZ=Uv-u&#WZ@U$b314*ixY?F-00ub;n)U+Y0mOTbWJ)A!_j%?h5~6Ggq@oe%e25D8 zDit6n4NiRDvjH-(S!xH?A+c_yLAXR#y%N%6!R`(L%pMpBndFWFEK-p0E(g>n?pYZ? zlS4OyP$&798@Bvs)PvXCiU^YTRWp7p7^J{V7Uk7NS?(O6=FGAzjX=VOVD?07nUwy= z%0M&~bQ(`3a?Bq+z2rf4jJWM_7d$Kbs2ukdYBYjF4n3|HqoX<@L zHmMd1rBq<*fg^A^X_5(EqGCSSsxh8Xws-3Ua^pgS#irK9fg|hm%xaP14V4JJOeeXZP@Eae)HdsYf#iE z(L3ZQ!sh}=G)J+zX4bSyv|W|a!bc>mpXt>^vLF6*bUOapNpB}a?51T!KAG~!#$I~W z1meGw3tkT{6fO4deKawd#T)Tm#%R5I&I%`eVY4vRtytrwc*t{Hc(;7A0$Ai(H={v{ z{-iN5*mJL&CPuP{d#f|1iKqbouOI}DLW0dMf+(fQY=b!{!xg+(82_mShTY%{mt2PB zGvXDiJ(Z4gH2GmVyRpCw7XJLqxyqkJH%x29ZlbgnZ!l!~%B<^`of8SSe<0>*%7&nXc7*A7!9GYr(F z!G>|v7&L7y%}e=vgT545U_3ZPUTvt~IP#v~L$g49Rj3E&4AX@eA|rE?n`SO9f(z$$ zJiq%l+m0nBH$LI81W?a)KvdiU|C_ppc6{)43u2eGA~2fkqV97;N>q11?dQ6gz;DB! z$gX2OQv&EX-bOxev3^iL*Sjw))M9;OO*=c+C(-vd+ zt)Kt-CiJ9vj;KJYe}42s-@B~X;C0*?lVAQ|j~vkcHY1toSeS}OUc2+on4sx+Y^X;; z(4NDnj_JhLK953G=Rd=4rjyy=_eB(Ye}>{rr@pJaFVS>#Vm~sOt_Xc!$~m$t7o;*% z-}k=!R^#sg#&otF?1^I7_}dS=e+w#}RS&J5A3r^j<8?_IcLrzj85j zf3dg<{!o`%rv4^uacNurUxSFgt4ICfa;0It_4=)=&useOi3(BMe(Rf0+s3P*mmk`( zc=B#kkPJaEr&kx%C#zt>!ZpG$uO6}+Oreh1I(5HS+PoY(+J0$+slT$(VBD zy;!`=C->xf*={zAe?#W-@6rTlwobly|9szea<`HJkl*$m3(-1AJeS&5QuUc=_VIWq zV*W=v%xCg*3?fH$c~|k=d%B|SEX{uTj~T>wF7xI+O@jHJy{d2Z_fH7bSOnQ3O|i%U zGO@2Ehl&QxX}uS7o`jE2*Q7tPLbws(!(pWOSpAW`HD_wE!8gXWhF}|kjh~EsF9kW> zTcnKRLERU;Pg&62qI0<<_8kES2}j^4-qpA%Y88y*dHn3`=tFX(5>vHK58K2H_Opi& z*e>xg(TL$;e!C3VE{OR?HdrK%ihe~kqJIR#Ad-~Ikw- z9S*b(hkV$<+ek}D#{u$ADe@T+%GCke2R=D5yW*4w0|7vm=gA;0i>4$7F1W{ z@lebScw$n}TiAf$VL_;jD5|zlvfFl?Xs90= z2n-)gy?_38sX48q4!ahk14c2^xp`St>cBAZ86fV**eF3rH08G-@FtdhKa8 zu6izgctKR?H|L>;TSC`$pOE|go;2xZ`~lSIk>zu2u2npv&a1d$QA<$3@G~8fG02q@(K?#-D`el_4Mnr z!7=)u6SA5XMA-peNP0DV6neP&Mq}D}@aqx+TWhQFeJL|NF(`wrqt9<-AY){xWh7wi z+34xBu^#oZVDvQ!J~y1Tr%^L9(iJfJizwd#6DpbmeCj5T z5!Ft<2z>{S+TMG1W-vH?`cvsB%n+{mMiMgpno{)Q7l1)$Tlv=wTS`5z;^mD-%-+EK z3u%H@qDnYHzNFmiA`W0q@cVG&@;ro?8w$B>{fG((7z21ohR1V7B&bCs!P->T!mV*4 zlzZ~>nF09lFsF6=l{ow~6n@57#QM8&4+VUwlJQs_SW3JG_%y7ZEFo!Zm^DOKDp2|9 z&3FJ*ub?3!W31;REari7m*6hA5COoxrojuvAido}UJ8ZTBf0q$e$M5FlGWk|A@!wd z+5Hik$qi~hU?h*&k!GVHo?>_~P^HoV43$}^@1t&~Eq6#TjL=@j+FUg=9@CS=V~ z*vA2E9KuNEN8e5%xpu%_YJ`enQv%l0G@6rQ4B_G*C&N;PBcQBb5EC@-Grz%MnwihF zsK%A$!to>_);Y9%%wsR5W^B(OE#cFeA17B#!i|zx@fpP9C`DY@VIT#VzYc5?MQryx zV_Aqa6cL*-&#-L?-GQsGHpJef=mJw>ySQRQFCJ41#w?p=1pr_;^OV;BlEtFg>>`Dx zsEHFl#a2q=1)5pj%!Ca%5_^}T0lYBM^!rjX0=M4Lxhe=}nJW$jgh{(3`aYRG1(;;f zhQ}xU+-;5{@}6CFGcFaJk5-EfhxjbcY~&&|cSg#Vc{ib;+1cP1El*YXnD zAKDum)(ZXpGxlhQoxZCR9E5TW0eX1J&B>{%+=wj(vGC8G6xc^E7!(GhumrS+j9T_-2tQFLYYdd^Tix{L3;TmEuJw z)PD@(4-Fn}PT&X+R6>OrV354J>Eo@?b(c)B)R!7>DRqPrj$h$h;5_Keq1+XF*|wZm zu%`OT*znc`dKYbuu(EinVDUWQh4;pcmA1F2zrX2|3`u0#GU|c(S$C8+_C72z+G6u038z!>DKLtVfh*QWPbbcdy z@W$Fa)N45)E;TMb0XEvB)wMj+^^naq+~mR>mi9ArhZYxQb;LoGw^o>)Bzjc4Jvt=r!KaY#3sTP9mF?N%~!SqHc`&~v!*fr5E~l39f* zM(bA4@k@Ls;{qIYoY#6;kuF)3{I8M`zXNoB2f2j*digzFbSkx87j~EOs*V979|cX4wlZH_Nt8!q=hkkAZL|T&NwdCtnx_GWPWM_?q=6 zA3MQNb4RVrQErClA{^fo3s@o(a3GkBKKyN=jem^Y)I;cWJi`#qPWC|7{+}!`aywv@ zfj2ZA8!Q_3Dtae0We3@o={1@fRA`G^r_*LtquRwPXB|!kfKPC3C(f|wh-Zp8<#L-k z9>DVH7jmDkK=wjSR$LIj zngp9IJ&EH#lBN-MeJL~V?JX%;fN&O|7xEnqxFmqid4^$v`)~EA>f^Vjj&S4G%3 z+Tt&!C4Lx_i4_|ElEL?eMYc{{K=?tM2$$_Zp^GVH+jN*aGDyfP)O#ncHnJ$9gsb=2 z+J;@xt0sUj}||1p=u@mrRo0OJE%ZP&%odX0Z{g4*T3oyxJ>!jEtM zK@{QJj{?gkk1@;cKUUHb@0(S<-F$sl&a-(mUORZgvb<}NkoBRSb(T3OpIc5+oYD^|{VUF|Pjt$JPCFO6Dl_qV@pcq3K&^|~qBxBl?Hw{qPY zE84Vu-QF+R4!P;HknH5V>86(KR=w%1km$9)=~tEL54#z}iVuFj8U89h+<)^^Tm0wt z%`b@fFUak9yVyAA?WB>|r0VT7so1pr?QFm3Y}oC*zv%q;+eK|+1z@-LC)?c3Eb zkyXgu+GCM5&by6s;SJTh-xb2Y?eDhRC0eq@8^Z2(IYqYX@Aj}ld$V^3^+E^dcbGRq z7;5Y>r_iwk_7p34YJ~k)FZk~R_Tr7;#aHZAw&WisncaHq?W*WayTUC53IsyPc^xqD zu3!RcVdotTb5A%qr(UVU5ldebt%UpD&JpWC9E<7~UdLm$q2MCJ!qW!w=ff$&_7kO! zCmf?0(jSiZ{+!s4enrp`FgTrZO%$kq6?WM@^KBwH)m>5ONl)U@7xfA{QBXNhKS zAm8hMybCo>vsKU4J^w7$xo=ODy*}rES@AaE;OF0S0dz+o1WVyLcfPaM6-h1Px_2Qo z(WAt!RPKB!yfu!?EwVF~r<$O3-MIZ{qQt1$b^pq4H?z`WvfSlb+;x;UcXV|B zS^|Sk@!eqKzmYuN99pEUpZAkG-I@5_ysSAbeYUqy|4!m6W9$52y+1+Vmtf1Y)2-R+ zH;>IfYFr#_PgYpGkXOI?cYbo{Z_@r0a3ui`z}sSnKZVjZz=McCealgUNlP+^P#uRX z8`GFJFo!Yheq%O+`$)1xu&Z#ahX|%Nuzcp*Wn)$oY?5S+<{#Q2j}+T&V2y<>&yB~* z5l9vIE4bRS#Xsd~Wc>0_tdT8ISMPlDiGjK|dvd3V$v8II#OLzcYjZ6&j+9qvjSo}F zzIk(`y9n29DY-RCab`lc!sjyI?>2sSaVJmW%nl$>Ps`rMk><*aFf}Z4<>9&FD(Gg| zoiB(t^(i)w;wj)R%=AgK_*N8h#a;Z(sh3-^D6NU-2W7F-*pIRy=^yI(Lw1kK8h1_g zORd(L9-%sur~V>(2z>Xx^^xcBR{i8C;jJE3ALguS*Yf47tCi+Ss-5@w>r%V;Nrt~M zT7}A`@elvQ@}~W!;qvCAFN75>XVw}Ot(W4TE81>>!xil~zX(trcPW%s%s4ilq zAyhXRDM4iq)!(O;y$|anD*G6M1}poSE%2)b*dINu!VYquhgS{pO%7BI3spd?N5p*9 zs((rug;$Tti40W#QXqxaj6GRYtr>q>A67G=nb2P|srv>}JM~;cwRYNsGpu&T9MoSs zYqbiln|l>DU-r%+y;a=Bw))o7!L?hizF=KAgn!xl1$+ICyl>TA49!;GG5l-~K~U*r{8_(O|k>qqlThF#9S zJL!F=+Ltm1d7}pme;TjlO%6HNZe@Y0Q|AIE`4Wt9G z{sxJgn2kd^(1yzc`$*)RbHNBzIC$z7IQ1h3FE^}{i04fZJ#QA|@kR4}Q2K$5*J@uY zylb}DE#$}RTtZDqH%*{xsOVG%@f(Lm`duA&o`MbmuMQaAjWv{kd7TV#{F#jdH(Wk^ zfZP|T%&c;Rt8%JFNoM^d!dVwi8Hb101SJz92o5*eV>bRo)5HAWDBPrng@SBdnX6Bi zQ=bjWwl(%eRy{ZtsG!ISMXQDrkw@3%GsVF!<7D`IqFhT^Rad;?q!g9ENLdviSaSOl z9rRc-Db*Nz5J+vs;KWeTXqMLg_DGlzVKR0MkE;M42iwf0Y;?dS#uWxC_2f~S8Rz32 zqauxXfnYr5JX#)RLXFKYfd53I_+vvxeR}YRFgsp&yar1Z4tZq}2iZ5v2s4L3@GHUI zN5__cfH_ODZ(~omh7d^T4GT#jR|sBSL!jP;RVHrghDe0tGhoe0<`HGM{AJ^4V3&Rt zE-V0UO-3LT-i1F(S%@I*8Vk}pj%nWeEl})*t8%kWohvw?!mx|0IS@$U8ID94E=+u7 z5seUj&;9JFlU`s`b?(GX$RjImjm)e>KpqlDSAgJWz)E7_Q-*NXbb?9!mm%)TydlJr zf)Mjy08TQphd@y<%(xVQlZNQQLJce<#AvO_cm@y;trdWQu(wpQXav12GZ0vFR`!;? z^LcgCGz^wQmIQtM&@v#PLmf%nOoKzFL>5FOs`$Wr2SDPUiwpFCKd{b0;!R-)smIuUL6F=%tN~`C7Sa}_sJSQoA2A{#ZD}{Sk?|r>$-ly`^Hs) z2z_L9A(P-D9Fn;t6xOP-Pl<}(xS8Z{Rz97o(LjN!(*95+V+W;2EkFbE_* zzb(g6%mw4iqyVVEC@~0@D9D0{vZVdORXzyObLbvG{QwruK4l3b!i~>bEpvJ3I=oVk6 zg`7pNhY4Q-y!+*gJo1^!oD7?@B1 zHO~Lws3WDsgkdd^T_jdB;`Oa^aqNN3i&n1H)!tE#kLhxMejf1POfuI~NPMyMPwWV` z`_Wa-!ahm%^38*In*D$aD>6&3UIbAN;gi3LIQY!g1si|Ectmefa4hmDq+O0A?-Zv@ zc*R0=GXpe{EF^j~m83h~eHPEms6o-7{0+2nq)Y~!Rp4^a+iWqJ^mL0uwUK5;24*|i z0v8l;r1#5u$E1eXb|P87pIUWu`f$8+W*|L;w+<8%4V(eZ`^*MpJVE)&4I3Ms_a@9e z#qVJOVfdO921qt#>f-_K+vi6&A0M@9;6{9s2L_QoL*n{gQ}c}Zz?fU!e74Vx%Hs-J zm$*A8YsMmubN|WUAnt4pGyYC=7|>jAzVtdgZ!G;~hx2*(%4D!f0C2`nh2B2C!9Y95|Csa#c zxq1MQxlW#j<-4<0?DNhmb}|-3Ab~QHawm|$@}-M6J%!^+F&Pqe10>}LyYJGub2RyXh^6f=FcR)dk!Qp2g(zGV+=ISM6jOD6YyIqG~^J2vq^jaq#Rh< z+j*utsjr}`00^!XaaRsG9ETT;03{;WWNE2PnI8x*lDn)?+2tbOffNL}l;UulxLn9w zfYP^I3i1w8Ae_c?ji!yc?Po4cmPIJWT&KulfeZn?=zP@E#nRKkd#k{k0;JgtguO=! z{_f%sUHO=V!?~l#MwG}}4rCK&r1e3<`xM#sI<()?g9(DchQJas09}eAuR)+>FAlzX zHtj&y+HJzBOrW9& zS%(Y@fL}a6J=#WwKw16@hwCPhtTJDEcWwFfYXaBi+wcy7r41L^4q9e5|0Aong0`+j z9RdalxZy}9=Rk=S6HKj^%*uzEg0$Y=E7!y zRx+{nKijz*HXYAs9giS;;MP4Tkpi%09rznW0~X7C`I7GIZjdRfKTQq@>b zL1#kA>$m2TE>)&3n*zB)T)a~FIJGX4&-%Bjq~7zF_q-;~0?XgN*N+Df5$(zc-Z<0Zl#Od&oRK*mx!z#(={j^cY zv~kk3$+u}!{CL0jbK3J%FaJY~&1WH4R^x8``!yVfGV;x4&mRH0Rx=xG1c#b{;r?Tz}7cn#_8A zoF(Vx_M3O`dB4NiNR30}_)dW<^lmyrFZq>KvYokOU?i2v@+=JDhzyL?j8RT2GYP25sx{1>tLU^x>M`)=Z3@n5S{JFp4tcPb*qNi(-3KFb7l#GU?eBApCS^fEQ8 zoF=!4CTegloh2nOwYa44&vk+A0`#>~X?h{vLJn10F*hP_Fg;%(tw1;Jo4IMWBkgyo z-JOoBG54b_X}m6#?$7OykC(4&$#t7QPBb@~wDByo*uCC*|EPrdF9Md9O3sS{|4oZj z29abSD9xsr=ejzV6%b1Z8pYx1j0hnz@>leAZMywdE8LOvgSSgR9NC9;mxgJk`P540%v|)bIr7Tdwk^(j|qp! z;^@*Po`VU;?BMi?#ouJ~U+~sO-rB$SOgI?_M+~kTp&6yLG^?X7dkZcsWPdkcZw#w= zuNzkhI))FXq+x)Y$Y6Yw+2!{;~K(8lC67=%O9>-@(#fX_&NXa zSQ4tN5i4#JCBNzajVAe*^W>=jWgGhX!5Y~y@`Rr`x!CHCWR23$ipD7yXOh`#jk&rw z_a?GpwV0WX#rRgP;_lTU@ZeyJIO|yWkRXK{MEK^`*;y^e3Ql6p%xKNRZ_O&4&z45R zZpy;ZB)HB}&Pig;HG{!(!hnIS0wf!k!sEO%*!6t*ae4JT**wz+#FeBb#LL98* zy!jl*HVSM)IBKk7rmW&s7;(=HDu|8b)rJDC?cXQy4?(sP971v=334PoV&y1t#<#@3 z_IN*96Hp)#|@yM3}Q$SU^NXqFUs!h^T6{sJ4ihfQY)Gh`4>V_!|+4 z57iR>B9dX%lCdIEU#q3E-|<_BNLPr+)Hhelw2R30SIhnqk(;fS`!4cmyITHGU=mg2tWgvYRg$PtdMv7}TBEEj`oyT_iG`?&eT~W+QPmGMs{W#C zVKr*8qEElpJk1tW|6ZeBA*xYdqtPzfoiRRw%Bu{+UFKxhW53FZ^Vo~)EfDV8Hd#x$BLPJ ztu@K6Rl~*okLSh70W<-4*#S%dcHlRhu>Ya=%d4yZKkA&NX zQ_RV~|B3g*d-r~Ha_`;`FvtHx@BbC=NBCB{#U%O+>7_wg_Zv|dY_nCoS2&XH97z5egya4z5kl}A9^47|3dG* z|3UAg{||ca9{3-6|G&k1Q^#O)d(Z#GdtGbqJ$kQh>aJ~Qt8VDH=kBPwHq?KtdqYW8 z^MAN|c})?j>7Kh6mN(ps_oC9O{}a8JR2CGY{)gW4zLjPdR^Frc?EI2@@t&Svo|0R7 z@7_~#O8Xj?TiCt(A9{ab^Y$LS8(Y8q zuXuMdGP8ef>hM41u5a~D*Wy3)UB}$*UcYObz4(zO6=rNhdD3%vH8`a6urt_O~xG=RYN$ z5h#2Q!jN7)C%if@CrwysNz#04>G9D0(ZHLV|G8(srQ~ZL@*=A_0$G9d8#i5PkFC+r z&oM$vnI%I%f_jV0g5QSG@1u{VXk-|Cg7-@y2?cM=3SU$N4vGz*y5C^|x^-mo!-XZ* zp501tBsBSvU5Ru!CpD6GyTg4pqPt{lx?Qz2; z;V8gpP~H!DVXLJbaPc`D7$Hq)=txPVDvAS?D;KXJj^Zysqm3}!b8;H(8qQR$+iG)V z001hI$xritu>OF>5@N;gsL96-&c@)|VYb#qx(Tg;m&He@)}eA9DDBgsrh|Bw1b5aA z@Vl*_x2p1X@EpJtM;925NA3HTa0%5ZuTHu@k7CbKy|L|8z>&v z5R{mPEuO~ZnfwaF+M7>8tI_DI@HSO*Dj1xxu9_<=A@C2lf_k=XzqPVw9bPDfH zQGG5zgB11N=vvCFw6FCN=%<&Cj-RP4wDWkRWfS{IIC^uYGUffVuIr_NtIcVPnSXPK z9=r{gtDtMHY!T=hM=^J%aX2i5#5vswCCPqhHcHn9L%$zN<2~G@mwNcHC`tV6Yluc- z@a@~i$7*mGAzz=^;*=vb1cM`Gt{E_vSFLxy5nu`5YSU;Vp1v0}ZQB8oThbqO|2X}w z=*>fBSJ<0`T~DRiDr1555`SBpjGx0VRa87b+5i6aIOaIN8_#8px@5A#mgt3K=xm*e zIV3LUU=}IW1!~|=2ju0eKffr)!RXRFWFSLOmRbr_6N?adrEXMj-BbtwwxBNM zQNlR`*|Q!7eH{buwhS+SGk$foV@S8v@FQgOo0zr5K~RXSILtlF?YH+HU&z#57+z8@ z@3iNS%;{^EgIKkLjaxfXBX0Hg!_IhCe|&PVm+>&^*(g4gXw{{WXPxuIrMwr>H*j`mkFAhB6xMEU z9po%Fx_y)(YfT7m&p|cB;(GiDO+33*ucICI8r>C%Mw0c$%4ZcC^yHJ({ zMM2xwkk7mq$l}Mq({ymfRKOW7DE8g&hyRd&te6r|8JJ=VRkp=+{>@PHmiy_8B;Z3Wr@Ymi?As5H)WK zG?m;h+Z5^Q4NCibF82Q4M$Hhw%)s?kc(?|hJHcDf7N(8*jxDwNjV8%T|G;|z3vd87 z_Oa%B+AvN0@OuyqJ|nEn)2_)C!+T$ii9L6{x&eQ;|5dj5OyHr|+al*yGEScgKhQDv zj80tO5);v?zE<*+ZnJYmqz=MWAue*r?_HF=`Tl&a2*}oIo7$z$+9aBf2Arn zuWUV*@3KD)0DkV&y511nAvFjc0Ramd-kEOhH4OSJhestu>Qzt%TooT-5&h6^;DQ{$ zvW0sjsXJyS=Dy1`uJxcSS?L_^$g!xKMuUZ;b-tx<`lO1i_Y6$Bj#MxYTn0a6m7jFv zFC3csj`l|_hef{gj)ejELqB)4LIcCiV&z3ome_-R;;4q#FS_|yz6z5oP~iFUh8aKK zR}j)X2YTt6c!D*qz6BYJ2rt(<_M1$S-5@kQ(iQd37PC<6q#V;pc72)lm&?CtxP9`F z`y-hCz0A+^vi>Rw#VL-jANgOOE&zsw+Z5lpvc55%lRw1ed|sUk;OUFb34)!pJw6<> zLRxvp132H+zUJQWzRC2lr6>R=_*5*7n2}k%z6eqgqqRi30ch5nk%ik6fb*apxJ>E+0StqFe2+u!zJy* zAYFZ2{l{g!d;5*Y`40gnQm2u=_CY)tY}|cqwUcll`}ns5GRL{&oTQU<@X-0|!^zAp z#zx79z6_aP@=iC~(>cPXtn;CuY;nV4^Zzf5d{gZNHq3q{2tkw088 z7+49rSwle34iEQGutI4@VI)vB5MT09LBfc0$j}w_-Rebua!B`1JJ@P5>G$`=q|7a{ z8^|U<>_5ju2$dfr7q}K7tV~5`btYt{oyt>xqaT7qahFK~41sEWge8SbDf?oB6^=NJu`-8(XA9xBy=B zfTQNB)t&*zs{#Z^0#4@3$Ndfv!3Bz%2MX~8iYEt3R|U%Co8B4>#PS85#|6om2g!Q} z;gW+CtAdnAf>gJI@O;5naKRep!B@S5wUdLdRR!yg1nX}H6Zk?5aUsU$A*S9T=E)(J zRUy_RAvd=}Z23a%aG`h2L+^Ws+9!uPRE0W@ggS4B68XYhabfP}VV>S$-pOIURbhT3 zVNbWiNPOf#9BcGW4)rFJlgSZPkxu6IO!aztTOMDa+(o9&1)zQ}jD$a3?@58jbg$&ocxk#!@H_1lp&zNpW* zs3!BMX78xhewciN)~8i73R0TEvO_#7U;aNms|o zjK<0C#9{g4&nv{sS;RMMBlZs%s2WQAjvN}yjAtzt@9-eHDVWO-kA7uv5nK`kzr};G zNA*(@uJ0t&5EC>M&?8hh$|bP|od9-81T)x*Ng(4L9&{P!I&0^LPtYHIac?=^DJ9`X z+Y8<07fz#SuQs%2TY^b-l3#d|@lF!4Aj#Px98N%e!NMXGV0Q>;{pCcvhQvMLEC*Od4@H zDWxFgsYTL9PM-SZWPPc0Pn~ca19hL0RL+@jg+C=rA*E>f$cu+Z8>Nn>DMRR2+EKNiKHaOB_-{yQ&0 ziqyXfz>!oc_ZYPXk4Qmtj1f>@DI88x>;qU-HwpYD-#wbUBAtxUrO*{E5}X1RVgrK|@y4!PK323m=Xd z0;&fO)o*)YGn($0a$_IO#o7+aGZHum$UJ7IUTXY4fU3bK$*g2!Q#pt!uP-^I1s{xM zpFRGnydX<(C2N2gFM;QpC!odw7#H#7ukaMqx%8KX>6yo0MlHd1ND#@?>GwTrz0(Lir$PCeM)6FdC2~U#BD?8U}8`h zMd01Fq7M;8A>qY!D@8O#^gtPM7SCZ$;@&TVUQT&gOojS05i1O??1h1dO8=31lm#y9`?#6d>mGoUJ5kHRjN?|u;ts-q=EGE-pi9Rl2v z!ah`nY8!od#p0hKAvZIgg9b111coRwCZnJ&8rSC)I1drNiNS#}mF`M_XJjQ85!pio zOv0i2K(<522k^HKGjwVrXO*f#6{7}qo*91(gYqu~mI!btGd`jyL2Ef-go45_;~k2) zxe1AsaVk!s42%ZNk`VS0H71;(B?6(4Q+r}LPUm9Wp4Z87=ZIzTS zyo%Cc#`6-8L&RLLZK|dMFtrmuL{Kt0(BWJMK${BaX-=?~5->ojPVJ}$W9p<8YnE&4 zQns$5mp7Pd88*X;UgAavs`haH?7Vh^Sw@ZCj+YV_%`s@HJlLi_`IAfU6(yh zK#x+fGLEH%k;sgcv=Q5TVBtoA&W4l4jYJFBK8-E=VN*>R1ei#wL9?y};L8(9P)rk; zC*fETw+sUnfKDeyAQ)DOUa4$SweNyzK29nDq_U>WG)`U17mG@t#_2CqlmRPcw9Q0H za80BBXv<~m7g%LL=zxHlA+qyoyv*56L-M4PINL0^!#BcH1LM+GX{Gdur&xw8CvC?frS z@w@ENJXL;<8tg~+f{!NrEvL&+0mM>qzZOVr%VYSP4*HAcsLokk^!(pujcc9y4?ExK zb_DozfCJc>4Ac$DxQx_Qa#fzccv3QnFLm0 z$6inCq<4CRBU%^JW5X_UY>M2-GXalkniO7tx-Wo=aHUFSSVWOiA+AO0odM^{MGN5UbsI{IT zWXqmahTO*>N;-OfOTC2FrL)%!P;kvL^cSXS)lwzgii}jU3UGmnYGk(7lp*D95-!@J zM6Aav_0TmL-S+sjQ6T&69&f(W7usGcm^#iVOyn*}#4}Lhdgw*c;60t8%PA8@2L<)L z#1^jzHa+Ri8XCmJFCmD5!VxCL*Aqrn&?>rUC=+>W_tQPy#6G6MgSwBGmtp%@Hh;aI z34xKz1;eKa$fB;Eg;ms2B>i+@qG}sG&}|YNI~i9}-={LF&p_D|YFeC<;Pi$pc(Rvc zA%EA)+>%V0eKMGim?wDGVA$5tQ?F~^abb|_ST?O{E(Q%0QG2rnKirc34(uY@aC{D; zHV38yD_D4()g&f$R%t3p^`>`Y!5mm^u2*l=(gK*VMP%$Ie2M%#Kbd4K%ed?_f4T{lkV{iNNYzh=R7ZW@0N`^q{N42a5^jCuyouEOq7CwmFXM^LOJ;Zm zSueS1bVe2l{lkRY(Ag)MNJVsMtm?`RfOH~3a~P-wW428U(jE^DVjx$EADxr|J}hz- zVC&piqsy)jsCK+5gL=`Czp#jKtF@NQwGcw``2Je2>bi~UWOf>=fe!UyAa|&fxwV76 zQ5&sq)-(!H^Joq`Cj19=d4BRmcMbe5k-gs*TUcG9_}Xus)qWg6@#u zb`PYzBqH1#ku;%~82I>?6LwM#f%<5h3+dR@*MYWSQ0pXazsV#+U?=Xz z4oML9ND*Pq;Np8y+KNFfGC7H;GGY2kDB^^)-~8INMg;tFdWR9QxnAvv z%2Nox;hc7ym|mtvis8M_x0$=z;fI|k!V)hY<|QPHgV=lw2)S&^^WU8SH|VAlQz5VD}yDsS~6|^cy3I7e8wSk^RM^! z`L35*FK_Po{#u{>6ee_Q-*5Nl`q!41w+^1}@9iCMVDTUh(K0+3rGZt09=ltn#vv3H zbLg3PdReBMX|GxiNM4lO7*X%?MI9v+aaF{oj9)VA5a*aUU!dv07pN(gPHYO17i3UD zn30?eAGQD3E!W($whfi>(qRana| zUR3#r>w6Iy0X-?h1d)scL+yjQ^ZnP-$`2V`kI&XPcCk{l@ukL4RR1ZQLP>si=*cto zJ*pNVCUm?~XQL3mpyj}gQ#QI0hI%)S4~iPhD}U%H^_)C!M$z!dA1xA9u4v@>YXQ~5 z`1){U+NImO0S@wS)zf{FW7)JE<#(DgobY@=gx#D^UXn=r2|V0k!G%4{%xa67W=+&( zGsi|O*Uz#X$PAibZyf=?iU8hM%fT&DOH{!B9KOG{lK)>nt^9(sv zcZQyu`S`q|K@#V5+u<3+9@N~{jB8hZ!o?*a<$l@^hT*-?C(NVPE(aO5ZhfaNxhs72 z@5;!vR{DEevE?Cg`k1B7M@RQ-34=GEn8}QH^A1#Yc>Xch>(6z)UZs^TXK6pW@x4dn zPwseg_0Ij;%{q`-;lPnP>ufqABY%Au@iv66W6!d(V&`< z2mBZ4)aJ}c4t%4KY5CHufX5X<+dF~qDFWA%Y=u{m52sSH7Rl=m7m;U^d&9PlJVD)W zjB4{TbUfpB^YhrB!2 zvvfOkk&zbYV(u%P$vW|@vB31q!sjMvg!=tQ0^IYa>YoPBgkn1dxCKPiF+(L`ayf>F z%(SJSDLbnlCtbP3$jW7NJawGgz8{n<9K)8}A(RtI!FbVyA%E2JOBh4?krcp-|^abW4(oanA!)%sFuzkX0C>JS4$#gT0y&uZ`x4|Iz z?wZCB!>rYis=T7&sQz3Kzz8Y=xch{G9nfb+44J1)}BA@7C9 z>K~eYTq#N4n9IHO#JfjCny0jDCU08VL;iWWgVVvBjKt;jWf}iNG2wRt1BKV#Mj+)1 zVtD&TgJ0OnnmQIB_Nm9^U({sD`SoVpq=y`BrE(WeYx9eJ6uy?h`h=ekvy-r#51k=s zr^@#|z}YmYvVFtb-~+DJt~-@mo`wc_3;X3*v*$zJW#z6s2HuxAr8yTx_HY@OkIeUZ zW(nkd4>c{{zI&)4w^nS`d4so?4=*?%44y&)g5mk6Fa4|;u9Q0Z?uws`Eh?(&3j%jN zQbk7TRGqre!r;4SV#VAZ9=5b=@(td5iy_YgO9Ihc{BR8U=-*iOR@aTY^RDW`*S(@# zO(->jsU=66sVYHOaAgu)OE$H`>gxhbpy9*;aN@68@XVo(SU+jA^Z8kk*V5HN)4^=9 zukPIm%4z4yF}~{C*m><)mEp6Rk#9*#bF(9F%$?71Ur1jayj5L*LwE;C{FGc8bM^57 zoLaRwuDG0cHU13Ux4l9F=czy9=X>jKg3Uyo9{KFVRlfvxd}Zc6?4Y8Psq9yrt+F=k z>{=xS#<-}-XB7%A<6X(GuF8GjJeO5R+BnDO z#B$DOXp7)ngD+pD-%UdTc{F^upRpo=+2gJt@Cvbc;&VyfBA+GjYhsUbal_DN8G+*lV3m~zL{DKZT zN{Yr2U&`N~RJn20>U!9TQtw-uAI->N>2tSqE~=>#kWcPvgJ$eoV7PKFB9UhOreB+dwBo=AYUF&~mV6XQq!LhT?eZuVZf33?u zoH8oi9R!{IA&A(Y`)emoACz>LjnLzt8`50=5HJ^YK|buy{{6N;oXg>>T`##dhzTpH zBRz+486}^#mQQq#^jj&vEG{ipxr{jY`r0;9$A+mT_a-XDkMs&*jtRNU)u7;?a0+SX z-K;z?^+PZv%i}JtQEu&aNY3IZKAYAcrSA>3d@nz9c@a1ik@CRlIWWJSb`b7^T0rrq zQ-bzD#L6th(xaBt9qnJtX+@&a2Bav$Q#@dKIIr?ekKrR_ts2sv8}#6pJu6I+QmVeKf6{ zCMDA%Qo(jgW$J>8$XJ$!%Y?~d zuuw1WFnjwTS01vmBnNA!6&B8SaQp{aTASU+p2OPqykIQb18SHH5PXM`!3Kjwq_E2n zLw-KN+@*xMZ3iF9mv@9g@seTf0jYx3hvNfz(0KMIwxLL!L|%8C1V%$$2847`$0evM z1YO9Le(xYaQmkuKp349+uV8uA6~GOFjHWBfP2xUZu!4;U9dWp8EQ0e3F1=AOHrGTF z<}?!aFh~M)N04^A>YBQWkX*Hp^cAD`S4C%V_EA{0l8ET2oOE&Xv1<}sMj~PfFCO^B z6%!)w?!>Kp4?B)Wc;?8!e4-=`TFNOcG-)y!jTl{(6V7S*0uu?U(kgFl8J*>P2}C@# z&VDA?O45Ztl4^aA(>|yYhP`2Oh5BiSZQ;mUtmySI%&L}4WgZ&g*io?3(NB-sqZFL=dg>*A`yQf9FXXLtPHM-}FyXWtAFSvIv26rzdbT8+0uatMMHg&HJ zbg$2KZ)|mMLVB3IJ>NuozRUIe(CFDR?)iDQ=a+lW@8F*8gr1$8p55}EKTSP<2YUAA zdiJ+^4j^>k2puFwXS+xTYtkVmbm%=gy9b>kgbqujbH1i?RnWP=(BXrue~9VGpL7(o z7k#9cN38eI#ooi3y}Ty9@%W}(dxPiR1~&O5%X|{wYnXsdui!nnDlDkDlkNHllz@e) z(xFyZG8zX7Yz-713^6x{gqVY`V-3YU41>0NG57i`=}>PuvL)Ium=PRE4pEUL3lJbS zBOwPt-r!&Y$Y?t#6w@ay2D_lyA8g-$X%MQYXB31BwvZ%CV8}TCzF@jh7~W7!g{&qv zaAC4PkO8q`9SMOYYef$PQ;l^7p_*MrS5?TT_YC#K$VUMawG3nJFUEulW22Hb0Sx)- zU`R?~kY%!zE~xX*co6D3Xc|NaM&kl>=ukYu%tmtP z(O`(<#o-XDiPe|EFoKzzN`Q)qX&}rzkk33w(%c4T9<)6iWIL#+LJsK~Jg^xI2~Hdd zDI3WYnfY_JwVM5E{1BSE&Kf#lKPdu9R&sGv41+XnV3gG{|gKFbUW-8Rp84a=Wwb38-NooBnHLYBglMLk9W zagZzYu^`4+FwrC;pRIT>L4Z#l~i2GLU2% z%$T^NN1i@&Q&JC>r~+GZnhGP4Y%h`-9ygzprcx(H^l1apVlY`J@@X9Gn+Mq$$~kZb zHn>NAY6AOF0{d+PdzeumLAn(|6A1yn##4fVm%fH<-%6{x6%4bDkYuDq1)GVHEeWRH zX9DPPxqi=^`Z}aMt zEkkTMZ%!Y*3A>InDjG53Xg0WgWFmr=t&M>M!DfW7l10Q}8r#zmgc)vmvUs=gac4uW zt9C-pWa(}lHFAh0-B^uj)F}o-+FNSq!EO*u4$jHK5^TtqM`j?IFrNEge?kYwL;9pl zU~=Mj6o#ORvSv!+WY*1C(GO4p)mC{3dQl&SpSok?9iUC?Yud95A%}zz?henh-N2B~ z4TW6qzB~8jt_^+ei2fx0KG}TRFev%1`c(^k_IcI|HQc>G+j+u}L5aP^LOIt@o0;jV zk-2m>-7=^-8uAc|92V=dyt;7Lbm89ph5Mch4?-90Un~f&Pru4v2qpLOmyjMV+;3tS z2toVI#SJg34yflE8lN$fQh^!j%@Vu&&Zxl7Rv4ckmO;P zy=4!T;WVd_K>J~@XtUeV4?|T})-RG1Ufc>bUx^Yk572~_sFGj5c=+tsP+-|g;?dz? z#&R%rIc3G+%~Kub>j{D82f?JNmuE)fqi+VcjsI(#22%g?}C2cb1xMy1g45*t{*2l>6t*9s@= zIKb%*bfVVDDTHR#f0aC;9#3_`|cv74^tO^_4pS?K07P7sb z2$>C}TTlM79*`%8A=W3(Om6AfP%@^1=^Gf8W7XW%MTAB0_C{#_O^eLOHj+Js3{k zF|5jgog@Bz`|h3_Jtd^nmeRYJ=Q=#IA2; z<82#6n3mFqaW~sa-JeEmFi@(KM@z}a#GNsfx8+rC6A=OYL$=SmZjXm&QO z-vjgQB=6fbXii9XGYjbW`zKQ5yt&X^jTZ*1Mjop&}3xBAs(EpNR{aGU%)Z+miKkv05* z!Ty8iM*f@U4uk%`@Z6vOJI`g+sQ!1G`%jbVzk*c%u2KDyrDD~n)|vn2xl7Bd3(M>O zC3I&NR%YiH8FNdFnT4t8g(=3|6st+am>-**9-a7~O{%`ZG5Wwby>Fij|^b@!Z;mwvV(gHMEx6`bHMdt*mdZs{M~9 zRYf(8C3MRxKmH?hD`{`bYs$)NO5ar#y{juKt1K-2$LX?oZb9+;ypl>5)n#R=SWY*$ z@NITs#q0di{}rcVQC$|&eO;EC{f0$#)2VrBFLPO1m*sR{yevx1%uUUFot*x!L{$ov zMRjA6a#%<=Jf6x*RFR_+SVH%I^{L1dR-B6Ebp1jTeFLAf;#4f8>+K)LLb@!W%W}G` zER~yAfU{3H%jQ0E4}9Pj{D|oLZC8)&CLZva(eFR=I}etRNNZVRzl)5zFT4m_7Qx^W6VvQn75VmY&6bs(a2eo;yLpE8xge+b=wm8q`$yG+H(Qb}R|g>+>U zMNccR%2XH$d6vy(WvK*3RJ0IZN*xV*kla?Xt1Bl?JQ##3i`BnXMHjsB{Gmr8TsK$w(Gj>+nQ2MjbsB zn%-uxa9IXm*5&a15a~*lc!u2K9Jt>bul+xAQzqH;``!?e#-w2~YItvX3+>pN`#|f3 zZfcy^^`uDJ@gs^`)A)47+3%v)%tUY1p!?a6`c?dAZi;XDlNAgg;@tJ7YVO;!0hDR| zf(gy@fUb0H649_`X+vpu(-#5G5{)1&b;+aQ*t_(_hhI(5Xp4ulamM*9v8`DRibeE z7~&P71JWSJb)y{fG5mMkLmC{AI%bRFgK9T2ie;QZJh7c%Lx*6OggC*~+vUR|6#%+P zBnxd209Md_H-33x>l+)J)L-GA0N0EBO_vSyE#?!K{=S`uIVRuX1PFc!L+Nl+`^60J znYM8CHjKF$S)qvF!{9sN*`=|mT zS-&q}$^DEPMDO+Kd4`6wZ|{M4Zoy+U_bynzVN<|K;&~6#I3M=YYh@b`C21;8BY0;- zU3urEx%oJU z3jmJNTi+v&@J_PLastI=5y}hbK;hz_Q)!#^dy1krswJo*D8=xR@l`_-Ptmt#!LvhO zL{8yxN$Pnx4Q<;WqGmRI9PKo(n7`U8H;FW0R_bUE1=6E6Ti^y5B z^2p16wNpGF1a|V$@IMp|vKWX93GcK;W$U6(>N`W6Zfa?~zbp6fouPZDu59!<`Uf{i zSPu*)5xsPdXId^)T@|2e>467{Zw)_GtnaRtnhgg`M8+#;D0`hfzkb=bMX`h$Cb=}O zGETihKE#@ORRw;1%P!!!j6WIQx6zr?+y@>V*RcTy^!m(2y0iEG6qr`zvY|yS`5yuO(K|JT_ov$SWL+AH$eb{!pHMOCn zr|Dl1z{qEjAvK=c#2fx!(@ti~z{ zH#pC>*26-3F`EYvyF+Y8DF#y1Rw$Xqt|bVTK#2BRc7hvLlnEl4>I2XVRSn3eShTRa zcD%gd71@X&kuLM;mF>@UV_nQ>qH(v1I7L{YjU5OtI{^~k&Ynt{heX0o-jZt#Ffpf` zDvb~!acRh%zJ`vB?YGm@T|$Ea+b1IYF!r}EG?_AiaZGxrlsUIopoZ#rWavupz- zt6?GlCZg^ezr-mEUWabR1MZmssYyvelMmn1m#o`yf@S=L&}Iio{I0|JQXh)cl6pm zIOPa2Pu(;?QnFPKJ9qAZqFK8kg?#0LAgoD_F&$%91p$<4C)%WU)om6qL4((MrdV`Q z%LO>RWEaw-4?KT1VatPDYEoo;i(0J^wg2IeqyFhsS4uJwvz^}{qKM}5&Tr&-(%F{p zeIEU~cdqDw#6g3Iqdau&K=)Up1+<3x>UV$2=QC)gA29V9yF_u<9j%imKZ74SX$Y#m zA2824N3k=H5H@jWV&%J(hIGMjTaDzw#phBzl6Ya0%Dm%)^y+V44iDK|-8Lxw8e%0ENMURRhwYT<4}*swINW=vFM@KTxZPL$FBEdbD^y%mh*IOWP9 z9senOLO8tns3Mw##0&H8C9?x;7wbHC?zUcK9aTUP`Hm7c!RyC5sX+<@zycXCZ}d(Z zA?95|p6E@I64^hMo8 z#qphP@9y`RzdILI|LpXTkmXbky`?JxyL7UCdG@icrK?K2y_A^p90|SUYgW5`$=&6- zm%5hqpX~Nikrnw`dMk!$y94?96@_M9E3Co)U|CE>@dLe8%kkZzs_u$6UR|p<|LhLa zkRQsz^w#VI{*1Khe|VSFwRT_W&nP|SLwTOwx`Wl9v61c%A1b@nouB*}XCN!9TJ$zt z)Ba2>=~vbacWrpq{+VRPRMsu(ZTgP?ncD8ItpDA$`Sj19uK=ow#-Yy)6!^=4-KhF} zteY9C^miKlysAk;|67FB-xy+Mc$B^3l_A0UGmsw7 z@oGYm{^UcUSx6rluq*+jXc&{Z6-mxGF2Pef=2Z{Nct3dxwFFU`;~{6(?rrETU&W4f zduABjS-R2tM~l|LRY>`W)%>kJ^U8Jmx$fSw!L!!cqNlM%4E0%Rr%nrY zAQ5y`$^@~kV{8owU40ekEm~>k1wGG$pK)EfNLWGsBZzn-aexC zfsv#@>108UWYU{3PF7(f5@3T;|4AwQU?83m040g110+sYRzwLvelPa~li0F|tSkm1 zfC?z)JyqmDR#Dxqv~bK~{~?UdmELOq(w0pJ42V}4ghofMlDJi|KtA4WpX4^1hZOsP zZl)rf7Q@GnMR<9gGAH;cR)Q8ux?{tf`!oQ*=*c?Qbqb3b!b2qqT=xws!gD~&>Fu!fq4~Ixyy6dL!BM7STV2z7fK|B5{V^;c#~0Cp__ zv72Ju$n>x$i=HG&@=qGl56#|5MHykpq(#V65fiNdhxpxF{rfRKo^rQS2X}B@EO92Ks*Z3%fFyqXJ-{kZ7`uGHZ)g z#k$#(AdxJ&TAiR*;FhJ3su7;Jh676Z)86~AD{`>^DTCXWu@4YX8?4&rT35oIBDs)rX(wqfVmfP9|C{R$)2OR_etm^A`_NUOZ}pBC}Fm zb&@ky3%jKq*U9K`Zb0$6<&arW}|E43eT07ePPDxUMe4 zpH`yIQ{9fBQF+*>VAc|w%7Gu|ZUizc`N@+@C~d0SVIp!6|00qAOtPdW)r}=RXQ`p1 z3#43u;kSR~)=Rv}v?12k>R38p3cko+AbIA{g_<64M6 zSg>Le$3B6pVF~dOn=RJvDOs2uiSbuD_8ML0W+HvVUD`DAfC6F?&TALuI1T6AV9DP? zH$`7xMYA9*1MRq!jg@}&)FB&;&R34`k1TYHQGA6WAxANhU;=0!jpP6_!Bk)g4_{7( z?i0LAb_LbA4p+>7x5#5R z_WIs#3ECj{L27O!EmsJXN2X<6Od(w$BTg%%C>}#9DFOwHsrY8EQ6fAhJbRAHe&des zZj~G7Xtd^v=jCd)Nu2vd1}d2bL3cosS@((=Z2K6P>n_^d*H@c?dJOOcFi=?fTcHjw zVQw&(z%@clUUKkNd;r~I!cmyKK@Ozz!6hj5JGzOG!@r8&s&QLYd~!7Mt;%)86u>i7 z2EV>o#&h~D?`kQqn(KE7NM9+Ck2#s7ZabUbr$5n-294yHmbNqYt|P)fE% z8jQCFWMs&L5554;!`jzhq+d=b`lCS=phpy@LacWrqIVa~X%!3aDCbdnd*sR6-rP*R{?F@L*5B-?$yW$))IbEc6x{mVt?AF3Wb(LR`BZ+0ICgEc=(l zB*A@&$sf4fp^=CSE1rWqIhvKwRKB`>D&%RrP}VA1;E$&&1Lcl=vrlB}7_ajS3y{Ea z{2;P^DXRU#*takUf2E*-17ZajEy2EmZXh8XIoMuV)$9{%g2wVA87xOz*HTo2=0Wsg z0ov(0xjYCMsC{?6_Cu|kvsp!pPsJ~8_h(7hPg~qPrv*QD zbARmo(>V0UcrhXi)$nnu<0O8u+Aa*yjLCClfl?acHJxLT*~Bps<5fv}vkJ1ObFMH@ zePub|TG$NcPBRvC${Oda7k%|JmfX6*u?Clf7-9LZ=Z z94GT`>=^9N0i9cb7X4tj#Cc9sQblqt)8dT%X+O~XO*2##`6KowE~%uSazD9S0}-{&iXU* zTZb9xGs>m8b+zi)?e^Qs&E0Egx^*mTY2>)poZZQ8s?~C4q{J6p=iAwW-fam;?+)>6 z%~(N4bhcirYuXrVGFSeWK?T?x^N{A9p`^Sog=@KR?A!g?3c@JTJ_<2Y3yWc(L>bL? z$2pBUJmsZZ!59RDb*0dC`(BR3Eux!k5l$R^u8Mt@W97u-oVs|ep!cv0ph(urpK_pF zANLoXnBMc}FM7YO?Y&p;`~2QFoX=WPJ{_KX|HzEqxSGrkr)SmD6uWwqhWjP`o>GcC zz%uG8Uyuh$#aq5ZK+a;>K(jMQEHG+?=7wr${M&vDxp5fakhz7{f$PQ z=+*QZIOfue79J3E>K5SbizM}hIK_#61WT{`pIJx8GEnE%PoD7g)i3rh@Gh8LMN!rU zifcV}iO4LgZVfEZNJNfHeo~DT2%!K-+*LE$e!hhT^Z(IO9X2I~x=^7|r8&#^LrF3~Q{0k5H4Qi?YCyz~6B)z4V_ddr`=rVlf1dAq=$wzRqcPI~<6}tB_`CJy^%K2F z(AT*W(~Bw-U-o#q_ksiEW{*m0qhqTF^?ElAMumKP#OcVnGInDEY@Lea;HF!_8H!$@ z84}VD3-zTUd#IsAOD;1S$1(|7w1=E2W50tzd?$u;uc9p|sA(qa$QG`BB1|z1y2Bb! z(Aa0`2zTS|XVj$GMYeSU;ueX08jTd9d-`oGl4O@+4iqLmaHwxY*sDb!Bg@`uN#wGI z^PKj^*D5&Uh%#VL$#_6z@3Tdj(q26{3BN_>SZA#`_|`hGYhRq9a_M;%_4D52%RId+ z2CDNWNa>+B^VX_?tPqyn$v9`f75g^}jUn>1uG0TNC@iPPIh*V|JS0a-fd0-hCGtL+@6H4gYVHBHk4_wjg2P-JmW#0emIASah!fI0q6}kP> zvl1$M_w;M|{=i%1pGefVL(gjSS5|jw-Hb`_Z!}~DVJuG(+KpuqV^}jfII`@w|G{qq zzJFR)-jP35p7A_nwW$u0V{UvGzZ@u8`h6*LBNG3M6}3V){KS?1h}-;eG8U|Mc`J4i zUHo{fbaT@pZ|m>)=E)a9r_6`Fvj04m3-X!yb0P>)hq9-k{vP8&R-k1*pH=Vbc@qQo zcY5_VEQ3zgZ+a8;H@?T|KEA8YA~O{j7G$fGdG)(4Q8*#ocP~)?)SwC>??a9oFA=geqyLlVW~hc7H(4<*G%30HCDmlr^o`BO zr%(aYHS>4>zwq2>SK5K2WaN-qvWo3=q{%VJRksf=zcd`%L(eKYGjYU#$w1Cd6|~^# z=Ms%B9xD3O3Z)*T znw*DzoUG>D7+|=j-5e5es`V-utc$HaEG6yIyzg0u7C!O1gOGFTLU!yco-@1O+QI@w zms-M7(;ehh{tAomJacCvIc>)*IF1Rih_*B@JE4+b3sF3q3tCcB7K&TR8N$b69B+EvZf&@lf=vp7u;?l9-F5c;!DT{yvQ zVObmvz5goruu5}m7{8>nzcbIJe(MX(6I}FD$GsQ6Ej(v4|6+7Qwe|oTP&XA z%obhan3Ma52b@r0ZC#Jr|A)Q%3~K7{AH08nkc5&#m2OZe(u@TNC>S~d3aE6DARPoj zP_TuBBqa2JGzBqA5wQgj1rZEVEg&Ez^w2w^f+8SnewWw(x_9^9-PuR?qkHBI!=r>b z^F7IM&g=XB9R6&u2}SE!7k!LO3r${%5OMz_V1~j>6-J;!K6;qi9BLDX*6Jlqkx-@* zz8MT3ljPI4Z~j`0Vnoeo7>~H_l8$zvO!% ztEaoO)j9E*hxw=_OMQf^)vhIpm?Yu8kG&hX#va=%?6-RRypA7{qUri=MwsY|xPCzkBvZTk=GH!%%>2`qbDI+s<$-`g-KW}HH7&cK zzkg;^cgD>UvfrRKGh^<}T!Ls5kEQzQ0x|H^qTy*JBftVdqT_$uNE-= zBan@FT(3MCjZ=8s+9&9?{>}_*pYZz8xCEb%iJX=(_1LN+k`is=>XGE8-kmJuwXQM> z1ERa>6MMw>)8UjbGVeh}!5XSYK&)Uj^ov`(6{ zDbi028q;c|tF=N!sUDF<45(qOoEwFyd!aFljEG^holeSq)K~YDvV4z-$I1QZjdNu6b+L=9QOwWUgpy9xfG>l3l0I~6$+dB~{q;wf5p z9?QOwB^-mW?4HdP1IS?!#`1ppjpbYS64z&h7f;ZyhA$NI*+0Vj>B?DT2^E0SW-NBy z2Wi5|caO67i5=!YWz^aw3o2_qBx(NzzahrZkZ-kqukzqAeBWd1kWy^^o#RZy41Jrs z_sM1?7}x~2+tduzm&g2#f6N0f?!3;E=#z3L4M%M4-|UU@rwswxp|CT)LX zfQZ+MMSsbChgpV&Q5OGMPyTX#?cd%aMQafKvNB6zE0G3~KV^LT747hPfqjR)x#_J6 zaP<1#-K`xBpzr)GLvQL|%HOop3#@`7Zzv+~cElY|N_+0h=&l%eEP3gY?3c&!S40XO zzIfDp{4(9&?%pGj7H((Nj6<%a<+b%u^&8_3h=M9c_ZExEs9UcOsL70)X$EE5xv3z1 zY8K}%9@(}Y{Q#e=`@NS*eAIy3^vin2YUrAtU%&}ne0gK8^MH&n?+EbFQORCtCGZ58 z(kwZbgz`<1RSoVRICno{x7D3%2TV~HY@ARYtk3>;9TFxciPp@Xon*|1sm7V4`rSGT z(8H0}6%ma4V)HB1BWF(}pP6p1w8Uy%$&i)TW9{8Nq-wUiea!oFbBV#Jx>#c2r(p#I zs5MXh^j%FIq*BC3%$$v-o!XM&m7AR8y+3g^bf`m&f^;WH%GZB#_u5hL+8 zP*X9V&fc=fj7MMQh&S$BA>+%8ET7J*?Ah26r(CQcb%ue`_KDzc3t9-h(5<$dA(Ua+nB_`2Qw2z0Os zAC7gr^F(q$WQ+BF=nso87=rYyw-hEXS)aY-?^vV)9oMm|-kDpRFf%A6xy{k; z{N>-a9_68UQ}v7j^1J2m$9nkR|HcMr75!YjHkpI{ZG8$=A>=`j|F(kVdE*5W)!GBu zTyFrl!|-4?!T^M23)KHG@e;_sKeYGTo8a{e{7EGFeXTIHcb%6n%$ht*WMlO^jwVGs zu_A7-|GXfCO$kzEWlujtinXUvq(b7M)8!lN&-Z)oUyBss-ZLdLvDzbL>n0(~uXYQ3 z{pFmn8q^H68yr>mRediXbwhV+RQA~j^|S2Hiq7Zuo_}@}Kk*%2ta1nSld7QX!#Zb9 zKYWdSwoOfcyY;s8^Gobx9kG7b_DXruYH>aOJtzBSLhmE|qIdO8j!;ZOQ+6%(TFj?W zp~kJY14q6S-`q!XgWPTSm|HwyPfu6z8d^dkp>Ix( zv$(Yr!vN|HK$BPngkvFwIlKmTRDUVSpbqYgC%%Kx;Hw?Q{hi{V#d||&;uRECn-Oaq z0J$usO06mDFRABlc-^N5hkb&T$wm^3J>ogp3u) zdum?Th!%$)2btQ+HR`-Dkrs!l9)X@-G&S1y8lh*(vkyCVsKG+qD6=E(N(o_4IL}Qc zcsMN9n?{Y81&MXQkeIk@IPg7bxEeMt8b@`Fels9saBAYswLJ|(cCol-8Nw zDEZ96IElO}EQ)%S9;bn$NZZi&hs9l0rDDN#Uy-#Ur z?k!htSlC=JB$Lz32&CER~vI?`kG@895-=7qNrh-~@QMAM-E!P_jZ^_bb=QRf< zsXh4ATA0*a^x}Qyk@qC`<{S-Z(UTL|H$?9qF&C49GK5?1JBt<}%gOX~w%x~A)sKY% zAI;o9Rt*`IVCAWn*41Y}zS2mjI7{O?-T$BGc1rkmN?z{VMd_5v?36C*l&S2LZR(Wk z>6D-9R9NfeQazMJm!eYF9__BZW?lOnx|DpolrMLwP`XqzyY?4#sa1BVH+5AE;L&IQb_5hiJ z>WN)3pb#JDnXqp zdq@Nx62|c&zY`7Z^o7YoY~?4mI!LS#j?(!hoXOU9Ad!_mBH$1mOf&Z&#~RSCHFd|X z01DzXdmMYm49RnmpA17mljLbtbeMDzRiXEHS;Y{Q>$3pwlJ;=9e$>t~?aRxc? zB-;$lCfW7hGJ_H%*wiYQx5h*dWGs>1dk^Ny{YgZ*-q)n%FR{&V?CT-4GznUvwrdfdn0-wGNo64iv2M##WKbm1s*Pt{IxWG|V88K1h4& zMrs{QkcS3oyHQ%*Nb3WMQbTmi5P2oG_9U(5q|IJuP_PI2e25!~&g-uYm6L*+pvfOK zY24pii=am0P%ES(F(8&3a!6d^&f*>J_VAz(Jo-b1+YD&^4zv$i_ot(zFk%5^a9i7j1aLA>j?xGOXHDqu zHjAChG@dxO?seB^^0cEluEbgPwhPq0=^-_sulOd7ix($rp~A{gkyB7HN$A_PK4p{_ z89f%L4;A={oAjmWW1zC9pmJJc$pK@sSx|Y$f$p*|` zB?a0yGpL;z5fUA1M(0p~F)oXgs2U;D&l4HvDZs=IrNc$ZZ#*u54C4%VsCsa@`E7@O z3X8|#0lBBv`)jg?XOnjae~-Q&4Ld^ekf32DX$Q}N)J7(b8T!1jrkO8$_h50vXlG)_ zFpTX6)1sY(dY@E)p3;HZW$~X-b&s1Jk7bZmS{=@4kCTC^5G9&JsBdfl$w>j~s?2ub zF7lKqGo-JBpD&TdW7XQ-TRUmS8jb7l_01ZuU5oYCp#?}no3&|v=b)PsG*3x~pnP|- zo#(}u(ikpefnz>p$bM58K_2!uy+X;Hg0{C+@3p3}N zlC#vXnbxy?C%GnC9vZ?w?V#;&)}d#|K65DKrbB#6j|*2#YmT|<_(YjIZOHeAYtf?Q zX{lN?y7rt|bZ{^l>x;bK$-=J$~E^=k}G^SeApw zlmppumeMizG1~Qp2k7RMQ%p}k@#f?m-}%SqoUdPYrg?zkJpvLPT#A)wsXc=fx~t`g z^VO=7Y?Yua zP1@%a?$vZ5!Ez|IYKi1Lv}0DX#NC`cf77k9XZ)E5tTp+ZuOU<<->1!Cq`hgV<006Y>4+n@j+ntGQt6=qA+(6z(f!L8SIlS;lF+rwp6hKFfA9wa{?MQBUPzxGL;)(~ z$pQR4X&6qY4h?*OcJ|pai(pS8gCiN=(<4LGQ zey^17Jc-~%qI*$W!->E+5%WFP^821BnqpMAR=%&-$!`MBygHEQ_uzf^IZo_1gsQ}i z6YYFJo)B3`nkp02fC=4{iG7Ap(<{&uT64$S-H$(< zprL$BexB1?p466v&eeV2GZO5>AK6hdX=XWXGvY&DnM`^b>8#azdO3u={tbxlS_ppaSIQ+< zO6~k^fIxcVlDof0x$%`SaJ>+RxRWzyAhy z|EcT#k9_wl*Y5t$Y2ELC`R>B<%KYN;?EKQd`R?q00=skn=DYtD*c~1nAK*52hdFF+ zU3ZYvJ22YI9{R!_?(Q4>(l^}QJHT!1c6PJ5g15c1_g{_OFMk`mA3k-qv~@Rs>}vYh z^%wE}6WD#@aVrU?Q-GnU&Q;L*zT?Dd#O2vX_PFn(#_pwS)W3nT=;O7xbQS;T}GxoxQn;muq+J z9Jze=!v6)|bw6S2W_ilh+{W45+R4=F-*DI1@+{W;^l>wX<7TG~P3@1F*#70a`q-2I zvAfzwPyC~H^)3FB*8LaUHTes7)em9!qYYKKbzP04f8nm;KT%z6F5H#hcj!NGS6cC~ zGzu-X`@mnmD?4&J{t!`r$aEY_PyZu-AtRZr!4> zRe)-=RqiVdk7wq$w9GzLl})tJ_gd%6^{bD>oL2$YUUv>@Zm>BnIner5?vW(YHr?au z&+mROOr7`v!UMZfuZMsafFwxTHSDO`p4Iqkchz-6^)S|`jAr9CVQgu@osm5nk*6}A z`_(I@7;UDR}_2Xe$?1<#_P*KL1!dCpuWJM{CW zAdy9!z;T?*KG&2CVP{;dSW|-~LKsOfmTquf9XLKj2C(pX;hrviB+J+b1T*aMTaXMB+B!h4s#4pUoJcw98@tQ(-BXOqtR)rsCm>G)(?n{>itIaaV=htkh zyR`~kY%ZQNF}{3|r-Ns=W*sz5J}p)pd`@f$Dya(s=;ky|)82EJ75SaBZ6+BjJ{j5X zcL0{q6mnW7ECVAGXmZOjNEtBY@XVQR!pvaT!lk_&DaE|SLH9e%)kE%6MKekw(e{VN zkEwUOv^qAUx(6`zeP$*g)zuo2EYWj!vdh+5qAUH_i1T+<-ah&V8AX6VPtgBi_l5uB z2oQKmC<7a)?)>DFIaVs49Pv1NCu~8pYCJu7uF3-|W~;-;i%T^66?v3TbG~tJvNSTe z%Z{?OA;e^XY=eKaVOpFtabaGAbe9y>sbur~fGu?Z*CBzpMtGq-b_&0f$!!rk zH+~iZR=<7Ehk3No%-lYw|Cpd#Y_r>sktQo%!uB_|*ys-3Ts;{1=P*hpuZUmU9h#AL z;+SfPdAvA)uGc@O_Namn@IDI3eL8QN-dKf_#?p?xwSRKe?QYHbXV=yhU9=3?>^rZN z989idEU{MM`qlKPuYZ0yIUF@VIGOEP_axhz{Q1J;Pm|P{;HP36a)7&i#=}1)1yG*O zr{2LmxdcnlJDkAlTG$>z9>QMZ8TpK@y$WwpAfz83Vc(~Q;Ce4lw~>c$*1xsDuJW}R zyeLaB#(24hvPMjKp#&aD4M8 zw2T*Y5xsS15m4E&{B&Majjppu!y6YiM1NA2IGpBFZD?X@(KWgIsQ$-ec4C0QoVAf} zVvEK&I(OfbIJw#jw9M^SB1fWea9?jZ+Eq?aJqcW3E^{y+GN$TW38dNFxFu@FPms4uKP?W-?H*YdnnO+|<)Fvp z4wspgOj6Dycx3Xr=CwM0owhf>M7_7efS5wd(jN1&grjWKi#56&OI&}J-wK2~A39@M z(b(Xijx#&f?G?|@oyfP5=cyV&J_(F9$=>HB?wAfB&33Sjh#`iB-X0M(qc>~2X#~qY zUS;+_@8;u5Qp{Fg%P0wIRKzpn03@_QbcEeeP5Ko1O1E#no7&{T4#x5 zfgkHj27cTISSNEgp<|M%$^(k`LSuflJ;(oE7+rcAL|RdEJ*9no=To;=)%-Wz0@8Y4HUxJ&w?(56Mye#K-o0ShQF z->GgGCExj7{+G61m@q#o!(Ebui@QV8ENur=p%1ur_m6_k8;(oHN^_O7y|*pNwHrWP zw$T1C=UAh!hZP6*wuYxhB)xOctFlP;Hdt^S-8k`jdDizF*X~;QDVPJFgsQx}ebjUN zPfcW$aqaF?j<@(}XC59bK(-9~+}zjx^JUGSrGQt31OK1c-PGz&{-=K28Qhx2e6DVf zYX6bHxiy1DyzZc$T74k;f7NaN`AR^%;XrKH-^p&T z&}`p~@A|yne02L8J@w6`itXLzEi>HPYeV z8Nyvg0xEO??wM<2fsPPi;;rJ6&P0B;h*&gUofb0-AuabIe_03|E{Q#8De=Q8#?GE( zRt9^Am3BpoJYz2Sf31C8tHl=&8mpSV%bKPzHe$>fd@n+%>sh%oS6!DK;;c@iL!96)6=V6YA(f(0AxK&CMeSOBkv zMdT7_D$(MMR@xV6yjD&^Q2>58Dj_{5;dEMjG7tGjnE>ga$c+_YSUqgpU*t9pGSGp1 zf0op@QIfWM#+V;o3<9Sq})w?PUoGT>E!Jj{r{2|z4!gr>WZY+OnP6Q*bf z%V$BxQQYJxznD>q%{r{A1Gyh9IE6(FVxfW@3e27kxlNa7rNaf%rkqk&XsH`J7yv>e zFwg)$J>@GFvFHqN`jYu@w|?cM+Bm1H+)ft^i`$B#5bW?TpmxakT?eF>`pWczTBj{(ac3Q5+@&vsP3HfmlI^;m|MFb+21ewnz^x245#J9Pi^{mof(&C3 zyU7;v>y#?!r4vfa|U#-*dV1@b;a@{dfLhy8F2a5CLxO za>3`eo1Vz|tGU_%dB*X19&_>Eybv!L{40~t46dNW3bgKE1;VgmmBs>b4CmcWkzoR3( z!tQ#;3wga~M4$ncAO_Uxwtjr#*e~EHQ+S*ae>SMdxwOcLo6OY*b5tH|3ox|9GJbY= zcmK$8z~K3Duo*Of<{%#Yre#$z7Ir!yeoWyFoax1DMJ_Vn2>rr0Y-F_6gYz^7USEiC zEn{H?wyg(WX2G(U{QZ4LG6)6w`uS5Q1cYASUFlBVjfO2S`7|)4Mb}Cn&EJ1AU+Tt< z0pCF%b}e{*rYNJdNK~LW!;bXM>PeYk`mXaZtuM&kNH23Qo(#I+kt#|L6X9hdAPFEw zN`YyPaCx-WHvQh=9M}*&F@wN62S7!_;Qbr`=JgL1E~mkMBY7p9VcWQ37av&P{&*>U zzHJs{YDTCLc#r`+uf_nYLC@DqpHB`vFPC~Y;ri?m5`UY!z;RC}vY(dvWhe;YQAYvc z1ttelRsh9s=>n!ZbVMsQX4t*--@Ltvu8ig&%%Hi$bfFBp z@&Y@&s(KpFQT*^Op5_j?X$N`i^}`Hwb*3ATom<_jFLKvfV0`vfQ%<#!`eUA>BEKG1 zGmj!l&7ZD_q&kHQj}9Pvn6Tw+SYs||8?Y4e z8Qej|$8!)=%nLrkeH0ck!2w&k5vFu&dp46#bueGgA`Dh;j~x~H;O6Em6*C8ci)=o@ zUdw1bivMsF;I?;pKa$s-Ds<-&8&$mJvIX080lTnzJEp)kDsI~CMMHEgEEiuB{Wh>F zE(I<4T0%$9u3jpyUe<>7-uex-)wx%;@RK9Hmmys2+~8`5XlG?{(eE%v_<|j{o`bkm zSpq#Of_crWexY9KN&Q_{*4V4JhiwQwEZDc~hUQJ!bQHg(JF$t`$S1%OeFR(H1^XOE zI7^T1;WR1AG{X)yAMt7~uSu{SdUsmm@u)iMvK))GsW9NKdnbq*{2#T4+SR0Boy>-U z_uw@YjQ1$uhHB*QR1ma-&D+(a(_o6h_~4JA`8VzC#E;9__r4`H=|8FI#J$13roVJ2 zz9YcI#y+xa1e#H4qwe@l4uabSrs9Yew>veo-YSAUh0?dWvhrSLgc&I4i3DN`%w@Zq-N;j~ zI`}x5Q@h~BNvse+aE^r2YxZvFk$jPlDZ!SQ-uEbe{z#EPMY9G5> zF5pr0-r(V0*dk)WAGCoJet`iS%qLF=_j*aQot6-ebbQp!;tP*r+p!1;s(&glK}@Ps z9NPCi@1=zcHTLG`8$hMr2>~(_QODwuumtJN<7phkL}@HLOmJDZ+8jG@IQR|u&ut;; zJ`LI@$>gB|fxaw3{GpQqH+mn*EW*lY$4q`4!#q0?!^_trgkp#a7x&u8?KlL4HIc?*ZQLEwl+rmO zhX;)etO*a6!|-VwL_VX;Cm4~%!8@te%MxLHa(K^kymt7ZuX3F*uPI3*PIzc1C4|&v zfIswfTq6{JKr1arla0N`_Y*5TxRcX2U0ZZ;sS{tv%0SLWX;{@4AX3}L!^g>rM&rY7#*z#s8n^kUsEh8i ztg&4@gLDLsYR5PeuEZig2H;go3%1gW+vv$b1H1@o0nT_eiK?j zdnG&+|GR$$#`8^R$CmW##>|1X+1X@R-!h_K0YAMo^s7DDZT4>2Msfmv4_9Sw#^x#PR_TC0ykI(yZMG@vo{bvFIVR-Y0pb1=@C8lZPO1n1 zAl#tuQ;mBc=-^N^>?fLB{$beS5n>t#3E&8SCn)!xtaj~y%!a|wCGd=e!Ec7~-=M?W zu^ES}_(KuDF756E1^tSC&OdoltV|QC$6Q;u&QA@a_hLUiHTc>3X*~t;J3Vz?&2pJD z!=5t)Jn0Ay8sdo-+$KO_WiKCwt;b*dn)CGc<(OYr5qPB=P#Q;I1&7#YDNy}(vvy}w zhaG!z_-E_R%Fl3Hp2r8ifpU0P0R)X1H~Ts3FcaR(0Z$Yl#`NfH4t$IQ4(v#6qiw$a zyjhnDJ+%BCFgn4Jv})~OiM&eYEed)MJ|>6M9020r`%iz{WsXwvVuSMnblcl9O%}Ud zuG8AsCdAG=0*zR$VeSLf-8y8NeE};a1Dmifu+hm{Jl)07G z%{V!i?}e<8cP0O2cl+$E9<=(qlsc*pTu==o$X-}`H+K7Hd2t)hX?p$PnYqSh^_ae% zL*@2LLq3F0wLkcMym^c#W>0P&66$N%#rV*p?!TXWvJ}rFl;tKh;9eU8~pUAD>XUM?Pew^45!@nf2htNh{4)ljYV2 z-hvL>9BctCSRMLodibPvpQ#uqVvAi8cT=$JdyPtk&tY4GUy~KK+HI?c?TjEwtLo=> zMnonBkG@lcfXXc!4&63ZDSC0*x{|zl`lRmE3k{?k4^<&T=fwRdHeWMdoN;jS)owfE zM$vY3@h_@$bi3H3?c@{A)MOAv~?44?+KHpa!QKJ;yb601IUY)!CwMp0W=C7$&o-sT8 z7#tCzT!kYc%rRb+U4B(w)ZKBI^Z(oxKA(7?8RMO-J6+|SdK|3hlV+h@?Q_f4T+cVt z$*b%ENU*)H}QB_mCmC~E!lMqq4HLf zeE8Q?r@-g;N-J|4&t&-czuopYsqvx0V`2GK;pmAISHa>Rb;p|@O&B4gwma@RW6`NQ zCXpnav%lRqC5Nis+3z7=EHIVX8iNwa1i3Nfee5{^9??M-RYFsB>|X6U_5^M8C!&jY zC-Zn+)vWHX_hk7O_Nf=wdG_#g8V-*bAwr>C`R^j@nS&I*#o@gPv)?1Oi4)ti{!ad@ zYMl7u%O)-J*(*x3VEs@Q(ssp2?z?lGM6?CvJgY&z&e9madOCTZR#`kk(e^5~KAWcy zCk`I~D~hwy59J?Bb`}Ghs>Pe{M^lsMSN150&ZU~caksLEgk(w;Q~egPvA5?FPSz2H zh#1lI^sNpN0qJJtx)9+nbPLQDIw1&sDWhm;pHlZa(yeQ6x*)RXD%NOkg7}V-<~F-N z)3|k)Xs=JMlN#~3qaxjOollS+%R8np4vj%sMz=dRNhG$`*zhDM{n+v{kUnwz=JG{X z(@z=Jq3J1?IQNArS6ZZcPgH3#c%<7K&MVL95!Bfe(xK>vBLnuNO?CkTmt(f3u!nL@ ziVqfmG&g*=LQgn%q(D|;5K>$QZ$1c;Us@qaXQZX7cQ*=e+JR+<>|od36|?2{lo*MW zcE}Ol?RKr=@f1WRUt}2x#hhuhw7%ArvAi$qX1|)1!-vy}=g$^s2h5)-usVG!!C`O2 zH;8&OiguCVE%{?h&CKsa{3FM;($DXwutL{5$cALtW=WI#C{OD7?@CX1?D&J-YV-+p zt`d(kqA#44)+6{cOn8p2P?Z*co}|%N6boTptE%;|ON+NehtVx2?}#bSPl}(;ed;k0 z@Z$B=@!pqLek@heEI((>UfO*)n#RvbOW=`X$rLbJ0Yh}E+QF54jrU;PJy;R(LO$fL ze#mLn`^2axvE_r&=6nj52)?w5g#O`W;Ps|Qwkb<$3B?epB`hyMuBNaDO?5GHzwZ!rLY4n0QQd6MySL zO1vW1w=65_L6=OP)Tg`(xfN9`4+LssDWPxnhDqjxe+~LBE{ehJt^-0NpWYaL@%}cx zvCaSM^_D&RzWHF#FJzca|LZGw;&EtrWl$JNt=Fy@VCKay z7!gHtS1e%XumZ*mrqX8=LZZ$);6>&S{!?`3Fmw#@r@E=*(}^RQ2cfFS4wHQaX&;M^ z!uaj_2*jK<$it%^i1AXd0Ukw9`$;Z~!+ZB1uKx2rb%tq0SFdp5V9_i_w^#X-zA<;$BkLz_*%+r?)5o0qEnM_#{lEja8k_d5Hvu{uc&6X+!k9TNyyFR)C-=ZbID zgROFh%*2c?U9B4@pDM5KcDPSA+zO{TRPcs`uTaG~OwXFzihBiKR^L#Q($Rf0ajccY z#}B6ez~;6Hv`k;?`&0kpwh&@Ks?e1EX96T~<?_H#RZdoC|IE8Qq*Xok`T^JT znaeLm*Q7-pduGwL5K(r`kXeVcVqf12$F}G?cg5bcBq>p@6TO|H2j-24f(gtPHA7|^ zly+LxkkLLrh2i zb{dTnxkG~4iV{Ukd78HFSu`kkqre9Eq5EeTZSZXzZ`V_tu0)Vx{i9L=eDMed`GQA$ zj?2Aekb5y2=&aSZnrrt$&cFC_Tw`kHu5S+G`TD&uh7vL0pR_L6^6sk1+gIy#`#5)9 zkjJVq3R{C!|KxOo-+1t4NbSeO(2!QNggXPYpxh&|LV45xz8Ea{vj{B|3W;loi$Fqy zv5>3RaxLxR(hk=oCmM|1HaJ#*8N6Pbw2(DCsFA*Dkj09ZaW?pPl@@7PXH#AQwKgKs z<4&&E=E@Qjq%cS8-@WQLPOss)9(E)y8ocx3s*HrCioAEcF{}6oUtPr)0QE!w(n>Dl zYp`U#wV5|jlp`D*O4OV@J}h9$MDfPV@{Js%@|Yg+$w|6`!A^HHlm)Y79+}*Z;B$YB zg$!02M%K>0HdVniZVEiIgjPRn1trkrz}LRUkvRG#ZlK@{ZI0~4V~ z@wiDqox~fl&a3bpve&ugickEG1Hoe9j%AeTzUT~#&(efzasZ`}oHPVYOlk>df&&uD zqzeqrqagvOm1EdRaa+7uv-cZ2Y9NjA5G>?8{h`@bBYl-h`P3RU3EUyK_S#um8u26= zD+;S3EnIl6Gr<-{*kA%gw8X3?Tv+K_<{XJk_*&L$ZWEyjF9E9R42e)BYliW;uTbr==sv+`h@y{5 zTh;n|lR2eXwUZC)8dK{IrwwvMB<7MeHsBjbVNQThb0J9a5i*O>IZ}0Z_6J!eT(}jx zJL`~eBeJk=;;uwyvM;XiUo-7s2_5z}u{5=*PM8tQYF$J{o}f0Is2;#o-VkFnc(xI~ z^N}UoCY7S9onq4{hPW?O!%EyMn5&hXGRHvD2wmejU0TD45FA7ZUAe1+dLKzcvnUZv zNCb}m)emKsk%Hq2HR1*o4v_twsqYm*qKvd%G*Czw#4&+S>POWX3Huo1p2PxWw%DF* z0mk7nRM`FW;_w`2rCTmdlDb4OB3(?s%ptD6=U}J91-%SYmke_ON3WaMBl~-Ue`nZ9 zXP$QH<<>icn|d$w^oC6JhOYHqcg+~#qhJa%$BIcj@4$9%-CcHHpQ16cbQG4uxXj>o%kYlLM1z~ ziJjELPM%_?tg%!1`{@$>X-fU++FYaCpW)D->D!-mxj&oIpOe{tyQu$8Wq)o{e_l`j z-KqZkwf=kj0}P3Q`$_{3v^Ww0u9u)1jQb>(19)8Lz)!P=?8y0yW# z{6j2>A#PFko%T?J*-)dyP?PUa^W~uy%1~?O(EFmH50yh7n}$C147E)SwXY3*<{$2m z817UW?$RFqVm93EFx=xi%+2ZcQHI%>!~I3W1C_&rO~XSy!^2aTriIFj- zk#X&j3A2$&hmk4Yk?G4LGnA3p%#pdGk@?Dzg{G0ko^uA$N!Ym&)!*kzp`$9&qf6SJ z&c8`9Jrr9u6lv*6WDt{`K|~~9={lbSDp3~+wV`WU|?LLxjg{dW<0vPl2zxdrdnH}2*^#dpFlF&+vnmi-)^YT z;q3&W?F3UeMYqGJl;flN$j1ufvuk7&S}a&mfBpO)vwh|=*#E#) ze`1bj3|^G<)6ri6#2wqJ)xrO=gJ0l7D$##CTlw>U^2~gz8*?6>CpS9j@$WiB%xnk1`HR6j7thlF83nCyiaZe1KWZD z3W=xKi-)hIBf=&MwGuy0`y3jX0H5;y02i8$Iq&<}9pPf4hB z4tdRNVKz&2Lo1ZXSU$D#l?kl)!-clB5^Xzz7r}`<*%z&(K=+m}@}>@gW_Edm4(@M{ zb%W=ei*wV6#@$rNi7^}%QTZmS`%TQ^o4C_A3IA`B)Pe6)=QN4oyB>x+ihR4VHufS9 z_tekpY!=Sn@#~+SAjM^`dkS1g9(3quAovO{KMPli3$T*%hwT>H1Mxy|m)kBcQJt~f z2NG4E`)yFtRdvUAl2v~^$3(yt66GL5I?Lgk{Fkh7-yta+fh(g-<-{YqWh_p8RmP92 zKD!XD{FTXEu^fRKj?xamz4??UnjKd#`SUM)CXxwLf$&K4lt{H#-r5Qw5^QfVoz*f` zA0~3rIyK#X7wxpxRI>Bsyp%}OmN6sARcH9hbKH3qg+=ASwZdv2SO2+FBQ)FTu>IMh z4!eFNv&av?6RxM$oU{_XcBZ_qgt7N^ZTp{dL zYI&us!z=XiGcnc*LSknZ*JBauI|s(1N~@x*0)CYRL>Jiypmzn8(E?)gXBST0iq8%deioGT^8(!|2>Q^w zS>d;pV_=5LmDH5L%&($33Rj7kpuFdS%+{-=Rlm!DpmKIJGiN<%cVfPUOsHoNF(CNh z$bt&bxZaiY>z{)Uj4hPz7J9OKL3DcIIcuX5oLKBH^khenB?+y_4rZb@RRcGkWk-kB z;fg$?YC>h|l`mMK7Yz)Fjpr7f@r!l4gj{sj%5tJr1wz`vH_9CzhJg<(x$X$g z@7iv-ag?|S+e`izweJ2)ty|l={(EQ5Mb`gN>z1~E zBkP9Nx9?i&8b5J)^K*I9S&b7RR|ZJnHP@4u*Z*8h@P$B>e*l5bs)O}Q3B`?t2fPWh{?FJ4QC zyh^$hMF_i0;Iiw#<~rok4ZlmI|CnpP3sGJnc&@qrPdM4jFWl27+{G{2$>+-7aI*9H za4x&%f@>Gg3*2zBz2_x6k1$)0uz#8Be>%!sbA8e^_+PQ)|C6@1IqhL>|L<7x30wF7 zT`$?#(&0bs+Q8(WUNRS4V+^fz4Xpl4TU-7wwYA#8zuNk5Dfxc@*Yey_^4^0odydHN zLCYv|OUZ|%c5CfYIJiqeQ&L`2LQdnq1J~;Ri&*k3AC`#&KREgN{G86nW5+3RAf@X?bbii3I*LU}w&Qq^F6ZC-aZp7GX8tR#6-udSCJI^F1s{ zP~PfANoN5!mMo!$E;W73yY^`3j$7r{M`;2jT( z!X`f4wSM(%xF-6lpqy#isNNZMVD^WE*y1T>V8y!^>t!qFn^J8>lg>Q3bkOUC%zade zz_iP~c*=YR59{>Emy$D&&$Ye2+*G@-#;7B^sbnnwm2`whZth+q(?cIA0OvteN5>mk zu+Gx!>K`*51vPOeyAtXoUha(<);wMF`SZRQ*&RvKu)JRr&Sn>fZ(<+-X?F>Jv^WyLB6|l`ZS{!5 z^vUA`?-oKT7BXMXJle}wGtanrq%R<5_{AmVTwl45bPFxL(^^hsmeid_ zKC!r-Lz033c&OmBK6uvqqsfQaz1T6+;2Vm-(X-+ShWB^GsD_=}Ul35JDlb+FVX%~B zlK1uJ)ulD7AM0oMPnen&)2litCn~GQ=!G zS6{a`4VRkij?bfv0l@9agamY>koNO92i3Z=%JWA1*gnLI>VyT=9%=$e5NNp0-y?M% z2B3JP`7Y74S9y5x7;?=1aw|Y_J@r~(sHd2hf)Ou@&(ddp&rX$vj{8B{J`5QK59a}d z@7M116ow?TVSGDAhm8G8Y#mpuABbjS(%fsuZ7eN4EmuScr+D5*&Yg&8)6$sEcYLM7)!IpyUrIZdG@ zLk<}^MOdYbitpZ^&-aJVKk&VLzn9DRm;LnoVV6C(%Vn3#^ZK~mALT|@3+v9MzCYG| zaC#!-z_Z`%odIY7EhgD2upF{GwNZB8-R~J`Z_n%$UQdhgsQD+~QaKK@SL3w@D;c4l zuJsnn$gu$e@&tAvj7O=O$4;p^ae7pL8obS{l(-bBNmkRnS)Uzr`B0xmtde33eaz@O zu#G;;fV>b1CWlJwmU`%Z>O1u3-{%H}P9;a4YreT7x)1OsgM9__p7HKa91?(uGpoG4 zbJV~@RK&7^=7vdgA$b6RUxZ)zJ|02@g$qLfOSzovz8#0|D+@j}IRGcdE%Ryq$Kct3 z@Y4SHAc0$e8{P{%`D!-Mr_TZG*NixiOmEfrg5s|VqDht z5O^19Kb|RC4H^Kjq~#5VP|6_^Qky-eV)p-ICF^K9-ClYdch4}Qr1`ndj_Tqk+*(jNKz6(=a|zWd&05ImGJ zb@%?-1CX_DmpQl&k=Cncsx4oBC%5>cpk}XBBxU%S&Esco&GA7tps@))oMtN#Ck|1Ok%DXXl;1 z`o5fhrEObZlhuD^Q$C>BB&z;Jt}vE-w1kEEtzGXLB=qa!#-sURjcvkMa-7n@jBUfK z|KqXb{~y5hjxa3J`YXzid;(vjxU>VwtFRd^$!G7$<8%zWJWCCKtUWocSyS{@NcW zI~`{UBts#T&_`rTEP2^f+Io=;3!)%=C{7qDYo^SRQlJbiE|`a8=!+dh%bdZ+?wC>y z>|>GPiuZX)U0!q$51CZ0Xk;&EVlU+_P({nfqumqK7GuKM($}!@2TP;3<0*= zf}}ZcG>9*37^b}ZKuLc=?-$TFHj-+v$y?;4a(2>OAas+U4r9mV;Ym5{#QgARE(;pQ zfdAydqX;Ar9uSFw3o-OK03yaB|EHJCOkI>qdg7jx-I(~0c^rt5^1;FReBs3sTs-N| zVk$t3G0vtBV8u4v=qwCfq%`fXAQgC+v4v;+qNSb+N^UGob{M71nWo;|5!R&%@Uvu* zVisZn2SpI$5Ekh@?BrJWboL@y?+yciO_RNorf?@+sVrT!49UYJ*Ar8=Q4%0r$_VTJ zT6L<{$22iww1h*F89v41%Y9odx=Uk%Z7Dg_Axr8~#_dH)_#H|bQzm0E+JT2m!INC@ z$Zmpo01CFr7ZbX?O%(hJPMpgU{v`nTS0nqX#Y6t1nN>hU;j8316d1%2-{wpB03e^p zh`Q?34?Or)vREb$*{?|zPOx>XrC!f@Ikyg0Fvn4GR5sxM280`a5Xe>n}Og zTDi4aG|92TbuF!pSNYqlGsTHH<-IvK?_@0V;30VN84hBc06j;5ZZnJ_;kmNqxiUGq zwGNrOA0KTV28doRB$EsOfzlKX6et~e_*enVF3Y>`kYYiM4YB zO?gI1PlOXWNNiSYP?jY&Yj1g~Ah39hDHnZ?q=*um;3F=%1IAErn2^D9VAI40qt)=2 z1^^5L5d{z?zu|Bec{dI*MFbS=WFu>_00|eE81j&Jh|w+ju)kSlkkh1x zcQdxp4^jGvVO*YR5Gh_hEB#dY{Oxij%KdwauGG7wAVz8OdB8MBa$`Yao(Q;cxdg^6 ziO8Y<*2lh*K$htXy)%6IHCYvnyg;cm#FKWjD(6N@P^{$GBb6{7s{8H(6NNGkt8|Me z#QJIP&P!~VKD#iKUR{oSaRgBKrmO>Bys}j_bb`^lg!(vEnRcWM;Zx?Y7%jxw;f8?Q zJY*S*EXIPj;4%vjNIkjx{Bh26@5DH0MJ2ETIsc{*b4#u} zpS(XpDQlDnQxU-PbLf%Dq=E{h=TV4M;w$%Y(Okz`o9cQSNrcq_%42PDku!LbP-cm*n~|67C<X8}W&XYoU$O23w*1oZSQ(~*(b%A}mV1ad-kRhexea0~-0AK%Q3F(r{+9p_S3A3f= z;BWa94Sn%gKJ=r|G2X| zwQmt#Yed%01$N_gx=zE$CyoK+j!7vtH*?8xAe7jyKa_Tq&K8fX`L!Lra|}3d_xf5h z`vbeieuCnr+zqhro;eD*LGPYRK8=WJ{Z`Vtjea@b(0ePdD@BIv?Vht2)@$wFb}Fdt z2DH{UsrYY1u^>1;C_MTc4c4eAuGEwjoA<`_E0VWP31*5F9PZlv6aT9!;sb+G z{}IVUs45G`{$YCm?elQtI3kqu~^Gzy@Di$I#nCb3{WpkY@v1 zBLj%`{*k=?>-6_xQ5=3A-BCx%Ew4{ns-KiM7;}sbb7!aR&`Ic(el^d2^{5gj=m$*H z`)&RA8*cB-?FSX32G=567bDq^np=U#IeS~)!=I5Wt~4B3={J{ryIoOy-6{UAeeKJM zEc^Gx^Cfj5SisRjYWNpac6rR49XyXCx{a=Pv9HHg_c_apZsXJ!7wRRk0C*wPM)|Fz z{HV7F{5>jbEn6gJndB5Ta5m_zt^$0kVbuN3;1+tsHzN90Kbee2?gb#Z_lFtik2iCB zj2OrcMgxqAfVOm=ZSD*?E=5#1`e2-fNIM>auN`IRm9>oRnjTBH{3rdjz3RQCsl%~MSk<4+Z-xd za(eeElFR@Q$?UnnL*C|7VqxPFlj)9;({MaV5dlG}PVcIj6N=+Seb{XIh{ERFZDO=v zE63(4$v*$1?)J{!06>H;HQ4!mp=D@R-+oO+lUdDToX!qtXw@!mtOXQQE5x~!HBi_C} z%A@GkTK(I`s;c#kk`X>Z_v0ZykY_v@$SD-q6ECq%fXroyZIWRDJn`v5INeG3Ig4Mx z!8gh3;VGndsxTi8_$veXED^{fA|i2OcNqu{E8PXM_Wk7AFVX`pLzqAY2LRxFl<4u3 zDWF2I!I8S~%{8DoU(CCe*>BI(TAjTo1_*-v=D+}gukX!PxTLWplE_yyB7`7D)lX7d z|4`FU9<5rLSY5flBe61ga%C71gYb~_VS$$!NCk)Q`%=Fjh|T~UMC+WMC^C#0P#0B@xCaA`19MbAe*L0HiPrX+T;P*og^+)+`#*&B+1rL>}P~KXHir ztcg>Y-?i6%4_9=8{~}rlN#Gq=SS0Kh2ex|NA6!gbK!x$x^4XF{N6qL+3wG%TSf?8 zkYQ5WL0FvU1?GO@_w3 zkHN+77RYAXzDAMqp}t)d7e@QhjW5=g74Bh(39Z;muhYX84*1hHX1y{Tf9KaKg&!m| z-Gr;z=DC0K?M_iK*ZU(_c+s6ETauY~?nl5tc1z1;`l}y7gHN58e@CAGd3pGmXIp&A z=-SRy|6}P3C$Aj+b@c;!;HQ>@-&7$d^N!lWiju%`x--o@LDlo_dV8bFve}it`oF$R z!jTbR+8br!XPnGNe*U5GZ?pXs32NT0TT6MbMz~i6+kY0&icwE`luzOt2e8`+onCW* zm?p{stVYqdgzhh@wv;^Bsc!{0ovpS)nu{7(OWA4GSWCND8`#LY`PbOUc~cB*6#|QD zY?ZEc8rZ4ao~^M{ixxGsS5MG}=e&xrm{FYz?7Qdta`BLTIU;2I#rzGCHs~@g} zpSN3fKBh``Fb+W?fORkA~PvsdW!Wdw+-lwHC1vYaB z%+8wd`c1DB8y$->{WPfesq)h}e}#GI!j8(TsV13$<-=*(plqLbmDY`EQcK9KaSGn6 zkwce$`MTir)?lZaHh#-GmB_mkkd*NlEuMdC=dRY{qG0SMMJ43Qw~}T2y2T@z>x=Jg zQDXa4c}tabY1%Zeu{#OO{rvKH>En%$dK9(s`qYPEm{8oFhUo{tH4l}?EGQ_G>%z$* z0@}RF-FjdVAj<2@SOH#~;jpLlY4Q0dO$+Q|)oTyNCBvSpBzRnZBWaKMTJzQ{_lJ)o zPh3S&obm9=%L`tM-`M+~NA5XkZ>oY#LAXSQri=e`VYzeL)t*B@=;oMv=Z#KEZIi8c_c376jTUML>C>x6z?M?!9EzQl!x469 zf-X@P78mmb1}1hJRn?^2tC)s|HCl~-=>QG6_aIQ0tdy5f;alh;#gB{eDT`^TB;_}9 z9oTL|%WG6!nD6csT4%Aq520ugXa1Pq0)(W|Dy~3n2A-szhUltmjwYzCz7~%j(tyP> zz(;0FkXs*-(RHWicK@`nI-$379S~C#(_)Ezy(^!;KmY3!^Vjg<|I=A zE{>T6^g|Nn%4Otih0c7`kv^rS(aKTe&dPF-WAuM zHRq}jpog??cG6ojV$qR%)Wn-la^_LYUz~$q3!+F=eU&s=Jv_POW%{oXI@Wv!Ff1Y*kbO5Z4QQPnd#Jh| z(I#7L8?W7aIS5nsTudTZ;zj_~}tLbLCUHjiCo9Z~f0jh1=bu*DHId3+zG258Wfz|a3# zDv^5IG}^R~vQr&vu9T6@YtZ}MLUa(ROx0}r;t{V!>0X?XkW{oS*_Rk+p3k#AF#8my zBZVWm&BxClfYhQA(^m>duc3bzIr=z9L!f@8b z=G*QdRJ*+Bx5O)g4tdGVk&Ov-dt0W3nnR`dq3z@};JBMwTKcTXQ%vP|}2mTj~^kuBr~@P~(^G<^W3oSwVV+P!O95g%#u z7md~p61ZD_@GXJuo?FMr`hq=@PM5m<6aHxbIrp10TK}_Rq1oR*q^WQ3;V?%_&#cT& zrIF{JI<-6c{FE`(V0EkIx5|HFD9$eG2WyUPoF1+`c;LCZkP(B851hy?%PgA6vxb%J z&;u+wWHalT+`(VIEDbg=dnK9*R4Ld3Rzh+TkwVsz0(v^7PcI zvE9a1_aiAj^6R)~4h+&a`XPenc>JrrORbkrZP<(+Uz2>B5)u}bmp2ez6Tfxp(S?i~ ztDlc2-!eOfk|;{=*maO&Cp~jvHPnzIzjSXmmcMap*~#;Ztj)OvMK3wp@4P8-!$_`5 zX)1ZDV%Kj#ry0goE+lxCt`!uUN?jK2v)RsrVu8crPh$G?162%0Dya=2Jbtp#B?$CxF&9i6Rve9M zU4CO$AZZn~u+$;g_vHxZ_Pd1UKdR;`-jLO-89Ch06Hz|#9w0fnD&P@8%^RIC_ryr3 zQybq%s#I$fqnfG06TOnEw^(WuasM_$%iQuw*eZ#s~gqhM!wz{ydt zCMGeeupWvA6XF0jM)zkh$)Rk_hoev{J{TcS-lw0S$4XAsh}}6syXOcIgCyMK)aDM)VdwVJBiR;kqbAsqkrtfBe-y zwmOg`f^<>~i>*Yor)AnF)y4yHcK>N7QyxBJkwfjlyMlUeUg^30CmD$CrRA}cM2|d| zPP%JtPxWUn1zF#fVMk8%Wa!67u_c_=U*2uDD>kB}WB1&w&XZYGG<}_osRsL_K_Yz# z7ia2Evqj8s31Q*;%9Yv0&AnNXJwQxv%B<}*(VpJ$V^OrXd9`^yk*3EZvTXVFt&o=r z7a%zi310k|jOti_a>C7OF^y`)T)*TBw#386L|R%8+dh^MlyHj~|8wvyHN1K(0-7S| z<0$uM@F5IT;)4(15>#?2*0`_eDKNZ`o#*s^^YG5%c<F)lY{}8N2$^x2hR3XZ~--l7}pf zhpcRdtj`SDTpY5!Ib=s2vcEs%@O0=z-H>C~kP~;vd2Z%rxE&T+kda=pcd39`dJdxw3Ehy84Z{m%?v zyf_?ib2yMHEVK?^diwt|xSkwz;(%Sr)l??62^}*K9@ob|SEvg$$3T@ZP^>%T>9b^y zQHmlS)JYWWEChApr~~ZtU5jJsg0Uv~m{L#3eg?hfBK`3ktWG!483$`T1ABD_)}WhM zWDIY*4{ND&4LS;w6_BZTVALqZ1`U+ue{8Y`b}z=HW5%iO6CM2V4v2?4cK2zrN$JH4 z9fG)heN<}D>vZf`6J(+ZGTu$|;5w<2gA|7_w9p*YZNiF;|+34l@Ev6GY*~k8r z&ij-u@ao`w>Ynt7s`l)Fcz4QAbu&Kohfhs1y}Jc*-9b~@EmN>pA|0b+RB~(&d29f} z>y+oEV|ZQeQ{DE2(I8$E<2B%tfYsf$zni@Zqzk1Ftv0*LiMrlnM; zQ6}_VwlLX!(;XP^{Y0V0pQaLgxTVu_Ui4+|q>3$FX8om_F!9dna5aZ1g^}J$d#j%!)MIgP-0x z&XZ8}H9QGBI}COkPPkx8f1=_+&`UJhO-0UM^frO{l4c>cb6&d%q9*irlT+!1^PP?J z9fW{xHi1K(yEL3|*@S*|U_QWj&hX2pK+lQod3Ygeeu4CBA=>P0 zvnOp(Wh|W**sJf`iCUx*#;AlD-~eH#yzYD@_q^~NK$pPl-zbl#o`mjELML{yXOz(D z2}!S#%&1CE^;+zNTgTSY39A0Zg}N_=brV$9 zg!ga)W7Q+bYmR&TqVSG&p+0@4ULLu5x#sEQOWj1>;-yzBp84IA6}G-J7wJ`Av`6!k z4^8O0UbLrY1M-IF>grwVhd(#5r$Xna{}oOOaT9LD`wXzBHd8KtTM5?IOT5?psjGC# z>cLd2*}bl_uuv~rlOD9Cbtzx(RBsjI{DXiVEU%w6M-^O&f~+**u5@8SxR8~rtMlnW zA%J|ufElb`HBk9)&>=6_#98>x$Kh!+y+q|<@FDS;CgSw;aDsgPw6e+7LHw!)B0$ZI zK8Kj;=7n?!f;#Mj{!v(NTJ-H%TpcdG+6xF?U8R51gLbHT>=#@eBm|=ze3wXmecSI3 zB0d{*`w4y#y6it2Ke*Nfx%S3{{=4|1R9E^R3*-k{I+&~Zmi + `Train ${overlap.trainId} matches test ${overlap.testId}: ${overlap.snippet}` + ), + remediation: + "Rebuild the split from source data, remove duplicated records from the training corpus, and rerun the final holdout once." + }) + ]; +} + +function findBenchmarkContamination(project) { + const benchmarkTerms = [ + project.benchmark.name, + ...safeArray(project.benchmark.items).flatMap((item) => [ + item.id, + item.title, + item.text + ]) + ] + .map(normalizeText) + .filter((term) => term.length >= 8); + + const contaminated = []; + for (const record of project.datasets.train) { + const recordText = normalizeText( + [record.source, record.title, record.text].filter(Boolean).join(" ") + ); + const matchedTerm = benchmarkTerms.find((term) => recordText.includes(term)); + + if (matchedTerm) { + contaminated.push( + `Training record ${record.id || "unknown"} contains benchmark signal "${truncate( + matchedTerm, + 80 + )}".` + ); + } + } + + if (contaminated.length === 0) return []; + + return [ + finding({ + type: "benchmark_contamination", + severity: "high", + title: "Training corpus appears to contain benchmark material", + evidence: contaminated, + remediation: + "Remove benchmark mirrors and leaderboard-derived content from training, then document an exclusion rule for future ingestion." + }) + ]; +} + +function findHeldoutTuning(project) { + const riskyExperiments = project.experiments.filter(hasHeldoutTuningRisk); + + if (riskyExperiments.length === 0) return []; + + return [ + finding({ + type: "heldout_tuning", + severity: "high", + title: "Final holdout data was used during model selection", + evidence: riskyExperiments.map( + (experiment) => + `${experiment.id || "unnamed experiment"}: ${compactText( + experiment.notes || experiment.usedForSelection || experiment.tunedOn || "" + )}` + ), + remediation: + "Move model and checkpoint selection to validation data, freeze the chosen configuration, and rerun the untouched holdout once." + }) + ]; +} + +function hasHeldoutTuningRisk(experiment) { + const selectionSource = normalizeText( + [experiment.usedForSelection, experiment.tunedOn].filter(Boolean).join(" ") + ); + if (/\b(test|holdout|held out|held out|heldout)\b/.test(selectionSource)) { + return true; + } + + const notes = normalizeText(experiment.notes); + return ( + /\b(test|holdout|heldout)\b.{0,40}\b(select|selected|selection|tune|tuned|checkpoint|best)\b/.test(notes) || + /\b(select|selected|selection|tune|tuned|checkpoint|best)\b.{0,40}\b(test|holdout|heldout)\b/.test(notes) + ); +} + +function findSplitProvenanceGaps(project) { + const gaps = []; + if (!project.split.method) gaps.push("split method is missing"); + if (project.split.seed === null || project.split.seed === undefined || project.split.seed === "") { + gaps.push("split seed is missing"); + } + if (!project.split.manifestHash) gaps.push("split manifest hash is missing"); + + if (gaps.length === 0) return []; + + return [ + finding({ + type: "split_provenance_gap", + severity: "medium", + title: "Dataset split provenance is not reproducible", + evidence: gaps, + remediation: + "Record the deterministic split method, seed, and manifest hash so reviewers can recreate train/validation/test membership." + }) + ]; +} + +function findReproducibilityGaps(project) { + const missing = REQUIRED_ARTIFACTS.filter((artifact) => !project.artifacts[artifact]); + if (missing.length === 0) return []; + + return [ + finding({ + type: "reproducibility_gap", + severity: missing.length >= 3 ? "high" : "medium", + title: "Reproducibility packet is missing required evidence", + evidence: missing.map((artifact) => `${artifact} is absent`), + remediation: + "Attach the missing manifests, lockfiles, code archive, and preregistration evidence before release review." + }) + ]; +} + +function finding(input) { + return { + id: `${input.type}-${hash(input.evidence.join("|")).slice(0, 8)}`, + type: input.type, + severity: input.severity, + title: input.title, + evidence: input.evidence, + remediation: input.remediation + }; +} + +function buildReviewerPacket(summary, findings) { + if (findings.length === 0) { + return { + keyRisks: ["No benchmark leakage signals detected."], + tasks: ["Proceed with normal scientific peer review."], + questions: ["Have reviewers independently confirmed benchmark access controls?"] + }; + } + + return { + keyRisks: findings.map((item) => `${item.severity.toUpperCase()}: ${item.title}`), + tasks: findings.map((item) => item.remediation), + questions: [ + `Can the team reproduce the ${summary.projectTitle} split from immutable inputs?`, + "Was the final holdout evaluated exactly once after model selection froze?", + "Are benchmark and leaderboard mirrors excluded from every training corpus?" + ] + }; +} + +function countSeverities(findings) { + const counts = Object.fromEntries(SEVERITIES.map((severity) => [severity, 0])); + for (const item of findings) counts[item.severity] += 1; + return counts; +} + +function decideRelease(counts) { + if (counts.critical > 0 || counts.high > 1) return "block"; + if (counts.high === 1 || counts.medium > 0) return "needs-remediation"; + return "pass"; +} + +function scoreReproducibility(findings) { + if (findings.some((item) => item.type === "reproducibility_gap" && item.severity === "high")) { + return "low"; + } + if (findings.some((item) => item.type === "reproducibility_gap" || item.type === "split_provenance_gap")) { + return "medium"; + } + return "high"; +} + +function fingerprintRecord(record) { + return hash(normalizeText([record.title, record.text].filter(Boolean).join(" "))); +} + +function hash(value) { + return crypto.createHash("sha256").update(String(value)).digest("hex"); +} + +function normalizeText(value) { + return String(value || "") + .toLowerCase() + .replace(/[^a-z0-9]+/g, " ") + .trim(); +} + +function normalizeValue(value) { + return String(value || "").trim().toLowerCase(); +} + +function safeArray(value) { + return Array.isArray(value) ? value : []; +} + +function compactText(value) { + return truncate(String(value).replace(/\s+/g, " ").trim(), 120); +} + +function truncate(value, length) { + return value.length > length ? `${value.slice(0, length - 3)}...` : value; +} + +module.exports = { + auditBenchmarkLeakage +}; diff --git a/benchmark-leakage-audit-assistant/requirement-map.md b/benchmark-leakage-audit-assistant/requirement-map.md new file mode 100644 index 0000000..df0e01b --- /dev/null +++ b/benchmark-leakage-audit-assistant/requirement-map.md @@ -0,0 +1,29 @@ +# Requirement Map + +Issue: SCIBASE-AI/SCIBASE.AI#16, AI-Powered Research Assistant Suite. + +## Auto Peer Review Reports + +- The audit emits reviewer-ready findings with severity, evidence, and remediation. +- `reviewerPacket.keyRisks`, `reviewerPacket.tasks`, and `reviewerPacket.questions` provide a structured pre-release review packet. + +## Reproducibility Checker + +- The module checks split method, split seed, split manifest hash, raw data manifest, split manifest, environment lock, code archive, and preregistration evidence. +- The summary includes `reproducibilityConfidence` so reviewers can triage projects before publication. + +## Research Gap Finder Alignment + +- Benchmark leakage is treated as a research-quality gap: contaminated results must be remediated before they can support reliable claims. +- The generated reviewer questions identify follow-up work needed to restore benchmark validity. + +## Safety And Scope + +- Uses only synthetic sample data. +- Requires no external services, credentials, or network access. +- Keeps the implementation isolated under `benchmark-leakage-audit-assistant/`. + +## Verification + +- `node benchmark-leakage-audit-assistant/test.js` +- `node benchmark-leakage-audit-assistant/demo.js` diff --git a/benchmark-leakage-audit-assistant/test.js b/benchmark-leakage-audit-assistant/test.js new file mode 100644 index 0000000..110aaf1 --- /dev/null +++ b/benchmark-leakage-audit-assistant/test.js @@ -0,0 +1,169 @@ +const assert = require("node:assert/strict"); +const { auditBenchmarkLeakage } = require("./index.js"); + +function test(name, fn) { + try { + fn(); + console.log(`ok - ${name}`); + } catch (error) { + console.error(`not ok - ${name}`); + throw error; + } +} + +const contaminatedProject = { + title: "Protein Stability Leaderboard Study", + benchmark: { + name: "ProteinBench", + items: [ + { + id: "pb-42", + title: "Thermal stability mutation panel", + text: "A held-out thermal stability mutation panel for ProteinBench." + } + ] + }, + datasets: { + train: [ + { + id: "train-001", + source: "ProteinBench public leaderboard mirror", + text: "A held-out thermal stability mutation panel for ProteinBench." + }, + { + id: "dup-777", + source: "lab notebook", + text: "Kinase mutant A had a melting temperature shift of 2.4 C." + } + ], + validation: [ + { + id: "val-009", + source: "internal split", + text: "Independent validation measurement for kinase mutant B." + } + ], + test: [ + { + id: "dup-777", + source: "final holdout", + text: "Kinase mutant A had a melting temperature shift of 2.4 C." + } + ] + }, + split: { + method: "manual spreadsheet split", + seed: null, + manifestHash: "" + }, + experiments: [ + { + id: "exp-main", + usedForSelection: "test", + notes: "Selected the final checkpoint using best test accuracy after three tuning rounds." + } + ], + artifacts: { + rawDataManifest: true, + splitManifest: false, + environmentLock: false, + codeArchive: true, + preregistration: false + } +}; + +const cleanProject = { + title: "Blind Materials Benchmark", + benchmark: { + name: "MatBench Blind", + items: [ + { + id: "mat-test-1", + title: "Hidden elastic modulus sample", + text: "Elastic modulus holdout measurement kept in a locked benchmark set." + } + ] + }, + datasets: { + train: [ + { + id: "mat-train-1", + source: "2024 lab training corpus", + text: "Training-only polymer synthesis run with public features." + } + ], + validation: [ + { + id: "mat-val-1", + source: "validation split", + text: "Validation-only polymer synthesis run for model selection." + } + ], + test: [ + { + id: "mat-test-shadow", + source: "sealed holdout", + text: "Different blinded polymer synthesis run for final reporting." + } + ] + }, + split: { + method: "stratified hash split", + seed: 20260518, + manifestHash: "sha256:3bf44d1e2c" + }, + experiments: [ + { + id: "exp-clean", + usedForSelection: "validation", + notes: "Hyperparameters were chosen on validation data before one final holdout evaluation." + } + ], + artifacts: { + rawDataManifest: true, + splitManifest: true, + environmentLock: true, + codeArchive: true, + preregistration: true + } +}; + +test("flags train/test overlap, benchmark contamination, held-out tuning, weak split provenance, and missing artifacts", () => { + const audit = auditBenchmarkLeakage(contaminatedProject); + const types = audit.findings.map((finding) => finding.type); + + assert.equal(audit.summary.releaseDecision, "block"); + assert.equal(audit.summary.severityCounts.critical, 1); + assert.ok(types.includes("train_test_overlap")); + assert.ok(types.includes("benchmark_contamination")); + assert.ok(types.includes("heldout_tuning")); + assert.ok(types.includes("split_provenance_gap")); + assert.ok(types.includes("reproducibility_gap")); + assert.ok( + audit.reviewerPacket.tasks.some((task) => + task.toLowerCase().includes("rebuild the split") + ) + ); +}); + +test("returns a pass decision for a clean project with complete reproducibility evidence", () => { + const audit = auditBenchmarkLeakage(cleanProject); + + assert.equal(audit.summary.releaseDecision, "pass"); + assert.equal(audit.summary.findingCount, 0); + assert.equal(audit.summary.reproducibilityConfidence, "high"); + assert.deepEqual(audit.findings, []); + assert.ok(audit.reviewerPacket.keyRisks.includes("No benchmark leakage signals detected.")); +}); + +test("emits reviewer-ready evidence and remediation text for each finding", () => { + const audit = auditBenchmarkLeakage(contaminatedProject); + + for (const finding of audit.findings) { + assert.ok(finding.id); + assert.ok(["critical", "high", "medium", "low"].includes(finding.severity)); + assert.ok(finding.title.length > 10); + assert.ok(finding.evidence.length > 0); + assert.ok(finding.remediation.length > 10); + } +});