x / CustomBatch_fixing.py
saliacoel's picture
Upload 4 files
2c92340 verified
Raw History Blame Contribute Delete
90.8 kB
# DereVisionScorer.py
#
# ComfyUI custom node.
#
# INPUT:
# vision_result : STRING
# The complete 1-110 response produced by dere_vision_questionnaire_prompt.md
#
# OUTPUTS:
# 1. dere : Highest-scoring Dere
# 2. top_3_deres : Top three Dere names, ranked
# 3. scoring_summary : Parser diagnostics + ranked score summary
#
# The exact hidden 122-Dere scoring matrix and the exact Q11-Q110 option
# texts are embedded below in compressed form. No external JSON files and
# no third-party Python packages are required.
import base64
import difflib
import json
import re
import unicodedata
import zlib
from collections import Counter
_EMBEDDED_DATA_B85 = r"""
c-rl~TXQ2vk|z3BXnHQ6qmrPKO7$?Ns_N<1_Vi`C)!R1b*m*#aNfKQkz(oQ@vDe0If5dq?e{X-
uhQ}>4c>yCm+~bnSmu|8|f<R<uc({MJ|M}IIX4kH^o3DQS>h-
^T{ne|l&X=2u)x~n(v|s)B|9th+&GMqzHR|8rFF*S)SG$$_a{GDfzC7&PgZ=u$^=f0k{J8AC+PVM!QT?C$a=UY{@EiZtZ~ZU*w(&3e+wB3|mk0mf5B`^axBS
P-e|h6x|97k2mmBxx@0U0FXV3ok|N8&V-mmtL{_SJ4+TY_puFkLBtNwoF-}UdiCHQ^!4u0?7*6$Ao|J6U;mw!0>XunwAFa2NoZ_C@|rfK!RH&@-
i|8{$E(X_4l=Re;2<@nP-w(iybv~lm?Pp!K$fAP!ymu>6c_h0qjJ^R1T{<^$yZ}qRs-hc0|miltP_dj#kEVpMLw%fJ;=6B6<@4xx$VYh3R_#c1iuIDdXzuv#
Jem&He_wHr?=Kk?-t^cR)*7@5%-
G}*4^QrY>{B?Ey`N2P_uRpI=_>WunLH_S;@9%Bkr|Yf%@@8qj{G{&V?!CDmxY+L8zh15UD{Pw2{uTaw@c(>!*sbiB?e$?}zudbg;{5#DU$6J~+rwvnr!2esu
lfAhzuCcM$8YeOH#Z0Odw*Qrx;6RJ!QbQROMjtm4p;7jH_O(q|F!?;-
@D%$+<Jd+TJ^5+1N?OJ$v@Jod;g$!&*uBBze4ZTO1K~Xw}bzv|90r^sNb#pX7RgjhxmEpUgEzs{y|w@`=@95+5hP8x80NEx1c|E8^|A5{!`+YpVgy_Uv}^J&
-eZn{<`el($_1$fcEyjUaj>lyj=Mu^jGYc?cvk5``Uk~zwMX3?QN@XBlVSfPVMG~d!gOj)U{cf&FxxkP<EgBtbbDN66)tR_7?DW64>9?Plf(Lb+4bB$54HBj
nCl!`|7Kk<$kyN^{XHM=U4yn`ZfOFPx^oCa{ajI?b$!VqPK19|Ku)<{>E?J`Um~)t-
o^{xcz3=`$4~r<BRx>T>q!v^uKZ2IsTa6(9M5wd%FHlhu)95T@}CaJG=fJzqy<5-
2SeA(CzU0KRw>$e_TAd&6}V0JH7q3_t}2mUpl|xo1Z&u`rjOSKlab=8@u)EA9{NC*FW{t{*V8`R{;G2z6juda%Dh&-
S#fGs|ENUx@w^R)8m3*{@K?A{2gBv>^JTq(%*KKq5D?XhVENe9rR240%5+}_O6+)684+>?$7+K_n}>}Fy9RBS64FhH;<~uqkpx91OI?49r|Zn^)PSI6_6hny
%OTS(M6>9=DQ!$g~WZMONskN7Zd+#x}3Q0bU|_7=#t{T*>_KGudbMX=t_&d+Xwf4S76Ld_o&9`f9}eR`PNk$bDz0l^W&mdZuAv)1;<`oS98p_uI!j6r+WsTD
n0lgeDSf{)O~LU+xE5rUxUn^<*E>V=j)JuiLMmcEuy~-`hujtb2SNH#8oA`oY&o7?n;ySZ`;8$-`jEjIM^S3mGa-
|LS??|e==XK>~rX!HCXg(R`1c%Rm&{*a@D^SpSvIJs+axlbN5DF1@l|{*51UnjQzOi6*BWfu9p3{=&G6DXR)BUEl}4qf7iLMyUO<CqN{E0={$FPi7RkFF8)h
h<UvJ)K}n-kwC4-
jpq{~?p!Mt7v*j$hnhlB>L{zh3F?+t2!SlteU(4XRV)lG3gJ+8w4650nm_blAgP?MDq?*Blat06T89XRx@VKHKDQFN@(Vj18{d)FfIU8HeqKg@jsu_$ZX9Cs
iNHK#!HG@Gp8&<RDiWyL<84Su9NY^uXzM#R8iZ&={FsNup3fiEa!JwcWt!MDCp!Mrna5;l#>lyS48XT!;5LVK9747+g)~{#KFKB=j?L%mf27{6Y!<zPdlXj#
>gTN*Yj&y0zFKjTVY%nNop?%uZg$;r#+ptZ8=lis<HVxABY0xii&-
ZCTZ5q(~G<c~|d%jPDVOfLcIyJ6F4f?h1`9=)}#SI444F=^6o^RG*P~Tu!;Nbaw4F(nNg#tIIZx71b)79-
*tH#l(K}f3xgX;Ett2V4{Ff48`sBXjJHmGfb()L`ZcBD}w^=Z#nwPRfxxk-azS%c@>w2&_C*(R-
D&;naD2=387hW2RT746xA_WaE8xfTtc@6l+r>`0e3u9m@(HVuySX<=;|B<j<it!5BX&W=^Hv3=SLZQ7A8?MOX)wwzJhv}bD>ODO|tErSuo44A9g^TmwVsX<V
ycBGoY^OHyRY6j1iGkCt9g?4F2rjEzDG-{J}yqv*v_3ZgB?fE9{xsvv5iw2=Rnks4k@%o#`cv^5}qm3U!@w7zqN4D`JFc-G4CJn;6v>-
4ovZ@_x(IRUagcr5(JsKHHW2kDO^G9lbHf{nLHh>In(UQ#{1;&s3)5qwt##PmbSQ=|p3mQEJcWRHPj|JjsAXm^prlQFKYSB~2@#K(cz-cgjJgrJN4U*w$q4l
gfG;IVsq%t%ukRVbJo<=EY(r6mv2vQJFBNR3`f~P${f`q9dYC#i7AdetJiH|IBS}<KidH@+--
O}M{q1?w@cpAh5YHYkm;slbB_ZR}Hh1Im<cv>inpMxM0Hgx1KXh|ToXcj*&q6P#=jY8x<j;X~_`9qof5JY@TO%Qp$TjQaJ93eoCDrk_3AW|Yf<S6?w3qu50F
tvI(B4L2q5r&Ay)4m;rr_Iwx=IJAx%=mG1i<WozI6-
aNOy*B1PJ`ekZ6@>cjvrsb(;%24!ocQ_A3u_swSZntj>%sXkw4|!5%LZmfoJl_PJU$J@ypWq<t5j=gGb24ePqDYWFR$`MvZayI9{LjY^(MXo)$cO%ujg)In8
zS?6DTrbyk*0;yw*HSR%!Vt|5VmNL8#yVUkEZ=dq-@V;<6Dv^)*Scp5uHBp04GErLioL@mBi11UgFh9Clw43Sx;R*dGF45{&UYAHD);uMjLc-oO3P2*{ctaw
@uIBiDL@}p^)yEJwxKb$0&A0`8*r6uzd;WU`>Gyyz~W?>wUr_q`<&R#9r!WiNZHAyP}1Q4|#%SR?Wt?23FsOF3!xMnJA$sCM{W$pO}je#O^q@+P!G%dcSg_g
9SiY9=kQKyf*fErLd`GM4^CGmQMpn?`Tc}!vTNOuw8bL5|$9%G$E=BeXo=A+=;k;Zx~#_bWpMvo9MdnDOC!V7p>O2KttK@*147)c@&I1MPg$8@HT91=B%WAw
+{J~A=-A<R%Db!lTm)F4rDE7A9n1gQ}!8#kbKns^#Kp-
=nnB|HsE1Js~Cfm(J%Ei61OqdqNd)C^O0S~Q{tM7u{Bw?}T`V=$ry<=H)w+B9~%$8bCi$Uer!89o9*c>MyNmNac<YGxu?Bx*HrA2U!NL7Mwm&ImG!**^~<E2
KWcq`8kkAU{6F(_}f1Kp8*AvK~t%J&I}5!Z?p{V(Wzd#lbBa0Zij@c`T0fC}7542;~uq;XIb%Uo6B90a3~$Yr-
{@il+rSh|oGT$gfX>+z!TKS=TWFwb8`KaI#1NY1hQSnP3OwoQ987i>zROe&FOj^81KD9t&e)Z97`pj<slFCL%BzP#dF9dn2Py16s3IbADPZkN=eQX(Ou{MWH
5;pEjkmnG+YN5!2ITn>87;M^P$&I(Zt%>(jU^S|*MNo84oyGk=A4kAf*PK(XZyR9(yG%+P=|8cT#u<j>cljhCm<Qf4x8`Gq}<BQn%R%pj#YwZKr&z+Nrf?-
6K*kG$qsvh)x!E<XuX<2A>MwtNg})nFlL`ACYoE-
~pkv{j=H9>;O`MU$>+y&5dUJRc#O>bevfKXLHLN_rfhod!I38i(SVQqkr>(8(mI#Y(Sv{T?McwK_PDMQYR1aURLW{7?Yt5oW{35u8Vjr+q&PPb<SqBt4)8;}
JDr<v*su)MjV?B#j!-
IyEUL5lEH_n#Y`9TBG)St48V6IP%lRAZnAd=7*Q?w0Jf@h5E>vH4`~`Je8ywh@3lS@((G(!ni!EN12?NlS~B7=Jp7*(IXd@Ry3Xect!glthlB%Y3z}pkt`8r
&&ToO$5cF#as}70NF53~9n(i)&&M)iL50cuc|0GhvwY-pe54pYj>6LzEcw$p^0UHe(U$zxfN3C#rj?a*ossxR;yubZ^T*TqLo-
2hg<K13)1EJ9)UX+r^fVAh)3T;qzksLxkU*ctTiIf*M1<TPVKFDDNh;Jz47z5{P|N8iGAF1p+dayfiLeROY8^g~!qW;#n-
Ly9R*^ST*6b0YHEMCZM@UY5Jkh|J=@B0%lsF?|_gLfju?D|KF^&inMG~olBf>3Bi?n=9?jmw>B$3%hq}oK#3IjoTO+?6e8oS|RE=ztfecIIIX~0bpiA)Afji
-(7(pZ`_$gWR2;VzBW?h#}y`AZb1!6IjTn)7>nJ1##B*t@mdevf=Kk$8Dp?p|&5;E`?a7}KfMY+p>Y#$t?^VaQMuZPy6$G;wtr&?3|rKsDgfs4+*VMP^+CZ)
MAhr$KzT#;dtb&g2(s)Jija%mJps%;X=9rcq2EfxS^<<vt2H8BdkUAM48xf_9JLZ2k|`avu3-kI4}=Ioe|m*)^miK8{biW^dEV#?unOX)Ns-
#5z3!(dv<T>R2V7He&F|3#g4Zd=$t}ONXW<wR`0A#fo?3hoU%-
4C!ejiy9l59|&yzh*pi6&>!w&%tPr<W{gG3P6G;>##7h=%^pkidW58S8U#@uN7po<>eIgY`Z1oyp}gj`drX%#Gg*Zi#2P-
1A3nw#K5}QMNjN@=mbBC<YVkD<PQmRluv_EE1m)4F2}~bzI6m^^r`4ml23keS?fIA{KrNciAI%X-
m7f;G5D93}Dp6dsO&`;0uA{Lub|{U#pry5Zghf`_HD`VrVg7g=PfP3f2njXUCr1*2BrFj=F8?Sa<9L=xsbu~dIU*%+`C0t2f^}+Db3{1g){Nd*1cu1bM9`12
DKmk9S_y%m$+c<u(?E*`(};PXAW0E9F=-maY157vK9-
NB#g?<sL{JE<XT(HMV8YVGB0+&)pZ3k`1b&aH%tXZE(*)tP+3%4weVnG@V+~;1sd$Ny#*Z~RJ{Dmn!tEvUK3H+h0I0!&*PXu#6JrXTmV@{hZOku-sF7%oG5W
M5C5^$BpUK3S9ZxG_?0BrAaVK082_m$T#)hT=3!$IUnO_!9V;5ZWCR_)PA0hYru@cKiQF$6`%ypoT2+*gGslYV(=_3>~e#}FBJT`qKDz0fXk+7WWU~jB&6Or
KgV|LCX0ZtpGPlI%LT0Vi=?Dv=~5j4(7q<mBUtPBx0NbQtO`I8}Pv)!X8p7u@Z{4`h;XC}fnevGyfk#omNV~$mZlgRk#<3#ba2)<)RF2CgTQMRV>Pajiz^XD
=X;hsLeFGF!Hho+r6Lj=mFh-77mz;sF)l*AD^1;uqdk)MMiLiZ67Wr*++L<IaE%LCMsc=88M9^>Ocv)etg)-
*u}W1ygEP$rxvKY2`U%+J8*m&4PN`iL;#X$f5UqY<@8PyQP4G?IzP9H4gcfEq|6Y9|D!om_ev<igWJ;WS`oh%mYGOX6w32&j$1)4rAQd(1=ThrDe5vYh#Ij3
0}0=7-
|h{5b)&lx%*QITny=8gc$eGUo@1K8+hsg9`O&qFL80`m{_HjpU3qzEh*j9}Bj|f<z>dYB(aJYFc1`TD0fmF;D)CO&Xad!rZ7$h$JFh)7Us7N&Ft;2qM6yPlG
z~G`^@ATE=yvCN0*IKb|2n6*w)SH`cMSV;YXg)U;_uVrftbm<Ef?p8R2eS^~ev6OvrVlSIn$d@MNT8hG_-OwnoevP7~7)B-
IZ6Ux&@6gCQ?mX9J*v_MTjpSE}bPdfp5T2XN`lx~ew*`}ThIzDuUJb&c2$Py2|j<0Dz%LV1_)~Jvg5Hn|Db3sLUBIyyedh}^kDXy~;M1t!Xg&-
2vr?GPRXTkM2en?&P5J<}p37<Ry!Iodz?vcU|36D>M;OS#($@N4O*B@lvv6!ciG=_-
K^s(ghv?NUaJfp`cWv3N3cx0VB!lEi?tZ=(W5=Df<5D6bW)@6>BQlAEOl{8+Ch*UiT0-
9F4iwLhWO_U=7WRgfZXj(xu5u!JLT+nqPED`PiHL8sWPemI~c`Tr!X*}(_V)N5#7(W)EPh<6aoQ6FXj2Ea?Hh%<>05ygIWHRO>k321S{z&3JGEg5g=cj=noC
ZbsJ#w^a6vM}Kz1qy@Pgc@0)A@NM*Uy)<1Yy@)ty(JDBe&{0r{^QD<zvoPEu0`Sx}e3@GbX>sBGP6mfzxKi^~qaf)j{T$hSbVP2Cd2+i=??O$sG$yW%G~Fr!
jCIDRz%3S^Rt!B8k*#@f9swK}+F_l~0`(Z6PAS-
)FHdhF~NO<dkU~MvvkB89VEdMRFZ6c%<QJFutHcOhtnTzsEPD@U#(TBBa_DIDRBdAIlv+7BqW=jKfEWP^QV4iNx7GCKx^j6Gh}vH5OKX<oFR7s3Igve}xfh@
m?ZX{2rg})<pasb6FSzGe;z+LM=<HHrqXl;AzQPwMrCfMVLL7B2eSjr$KIk+IYW5M!QEC<M)`9A;Qb%FWMceU_@<N3bh$dE7u#V0*P9AB$3mSs1bX#kMXWpa
@sU@uSY&lERK-t>@@y_j#x07$e%9eT0nH2&PW94ME>YfR)QlIiy_v@I6a0t@{gxHN*Wjk_h=eV`#!loO-
`N`%H`)8J<@oOP_8`f$mlUzcO5%<1j^_!s;t!?pC%%>4oA}DGOmGt_Q+7zScZ=@Du2ZAF`+qDG@V}{KdlgoNN6Z1EErYS+=e1ys9GGT#_T5&ZO+fh5(y-
UM2sKva70)rA_WD50)s}4nIi%V$z0HEERk^`YRoK=I7BT>aUDE;{E%$=2y){`lG-
}AAAgvEF)Y$J7|TXn&$GuQv&Ymx8o!AM!Na&_XRP$;X+@788K#d%@icbDH8(+orcje&h@2onBnnX*SJdM3(+V{)u3vFoHbEq1%5^+FgcdWy<RKzS4}mz&<2b
*^A702$tC`HtI(y9Q_{i+d9|ozVk57}<UJLN~%Scb-2~Ug9PU9aua(F(LQP|?0M7RczR5pJ#X)^+Tk3iu+Ci8pD0j34gL|8|Vv2dC!OC-
v|m>^Gsq;MKT+Dvw@M?sPZvqFtV5@F}@^FwJ=zsHPbA{35@D4xc_c_g)H1VoLZwidF+V#d>0Cy(Rdv`AsvSS$_L=8k26X%LU5L9i!(YIm$6O!-
+n`DyXiKq5V+^v24VaLqY<<igXau2`w!)2eYW=ETz^Ng|;^GvW@$e75}KQ$Z(=r@<n|Peck)i#2`BnF*SVCL*Fyn{*<m#?#)8!qfQ4{Ly$C!R}FN_832Ul=F
Mckqio~!^iZ&pd}bSatt7AoIjRe_Xw$YBDCS7$n0_Udklfo;u#{4k|L7APNW8H+U)k2T5(OBKF-24AkrSQ`8`4{hL4HhG;W5-
hm_Mt5~3FE$v@jYmhScl3og6I(G_i;JhG;zr3#z@295~(>@k=l5}%%y!XK+tvq!o<EuJ7!p5f!PG}n*}PpiuE5f(gIGgGvD%w=E9tho;Md=&RDuFvz48BdGl
i16p9ef#wbcp4Q>qxEVzTD9E9SdsL{tUktcFb$%p{7Kl4QCTw*@oB{Lv^dwtTIi1)>__2NjoBCr$|OHhyhMON<|q3-
vM@yAJ&fzGY2&AlMTyf2b}*j39{CDd!nEsTkeYBogZSYidqv~&FwPC9!GhiIk((eAIDJg%Ad*?424VBZsg57n{IOzH*X7S1fi`?R_1PoY8Y__V7&>|kwGbgH
)Uvu`2~8hG^l7w`#%lLCfr=(*Aj0kUD9U?`WQjy4&5Y$eMuvhW%em$%XzaYlbZ(EpAx~qb@e9Lg5{CTY6%B$YBI7OjneepNY<OB0;dMci$6zomkvTshKTQ-
+%dfgl+NVhXYElEp(@F<T7!FD)Y#ae<<u_`SWY9qJ;|qR|MW@XKdSg||e9Y{Q1&g%ySk*+$$Tex<<Pa$yKeszp#q1D>r-#&-
bzO4Q^%yq)2af5ZWFBZW^T*Jj>+$o)BF2xf;MQGdm0gp0A|PqXU()z77eS<q=_6tI$YNp45;wzTVGN5Tk)SXdQai>F`It82TGmEH%tb_y_6Rle9?S3%iB(-
gP9{IimcJY)kz6$`Ur7TJpq4S=x<J~aOhI#a+Bd23G?*-)26ZB8Ckv?YOduHmwHZ$nj=N4ef}9edHhDa)@QNk~sl`WK2iCL`N}Lg^XiN-
|n#x(hpjy1<IyONqAEY)TYU4{9rJ~JvT5di+XU4T8ng-HvTGou~8BQw|PMb)?HB^bG<%iQCA)Z!vMJv{kKaw6olBWe?X|R}0`MJbtxe8jLSXvSE5Xi<4sS-
@9kstD;KJ8n+{Ipa8wZQzeV_wF!B&hLLw+te+5+F7H0c2=tJ1LEtU@|DTnX!PEF{FajrXLL&==Vs)(;y|F#!P%9kwnU|dxS-
b_%zm5jZWj|vwLLX@z)hL!&S;AN#>VGo8hMO#~VH-
cgGSld}MGi4$e=L9X_&Zu8+(fXU(+?KLlb-`9~RJrI1{w4+MqOP+IBK5N@_(cpfMO^7!NR*1+M6l|BxX)9f+SznIDIk-MTnG@cep5GhJ;4e7OM!3+^eg2-
4e5ebTjh~uM}g>l}pCTsYZDd$=?<~k)oq*!$tgf(eG0cua-
X^Y2r8b@Vgj=IjQPaBV=jUPOMnBY2AZ_Tev8{4Oyo;OxJMI<gYO|+s_txJ>a)HsxB5sex!cWMxuo<{H0`1NU&xEV@z8b6mmkwA@?B9bg`hM`-
NN}GXL0wfPdq*$36KdYZ*4hhM8jD-zF0x2RuQK`|UkVJ||w2N_M+6)NeX|p~}Wc~=F2auwMkF!Ee!XB#%ecFj7g0c@E1?c=q^l4La$AaVfw0xXLUK)S7lt*C
XJo4((<T9=y2j?-
+LWGI+2$P^a=3(#`$$6Bd@WX=H7ONP}V=Rk5GUD3dY2VTP9%*=5rs<;$qDCY?rm=f0G3h$CQ42@b2;rdQ@gt>M3)iOw=UvA*WC_k5OGr@TXx9?urvZt}KPna
!n8j&?(PKWpN8kX|Xw90eLajP${!-gDD7;@wsZoQ|5vj#DYupMo7CVswyR~AQHThmm#P2aW5|qT{pVeuQL7qlH(ij^w0fAadecFl0(?-x9lL^#-
gZC&bP=k~TwFCk+h~_;q+C2i3-
y<yYXs#)ES|mS25>Tr;JdLeE8>P7Bb>s&DBLA`eEKr+vEae)gRZXC%RTZ8_br4C)5TO+{83*IM1d#}R+M5J^j|4o8+3iut>=9T3)KX<#iy>-
q4q3F?Hs0(J1faB{%^o2gSA?UwLG%EUY5tgM{78+QfkhFPk74siM#IPBc-qY77h&^9+VeBRX@PMwleXud(`H_R(q?!5JnsA{+(ahj&Y#%tk!T_Ui`hlwgk40
aWJe~h<Egob6k;Nh2}}#OE+%DxhJtDQbjL!mG-
(f!kM(9;hx$DRhg=JV%*1*aa~nQ}co_5hhZGib%~RL79Yj(Xh>U{OY7Ds!&X@^ii3Aolk~tOwns&+&*H7lBeNWe?6;auebZU9(8YFMmSfRAJs?`#mCR*1@P^
U@wK7yD)jl=bkiOQd$vPtOEfLPMvHP@_hGqhH1j++_RsxeL=6WbV1lg>Yq8N!e^Q+C`;%378-
ZU&gx{CU!5qAS|*>ErD8SSCXxmD?l4;c7EQWTuFeoe7$dB2tgxBhA9N4u(ju-
(#ranmIm=W*|Z%@)u+v0%`PV*%*&})hybNKeLAjzlF#cG%Y&i8ZxmS%O^dGW?YXbXY{NYxsdC)dIm4)(|#DGPm8Z?xjHq7l&BFZTmGz>NCKohQ=}*gB$EIes
v|)%S2pfoP<9R^hdqC6&P=d2Ep=gw1=M0=LB;da1pFRJY<>p6#}w>G!EUXRW{uZQM6y|%oZllXQW`#1CQ@T@6q%^uBP~8H%$pyi3?Ie)9)V)`D8~<}-
|(?U|B%xB9-}yqW6Rlz7(OOs@w3w%VUf-
6u@1XONJbHn6R5r9_sGwAgji#&Xc|8ooEDyNO~_9R8#yw^fpW^OIW4i`{rFQd9t(2gPtAG^CWss<Y7nPQW9iL;S&u{u5e~b@80B>`3z4uM4fvWgi>JMv^=YN
*)21y?gURXBSZE@%K8>HzKg-j2dbKI)(-;y#VV0-O@-$w>b<*KuMQJlI-RWbI#jF~A8iPSrxx>d<pH_*3F=+rf3)D_Vpf+0=&+@d{!FaSjjmUY7u4uDBjY4^
ZS)i7f^H?n9F(c#g<TTe6D%5yMkJ&koh^H;Sp7FHgcv?wzk5K>c@kmvZFz4qkYuVwn^3XI`6mR!9!)c|$X^<CBgCOeTjHhM5)3_-jWw||`0-
h!Ur$Kpm8cW_xb$FTxn?H}=BR`w}L$>LosNVWSrjH~zEzk5ZyW|=clh#}(m0TBysR?9U^F~}VPah#-
{K$}S%?PJ4E3QAz43WeP5g2V@%pITx6l1K47$TwsktrD%!=gk7V@|tAek_ecpf)=gi!nqrp7wgi(`J2Inff%C1!^aYsLlGcJb>CM;A!l=8Zb9&1hdD{v&UHf
V!1vo^XRbzugB<e7BP5a_AVCY@r&bWp;O1$E-jey2*Fk&!RA=9ME=lv7Sp7WjroK6G+=DhLh-a0^l3G-
`31+1K$$;ght$|J)G|=|tJ0@2j~~fwem05P%;wL)=9d^hmJO&$>eFO7kE|9V#E_ZXq{s0z{%9~Q+Czi_rKO`i#_H3kU>dtPEql&&x|nN-
=JAt)t|5HvSO)P?-baL&_Q*MSq=Z~sJnb9#{Iqa3KgdlVVXEWD*y&>>!$&A)`WQWYgk;l40UWK6Oi+RGBT<|t%6bGD+9QWTjWuhAefpR-KMlC~{8{b!Cr9Vc
m!B3<)oKsBuAa;v$`Q##<|h}nsm7-TvqT__CKARHVYCt%gQtn+r?Kj-X;4~yf@_Ab88Ie5<MeS9lol_#o;BAj9FbrsEtBTDlmInYB-
C7k`0NolL#`>3>#X@{fq)u;#Gk6B#RsSrjHp31kXj*#TD&n<Frp^p_gD(1mWs$vENex?f{OTvaQGKT0&2o|+P9DKv|7_<#N=t?)oHUhEm?S4K51GBeVSNm8Z
62+daN&Mh9MJ_iy@LQ=o(7Vrv>9_{1lO7a9U<GElt?<$#6s<cK8S!+{bBPX;286pVLf4+QPU-mPown8W{030!4&JpH{lvBa1u@81-
p^14u|TfrLs&kU+!L3L%P!wrm+SY8(w)XjNmd=9iVI@lr%s^l9R8Gs(w~K@^d^cv>Egh?Lu7$ztm%Ypt_kX^=5It$M%5!0BU&YHOn3V?sD>l6jz`@U%s0JS|
k7Hsfh&@ifQ^sKJb=aUg2)fEp}HGkSz75VeGW+9-1@X5ynLpeB!}v0E62kspcE$6z~=^yXNFd1K{k*RpkMke2$GI_esvM~`K=V@)P(hG-@-
YWheueGE{jF#>9YxEUFJ8qL5s&_hJfz*weB3v>`EsY^?o2Rf1;Qe=}h8yG{qO<H`qbyCtJSBnO@dNjE}&?oZKvL%8}-
M|<YwKy0bnLvuif{Let3O8yz)W<9;HP%k8=0+_ZSF3^~!r);X)uqWqf?7Q7yO;1Zj<lKC@o{#1{BbcGKBjKg;t;iThL2Uz_yen%bll7sCO_~{`B}qeviUtSz
-h78{1dbi8BY^Q<&Pz6k0q$Qj@GCZYR{jeO(V&#Cut_aKoy~+g0eVd@sRnma`}PAeH6)0%fk?v2_l6PM4&2$2+S0bnIe)WKrJyvBs)WdZ}<o@`Dwg?pke$FD
3u@*t+bY7h(IKf9}<#81clZ-0yQX($Ukc8n2p8{1ZON@A&CeIt=Xk%2^b<&ZJHcMghCOqc-os$`Dq+X5%#c|!nh(?s3MbQiU{^=a6**+Qp`jq!xO2SCIZ>%{
6cXvkMXpW4#u%wBK0Y*tKjlOelkBZm!GFko8{J_>=0PYg6mi|e*(dEHa0)V(fQ@`(;(6Gk%8_Asrim_@oBR@Esh(Kf$>;lRXaU>TKTU08N<`kRkSMfX|Ry=5
Gjix0y9M<i#QFWNg{BvQ^(1u)1bJqBh2!&F$@unr+qJkr$uug=dhVXVKY%hjV)|u)c7%;#t%~?KBf(usR~F7r-
}%N&7{nl$%mx@AM+6?a2lk>(+Kq}Svi}igE1`1w=hoZU_2Wb&$CBv!L{`C5$5qDQJ<EL%}?;;PnMoW;5!zUoff9Jj%V~kMU;MarbsSOt&+5vOqGo&w+?j0Vy
|jh(`Mp}S|U3C(TeslAB!Ird3;1*QIh7mun@JPGSr|h5<ko={*$qL%#sK?llWhpI%+)ahm!C#27Ov~HxZgcmYC-
w5ay7~?HXs^3<+10V2VuKO$0JAMdF+vX;n?w_>mh^OAt3h@yC)u)Tr)Q!TD)NrjMztMDnPuDScYBfpKh~Mj}2!m5rJV@iEd(1fHKh2G_F$<HvX#V-
6SN=q61d>>5f)Powf4L7e!QJnK4~%^!@X9qrNLIU<zdW1v4)BuNCuvqVDeLndWlT)2NRzwSDWBmyKbEe++7f~OJ3j(N<BsXUQfIoF|+M+kN=mWa6yqloZ^T!
(vzd@LjpRLVo7B*Ar9G0W}t7|GxtTgzmKAu+`a7PH%9qCn88*gd8+5h>U2F-s)qQ+V3jm-
5pRdWpbdlI~c!$^7|^L<og#=JMC=jy0QO5xTS#Mk3*bt!6I~{`@pi1LHh={&Zp2*(v?J2F4Ic5ee?vqS=pG$&dLn)F73cNL3MP1Wtb_rpE1HoY6=`C_s%mef
%JtaZTZfOqwE+gUQc85#eV%LTZvoT8>Cyw^k5CL{^_x8$~2KJdK)k&Em<QSfiGcBa%R$c8ac8@e$WWxnjX$w#BN3z(2cU&8}E1;<Oq~vET_j?Ypm2<fqMskA
jAeQ}ug<>I@%Yu5EK|n;jp2T;w%;l-H-x^3zBPwP3w<dIDtrz-uwv$BON-=w>1lN}U-
Gqw!Ehj+Qh@vIr0<{jo+OG=Un;Nd%a>wLp1VEd4Pu>^g0JS|Z9L1hV;adp$z2;<Um7)bi3ED`7pd89l;c@=lL+$EVE(#^n4oe#Nz*;JPrk$BCIe#uFY{K{Z~
pM;?t@6_!{MF2)R&SVsz5;ZBc4Xa0m1#?-1dLZcRksEwzGKxjqNc-rgw@ia&ic+Cu{C2@O<ojw-kjuq|pC_xs1=|O7Ih#HGS7NgxG&+suGPvagxLf-kK44%f
p=6|2Dq5*@4afKX_nIe*;oPjWt|3s@96qs@yO6I4gT<773$W${9dI$uyY9PiCIhqJs2A;+Vr$P028srAlATgpgX7Wh##{z!0#~D#8!t9a7NF><2IAwSmh>RX
#QAv7Qs5=(O`#o}^Y5Bo4!IB2~Yg#Cp#=&@m3}sEg9V-
k`n{HJTPP(ReJ`&ACAZ1nKriqjTr%^nN<vjT{p7zZsJS{$LhGdR4xvXnQ#O9~DiR9(;v(fq4glchP$SUevGKCsY($j>iTYTP(WGpB(ZH7yqmem~#h;07%1$v
3hSejIJS`v5uOhzIgObscM_Lvh(gW|2RASIjz4EzudK&>h-5d!rQnDWzPJ^5!#ei37=*^{4(#y?d{er{=+<jheZW`-
r#T3VhauTJC0nwhdbjbvdA@ihMY8Z`!2{%M)=rwY0T5lpQvB0rx>4Km5p#Hjq4Jo)oU)T(kYj#H=&r;Zv=`&J%LlXb^RBTmcWja7v;mZ+TwNVaV9`m{`4TS4
>361}n5)oHUg7KQu>>F_jQC}|ApG{K5C7EfahyDrL@KM|ZJCrwLB3W=^{M<UaXbZ1p&f>Ob>(cKvlO5-0u(yAH9{BdN=46&erm^=-
_^l714*AMZul*31ggGkYVpq1u<PJ_`e;*phwE0QG<R4x!S6RAI8hFUzMe@+A)=OMzQPn(M0V{W_0us$ujKy6}fk4eR8$7&fV4s=8zs}MzFHG51ePlIgwG?@J
!pVy}?Ucl2tlddW9G?;=uEk~nvV(PT8^2SB%hgqM-txqdNejVGYv14jn3boMMR#2}N?j`cR6bIvUc8^d}CTJM%ksDVFVm=buH9D|Xf8KRHo{!mTS^*ZuJiyu
<cU^8G=!am22u*TLB0W;ZkEH2i1rgVB8Z}PIb<E@u7DWZkOhs^=Ri>6~^a%VY8fo;%&f*7o8b7dT)TAgPR0rdaQ&3z3iSd|u<S0hrhe**iJB1$#<M5w=fiV<
R&w!#xE0e&VFcVZpb1i9L94xt(R9tI3?YqZ#8k7yFohY6Li|nH7O3fZq`tw%^r7<u?fSdI=zPbe$w``JYnmIoaN~0Gy2(E4g**#7TPYWGDR*$HqkzE(XeiY4
669ClaM9|{VG??Ku$vjZV45z)MPm^{qrr>GJlIujqSjkO{X_Uv3bk~wL##3`K1~z&~bS7wtHpZp67)P5JQ~CVim|AouXl6hyXMS2DI4!GzF)0o-
je{{1ji!m_fwE9U%JajbvqK;soEFX9hfwl<#^^MTKu~d?$D|&{L4aBWp7v&x-
y?DQ$m=Ciw2g7Fmk8_d5z=YYq@9d28W~HZr`6<S98KtFpFqY+u%&5`f)mITA!^KiBBKfYQclLl{T>+@BHR|n6*?H_HZabgI8(BH@r2xq`N)rw?!~-
8pe33YSHgT8F?TF2Zf1l&ja1Mm+BAZDv8*#z`h4qH{}4fItg4(Iqy0k!5ViWvLt+V!q7%qc%&|!JA?&>wmnDC&fk?C`zm$c@i~6(?V@C#w8Z73R>rzr?lBQh
q@k2(d(|})|RuS1To9McDV}AZ%P?9k}3*}KDJ1s%RHN^Nmen01@&G~6TbdeK`Pm>D;4YuV!R^8?VwT#6rHb8C8PfM4dmb6uir-<-
&YOpBM9xH#Z2C`g!daFipdQ2LiMr_qm)Uu@f$7G#aF#C}uLXE+^I75V5_MB@Ho)*jy`M{Dk6Ucdl_~By~(xYJBOe)G_`GGT#nDQu%sR0F43(f|GMQxV+sq$v
R1=pf%M@UI`WD;CQ^Bo}t;V}YFlhmgbM-ky8@JIVS&J2<4S)ft`5eQ|7B&lW;K#dlkmM#iZI$$PYep(&zX%JS?!USqO`m`T}^l9<tShC(&wYl?iN6m0qW7Xu
0B@>-iXV}aMXU)v+SkrgMN~%vQTAn6iClWYzEJ>Y4YSc!cX%IzyglIsG-@CYSaT*Ov1Aa4+GOYO-
&@>K(8f2EJaVykf*^fcZnvmZkuPJ|86A@TQc|HbGA15Dl9oeph(nWF^KLTkCNo~|bK(*OHWK_nrsDa32{6ix3X(M{Hgm@aozgW=lvDo;uEN~j9JPml8wA5Id
m^_W4N#pauDkVX!M9arA93SN)LD?N2a~nQdJne0Azegy)SxfKFAJ(dYWUqEAevi_5GZXQ9tP@iM4p5EKtWhLtd7Cv3LlF_T$Jl1=B|ME?a$UCKI)&gmkjbA$
o+fC}9~(9Uc_nH=Lq}%E$HbUgnewzUa%O@dHJG$QEpysTAfQIpr;(<Q@s!8e@=<gGDW*-
!<@cD7B~rKBBYVDeCXxt|%AYcBh9PZ+s7(_gh=@`7qa&`Lz|)9=Yq<b5;H5kY8HmJMJpyMMXl#I5Y58eT2%ZMAeveR^;p6x=jlq_mS)K+d(?Cf@P1u&d%>1+
v8h?b}<D0J^<7w<{enB#SGU6izON)1WWaT}EHESSDdyI8`WH#te>XS7kgZ}U#WC^T(b}S88{T_iIOyeSpq~eOiQAK3@9!cXzO1D<3Gk^7RYe8#%DYr);MO_P
+VpS`)24=N&Cd)^D`{Gc!HI4nqF1W5&Z%uN0gsl2BNJx4Nb`hzO^q57Tme}%9#7jgrcBYur85mR9W_?<k8DzYTNU%c|S)m3rjz|>ek#YPu8NbJ9c^X^53^O-
`wLc4k(^%P#+;Cbc`PPAOS_u{+^08^TqCj~9t~tU$AtgVAOPdB|z-f_$M|crWd;JogCTu1Wh^U2n^GBDrB6C6Wb4A$A9^-
H{UR_#NstEt^F>8Sfo;{|c^Hb{^dwC-nWK~k#2=-WUPR3dN9+?wCOQwj_$q)(FsMW*}NmtnFX7uOE0}U-
{?5>aO)>!EXBE*U&k~Nc=A9B2+eF$XpGsx3or;k7xKSF}}BZJ-=Sj|Hs4f*3ej6seh5-(2U@eYY6I|BCzGC?FLN7f8v(Wm9KFlG-
=8v&&W1cC<Dvp_C?bRg&>hRDa7Q)bGdhm^&8jJCvLwh@7{sE?;)i3JPkR8R(n$Y|Ol2S22)n3?1Rky2x3UhsQ-^UXLst%&hsu-
#+m_%TJpmci<gm(O3EIE{1i$U=LBlc$RSDdn1Z@;HNOWGj)1jEv{}w5Ucci#b+VYHRK`ZFDu0oIhq9KgQcV!U(@d7JDplcm7D@Vq%0^DG6#sgDl`8iO>|+<N
Y4T_iCYDA|m><sxq!~i_;>tX;6_kEyMUx!jK=Tlc!}g<cI0#)AGyH%2%gFi>?_w`C0rP#q?=F5vT#9LM_@sghmmWx=4-9zBnFCJ8{d$R8Sf-
Ndy+$F2)QlBHVGH;b@vf9%xuOqgWV22<0&bPy05t-(xy9KM74M$?Y-o__1sve=Z(>0bm+cn+7?>X~3&a6IWjs7z|p6_PRhkEu-Hf%^j<#@#6_j9}5B1Vm<n&
K7TCI9_ystu|y5}6G+tL$&XYve{g;pF>WSQq88_mRc_jhphH&V>@nHsF%ga?J9tbvcRW_bW_4OA>NH@Gr@^dG%coBxMO^dZX~7i!Bo-nx5<jrA_@iz4V`HvG
?Th0hW<tIB3r)HP0UwcgJdK6S&y1)AllfVKL1SG+IOJ&~NRRyTG)`BnNW;gc{2mvN@ifuOMuO8gsE<kb{BruVWUl;>koj1W=_4%I&9Q*wClXxNpbCF1QQ{*@
VUv}qg`#TFBDHaa4H&qOjBNgFeviV_M@S@4n-=@AmH{NEkx0Be4OsMPfjwHVh4I8Dkb+^LEK%2@#7E-zkv~06A`LV<^$~dRGzP;*N+4)--
b^q#B#C`7r{Wq|IgcqJwXuma$raa8pjr{;#WI3x>CBldfLeWW>xha*3<Q-
cX`z5xPQS;h5Vb;cX7b}{g@u9g4jtvOw1SeZ#~^By!6OHrCK)wTm*5&g13{SpH4a;T5fc$wBB;jGz8i(7mEk2avmeVb=PzRV2+`w59v|asrPq*0dp$`8WPLu
y(Kg1VDX)*})3U*7(QYEAQqk%$6M13$$XC+Ja1%LF(LyJXPzj<2WkYJjiY6L3lNV6~IXZu6NfWJTA2>YuOR(ghIghbXGdYKkrCIWGM$OPBk2B*jE8`J(M~{J
oN65-
}Og4K2NzUWPgsxcWGG<PsqQ!GWroiHl#M5|MwDL^(W6Bw`BY!R=En&cQI7cL0a~%q%jWfpL51J{7#jo+SH^t_s$x=lMDs3UTpvhuEM;Eq;R;|+bv@mg+sN$N
fQA=w1czWWrY#D0lBh+Sn+Dq<Ory@@~RdpKVm8T`-^3VQQr$P=92Gd5+Ltv519jgq6$OmCIe>Mx_;8@VSe10)VEeD;So}M<+zc^=p8i~#y-
>HRj9_eW_+03zEbSfxu0?EP=5j4lj3#i4KV@1Phz>cSp;54!HG*ZA!60b)Nz4Z|Tk!W=q2~J~ndt@~-PEUFqZO9J`VK3ugSS=zCv;-
DEq)Sj^;D}5&Kn>)0{Nw!|N9Ct|Uo4)Mk}E>T(+cK367*?FnU5*pG!Vek<Py|K1&!=qJY#9uurwl3gxaam%|tT8X|O0KKaFGh$ZcW_k^D!XutcH<kQpfb3F(
h=1IPj?{lT`6Jn?Der>8NOHkQhkE<P<(bj<{+@w@Yflr+Zpv@p9z;PQKn9Y030Lqg-N>+^bqJVgG&^wy-DnKH!Ip=SK)XO1L(h)BvcZ_o_C<zpd^j}nweqJ=
Sp4Ig<!u7m8c#^7m%dx%Vo^T=!WSb&E}GFz;qaGG>9Xc}LvI&HBaBlR&7Ph&U5stiq&<UDFT?S}}zM^P^k(fqWc29Jr9*F^-
V31VuMC8$A3GPM%Pk5%cfVIi0eO2gF3cN5_oKaRoEW&>juB0mJxGhi)fyscW52F78fjED9(jW&(Yr9t7m$757}U=gXMYSoxJHI{0Yq*n{gPh-
N<iVFk<25lNdFdn)2{L_+LC&~kro;*^lv48<mn<dx9NUkN_i+Py*l?kq4!L7Ldn3BamX~V}r7C(%pJHjHrg)uCuF?<|f*2qNu${im$SRw*keoiX?v4LZ4M1E
jn^2b-Tyfpro@U*ut>C-
r<BJuh(NXCGS!qcMXkNKU9<NO|(!>&^Wg9^|elg}UdNRZJD8w&$cFc~zXOpVmCW#vBtp<^Rxug5z+9^*c~XZPl3r9SeaY4hkYph=s$-6JHOK9aK2;uY5-
lSe2aIgJBKV@J|}0;TaMr?IK6<Ei|D%*Rpsw6u!rWTeMXmPm59M=4MK9A=LZV(U>gjkQA)Cp(6BXjKqIip-
m#4IfjAt>gKQA_>>rgh!CePYaT#z2x`E9SBNtFHQ=lMG3DV8c-u?)Z{EgfE`j}jRj@y)+B61s*6zrLEa+?Py-602H_-rLBB^>FnAaP52D5bsExG{8PDMdf-B
Y&c-
r@0N9fat<VT>eAGxjhGxus~s3HW%$I(2IaAST*)T<Rz(JE@!pzO*PYmJpOaV8W@1M$QeayV#YWdlBUEHd{or9Q1VGC#y9)Tj)RJiS_?R;>h5fAO4tfgCj$Q`
APaYmuz}sgU~9c!^}S=AUGK8i<Y`>+}$raKd#Qo;Lf35d1?<#y%t=o+j=e5(ub484e=!{Io0<BHS6*AK5vN(eku(5o*a<kHtEOz-SARnCvtV1JX($@t44Plu
wzF<?eIX7fZ*?2pbra`1_E8zz;zTeqICP(2Az<wAXxi8Uy!{hNqo4`;nFWn9}U=#mQr-
N!KtrL@kv=Rwnx6bi!u1*pJi%wX}E|*&XXWciIdD;%O8sk?9K5So3Cb3)G?;wc7M)rR9O9SEr?mnjyAoNd;=eUad-r8Y`KfLVg^Brv-cRlPp9cr;nuJV|D`(
NHc%TU?Bo><PcJW7DEoH%fUE#j2g}JQP4ny-9LmqL5<z=k?tT;iGc`{-
(x62gl38*8gL!zij`fTR<b+|WJ!;Z3|A9XUKi&jl9MG8nlS^p^l9vtkCKvW3A;yXNekqM!~$yL@w8(eA{tNoW|ZF}cdHgy*-ky~TC!6s#~=$9OmWv-
1IXm_$K)kV*dMC|Z~ox<<AfTu#3sgaN!LKDY-#--6HOmOsUazB`2!1Dj(R3#UYts7O&UB-
226tyXqv#tk;vmu=31OKI8A{0$fHdIHYz^|@k0pC{6+aak_@u=Igd~f=@Ei_`4icQWb}LlKHekG=y7H}vQHojPn;>;^D#N;QB0pEMS2{Crx7b!poxfRIg6w{
rYdKUqn?H4T!XwyEdop{!^jvGrDk0V6ZuINBEdv{hOBFTrCNR-e*#2}kaJDoh!9e)pRZ^dPy6;{^=b9>YO_Qwxk3Njt1%b0S)f*auLgA})WY-ASSlJPn?K&Y
I0>7dS8UD8<%jH$T4pN0xY#;tC5z$m^NOuYPn)6EGKzn(F!M2YISU#-vI1(ruTP8TJQiS%RUhXO3MM^{8a?KqJ<hDhjI77<RM#-
m9%U_z(~$XJV2B9&Ju(nPL|yskoa;0+{#apJa!39-
<~on*qo6j8SkEBHjz6i1$jstDu1{N}$I}Agv^v}#tDQjRiMyWhw3EQo=EUpr0W}7u2;=;bIqUj8hdWkL=2)Dw$4ur}85Gw!J2m>;5kv-
$W8*+WbJK`~$AX=)rXsn9#mSgsaTq?v2B<|cA7Mm(8ZADJrnu&ir!l)2M@%0>y|JJuZ5n5QT10=wXJX7Iyp{@_fnsco3E|cwOpHTS*XhF3sBVv8(?@P$8jpc
-QhG?dfia8Xnue!=Xn-0lk|n6YB4>nJ4m?dR-#TH08UasBYV`=rfEuLWh{PjmL}^-0W{(h<2g)CzM)rFwoFWp7s6C-i`)-
uqBhajjqwzG}xS1LUki}LuM#IM{6K5zBNRTBzLUNz1vgeN}6K6={$MgnSa&;|q_y{4>N3u8nF+2?x%wg9MOXwH&=1)QBAI%SmH|8hM`O8kbjx^;5mR2o`=OY
x<rtu`G5yp=k)W^!QuGy&k;q%8VVb{@F*F1Pyd2kvh<&hOm8yyK+0^c#HMT2B`TD<I<iSWoUex&jp12HveqemK|7VY#%MAV4W$3U_pr1N@=Fvj8txGoA%djU
@)4joh3J+i}T8SEZI1!}Q0erCVN@!@HaMk3r^j{>2fu&Bc9QGhDK?ux~0_Q>Rl1*z;FIijvp)A*xU{ET*w@C2Ur<|RCh!yXHw$B&Q<QIi7Ha;wt<?Xd*8k2z
YkpiXV#_E-=#gya=p=ZT$R@yC)4oeAvL<N>u2c{7~e{IC%8drX)JItPNr+VWQ<P%A7EbhgGyo1s=VJ49HU2HflrE;NlBOrxmND8$Ed^V4Qa{&-
L=uRyI{3lRw;<Jm)G_T*=uK8g<?iI`fEmi(jeGzP|Fv^ov4;Ayc8k^Fv-VQHY%hNnf-_$3r-B{)9D;AoNt#-
R+6GA)cHiy4p{AB8v~k>P2P4#uP=&Ejd_3;8|9$EVfd_sBsNDJkf>s7{T!QG>;#BWH5*L?BPA#?h%!C2Fb69%X1ER6>6-ts0E*d*t9g3i=nvJM-sPT-
WIM$d0G+>8*j_BqFT04lidsv`0CL2yj-kswCI(1d({fH48r^VW*Z%a6QKFQCM+Z;QWzKa$Su?Es*udFo7&sbxoc=0`K?{@)AWdQ6AGN)WX3u=1fosR;ZQhk0
r=@jMH2n<2(xIr;#}Pg!nX)fk?68X{;m1G=l4N1|kdw#s#LQrG?VU%ub`|(!#+ss)O-q#HUdi{JGs8pUhADVU*t^M>1%Z(v~?Gv?v>6f-
_c*y2kAFC@^{~fy>Vo3YyUxD;Y<GB^4CX)ioApevzb^_+U_wqw~w_(-Qd?3&Lqx1Zq>uPb<v7_ypu>$-
Rrqlc({7r*YIY_Vly}xiu7KV9edC$?MZX@ibA+<4k$XE>9cnA`%LvMVn(yU7aQ#qgH}EjRjGIP)Lo!dE`z}i{SH1hNuzXv~>D34i-
Np<eH|p7G#J3g(4EldkjkjO~(?6^?a0+T_;yv*HzYH0<U9L*RUvBcFj)|5oGeSkVNV<e2k>=TRiQp44xJ^eGK*HuN+Rxue%-
(s6k>xt+e4IEUFh>Ltd&#Re%~%n3jzw!iK0-3#g62)2O`~ks}h#=TB4AGP(0tXeTnJuCa-
(^LdE`RyAUmMoXJ1M{mu*<}Wf5G>_gI!Ufml;bUgKwY)wpIzLU)kDoSrtSt_-
7DN6~Ge<~{rj2Ahj+Um8veQB*j#JMB6~xj&Se}+L6f~63&lU+<tU3)+<7t!twRnPvTv20y({h(J3YeA{ON$Yy#b(WvWQ+whqG_iFrooJ+g=o~qJ3dA;L=aE=
;cFqkN80dlHs{Z8&VM@I{IfOxT-HYAg60`OCU)kJris+#_ZU5VB+MQu;xt;5R%&`$p5Y^fAp(LF5pF9Hp7A3=phk3iOkj)!B~Bmt{Y2PVkD-
2#DMQpo7(P;mk0rT@NZ@Jg9wK=(*U76|P<xgv612vM>u7RFAtoYq2(HQXj6iy%a2^FHkJ+F!s^B`gjYuHradg5p*+b-
Gu!Hdk(qk;3#xQi`^n4VYIVSRaEP?XK%M%H8=I6IC4&?G58#~fGjD;lEkZ9@%g|i-
Mru@Mc#>{!33H*x#<<^X78jOe2p7MMA;q?o6T7mIt%)J_4Sqm0jkN3w)BT%c=9SiccYsJLPL||!<EI#c7EV820W;hgTX|vM`Oq)sR_eiN|yn^e3^wzP}3=-
+BrL)uI4UCz<v{Tnx6Lo34m5j%jzdF0eqK1#L6-~~-80P%6a`V$lWAn?$r*WmH!J^vuw35=(m?LMV?}`-;s+Aj`R*OO{JU=a;gRv-
wKfm9jSWT161f80TaTWTsI^=0YgDmE{mM<1GnTK&&eOf95W02LSeNzyg773?K3{8Vmhtr@2JPkP1X@wzbGoUt-DI$lb&2ZXiI1Q$Qr_BLsk^yQ{%TMEn)J`)
%Efc1emK`#&{InTQoAI<fcv@sl;~qUi!L!F~a9V6lD+o-
3iqSNIu_F|Mr$K5!ZN}4N5<yb}YNdK&#ZDff{!q}m(6mWJf>s2kK~gjg3V_qN5<#gpBIN^W7Ek+@j;EC{eGKl@k`ww*L!Sm??6D*vwK5cH0)QH^t`!qEQ``g
+WK-1Y)~B5|o(AkCtx$a$<PfMq8ig99g3}m-
(}F$ufj?}fG&D_2Xq^d6qgXu>;WXeThj8F&{Pj#IYNlL3jSWvLfy_VSX`)<yd3_oiqBaqI8e|rz<wDfTn>?P5KCN;%4eH0!qU|1Md0HY9W5|rA!7NUbO9rh;
o)+x*7)pKQ#?vHf8dY*#DW0}Stxw~%dxV0<j}VNfO=$iYV`D6*P-BX_&O{W+p-?MMpjN>cav~D7Jh7lbh#FIV8cm-@Leo-
3sLlGcXnk5e0cu4U7|;5&67^}o-lSEjPb<Ojk-
`y4jHk`|w4+6B0@hd+!D&_bJx<r|aXLi)Lg2L7?J*>6h6_&%Eoq@V{&av^dU+aaC@5!qTDfQ%1jA`zO&Wi8TBX`FO2|wZ91(6Tjq1!Vs!juWG>yjOkIe)nM1
qdN)4r=4PpdLM?KJ89`Sa7H(X`p^F{wO_1xqVDKP|7{BghHVX1~X2=BL3N2U@e(dQMO~3Ae{nm8TVkrj4m+GoF@Op9Zr)Esx_PEan8Yw5E@zoHR2D!^he0aT
J_32dF)qJ=P~so37tu4C`^uPMd;_aWqY&LVa2mzemoBrt!2l1?8ueI(?k;)3VGTtBX&Y2A(!`zsIWZwAt@b7*89)eWXwy#Uep7SF{TGAt&zkn8!t=IC=<hmb
0q0)|nu+3Jo6x{T{<4*E60r`#sKnkF!2aM4tw;K&^Ja$BFCHNCw7`Fg`8ZNF+l|12K|_AfN`^h#E|TBNAqeRX;<7x2Qn@0yV0UNV@biNG(uf&QF7dl;PtDJn
j1z@HEIJP-
E6zR|%=57hb0nsAUG!=tjn+NUzJm(@1byXkCj3)Jjoaa|^HGSYczKi4=^e9d*Z=hCYp^xK81W6<E%4xIM<DT(cR8M3l2YKn)}iwGtB4BAPT{;PFE!k)Og2iJ
v@Hr%;1XhR6{-ZOq`2s!$8-
)8crKB)i8K^=Z=XSg|I?9C%t9U;Z#atv=%8sGMs#JPpPJYAm^+p`QHlWi3U*wW#cxmm|VrVjPdBk;G{XZjW_PL?F&aB(S6<#?#UTf=-
Jc0y9CR1_$FnFpXvGIP23+M4y&Go(9?VY2zs(WBeX}VAH3;EKn;KO`G*;RqE5yN2h@#pMOGrkJFT=!O7{<NO)SR(PL<*CKaC+%zCUbJ}o{y4Osg$5~h}N^jK
qn+KD+n(t5OnXj<m<v{a+VP<dKvzsC~Bk1)?4XTQh#0@R4p#}pE^dG?q^p;nMUt%?M-)P9fYX#ByvN1*^U=KM5B!{d)5K0-M2k&*aFV25zA_+^bmrWT-
9x`T0%32HRvql`c;R-eWwPaA`$y`|%61;nQjRo4mfW|$2hOU+M%67^~0HP-
?(k(wN`^61lY8Dzo9BWfiCsKI#ZV<4bL$J1CCA}}F}2t+H?DyN7D18Nn7sLdH_lgUtv7pPU3pq4B^tp+@eTb?!>KIXT^nzY>`l;Mn()fkJy5E&u4rlM&jWT!
zONu*Z4$51wZZEzYS&rb`p<YzX;N|T)yEw|>i<!2{|#2Sf2jU7qZX%N8<iF6T}^V8(<G#W*u#6-
|?($ip(BR(z6MPvk?_V()<^3z~32dHJ9K2FW;agI+bIX(>*bADO@aN4X-
tKaW2N}!enPAf{GmP>P;Hbku=$H!Tq7F^D_(KKPZ#|S))!|rj$)8_oNa^bYoiBE$?Hg~Klqh_)NsLlCl#o%ePKCME>M>csH$RTRcJpOojTFH9rKq7zAidK(8
?X>VT7!h<0v5?yA_n1eY_V)E-
JPon|YBQcz8JT~KJT1H5qrmKO`f%DQ^ZBzS&SZ7xj~+l0=8w^c+HrlFw3kRcM}#>Nl#%@y+^mI#sO7Q8sv|{>M1LG9x~|Jj1jf`ghQMnYP2^a+7Tc{ckRNHi
8gC?Mc-D0aALBsTbx=)9!+m^+r)7iFV8+u5_j`;VKSE}|M+rm?rUa>-
_Uw_D%+K$PRRftHlA~$xf<8^ulRugtk|hpw{M0efk{=2hI)bc)NHR<QbZYDPQYPvjGHTo?%MCd;ZiL8rBe3{k(I}5ZdPw}lF@be)C^G~Cs~Lo4ToVF8A67Jt
r+rgWep(<?Bt4+UTG=wdX^=lZtu}dDB6S)N)6-
@&O_<J4v3ewyworLmg27`JeH!oz)aur!L7+e_QlA#@B@$cIBn4`)s3aDYq)?NF(<BvYDY9l{Yg!43pezKDO#G1PD%1!T#^KY)Lh{q%@w8x9el|QU6`Tgl0yT
o&BR`lH>0wN;FdjRC97}vm=lBTJiZ+$-w4`8~Y|zZCPJ=LpNCke6Kw$BQrl+Nlr$IgXv=aCsvpg-2JPl5sBm!iG8ZS*G-tlodhL3UbG<ZRu_HA-JZT5SdXx2
<FYpjfZj~r!f;sUifZ-#OD$c3oIhk}-
6_?W=&Q8H{sn9N_O<zp(x$Gi+kE<lY*bsZzRF2e6IdD=|j1Q8%IL>SPtQ1T;f_*kai8gdvu%HU~)teG%=2>a+Uy*3RJh0SCUr_I{5?Ao-
5pc$wMP!pG@MLS~wyWU!!(GT%>8iXmX$LrG|h$4~#PlNa_jpq1B8a-BL_!vzQiE<E0h^IknK&`R}HG0(bcsy;Q`Dx*U$2@kAN5+m3c8^er-
(w&{MB{0TQGSo{%4>q#BfF7E?$KjeVKb3jei$=*9FM9cin`|M)oPzUCdASp8J8ax>`o$K;c5JFGhEY0sxg+7-
6Oa1S{zRUW;%Z~MPxMfk(J6HIDJg7xE^oI&lPh$vP+{NX)qlkKU053s%A&QwBXTWY|soti^gJIERZzAp-zJYcv`GC7BG_eU(%<gWb;Q`h$Lf(h>ETuEa^IW0
2$w@F*IsoBoSCh$gcBH`pM)+Hf$|+2AM4Cnq*?k538{TU9)J^#M%4_T=|I(#sYX64NlAB$)6WZ%blJU#Sf9Bhj9DxGht~JKxrV1rIoL_7N_wCc4~FNX(VGT)
+UW$$^Vi*?K{5wG)aG~Q%O)0WIvA9sPR^|viu%PNl$|*sMA>dj3W!1v^iF;^fVcFET9S0(zs(0!e&JL9$BsV$3tmA!_xS;{3K-
><R^%*YSWmlv3Rv<0yV90eh7#VM9TN)FVgK1D04?5LxhXVFNmkb>eKS#X|x`Vfz2OEe5`u@7(0FxG{*vFg&H%R9~P%$kR?p#=g0$%rt`CyV~NuF6OJDXA@eh
G`A2w&d@Lgow2VN|C?bEfg)zZHL@?o6fXJVS$DgvIK>^gqoPnV1>DB~d2&ColS7%`yPUPq1hd>%0{}XuHo5cFG9OK8L#A%iIVx{(boM(@9XwyI%N|Q_mt<4&
%lG!7$lKEMtk16xh#7Y{89Ws}+THrL2LXDrqKci`Nm9%2$A^bHBB#N3Sp2pd#&3Ib8>^i$Y79_&d^7&(xho`{|sGS<11~Z~Y89){Ts3p%&lPzfJBh)5c(8Tr
DY;YP$rFKdb5jgFVRtBcVThBzwS$4rSlRoYH7xL5imDkbgG>%4XG^JlM8dOYs9ZwaJPP@+At4*#|gS6?OBqx#LD_h~c8gMmh%w{5~d$keyX`$Rlj#dpAdbKc
6Eq$X_NuO4%mk1DDvADXl(Ik;$)hweg|0v0IJok~uOoY?*kw_C^FntuY5=mjqFJ~nZY4-
?Y@U&nL5q1laM561_4kC#RL^5Yw^AbdOT^>apjAg?>L6jj<Qnqy_f{1`E7CYyWfgU2_9|E$R$5a{DFXX4mW?at^*EA!MsRYe%viVt4K~FVkhMV}9$+<X~_?X
bUxJoKNbIwc}*5ea++7B<_X~IUvC+YSW)~iACW=*tL1G#1mWV*FPmXC!yKFUR?MVB_|j*Y_Tk9Otf@7B1QwIpVb@vYi8JdHhVh9T*iVvRK^;v+1??6F``R@i
lFf=Hy3NJ&n{84dc&OS|Up*D|GDGr5Uyq=Jsmx(<QVh-ot)5-P4KJsN5HIE$_cqH9Km2*lDIGXrYChNxw7#gb862X|-
F!PB4&Kn)TgY9xM0swQnjk9G`CJ3Yh46n>B6bFP#5J&rfWifGb+uS+YjNee7&P^{l0vylid=TW>rBeZ8cLDv$Fj}nL)q;Aqi;AwAXJgsg#EjB$ZPIO&FuNE)
5=Fz6r3aF*hT&K&pmhaeRJgp);4YHNA>=i9aZ5>_F1l86;V(an(L6erVI-{;(j6f|HsbA0<i$`spE)-
OR(@*sh84afqJd7FKu|CXcGl|n?Sn2%n<HtyunlPQ8Hh&yV=LZg*8pHIlEIvOcou4#(<hC&`pB+ME^GArT8S>L&>HM5*{&Mg%s1Q&~g{S3r5n({nV0z`Ovhc
K=w)}Y<M2?j+5|#!h1g7N(xsFenK|JmC*Iy?$e1v#DKZVYp#F<~r?lBdj#+!95H-eleka-a`9)m32@nf(-
7TNAG7E%KOrj{2{i;cR5xalKM$B*x)8aI;wRU=B&IN6W93bkyWk23zn5YwzpCv0X^QKQJND>5)1QP4tJB9aEi!8{SNfiZ_Wzleh|P%Vr<WD2<!b9=1D8cWRY
F;<;cxG`3ABq)pH<0+<G7wGsHo^l;NeVlg8H84*fDZIx>bF3;1AHyWq!EoC1RV@QS1j0;2fIxiA&JbZV$BOah52|Yg5=2TWYw7$RnHeGsIoIr#{1DooRSBqt
RkS1ok*5K**N*`;Dxbe(dwv;(8kNuw<27o$fEuThaWaQ~lILSWgMJBbtRuBeiqKDL+PF1p<xAAUJRi$7=?}HXiuE$)ENv`+T4=8pilznfLjvg`!OTY}w^>Wn
t3^*B83vH!Q`CqaSwUVR<dT-moBt@5#%7Z>3CT5i1j&M@g=VOQQAC2fH4&0XEp&b!g&K|YC`%Bb@Ix|l9$DG^f)>VkW!Lo)L`vm{#Lz>sar$92sh@!%677%0
ZC?!H{>9Av5MWeXhcZOUb$pChs8u3ROQKJMS)g{5$S<CAU7bR0iV@e<3Di=+X^>x_2I2~}Z&F)6irHfkr;j-
RH3C(n0Mo||fEsZ4WTngoEjMtc6heOym>LxB`v~G9HDcThBc2B705wrQf3mz8NKby8R^AMyp20#e7c_a~O#O`h%$YOAMb3~b*&I1TWIx8iX+Y)ke@LpiPL>M
FIeipN1&#DEPM0?mG=F4`1Z9BK1g4MifSLfF1|tBqc#=pVcp5EohKQ&IDy~5^5)>BFnV?14h)^}xNdYxThp2IqL{8rEksnXX3a0@No<@PwfEiDdfzu!xo(5u
o8V5TB!ssC^cp48uWK4?&i^*6%e#-`^k+>q!hL6eo`Qt$~0$U^zqd(9fi$5H6B%;O*s6nZSTK0|&1bO{gLT9oCgtAx~6J14;VrruuvLXSsqvOZ-rI;88=7Po
(`s*-
!WEEWtb!%*nkMh$;5FkHhr1NJaht%cJpDh(M*iR(h!#E;tCLB;p&VHmYMA$4JqdAX~43S{Tb#<PPg0Z0Tn(L7q5r`&<jH5ip0ctgJL`pCaVGg+#(OdIch@_{
85Zg0WiU`5PxKM=}DQHHLArirQ6g3ga45y_g@fU}urI)91cw!X`sEIm0hMR~a)Tq&1j00OVAwVtO#F*kC0<o;e;ndLpYKw9dY7i+=iyuA0LdHu3NMYB&S=)k
2TUI0Eh)xYCe13AT#tx><>a>KE{vxz#>|rxZS=SIcd=%@{fRdp0xLKQQh8mQMsWCG8^Jb{QqU;d0K!(U1qL#+^v9b`g0y5NOFtx%wvY7e&ANa?Q6o0JP;UkF
Y)1p0$GqCv?1Zt3&^f)R;E!M-
BJb&c%FovZ5SS*_BG6*6xb=nBNBP>KsviKQ~zyqp<V``BaHAY5%&J49sJWbL@BzrPwu{I)+oJUB9r~xaa#t}4glKE)@v7oRhA{VrbSWpiBe#L+q%|*ljYTu0
k)Z)pH@kEg+RJKq^Em3WY^nBz@y^e8xOqrt=IDV|6z{T_V3-
HL2554B)^K;N2nIJXb#neJe8*tXPF_w>2u^(ZkKgLi*X7*zx?8lk>NRCjGbrfOITPNT@a?c)f&>s_Ls8L)*NQ!F)en^bvBQP*T<V4prP%WcL78y~i&Ga#~hl
qGKXhjSWpi)F)d5_$Vj}VThNyLK2F+}1BB0yKDQH`<cB=RTE1Pv-?rTRT)au5l2#7auyua)h{;LJ}WiNsoB#nT;GB-h~#5e9-
tYSDGH<zp1(k(vr>0JS#~fEpx2)B<OZ5IKB|=kk+6)Zz>ur#611Y1D)xXQTtvXbSAq4q3;hkl{nfngrNcS~U@g8iPMpDm;yvp_X#|$PhR~3cSwD=+7~HOq4j
oOXmkxK0gQ$`lBJW;BHOGBCANpN8Y>{DBr@EJ#a>T`Y4t-!$1-FkS-OpfKX5=ff}>rV|2=OJenpzdSnFDAh4!Mn?CX;gFfG(K{=%U!c8A*^L!L{<S)|mu?)*
c7DNr0St50qV-Y~L^q5*YNG-}1i{gqUk)XzbsENB`2~Zz{S^U9xTBIp|sEhH00W~h#V>qNHP7#R%)V_ZKP=k0xP0~#Sva<PsfzBW3`6%fpQYocBTAx-
3r5|b+sFiPMOoP)%ej*IhM_8oIyUqlurBkS7w0xxGf|B_BAVuhBPEq5ud@O*?U$Et)0HRi_;UjY-XgH`Q$L9wgLVp$~W0uevy6Rdg6g0@km`V}(kd)64i<5W
eFI00qaa;b8<t$MsXm(S6(fMP=w)_-4jbr?nfX)wrn(MM6)PM(2OXp!6#t{Jqk_Z<^gu%l&R-
r~?h~%L>M#|JEl*cf72$atcDc18bl)xVhrt$I}1ssg?85ql(h>T!}#0Fh6S{OeKsQvI5Pzx<>K&owdBx*?%YJzwg@D3k2?fFIVw0Q0#znih7KY#5OSsZT0b@
^k7wrYWejV&CMTG<M>d_0j1HAoSnmIhJF$$u1e#{y;sBuG#o*@HoYRcfKR*NNe@(3UM;qs9TJaRh^MlSEkfkK(%P<bEPS1r1)Xd^}Rnm}9O9SX%J#G1%{s&y
_zOPs<{?F3pvn!zYWbP#fK)i5rQ8%hbxWd`u@(1A#FlD@BBX^+>0P5C@Pc6l#GKkt2ySV-
#u(w*1VHS{Oqly@g0_d0IS)Key>4uPHw;!D(U%Y7*AP6AlF>A!<Z^2zw$Zh)04l`SKSP2|7}t_I8Zrqcr)kHtr*Lvz7`^BlT+Ba9Z9zji1Y(mHZghr$KC^#t
*9TI*Np2YU~6^Nl;CM{>b1bLTJ_?(xE?VhFUt+bq15HXwOGZ7h~>z4cH}WDRR^*(_9xNxvsTc3m-
jZBS41tYbV#Pg;7L~jvsjxYO_Esv`6DLeVip~86|2oi>z6qHdTpQe9kq$Kuwq(!m3Y$!0F?t47FUoSWsb$mY?uAZHXH2YSdsMoC+G+qycxAMrQJh+F})9%AZ
oC#_o#61gM4EVue#gVkK&ktj8myM-8aG{<?0<$E=QzM8ii&<M<ex51O9)$mjS-FvzOh^D!xzUnD_I((;iQp~g&o<Yx0nls1~5$gEJ4vV7zhsAVvH%+RnEVEP
D*M3H#YM}c-NtLbA56Ea$+7C3?w?b!r^XLwsSZXe?ajas>;kK+mbX*1NqgRTiNYDw@k-
X5(`zsGt*u0_zaB;8ro=8WA!1Qw}0MEIMt673#YGOmFMP7AXT0aj1`P|0<K<s*^DADauxA-
j&#s7adg=W%`HYth(LYGlht3PXe^5tN(9pI@fN8w(nop;kh3#t<_j?)q42ENEF7YO1DfZ}+S1rp3zk`n84D)QU7`>h`&9&ptK#yQbNk?XR1gv(=5-
R#wY>v%Wu5ALwk=p53WcT5Yb*_S>^V+nn8A-?yvt<@)S$yE|{xZ>#@r@R!fNtj_nV8~l~)!)||ess87>+5O8`dh)6N;Qp}NwEAb4S64f=F>iPO`+xoa?b-
Hjb9TNvH0%BS*{9|C=d-PSJNOoEmY*B*OKbd%&Drg8wYfa3&zjBlaCMFE>r-
>KYi^pGPq;(kAN*kd;O%a^Z_f8^|NZT+cl!6#XSg}L+wLyfSL!qFRRw7q_2qVVwmj^ww>wqU^xt28QCnPlhMUj1`kCeKzFULq<?aUm-befQcFm<)qZZ#&gL}
?dtG4Owra5cYZKFTx`F3-
;R&PYT9)6s&)gIrFx?GJ~8MR#a_uVhG_z9Yw`pnC<x*lJa>qFE2%U9}>zW&Bt$B(Oa`Dxt@7xnCN+3xk5zi7@^?dprVobS}Dx2ydU?*+U@_zPG15AV*@ZFJR
LsXwR%$G6fp>&vst-RkOEy*a!C+=5>(_YeLaUbOb?0<WW5%Ioc6f3|7Vjii2Qi5FqLRe#{(>=IwgU7ug@&*00n>+PL-6Y8q$8~vs~H_h!?^QGC{>s44@Hu#-
-*O$0lmyKG?v-RpTF83Zkfx7~~$KTs8*Pr#@RWH;w%bhxrsP8WEvYu_Wn=h;FT3wp6?XCGtsx+*+d-
+_gJifMC6usnXpTMh+D{R*NTet2XuGJ4T>Qm{DV6M`mr{~6g#2Z|LFZLpxpC4L%75tNNvsyQ={2i=5<12IH?RI!w)caoRtE8^p!Cbz(>&85DtKHens@>zCac
_S6Nqxja>sMJla{BMCHtk`(SO2H(&E?rYwx659TU5)xy-|M%FECz0eJ}2o_ypqx-
<+vyZl7jc>c8k49q)7X(bekR=oQ9~`VKFX`T`dZH^){zbL#EmFTXOkj=A~NTUU#s7WT%h?82|?cDHGE!{^^TSn7duPo7#}{h*w$^w+DK+ilyf^ks54@n&^<z
=bpSm|wegYu=r@cW`Oc4dw1wJZP={i8lp4c<S1!lQ3>5`UTVv;QG2{{kT0p+~9qL|Lt;#TMWKeT$4NX8S&;-
+tt;|et7js&zlQXZO^XM!)PA9^L2yAR{XnoW!%dCe5wB7e2aI#+ALc2!Z*0V-Zsng##{;gc0T<>?Q42_-
l}c(mHK0Q{47^n{XWhQ=AG@YSIs59_p|M#zM|{ZrM;8C>(+a7x!anLa)UeC#lr_({rPkCchxStQ7^9!8dnFsIex+yzOz4ce|E9DaI3Emo$9_+Z{_a#-
v4KOhqhqg#-#TLbuZhS41Y!)<9A>1&fMyM#G~4|U-Uot$Ep1vhn2eLmiVad*UdY;`*3A9dP{B3+}-
f%>=SN9TmNVA%3d{UQE`tw!{@NYt7o4^T>p>FetCX<rvAYB=k@)+oc+~q#&_4M&Zt{N?Q`qx{Sr&n^-^8+wfeUUeB9eRf3x82xKp2guU-Zp0DN^^^EYnIe>y
*JZukBD;+=Zc7S{vcy}H}g2Bf$3t-8RtnEj=~4{^RdtS{6%P>XffZ6)?~w;SAmF8BH-T<-Knr=RhIsyXhV(l`3S)^1gn_INM48_N9ns{QP5_~u-
l0vfY7*!$!9aHAi3TMpbay4vf?;Wi{)2=}UHt3TK+vU#=JwfcAb@c5B%In@n@g+p&FddqZmNd3bOPgrUzZML^=JO17;sH*PziVv=-
+U;<O@RP3bkz8Ii=1*;w>fzTr$6lW<@Z+m0<(E<Y&~m4CHhhEkc-Cmuned(67yhxmZ*jL)t({%i8+~`!>wv5Ev%9ePaPN2OL+;<Ht8%Wt#|<05R3~M1-
oV#XmtMVzmF`mTB5d#$Xj^yLmKR^ty`<i}TfU#}KHsV5RNw7-8@$xV7xgDTsXyD`i<?su-Yr-!%^sp&^Hv=M^)lJ*`pi6Mrhb_<b^hS$Tz|g9w#2*{yi-iE#
9vd7{^kOYLMyz$x_tVF|6_Bb%h%?t`Bkmo#_YTJPn+gyyVuL2?@&`HR3X4WV@@UNx*ha&x!P|1N$$PAlgwvui_~1a>#t>ZNYp=IFTeii`lPVLt;7^;w}|icL
G$v`oy7DntIvu5tM-V^LEWIXwCHE~9i9ufo9)fL`fGdjN9@k5_vE|P#@yeyg3b^^_1os@YA!EV=PN81=AfaM$2_Y3nyl8ir{ZO~*qVo3Jyz;j{M;=pzMNh-
d`)%xt!@rC_7(A=X)dt9s&i7i-`={KuXmx9ez4WErO#ozGk=nGEBqT<?EJ=S4>aoC;A^PjX&)y%Jlx){@7=BGZw>r0wGnpb16`kV=~ZXxy(&m*{q$9}^-
g`vrqx?BE~{EmTh{+^P=D_dOTl`jPDA?WpikLqWxsLEz_0$Gk5gNG3iKZ_Cs@6(y(5A>{^QfBEAk32nR>v!9M&7N!Mdfjw^;AAi#w<}-
>$kp<!iforSs*%KcU?L+MjII>)~VY%HC60?agBD4*KS7%^~l0x58Fq^-puBT&usY?ry-
N(bC=%YJI<~zTn|UJxp$a|9n`l^|AYvI#uIQSDljg_piE+#q7W4Bs{pLxK-ipf_qqdesFi~H?O+`!Od-
>t~q{g_qhG2_o+K|GqtAS(#zF5`r@mqMd=P=_+xmAS#P(W-75dm?9^vfpBJkHeyO*HAI)vD-
<fNOWdJ+VTU7@14cFe{(Fgy&E4J!bvr%<eooSYOkMpP9pKjC%OrOiPc)V1bwkc2jjtch!eMnG;jg_ymZi!57F>Czq`f!1}lfGi=2Hx-
8*5dEpbKOJX2BT`(jsD0FR@^M_H~r4rEaZhcYOL0#Agl96vsd@r-
Ex1f?f|_Jw5u!KF5CL5ZcZ#2*Q;B6_x|$YXWQ#02b;`(yNx{#?qC~rsM@!_l<Gx&bSU=y*1@0MTH9!}`tsmE^m}V{Be$lrzHwWqK7!#=Y_W9c>Zdn$T&MlE-
zeiMVplro&{z0?Tr{^hLfY7Eve!}HZdK!K4r2PS*y0bKsS~z2m6&Dr4caewT}=7xPONIJZM$u2nLbLX{rh6on)AkL?^f``R@Do&dDyapO^)rpY`>s3wmY){;
2ox`hpLPEyzEQYPwi**+Uh{BudUwF)tk7|JG?E5wh-udst=L2&ow2jZ8mLJ_tjQ%WA;N_Z(aX;)$3D(haGIk?a}M7G2Nl>U%&BP)?d^ztNZpsy)AXW;Wl!EC
E=<uTZnzYSLO&{wnNnn+lS>+bw90n|M!n?y`MK<@X&jOj|HBQ?5DRU@a4sgdaNF<n<{kb?(SwccxCZsG~1?qYx<q(3U{^oVmkrdwqjM%OZQ-
*^lh?KfArI#H61w~^j4cgV@54^>cTJYdrP>}Z>IUOGB=>wVBF4*i|1RWU+lz2eTsK@>-
5hc_`>*j>pK=VY5g|O@rbwcU5I~VvB9}&T7U4|Y___#>bAw4mOicR$<u5akDo4m6WNu*b-
Pt}m!GoyMgL6qB6zc_o1*nwqPv^*PJd||jg{#D9z2j;abK^RFZ#pdOPaZqJ7~9uPx{b-d%W6#zu;Y9c5vH*;;QerTeGCTBl<i3ikULv9*+-
zy1DL_c;8^9?Kf4oOWe+I_t#%;bW5cBA=BxpH;oU~a3Ju@?%uw>Zi%|C2N$(>o4WN-yVCW>o+EH*wA}Z*sBU@HE_CkylRp;gdDY$ZYK>2&ZtL(bwYcM*A9ij
)V~(La{jbZNUQoXc>w~qb!fVqFE&XEZ3x!X)@8!SoZP4G%G1S})>Qx_3>~{JbVP+7A+up#>e>VLCT?sJ1dVAQl2R%kJGhOw#`tDP$VcV!SSKS}I@?dtxYkkY
N*ak1vQD$YE&vmo!7E>Ml)W<yUx}Wpwt$J2ewNr=p`@y`)UO&9%>NtIC7Q;Lcx_KScUe%oHnT0>!{P4H{;{&8lSNiGg_2RGmxUtoBZe>e7UIw+FV`)~mr@Le
N757s8TWdzx`Wag4NrXK^s$V<r9j#Ocx!ipoR8;J?Y(+K074r=4m#v+Q>T#zY8sovomVrTCRVUU)H-&0jvfpg5a;>q;Rv$tw9Bwa-
I&|NgrU0K9b*#BxuFX36?&YuQ5QYo<<Wy5nXY`3@SY+&4?A3#-J^&t?vB<02vBf9XJP~*_KH#&pHv{6k-
iSio1{(aqjSAlz?p1D${*GrOQ$_XZ%0Bye6np18N!?C0Pqb+LL#7_ZyK?D|dyRQW-0LDtxW*HKeh~Fj+Et2se=A%!>s!5Y-}>(4-
&T4{#@$QZGgn)`q28z+vr(@<n8MpJDE?@>U4QBCSa!hL9+nrj-q}V3o9qA4b;@T3<1tSwj?Y^=ky-
1%q|c03&Gu@yyuG%2nyyG)|98GU;CRIS5j__%oA5=i6<gn%tB-
vo_`-)>s$0Zg8dvhvi`YlmTpHZ>)iZnDpXKA1w&Ayx@X`A=OZ)oa<g9!C>@jig2Hj>TtgczJyXv;{o5T5a4*~IOY-hlBEbhiV&9CO|n1>Gcmfo1swLDl#HB@
1C(%z)P%%apgGjkez952@gL~GS%-rrkr*tgcuuU|iZY{k7|z0%E$zJ1z5d#j#a^@qEo>+r1Z2C=xgnPu}$$Di)Z$9(W8+ex$e0|-
s6e4+P9kOd3e@ojVO@GQLmdV$>1H#eUUv}jxXuJxMw=^y@+Tc$s$zo~mTjdj?w=K1<?)7lOeyX31YbJ*N9SL)aGa%^$b;0veuwTtFb71q@kH&xMFqnRvuYy{
8TYQu2{2(va_r{HeccX-391Gk?KnEo39&F!6eVDypO9j_5E=vrI#BFnQDcUnA;G<ZbAdSHP#-
PfI&ss0k1nzd^P%;4MZ5vzSSaJ|&+hXG^sq}A@8wzgw;xLo^#w7&HZ1{ScJ-w>=ZqwMbR<k~^3Lwe$9ju+<k+?bC$coGft_^odteuI-xysJ!~`A*+WSDTd|y
|r$hKbT<YMrV)bF1=&npm(`y-7o?BZ{6bIwyK_%>u#*_;NkK&TXSJnc-
3A%cyXM_yAS58B$g&~GudC%Mb*6vym@x&Q#Uwr)jzkyht_T2c0lPd8(fg@216;mZm;k+)$?LOm2M>EZg1Un*kS5DevkLsKmCLElddhQ$!ab<gka82x<YhwDB
laXB4U9W{bRnJ`nYQK;KI(T{T=m6{gLg3YW>t3$LI5vL!V7^qVFq<d6>t0i*H1Ie6<tp%)O6G`di<P>4W~e-
Va``)gQ311~!togKpLRxxrfF_Ga8h4rXp`R@8R||Jm3vnQId}E<(H1_nt)%+`{Q!z~-rIGkXJtu0wM-
(P3jRlsmnA=#c!~5+Cx_aQ3A>rTGKB!GyZY&v&Xqngv4?qv<yEKW-
7)`O>%+Mg5rFWy}e80J45MtkwwMob{V7T#@e|Y~<~=dUJRdd4M#VVfwi}3=BZlVZmGOuvM}3ZQ0uEcd3U5W~$U1wLf?QyI!WNYUV-G2Npf`N94tBFrSte-
9i4h>Qt~@UvzI;y%*EE`dzF)Ytd+!eSUEr{2~5#_#z%7wk728=ojzIM~s94Ffll?ss|0XanqyN&fT10umb2aL{|oSwbtG@`z1c{hJf%2%clN+J&N3Z*&13xc
SP-N0Q%kQw!w_%Zq&kkY1(d_@!1}Gm$&M^XfAv;#yN*a3qIqqQ|;_-
@4*K~y*<2sxOV<v|A)q&bj=aUfTqtK^6}W_doy?D)K~1m?A)E5hQtRv8+x2pqss_MS@5YF+Tim2n|dDpiIA<jsjkgdv_IV7$Vok5x2wH*23@1jF7<$?2P*!>
92ESqRG$aj$~C_(^<hyj_f4aBNxg*XRxrS;y<PQME&Ngb!+Ps#lOz4OKcLR?svhHC^21<<UD}%3+}!T{*7P1XQpYziw3shj?19uRs!v$k-
4LYu;A<@Yc|9PuM~T(`N&Iej<W_Iy*S(&kKR7WBhI;ss0ekp!97_0Miy05Ed%LC{Oqlacx5em&vhm}^g+HeEA&>`0M|DX|DckO@mKy{<{LI5`N_IzZc*lGHg
6jUc8i0p5P1)gLQ~kn)!DUt((`EP0o9~~5E8NJvPcYLj(p+BJt)ss;?;7=f&K=_GaTtx&tGBGqp~#%ZX8p67M3{~AA?WHqtQnc&s&&wSeJ%}7@AgkzD|MGPu
6`TB+vRqRyeU1>L>N?k+^!7%(F3jKrS{t^-M(0WaPJ>ab5R>Tky?8=&=9Ho8(HgHsyByIckZh78??G-
&9%5ZY##Q~gWFScJ{s4EAJj=|XP?rG<*gpCtjv*W@oj%rV-a`#7gWX6qw~uLBt3T|*X_P*WcK~h%-|<3G(>GCA?8!52jad5pRYDw)P~a<JZ@)~-
<*W|rv$Yfs~r><+PpuFi45M*6<!zDVVT~x5BT<HH|EIPt`UNKcr>v$lX`>qw%A&NitT(`%k<lBz*0MGAu;Re&+LC5kSZQ-
`i<Xh>`1MD9(DJgTDvX6n3h_lju`5pG-
N0}VK<GlzU*paH8;s+H;VTN|E^C4bX$`F1+l>0+X~h0%!t+R*VvylICJhMvhEGI)%?#@qfQ5^SyX2SyU*c<*V{Ebd(T{@%iZ>701_H-%PgKh-#cQ1-
rljDGlw4B={shGMYkUe_WvD;b+51rU0pS9>2&Y9v}`}!L=PZ3?9}j1$DhOk(K}}Jhikr<F{e^XA~f5zK^XBxkcMTcCVrWIvFEQI$Fz5t>KbdDcj^n{_xU&7{
bYzSYN?m4E2D<GW1k3BDs(e3q%!>G*K_rJ;Wfjl<eh$NYX8*lLDQ-
X4e8ztq&=<1W5%kr|JAi+>O(d=bqZR#hEhEej@7vCaT3hv$W(5R^=!5dMzLc*wL{=DyjyNQ_fBGGOFUcP2?)tyTSIKdmH58Hgc`T|*y`g+6nGR@e+qBo)g_L
)H6y_G17=g~?^J{T(3=$L{nRqiuo1#3?#>Vz>|OHIEMD*Bj(F2CUJ<C&&EoYINr-sqI~>yOCC^Pp{YJiO^*icSD1AaSHR+<Ub52cPHOtiv;qkP+9S-
r%x4WHVjrh@xo8NU9#qxH$-
d?GjTQz7%8}XZz{&zf3s2lOT(F0riOyDE><lI0{*)4MQ30d%WyRA7<et0nd>X9hTW9#t5^X8)Q+xv3A(`Q8gp!ZhskNR+kaP=c7RP!Rclb3o(^@!#YVY7~k)
Z)+DP3i69fv6@rnJT2-hu)A|eKGY6#&1P_Y=di~=a;zk_{H_Z6HCOmT!A5>VX$fU7xdG;tyHQx#_!ER2*Gka5K|vw-
J$HgGbwf#YfWKOcR;h!*8}Hdj_r*tO1~VMZdv~&wP@IO^pEe~`Q!VaZq<V0ErBhsr}MaSXKq<DXw@x)rs(NXrLnCG)nZ@ia@5fR+z8832G4Hv{6QbR_4~CnX
arUEJxY)pky=uBHz4hX&h5QA#ogd_-!-_mx3A3k>y_`)d(DoXx#)(pd-nDF(U;BLOnOd_yJLR}VwUuPB9XP)V$F&TPwx7%-D<82-
p%@xxhGLC7cv_A#p19zw5F`v8|ceJYE_F*#@;@0c<fL|>BbG9+O@h5&F3`dS_7GOWU0Y!rncFox(5!oUdw?`s9L8BJLl@?Rk#uD{UmSW&s)E1*0(s*-
(WjpYp0=2>4&FJn_6CAJ=a8|^{v6%aKZecU0;dS-X6I89{FJ7S~{Zq(h>_y4|~4DiqNA@;8EUefSM?vk6Bj^hQ>n=-
u<?)*g|2Unhv#eEPMY*^k``a0+@<|D7e0}b*J5^M#_ArXV#B-
{&vqlY*u<Z_ooHRXw;0^M$ab=SJ1r~U1(JIan>!JKi&U%z~=Y&o8~O3zp*>q?jPv@TI6$Ypd&|kzD{3}abuX%yU*CM?)HuvXjK?`9&o)pw62B0-
rbQj&wU%Ddb?ZmOyfOjw;wx?JlrCf;5t@6SH&ytYZ~$}Zyz>{f4Ag=8@<`shRmID?F0``#%4~c>(8g<MXw~|3i|WBzK<<YV)ghCU^WvAo!`0nqwV+H!s*x0J
+*^@E>>pXj10M}jTvcpQmG!ixbkcUQu+qR15!tez}x1XXBfJ{zBy;>k%MN?Tnyi>J}laUqb|f&^5qY#L$pqn9X;_eEH!<j*}tRz*$=YKntj*d+Uk=GsP}yd5
6)##4Pk5jGdQw(n-_-x-faf>4|sL9NqdE2v$ftpUJYqTy5P9RqdPeoRyyv&9gz%a1RjQb2*Y()wWAo>t;HS5ag1j~BX(>4Xrv$JZS+WFo`<PNDcw5Z3hZ(E)
e)k;d(&^zEjQuw#_Zm1;MQSWhKP-4do38+>NaO}vpm0ltP$)ka@VSv*_Y-
(ZS)kP(MJq7jOzF0Zbs3xJvhUDGQHK<lG~r}bcIJVO1_w5kj8q>SGP^SQ#VhN`{hoRqW+Y*7n@(toAui0gY-eg&7W2~T}<(J>>Bz8;y>&-
N_{N#R9WKq=F3Xmjrz3EwV2oHX50G-!^H|WF04^_2C-MPBQf^HJ%iKs%GDZoeeel?Ob9SbX$~wM?lB|*t52miM0LtswS79p(iFy}I)$jcK-
W>D_3Q`%xV7nzV@tyi!(Q77`Si`?31@b<v`iVxak2y*qb1xs#;Q6-bxYy7H|Aw-5AC(1+kCorh1xXE{ZIX@ia^&0^`N`aM`IUFztIj&_j|~)H7B-
Z+p){ACh23TUp75#bH~%2CIBqg@9ZVAt>LOQxP7;f?f!l}oL8Q2Z|^NR8dvu1;DBd_Y0F(V`|E&7gZ%c;3B3SFFn3G&=YxS-
J27pqfx$Ossmb~;=vK7@1k_Ww9gyWc`ebl5&FIqQ3{j>FQ+#d)@7cT`i?k0uGHQrB7N{F4sgs|+Nbhirv2@6!=HvGtA4BHR0A+iRzjKWnx~CSu`Fe2TJD6&?
)#m_pD%$%3>`s7P<7VjgJu$SU_FQaD)#@{1%(<lP1K<9N_5T>I6~cuVeMYD|mf+;vLj>;q3=Vcrz2*?8#0P5EpwLd;j-
OC_rR)67@*kFacy_g2uFc)`#<vfD@9|`;W^yPhQwN#m*3jvVjAW%r+1u^<l~IG3<NcW);I2COr2oR}RZrU8AKT&H+L|82lg9d@IYgSzuUC!L;_9P}9oTj4jX
rnkV;c?uhw|=!cr`h`hwi}L*g1+-
F{3<W+v<Tlu8!{&bo*l1drpw1xfb>jxIeQq0QD+(&bNaTdrVtx_8n&$%dF~gP%YCF!gr6Ey5L`WGGO<I9m~T#tcQ(ecp%Z@aHCEzmf`MBiiQ@CHwZp79f{)K
w!14WIK_QV)8zGZx~HMg47)y$W@SVdc!?dHXvfrUmiDF-
S36CGftxe|t0U12Su@why1<G;)a!rRUKm;0U`Ff#@Q$p2^qq(LEBq_}I9yyAz}HR!?9u8mr~ko>e#q2tZ`R+oI4ReFXX{2%9dzSol)4AKLgv9cizfJS;Fo5t
4zBtLz1^wOh&*FY`8VZYbz=s!Zx)>l!sv$alzG5T&7odKk9SaKB7bgm91`pvu~Mr(WVLzfByqOWe56}-ldjGOeV{#mcR=Jo-up=t$D97%_J{5H=iW=A5XGTy
8m(R8CUk3C^4_V(51o25k)N+NTC34FSJ1b4+wG7@ntj)sa_PS3Qt!`Ks9Z99!#BQJ7zj9?lxqygas~m=H-c9SeQ5?f>~^WQy?cZmIxB;&lB<nx=~l-
+2<NNa`QgT(&Q6r0$-NfA(kr6Ri%7A-
dGKw2@Thl=rIl*;lmstf$y;?L?4$Xn*F2de_AIt{ZUWcmZ4CMNcT2}2aT7Gp8pG|g+X`DNWPEpay}G#2PqyhQunpIjw3AYrim!oU+<M)TxmUMCIVS;h$jbJS
#!){|me$qx^Z02v4ssyL;A5(N+yKz$IQUX0UBjV%^ZgU}B|h<12CXS5YgML<ywQAAU9(+txZ4_{li@Bn$XIXsPl1}<_|0AxeLPURw-
LLkZXJP;UL)SMvyZf(__~HdtZsE(g$>Qv3}w|OyYFZMjfZg%Y0)$>eID!=P1Rzr-BBHhi|XmySpL4jYYa4PG;MfLwtf?OV1}2@;&^sv^Bux_d-
y?Fn7XMi9P#%C0eEM|fIQ(Id)Y$;8gSOf0)1}wqj23@n|r~^S|0dmJT|_t-
N>?6L3(?{Lr6xCfYedrVz<0Ax8B1(uYaKJn`>BX{LR||%Y4Yo^Kx9R0W$lIK7V`E^~U;;_<D$3T(zUDZ+hgwUhmT50(tiseg3}O>UH;6fW8`iFt*2YHNAvWB
$!sx-nn=d&<6<f_WWtQ<3sDmc%VqZozh+%U3rkGr%%DywB2YHXE&D9i}wB31G2B>oNYYrbfcaLGYWE(IHU;Kxgbu%G?8QJCEEQ3bg!t9{$3k}CmVBg7+#~NK
)R1=H@JTwNpu5!UAwJf#&EVT4z{FUEN^<Q0C-znBQ4GW$K9;g7G6uD(5kfEL0xEb(9?UR_H61F?DbO!ig|-
rkM{=H_tn_%%KGDL3x<1JnV+E9dl`&XQ}%b~zGKqh^r{hc2UFopXRH2_>h0V<ueAiKpHl0y^_%#|?LaJV9!z~$Ti&Q!#DjRoc0^M>v~IvGf<L$ad}%AY<vm&
W6&Wv^d*k6?6x(<UZ#8SQzfIeJ^28-C#B)c6TMak&c5z)2hkR5#DRw(=Yse91f4@EK_r1*>O$|OR*G8M++Zy#5H-
m1*E}kc^X&t$KCvE?TZkEEI&HsS4eAns!x3`AScjG61OZ{09#n*@`?ny%uxPO+1z9e_}AoWeX++ba!aE{*e!D8(_uywh=*|xXlRD`^;UYFe4r<bZX>$Khf9j
83bJ;sux3@(5}F$?PTFj+%h!Kce{RD9p^{i0v(`<$9xqdRmf^U?L)Z%nH2-WhmG+k-q=*7d!po;`IQauZF1;9nSToNa3?4R-oY?70K!lY7rGz}-
dK`bIrQyY92u2S+~>7dXGWu%01pk9?!HjQ*Wt{7P=NxRW1lY%76B=svpbZt`6N&E`t)Q@B|EWc}|*!qgi=PeNcJUGpHgesS4Jr0^1%KXtkKwYS0i=GWV`J(z
avWM9=$O|9GUz8K(x>5ew7Hi>eZQ*T`C%%ysphW}_<t=WUI{~dds&cMa&y)CLnE#Z8>-J0Q_>E8x!1Gp@2I@gs>J-
z+Zdce9X4$W%cBiW$T4?SiaMebG)8#hC=U;hVf%wP^F-
W0)n)K9B_s%EM&WIhMz7zwN0KGYxEUiPpER9By?Gsd|+CfIkUXM3G~8eePK=}_#68vArz%rq2?GlT=uHSlb#-
rCmUmzvDn5%wPt^>N9*@AqhTUgCPdW*y;QPX{qTTSqi>-cEgPmi0IDgiF6QFMD1)eMXi$ZEkNK6HPDFP4|RC<z~A<_Y41bx&xB-YPz$->W>-
>Fr$Ql?o59)N9XV-m6dtsjnoJ!s&;vP=)95akoW9jXS__%u4X85MTOM)Qa^M{td=;$!qfVyKeJro)OWf5{K}CLUO7d++lkHWw=W|zJ-
#7jo~xCzlptfv)jJif^lfix5j`SZhbCb2hc~k!jrETZ<}O+4Jy18?c#C&_jmZ1k8yf{&7k_^CglyZ1zHoAXujgKf5or>))?(pV*DGaK`+>#-
u_mvau!_)b?7!Y_Z`EPVqS46fw6Lch?HB_9!!zn^jk@yf-
^V+1MAeuDVkyI0#P_VdL(LCg_Ze#gw=5$az@xAowpc<|_o)2g(5}v3sbYj6hrW`Ur{5<KY0&+_R=QM|R^7)I+t*jIuebTxfNco(8kvAcS<C@{w>%#Z^z9nxI
m^Z?l(bUS8f!S*5m|F;N$&eyF0R4r&IZGa7cbW<^>pij>arcqe419hb4S{IER%e2y0s67m!+F|&Bkuq$cuAz*szY#mvr0p-
*ykNedj=AGkjvL9;q8<b+E2&8~R>Y+MKg1BFIN{=5G%AZaZcGF48xTb#2<7)Xd`^IgWVtnm;Au5Ecgn{YIeE8a+)u#<8nj6@TWkjt~!)r(2Z%u6;FBqV-
7nh$T6U(5M!?6XeC#9DlK^)oKtcnE7GALSGp&xK{P*qu^yv5z;gAQI+yn)iCV`p!UJ)`g_y>qzgS177yl9dKm|7b9XF6gRIL}t}^K-
P;b0;^keF3H@wj|qIxagQeQWHF{|;Pa2Vx7FBE6F)YJa0t~SUlSUPdq5B~i7)4<7V-
9O;DgM@WC)Z)gD_2kGtcRfoO^DGba#Y;09_D*CT$M4<f{*dB%ks2wpBs52^e;|ADZVT$6(8IVsGvT8bWbUk!N%t73o@u2~<VWw^EX{ZQTj<7mI~k{t^~FQs)
qn=k_a$^^4s+PBntVfg_VZwE{)wGj^XtBU)0ll?uneu|!fxI0r_myHBf%c@i2LnrYkB<*di?6oX^WdX4qMWJ|Ilxqn0}grJ(_{xVQ6n|K>y@hvcVJknOl#gy
7lh=>8w*bX?$O6VVza_#-zR{HM98U!jEnqK2-
W}u3M@8u}!yBt*3l^x_3HZOAl&4uh#2!h!L97;ohmLZ9Aitd1ueV#%{#4Hl4wixlZ;}si#}LBa_>a{E^qX#+aG&y9YI4tF5VgJ7VS&_)ib#5(7<4?-
=KHb0<16{rfW}i?03bFRI?}+QTO-
=0>M(+iP!MIN0h|1_W6@81)Tq#KWy2{NR?PIq!%JI@3?zdG%NeT5N*U6SH(Z<>;MlRP?xfbM~|4*?Cft`c(hwQRn5}sVE*08H_Kh-
@ku0vfi@}Pr3P54?(*zEZ+adu;T$e6i=b1;G1>&S=-
KB{Hq)7X(EM(Z~ES^yN%`l4eaR#7`~zLS!G6pW1yw?r2WAkhyQ4*;u9_rmu}FEW@PH5a(21YSn<xBv)$0j?3w$fkMp#60}l;Eye!yFvh0iAs1jogMQ@SAV@D
1{5u|!3@d42v(YEro&o|^<PquYjFO(qaZNm1NLz@lr?v}=}=gN#`dhpIWe}Kjh>MG==7G3PjZT0r+M{cRtMwV~Sh;F3ec3f>b*l1(@E9`{^V^Zh5uIrj36gz
3WBSQ5BL~2t;CT4ed^smruXRFP%YWw$3Y-UZjhHhQ_@3+^sAev)w@AT-82KtxJ&58Q0KT-eOyLoimf#-
fZxYpi{NF`nBmozPd(KMI^&>T2@J^e)aogHAShNIQ&3iJ)$YZ0j}KRqWz+&uk0w7tZwPxVQrP3>;fouQzZR@jN9b@{W0A`d7#)sv={qZN_*CHHDQF9Nq6IyZ
UK)88#W+ndCnq~EV`=IjVbPNQrtmi9@-
gJ7dNTi<nmu&yZWYQ65k{raT2MO5hK_Ml0iorCcQzmRtpXhQy2>#Mo;O*DS~+9F5$KkBm6qp0^qD=U3axitP&da;}!+gVHD{;xM4!`pXa14n>)J2*){uDF&>
VGpj|E~Zz<o%x<BTx*@-^U7MJo5SEMJ8RiBc5y7>^<lyHCt76IdqY?YVCZff?@VJC?LMX>9d$B-
9e(tSrD5rzHc}mWjgVWnuV4COW+&_MQVm@MTR%vC+YORkwY%sq#{-Lha}IopzP4+wwDrATvfj&SZtu{FY+!10Zh0P-C+AnL_Y(dTZn?S}D}LFV-
B&+JEA1#eG$+&X5B7|=HqBeBC+SZqcr^sxTnyk)FClo_x!2>Fuhr~3y8(9?iKh5<hZa2>8@fpM_TC3dV|Dmne!>$(hb<To)-YJx<>$dog@_-
XOq$)^c7aYi<I@GK(Va;c7@7uud~j=8@>Y-WY-|g^^lg~zXvHC?W^ikl$DhE>Y_ng`SDm0#)6_K5WMP+~?~~PVTr78=)lV1`C{-
s1BP~4bAO@!QY~3O}q^R2y$Iw(;gN>i=dj<KU!Ls^9()Ms^{V1J6qvs^AjtfTM=TzxN@p6B=b(1Eu65n@2jNyHyZzx?6-
dQN6XPK`3O^pY9j{tSkrzC5>liF_HdBhSCFDwRHj(eto6t`BtGn0d1Jsb$>-4?yS-f4G6ZS!@D&BVEZ9xMdnAE-a|S~q+(?>V;lRc{XcS&^|dF-
C^_ZRc3`_J=2VDmFi*cN}&w`3>Ixro1`1)39W`ABq9`nx|zu0KvTlAdWhE&$x5d-
CkY}l6*ST$JXL2*6PU30JaDc^rS6Avc5m@n{WkV+iZ6$uj1FAd)Eys%+XDsg3P=E*X-
@kgWM9JeT0fPALzIMyZxXK90;@C_lX?3uj#WRI~#U9o!;6T4{y#6<<jS|fi`?&IZ-A$$G%2Stk-
LGf4y}M=bZqkuU7htAu4v}BW4#e4rFF4@jB{<zO?3~XHEdGzy7)#xf-dbNi1lMwTZSeFq)v&;C;xnebr5Z&6MlV-
?2aRsF&?l71@Im;x0RX3DqZUKYh~rGJAORM`iW*v1(i#)>n8I`|+&ZV2sf#-
#q9|^Hq0>zz$EfNG%eT2NEEI0N#N;P+ja+mwOG5Y?fL8rr*4sXPM>|DQG?~;Ooxa(EQhdILU%)oj<C^RK0nM-fBD1Zil^UvY2gEoAtVP#5R&7t;#hl0(`RvG
sn7Iw&zYt-)F{|hksz=*M*GiteJF&{PoP7R@w@<x0fK+gege79-
&yn@px8W5Bpf%z#EE!#RfFBw6mD7_W3Rs>+5fNs8`?r!SwK$L3Pl!Bb_5kf1@;OW@^c;c6qRdgYWI!RD0@NHg{%bg_p$M!+N4`=x|+g?{E=^4P5BQy3-
eWM0VwD%i6v-x;d_QNnGk`Xg-0FR}CFWjMbQha~n*;8`$IYef-
|J$4l=;dhFflBJ48y1N(1x*5usL^d1Z1EWyKVm3rjnb{%b(ptnFQQ*BSHxiNfCG|ka;c)Z7#ZfK@$`!toyItJ^+omJzR)2iJV0&Ks*KV<)?F>o+Hh$d@2v68
FR-
3aOiH%y<5VOYygmJee{*%^rF9m4vudX0r1o_is_*XeeY4DEEfwOHiquX}#%YaP|J>v1aIp~LR3i(2Z`+y)J_7)!@99t_m=<j>tx<`mg?3v@X9BcE8@Urgjg)
A7u;-
opz>h7mVfjik5Ycf(AO2Zrdj)iX5a{t|2TwjnZT&&zfgJZ4Psm!4j4*$WoaM#eZ+BfFqIk9IV5eCXL_#&CCydS@Fuhu6(~&(^o^65u$yy}9WxW{l}H+&6YCh
?Z)7>Y78whimJ2W2mdZZsek4G-;wco|LWQtNKAz^Y8G6bI74ni?=;CiKT829do;-
MNDsb(ZyhC&VZq{xU3Uo7ugy!aQhtG2tylqz0$tNu!mb+p!RpGjioDDvvwz3Zrx0BU_bWBK0{g-
ij=LvE$rtA`u!Vfs&zlLow&{jxmc;PgGK6Mix;fzcn6yE)0;=U>j7WA5vgkZ+b(L+=luAEG?vXn8o$GH?K9rBZZ0IUZ=9jkFrA`Fuiy7wl%Th48{8}pxtpuK
cZ}}#6{C{V$9he5y7b2I?re!#aua`O{iyZN;q2knK){4#8TEXfyHSAJi1uhF<h2Oh+k7B;v(#`Th~C=$vTy8cGz{I%?O|&3f*8*+$kn()6U=v=AJ0HbU@Typ
{nqqI57yN8Gsp*1?ZSRw@$UCK{m|Q(GL3ql_saZF-8SjQZFv4M8@{o_@9d2nF-<?OX4-yh6DdFR3=Vp?9bO4HK=F3|-
m&vJ!_`OIrgmJkVlK_P(_^0&MOppvn>!GqH7^ok_T8cL?W2jUx14<@^#@N!1H9UBj(SJC)zB2Vd*vbCiVN{$|CrZR3SA^TvaKE9A6dG2Ou!vERPTFuaK^OvQ
h&&2C`M*N*{AM#&dCPj;d&UIIeeme5s5<mR1obZviBZtxSp>Eyc%~N*!5#pwwgA1ZQQvw)_w#BVH@oN*&6a`@3^_bXZefC)@Z!R=>waLD~%7Dq36NT==B}Y=
h*yW6y8<~V5*zJBAjcRRoQn2S3_r7H)OYSmj|}FT5hW?rS!9^yF^=c>^^B&n;y{Kb&fRMru29Vw8Vw~`mg``KZ+*^pa
"""
def _load_embedded_data():
compact = re.sub(r"\s+", "", _EMBEDDED_DATA_B85)
raw = zlib.decompress(base64.b85decode(compact.encode("ascii")))
return json.loads(raw.decode("utf-8"))
_DATA = _load_embedded_data()
_CANDIDATES = tuple(_DATA["candidates"])
_MATRIX = _DATA["matrix"]
_OPTIONS = _DATA["options"]
def _normalize_text(value):
"""Normalize GPT formatting differences without changing semantic words."""
if value is None:
return ""
s = unicodedata.normalize("NFKC", str(value))
s = (
s.replace("\u2018", "'")
.replace("\u2019", "'")
.replace("\u201c", '"')
.replace("\u201d", '"')
.replace("\u2013", "-")
.replace("\u2014", "-")
.replace("\u00a0", " ")
)
s = s.casefold()
s = re.sub(r"\s+", " ", s).strip()
# Make harmless output punctuation differences irrelevant.
s = s.strip(" \t\r\n\"'`*_-")
s = re.sub(r"[.]+$", "", s).strip()
return s
def _extract_numbered_blocks(text):
"""
Parse outputs such as:
11. chosen answer
12) chosen answer
Q13: chosen answer
Question 14 - chosen answer
Multi-line answers are supported.
"""
pattern = re.compile(
r"""
^[ \t]*
(?:(?:Q|Question)[ \t]*)?
(?P<num>\d{1,3})
[ \t]*
[\.\)\:\-]
[ \t]*
(?P<body>.*?)
(?=
^[ \t]*
(?:(?:Q|Question)[ \t]*)?
\d{1,3}
[ \t]*
[\.\)\:\-]
|
\Z
)
""",
re.MULTILINE | re.DOTALL | re.VERBOSE,
)
found = {}
for match in pattern.finditer(text or ""):
q = int(match.group("num"))
if 1 <= q <= 110:
found[q] = match.group("body").strip()
return found
def _explicit_letter(body):
"""Accept compact outputs such as '(A)', 'A)', 'A:', '[A]'."""
if not body:
return None
patterns = (
r"^\s*\(([ABCDabcd])\)(?:\s+|$)",
r"^\s*\[([ABCDabcd])\](?:\s+|$)",
r"^\s*([ABCDabcd])\s*[\)\.\:\-](?:\s+|$)",
r"^\s*([ABCDabcd])\s*$",
)
for pattern in patterns:
m = re.search(pattern, body)
if m:
return m.group(1).upper()
return None
def _answer_letter(question_number, body):
"""
Resolve a question body to A/B/C/D.
Resolution order:
1. Explicit answer letter.
2. Exact normalized option-text equality.
3. Exact option text occurring inside the body.
4. Conservative fuzzy matching for minor GPT punctuation/wording drift.
"""
qkey = str(question_number)
option_map = _OPTIONS.get(qkey)
if not option_map:
return None, "unknown-question"
letter = _explicit_letter(body)
if letter in option_map:
return letter, "explicit-letter"
norm_body = _normalize_text(body)
if not norm_body:
return None, "empty"
norm_options = {
letter: _normalize_text(text)
for letter, text in option_map.items()
}
# Exact normalized equality.
for letter, norm_option in norm_options.items():
if norm_body == norm_option:
return letter, "exact-text"
# GPT occasionally returns "(B) full option text" even though the explicit
# prefix formatting varies. Also allow the option to appear within a longer
# sentence.
for letter, norm_option in norm_options.items():
if norm_option and norm_option in norm_body:
return letter, "contained-text"
# Conservative fuzzy fallback. This is intentionally not permissive:
# a wrong option is worse than reporting one question as unparsed.
similarities = []
for letter, norm_option in norm_options.items():
ratio = difflib.SequenceMatcher(
None,
norm_body,
norm_option,
autojunk=False,
).ratio()
similarities.append((ratio, letter))
similarities.sort(reverse=True)
best_ratio, best_letter = similarities[0]
second_ratio = similarities[1][0]
if best_ratio >= 0.78 and (best_ratio - second_ratio) >= 0.035:
return best_letter, "fuzzy-text"
return None, "unresolved"
def _parse_answers(text):
blocks = _extract_numbered_blocks(text)
answers = {}
methods = {}
unresolved = {}
# Primary parse from numbered response blocks.
for q in range(11, 111):
body = blocks.get(q)
if body is None:
unresolved[q] = "missing"
continue
letter, method = _answer_letter(q, body)
if letter is None:
unresolved[q] = method
else:
answers[q] = letter
methods[q] = method
# Extra fallback for compact lines that did not fit the main block parser:
# "11 A", "Q11 = B", "11 -> C".
if len(answers) < 100:
compact_pattern = re.compile(
r"(?mi)^\s*(?:Q(?:uestion)?\s*)?(\d{1,3})\s*(?:=|->|:)?\s*[\(\[]?([ABCD])[\)\]]?\s*$"
)
for match in compact_pattern.finditer(text or ""):
q = int(match.group(1))
if 11 <= q <= 110 and q not in answers:
answers[q] = match.group(2).upper()
methods[q] = "compact-letter"
unresolved.pop(q, None)
return answers, methods, unresolved
def _score_answers(answers):
scores = {candidate: 0 for candidate in _CANDIDATES}
for q, letter in answers.items():
row = _MATRIX.get("Q{}".format(q), {}).get(letter)
if not row:
continue
for candidate in _CANDIDATES:
scores[candidate] += int(row[candidate])
# Stable deterministic tie break: score descending, then candidate name.
ranked = sorted(
scores.items(),
key=lambda item: (-item[1], item[0].casefold()),
)
return ranked
def _fit_percent(score, parsed_count):
"""
Normalized fit index for the amount of evidence actually parsed.
Not a probability.
With 100 questions:
score -400 -> 0%
score 0 -> 50%
score +400 -> 100%
"""
if parsed_count <= 0:
return 0.0
max_abs = 4 * parsed_count
return ((score + max_abs) / (2.0 * max_abs)) * 100.0
class DereVisionScorer:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"vision_result": (
"STRING",
{
"multiline": True,
"default": "",
},
),
}
}
RETURN_TYPES = ("STRING", "STRING", "STRING")
RETURN_NAMES = ("dere", "top_3_deres", "scoring_summary")
FUNCTION = "score_dere"
CATEGORY = "Salia/Dere"
def score_dere(self, vision_result):
answers, methods, unresolved = _parse_answers(vision_result)
parsed_count = len(answers)
if parsed_count == 0:
return (
"",
"",
(
"ERROR: Parsed 0/100 scored answers (Q11-Q110).\n"
"Expected the numbered output produced by the Dere Vision "
"questionnaire prompt."
),
)
ranked = _score_answers(answers)
top3 = ranked[:3]
dere = top3[0][0]
top_3_deres = ", ".join(name for name, _ in top3)
top1_score = top3[0][1]
top2_score = top3[1][1] if len(top3) > 1 else top1_score
margin = top1_score - top2_score
letter_counts = Counter(answers.values())
method_counts = Counter(methods.values())
lines = [
"Dere scoring summary",
"====================",
"Parsed scored answers: {}/100".format(parsed_count),
"Answer letters: A={} | B={} | C={} | D={}".format(
letter_counts.get("A", 0),
letter_counts.get("B", 0),
letter_counts.get("C", 0),
letter_counts.get("D", 0),
),
"Parse methods: {}".format(
", ".join(
"{}={}".format(name, count)
for name, count in sorted(method_counts.items())
)
or "none"
),
"",
"TOP 3",
]
for index, (name, score) in enumerate(top3, start=1):
lines.append(
"{}. {} | score {:+d} | normalized fit {:.2f}%".format(
index,
name,
score,
_fit_percent(score, parsed_count),
)
)
lines.extend(
[
"",
"Top1-Top2 margin: {:+d}".format(margin),
"",
"TOP 10",
]
)
for index, (name, score) in enumerate(ranked[:10], start=1):
lines.append(
"{:>2}. {} | {:+d} | {:.2f}%".format(
index,
name,
score,
_fit_percent(score, parsed_count),
)
)
if unresolved:
missing = sorted(unresolved)
preview = ", ".join("Q{}".format(q) for q in missing[:30])
if len(missing) > 30:
preview += ", ... (+{} more)".format(len(missing) - 30)
lines.extend(
[
"",
"WARNING: {} scored question(s) were not parsed.".format(
len(unresolved)
),
"Unparsed: {}".format(preview),
]
)
lines.extend(
[
"",
"Normalized fit is a ranking diagnostic, not a probability.",
"Scoring evidence used: {} questions x +/-4 points each.".format(
parsed_count
),
]
)
scoring_summary = "\n".join(lines)
return (dere, top_3_deres, scoring_summary)
try:
import folder_paths
except ImportError: # Allows tests outside ComfyUI.
folder_paths = None
import hashlib
import os
import secrets
import shutil
import tempfile
import threading
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Mapping, MutableMapping, Sequence, Tuple
NODE_VERSION = "1.0.0"
HF_REPO_ID = "saliacoel/chars"
HF_REPO_TYPE = "model"
HF_REVISION = "main"
OPENAI_BATCH_ENDPOINT = "/v1/responses"
TEXT_PURPOSE_PREFIX = "salia-character-responses-text-batch"
TEXT_STATE_DIRECTORY_NAME = "Salia_Character_Text_Responses_Batch_To_HF"
TEXT_STATE_DIRECTORIES = (
TEXT_STATE_DIRECTORY_NAME + "_1Image_OptionalText",
TEXT_STATE_DIRECTORY_NAME + "_1Text",
)
CUSTOM_SCORER_STATE_DIRECTORY_NAME = "Salia_Custom_Batch_Scorers"
_SAFE_BATCH_ID = re.compile(r"^batch_[A-Za-z0-9_-]{1,200}$")
_SAFE_FILE_ID = re.compile(r"^file-[A-Za-z0-9_-]{1,200}$")
_STATE_READ_LOCK = threading.RLock()
def _utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def _clean_secret(value: str, kind: str) -> str:
cleaned = str(value or "").strip()
if cleaned.lower() in {"", "none", "xxx", "sk_xxx", "sk-xxx", "hf_xxx"}:
raise ValueError(f"A real {kind} key is required.")
return cleaned
def _output_root() -> Path:
if folder_paths is not None:
return Path(folder_paths.get_output_directory())
return Path.cwd() / "output"
def _write_json(path: Path, value: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".part")
temporary.write_text(
json.dumps(value, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
os.replace(temporary, path)
def _read_json(path: Path) -> Dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8-sig"))
if not isinstance(value, dict):
raise ValueError(f"Expected a JSON object in {path}.")
return value
def _write_bytes_atomically(path: Path, data: bytes) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_name(path.name + f".{os.getpid()}.{secrets.token_hex(8)}.part")
try:
temporary.write_bytes(data)
os.replace(temporary, path)
finally:
try:
temporary.unlink()
except FileNotFoundError:
pass
def _load_hf_tools():
try:
from huggingface_hub import CommitOperationAdd, HfApi, hf_hub_download
except ImportError as exception:
raise RuntimeError(
"huggingface_hub is missing. Install it with ComfyUI's Python and restart ComfyUI."
) from exception
return CommitOperationAdd, HfApi, hf_hub_download
def _repo_commit(api: Any, hf_key: str = "") -> str:
info = api.repo_info(
repo_id=HF_REPO_ID,
repo_type=HF_REPO_TYPE,
revision=HF_REVISION,
token=hf_key or None,
)
commit = str(getattr(info, "sha", "") or "").strip()
if not commit:
raise ValueError(f"Could not resolve the current commit of {HF_REPO_ID}.")
return commit
def _safe_relative_asset(value: str, default_extension: str, label: str) -> str:
normalized = str(value or "").strip().replace("\\", "/").strip("/")
if not normalized:
raise ValueError(f"{label} cannot be empty.")
parts = normalized.split("/")
if any(
part in {"", ".", ".."}
or any(ord(character) < 32 or ord(character) == 127 for character in part)
for part in parts
):
raise ValueError(f"Unsafe relative {label} value: {value!r}")
if len(normalized) > 500:
raise ValueError(f"{label} is too long.")
if not Path(normalized).suffix:
normalized += default_extension
return normalized
def _normalize_result_file(value: str) -> str:
target = _safe_relative_asset(value, ".txt", "Result_File")
if "/" in target:
raise ValueError("Result_File must be one filename, not a nested path.")
if Path(target).suffix.lower() != ".txt":
raise ValueError("Result_File must be a TXT filename or a name without extension.")
return target
def _remote_targets_identical(
api: Any,
results: Sequence[Mapping[str, Any]],
revision: str,
hf_key: str,
hf_hub_download: Any,
) -> bool:
for result in results:
target_path = str(result["targetPath"])
if not api.file_exists(
repo_id=HF_REPO_ID,
filename=target_path,
repo_type=HF_REPO_TYPE,
revision=revision,
token=hf_key,
):
return False
local = hf_hub_download(
repo_id=HF_REPO_ID,
filename=target_path,
repo_type=HF_REPO_TYPE,
revision=revision,
token=hf_key,
force_download=True,
)
actual = hashlib.sha256(Path(local).read_bytes()).hexdigest()
if actual != str(result["sha256"]).lower():
return False
return True
def _publish_text_results(
results: Sequence[Mapping[str, Any]],
target_name: str,
hf_key: str,
*,
commit_context: str,
) -> Tuple[str, str]:
CommitOperationAdd, HfApi, hf_hub_download = _load_hf_tools()
api = HfApi(token=hf_key)
target_commit = _repo_commit(api, hf_key)
if _remote_targets_identical(api, results, target_commit, hf_key, hf_hub_download):
return "already_published", target_commit
operations = [
CommitOperationAdd(
path_in_repo=str(result["targetPath"]),
path_or_fileobj=str(result["localPath"]),
)
for result in results
]
commit = api.create_commit(
repo_id=HF_REPO_ID,
repo_type=HF_REPO_TYPE,
revision=HF_REVISION,
operations=operations,
commit_message=(f"Write {target_name} for {len(results)} characters {commit_context}"),
parent_commit=target_commit,
token=hf_key,
)
oid = str(getattr(commit, "oid", "") or "").strip()
if not oid:
raise RuntimeError("Hugging Face commit completed without an oid.")
return "published", oid
def _openai_error(exception: urllib.error.HTTPError) -> str:
try:
body = exception.read().decode("utf-8", errors="replace")
parsed = json.loads(body)
error = parsed.get("error") if isinstance(parsed, dict) else None
if isinstance(error, dict):
return str(error.get("message") or body)[:1200]
return body[:1200]
except Exception:
return str(exception)
def _openai_json(
method: str,
path: str,
openai_key: str,
payload: Mapping[str, Any] | None = None,
timeout: int = 240,
) -> Dict[str, Any]:
data = None
headers = {
"Authorization": "Bearer " + openai_key,
"User-Agent": f"Salia-Custom-Batch-Scorer/{NODE_VERSION}",
}
if payload is not None:
data = json.dumps(payload, separators=(",", ":")).encode("utf-8")
headers["Content-Type"] = "application/json"
request = urllib.request.Request(
"https://api.openai.com" + path,
data=data,
headers=headers,
method=method,
)
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
parsed = json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as exception:
raise RuntimeError(f"OpenAI API {method} {path} failed: {_openai_error(exception)}") from exception
if not isinstance(parsed, dict):
raise RuntimeError(f"OpenAI API {path} returned a non-object response.")
return parsed
def _download_openai_file(file_id: str, destination: Path, openai_key: str) -> Path:
if not _SAFE_FILE_ID.fullmatch(file_id):
raise ValueError("OpenAI output file id is invalid.")
destination.parent.mkdir(parents=True, exist_ok=True)
temporary = destination.with_suffix(destination.suffix + ".part")
request = urllib.request.Request(
"https://api.openai.com/v1/files/" + urllib.parse.quote(file_id, safe="") + "/content",
headers={
"Authorization": "Bearer " + openai_key,
"User-Agent": f"Salia-Custom-Batch-Scorer/{NODE_VERSION}",
},
method="GET",
)
try:
with urllib.request.urlopen(request, timeout=3600) as response, temporary.open("wb") as output:
shutil.copyfileobj(response, output, length=1024 * 1024)
os.replace(temporary, destination)
except urllib.error.HTTPError as exception:
if temporary.exists():
temporary.unlink()
raise RuntimeError("OpenAI Batch output download failed: " + _openai_error(exception)) from exception
except Exception:
if temporary.exists():
temporary.unlink()
raise
if not destination.is_file() or destination.stat().st_size == 0:
raise ValueError("OpenAI Batch output JSONL is empty.")
return destination
def _batch_status(batch_id: str, openai_key: str) -> Dict[str, Any]:
if not _SAFE_BATCH_ID.fullmatch(batch_id):
raise ValueError("Custom batch id is invalid.")
return _openai_json("GET", "/v1/batches/" + urllib.parse.quote(batch_id, safe=""), openai_key)
def _request_counts(batch: Mapping[str, Any]) -> Tuple[int, int, int]:
counts = batch.get("request_counts")
if not isinstance(counts, Mapping):
return 0, 0, 0
try:
return (
int(counts.get("total") or 0),
int(counts.get("completed") or 0),
int(counts.get("failed") or 0),
)
except (TypeError, ValueError):
return 0, 0, 0
def _short_error(value: Any) -> str:
try:
if isinstance(value, str):
return value[:800]
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))[:800]
except Exception:
return str(value)[:800]
def _response_text_result(body: Mapping[str, Any]) -> str:
status = str(body.get("status") or "")
if status and status != "completed":
raise ValueError(f"Responses result status is {status!r}, not 'completed'.")
if body.get("error"):
raise ValueError(_short_error(body.get("error")))
top_level = body.get("output_text")
if isinstance(top_level, str) and top_level.strip():
return top_level.strip()
output = body.get("output")
if not isinstance(output, list):
raise ValueError("Responses result has no output array.")
text_parts: List[str] = []
refusal_parts: List[str] = []
for item in output:
if not isinstance(item, Mapping) or str(item.get("type") or "") != "message":
continue
content = item.get("content")
if not isinstance(content, list):
continue
for part in content:
if not isinstance(part, Mapping):
continue
part_type = str(part.get("type") or "")
if part_type == "output_text":
text_value = part.get("text")
if isinstance(text_value, str) and text_value:
text_parts.append(text_value)
elif part_type == "refusal":
refusal = str(part.get("refusal") or part.get("text") or "").strip()
if refusal:
refusal_parts.append(refusal)
combined = "".join(text_parts).strip()
if combined:
return combined
if refusal_parts:
raise ValueError("Model returned a refusal instead of output text: " + " ".join(refusal_parts)[:1200])
raise ValueError("Responses result contains no non-empty output_text.")
def _text_request_mapping_by_custom_id(state: Mapping[str, Any]) -> Dict[str, Mapping[str, Any]]:
requests = state.get("requests")
if not isinstance(requests, list):
raise ValueError("Saved Batch state has no request manifest.")
mapping: Dict[str, Mapping[str, Any]] = {}
for request in requests:
if not isinstance(request, Mapping):
continue
custom_id = str(request.get("batchCustomId") or "")
if not custom_id:
continue
if custom_id in mapping:
raise ValueError(f"Saved state contains duplicate custom_id {custom_id!r}.")
mapping[custom_id] = request
if not mapping:
raise ValueError("Saved Batch state contains no request mappings.")
return mapping
def _write_text_result_file(results_directory: Path, character: str, text: str) -> Tuple[Path, bytes]:
results_directory.mkdir(parents=True, exist_ok=True)
normalized_text = str(text)
if not normalized_text.endswith("\n"):
normalized_text += "\n"
data = normalized_text.encode("utf-8")
local_name = hashlib.sha256(character.encode("utf-8")).hexdigest()[:16]
local_path = results_directory / f"{local_name}.txt"
_write_bytes_atomically(local_path, data)
return local_path, data
def _text_state_roots() -> List[Path]:
return [_output_root() / name for name in TEXT_STATE_DIRECTORIES]
def _find_saved_batch_state(batch_id: str) -> Tuple[Dict[str, Any], Path]:
searched: List[str] = []
candidates: List[Tuple[int, Path, Dict[str, Any]]] = []
with _STATE_READ_LOCK:
for root in _text_state_roots():
searched.append(str(root.resolve()))
if not root.exists():
continue
for path in root.rglob("*.json"):
try:
state = _read_json(path)
except Exception:
continue
if str(state.get("batchId") or "").strip() != batch_id:
continue
purpose = str(state.get("purpose") or "")
if not purpose.startswith(TEXT_PURPOSE_PREFIX):
continue
requests = state.get("requests")
if not isinstance(requests, list) or not requests:
continue
score = 0
if path.name == "batch_state.json":
score += 50
if "/aborted/" in path.as_posix().replace("\\", "/"):
score += 40
if str(state.get("runRoot") or ""):
score += 20
if str(state.get("statePath") or ""):
score += 5
score += min(len(requests), 100)
candidates.append((score, path, state))
if not candidates:
raise FileNotFoundError(
"No local saved text-batch state was found for custom batch id "
f"{batch_id!r}. Searched: " + " | ".join(searched)
)
candidates.sort(key=lambda item: (item[0], str(item[1])), reverse=True)
_, path, state = candidates[0]
return dict(state), path
def _new_custom_run_root(node_slug: str, batch_id: str) -> Path:
parent = _output_root() / CUSTOM_SCORER_STATE_DIRECTORY_NAME / node_slug / "runs"
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
safe_batch = batch_id if _SAFE_BATCH_ID.fullmatch(batch_id) else "unknown_batch"
candidate = parent / f"{stamp}_{safe_batch}"
suffix = 1
while candidate.exists():
suffix += 1
candidate = parent / f"{stamp}_{safe_batch}_{suffix}"
candidate.mkdir(parents=True, exist_ok=False)
return candidate
def _process_custom_batch(
*,
node_slug: str,
batch_id: str,
result_file: str,
openai_key: str,
hf_key: str,
score_func,
) -> str:
normalized_batch_id = str(batch_id or "").strip()
if not _SAFE_BATCH_ID.fullmatch(normalized_batch_id):
raise ValueError("custom_batch_id must look like 'batch_...'.")
normalized_result_file = _normalize_result_file(result_file)
openai_token = _clean_secret(openai_key, "OpenAI API")
hf_token = _clean_secret(hf_key, "Hugging Face")
state, state_path = _find_saved_batch_state(normalized_batch_id)
request_by_id = _text_request_mapping_by_custom_id(state)
batch = _batch_status(normalized_batch_id, openai_token)
if str(batch.get("id") or "") != normalized_batch_id:
raise ValueError("OpenAI returned a different Batch identity.")
if str(batch.get("endpoint") or "") != OPENAI_BATCH_ENDPOINT:
raise ValueError("Saved Batch does not target /v1/responses.")
status = str(batch.get("status") or "unknown")
total, completed, failed = _request_counts(batch)
if status in {"validating", "in_progress", "finalizing", "cancelling"}:
return (
f"Waiting. Custom Batch {normalized_batch_id} is {status!r} "
f"(total={total}, completed={completed}, failed={failed})."
)
if status in {"failed", "expired", "cancelled"}:
raise ValueError(
f"Custom Batch {normalized_batch_id} ended as {status!r}. Nothing was published."
)
if status != "completed":
raise ValueError(f"Unknown Batch status {status!r}. Nothing was published.")
output_file_id = str(batch.get("output_file_id") or "")
if completed and not _SAFE_FILE_ID.fullmatch(output_file_id):
raise ValueError(
f"Batch {normalized_batch_id} reports {completed} completed requests but has no valid output_file_id."
)
run_root = _new_custom_run_root(node_slug, normalized_batch_id)
diagnostics_path = run_root / "custom_batch_score_diagnostics.json"
failure_report_path = run_root / "custom_batch_score_failures.json"
output_jsonl = run_root / "openai_batch_output.jsonl"
if completed:
_download_openai_file(output_file_id, output_jsonl, openai_token)
else:
output_jsonl.write_text("", encoding="utf-8")
results: List[Dict[str, Any]] = []
failures: List[Dict[str, Any]] = []
diagnostics: List[Dict[str, Any]] = []
seen = set()
with output_jsonl.open("r", encoding="utf-8-sig", errors="strict") as handle:
for line_number, line in enumerate(handle, start=1):
if not line.strip():
continue
try:
record = json.loads(line)
except Exception as exception:
raise ValueError(
f"Batch output line {line_number} is invalid JSON: {_short_error(exception)}"
) from exception
if not isinstance(record, Mapping):
raise ValueError(f"Batch output line {line_number} is not an object.")
custom_id = str(record.get("custom_id") or "")
mapping = request_by_id.get(custom_id)
if mapping is None:
raise ValueError(f"Batch output contains unexpected custom_id {custom_id!r}.")
if custom_id in seen:
raise ValueError(f"Batch output contains duplicate custom_id {custom_id!r}.")
seen.add(custom_id)
diagnostic: Dict[str, Any] = {
"lineNumber": line_number,
"batchCustomId": custom_id,
"character": str(mapping.get("character") or ""),
"targetPath": str(mapping.get("targetPath") or ""),
"statusCode": 0,
"responseStatus": "",
"error": "",
}
try:
response = record.get("response")
if not isinstance(response, Mapping):
raise ValueError(_short_error(record.get("error")))
status_code = int(response.get("status_code") or 0)
diagnostic["statusCode"] = status_code
if status_code < 200 or status_code >= 300:
raise ValueError(
f"HTTP {status_code}: " + _short_error(response.get("body") or record.get("error"))
)
body = response.get("body")
if not isinstance(body, Mapping):
raise ValueError("Successful Batch line has no Responses body.")
diagnostic["responseStatus"] = str(body.get("status") or "")
raw_text = _response_text_result(body)
scored_text = score_func(raw_text)
character = str(mapping["character"])
local_path, data = _write_text_result_file(run_root / "scored_text", character, scored_text)
diagnostic["textByteCount"] = len(data)
diagnostic["textPreview"] = scored_text[:1000]
results.append(
{
"batchCustomId": custom_id,
"character": character,
"targetPath": f"{character}/{normalized_result_file}",
"localPath": str(local_path.resolve()),
"sha256": hashlib.sha256(data).hexdigest(),
"byteCount": len(data),
}
)
except Exception as exception:
reason = _short_error(str(exception))
diagnostic["error"] = reason
failures.append(
{
"batchCustomId": custom_id,
"character": str(mapping.get("character") or ""),
"targetPath": f"{str(mapping.get('character') or '')}/{normalized_result_file}",
"reason": reason,
"failureKind": "scoring_or_generation",
}
)
diagnostics.append(diagnostic)
missing = sorted(set(request_by_id) - seen)
for custom_id in missing:
mapping = request_by_id[custom_id]
failure = {
"batchCustomId": custom_id,
"character": str(mapping.get("character") or ""),
"targetPath": f"{str(mapping.get('character') or '')}/{normalized_result_file}",
"reason": "OpenAI returned no successful output line for this request.",
"failureKind": "request_error",
}
failures.append(failure)
diagnostics.append(
{
"lineNumber": None,
"batchCustomId": custom_id,
"character": failure["character"],
"targetPath": failure["targetPath"],
"statusCode": 0,
"responseStatus": "",
"error": failure["reason"],
"diagnosticKind": "batch_request_error",
}
)
results.sort(key=lambda result: str(result["character"]).casefold())
failures.sort(key=lambda failure: str(failure["character"]).casefold())
_write_json(
diagnostics_path,
{
"formatVersion": 1,
"batchId": normalized_batch_id,
"statePath": str(state_path.resolve()),
"rawBatchOutputJsonlPath": str(output_jsonl.resolve()),
"returnedResponseCount": len(seen),
"successfulScoreCount": len(results),
"failureCount": len(failures),
"entries": diagnostics,
},
)
if failures:
_write_json(
failure_report_path,
{
"formatVersion": 1,
"batchId": normalized_batch_id,
"statePath": str(state_path.resolve()),
"failureCount": len(failures),
"failures": failures,
},
)
action = "no_results"
commit_oid = ""
if results:
action, commit_oid = _publish_text_results(
results,
normalized_result_file,
hf_token,
commit_context=f"via custom Batch scorer {normalized_batch_id}",
)
if failures:
return (
f"Partial Done. custom Batch {normalized_batch_id} processed. "
f"Publish action={action!r}. Published/scored {len(results)} character TXT files as {normalized_result_file!r}. "
f"Failures={len(failures)}. Diagnostics: {diagnostics_path.resolve()} | Failure report: {failure_report_path.resolve()}"
)
return (
f"All Done. custom Batch {normalized_batch_id} processed. "
f"Publish action={action!r}. Published/scored {len(results)} character TXT files as {normalized_result_file!r}. "
f"Diagnostics: {diagnostics_path.resolve()} | Commit: {commit_oid}"
)
def _score_for_hf(vision_result: str) -> str:
dere, top_3_deres, scoring_summary = DereVisionScorer().score_dere(vision_result)
if not dere:
raise ValueError(scoring_summary or "Dere scorer returned no result.")
return (
"Dere: {}\n"
"Top 3: {}\n\n"
"{}\n"
).format(dere, top_3_deres, scoring_summary)
# =============================================================================
# FIXING TEST MODE: optional NEW cohort multipliers, with NO extra/tie-break scores
# =============================================================================
_DERE_RENAMES_FIXING = {'Sunao Cool': 'Sunaocool', 'Sunao Heat': 'Sunaoheat', 'Sunao Surreal': 'Sunaosurreal', 'S Dere / Sadodere': 'Sadodere', 'Yandere — Sick': 'Yansick', 'Yandere — Yankii': 'YankiiYandere', 'Megadere — Mega': 'Megadere', 'Mayadere — Japanese': 'Mayadere', 'Megadere — Goddess': 'Megamide', 'Mayadere — Western': 'Westmayadere', 'Kamidere — Bite': 'Kamikami', 'Kamidere — Deity': 'Kamidere', 'M Dere': 'Masodere', 'Tsun-Aho': 'Tsunaho', 'Tsun-Ama': 'Tsunama', 'Tsun-Pure': 'Tsunpure', 'Gou-dere': 'Goudere', 'Sunao Chill': 'Sunaochill', 'Tsun-utsu': 'Tsunutsu'}
_DERE_TO_AVG_MULTIPLIER_FIXING = {'Himedere': 0.943226, 'Ambidere': 0.918176, 'Bakadere': 0.964209, 'Biridere': 1.197835, 'Biyadere': 0.93106, 'Bocchandere': 1.007229, 'Bokodere': 1.158822, 'Borodere': 1.191923, 'Bosudere': 0.894561, 'Bureidere': 0.952454, 'Butsudere': 0.841989, 'Chindere': 0.955586, 'Dandere': 1.41492, 'Danyan': 0.926542, 'Deredere': 0.908661, 'Dereutsu': 0.908943, 'Dorodere': 1.029681, 'Erodere': 0.935712, 'Erohaji': 1.299714, 'Fuandere': 1.328516, 'Fushidere': 1.013867, 'Gundere': 0.823542, 'Gurodere': 0.920399, 'Hamedere': 0.811333, 'Hikadere': 1.200137, 'Hinedere': 0.924739, 'Hokodere': 0.834814, 'Kahodere': 0.870576, 'Kakkodere': 0.935341, 'Kichidere': 1.016932, 'Kidere': 0.963218, 'Kiredere': 1.132622, 'Kundere': 0.832525, 'Kurodere': 0.979905, 'Kuudere': 0.950722, 'Kuutsun': 0.97301, 'Masodere': 0.970982, 'Megamide': 0.976595, 'Megaun': 0.974824, 'Narudere': 0.8981, 'Nemuidere': 1.298231, 'Nisedere': 1.006706, 'Nyandere': 0.9014, 'Onidere': 1.021052, 'Pyondere': 0.910858, 'Roshidere': 0.829068, 'Sadodere': 0.900583, 'Sashidere': 0.883635, 'Sattodere': 0.821962, 'Shijidere': 0.876167, 'Shittodere': 1.120021, 'Sunaochill': 1.295406, 'Taidere': 1.478803, 'Teasedere': 0.888733, 'Thugdere': 0.992674, 'Toubodere': 1.00978, 'Toukadere': 0.858785, 'Tsunaho': 1.097955, 'Tsunama': 1.117567, 'Tsunbaka': 1.096329, 'Tsundere': 1.153682, 'Tsundero': 0.987008, 'Tsundora': 0.962384, 'Tsunpure': 1.149089, 'Tsunshun': 1.213801, 'Tsunutsu': 1.140001, 'Tsuyodere': 1.00727, 'Usodere': 1.045222, 'Utadere': 0.88179, 'Utsudan': 1.326589, 'Utsudere': 1.299547, 'Uzadere': 0.899334, 'Westmayadere': 1.040059, 'Yancool': 0.85428, 'Yansick': 1.065965, 'Zondere': 0.990691, 'Amadere': 0.863264, 'Byoukidere': 0.809163, 'Dandoro': 0.971538, 'Darudere': 1.432758, 'Doromuga': 0.961023, 'Gandere': 1.154484, 'Gesudere': 0.937201, 'Goudere': 0.936738, 'Hajidere': 1.378644, 'Inudere': 0.833058, 'Jendere': 0.921319, 'Kamidere': 1.021282, 'Kamikami': 1.145439, 'Kamikan': 0.919426, 'Kanedere': 1.045112, 'Kekkondere': 0.848293, 'Kiridere': 0.987166, 'Kondere': 0.925612, 'Kumadere': 0.851288, 'Kuzudere': 1.036878, 'Mayadere': 1.134827, 'Megadere': 0.881445, 'Nipadere': 0.910701, 'Nisekami': 0.860631, 'Norodere': 1.400411, 'Ojoudere': 1.000232, 'Osadere': 0.908937, 'Oujidere': 1.021354, 'Oujodere': 0.961048, 'Rindere': 0.926787, 'Shindere': 0.969181, 'Smugdere': 0.92349, 'Sunaocool': 0.903576, 'Sunaoheat': 0.934234, 'Sunaosurreal': 1.028935, 'Tekidere': 1.051304, 'Teredere': 1.321377, 'Tomedere': 0.913198, 'Tsungire': 1.204133, 'Tsunneko': 1.153867, 'Tsuntere': 1.429546, 'Undere': 0.977391, 'Yanheat': 1.046777, 'YankiiYandere': 1.03601, 'Yoidere': 0.870548, 'Tsunpuri': 0.942959}
def _canonical_dere_name_fixing(name):
return _DERE_RENAMES_FIXING.get(str(name), str(name))
def _score_answers_with_mult_fixing(answers):
"""Original matrix points + user's NEW multipliers. Nothing else."""
raw_scores = {candidate: 0 for candidate in _CANDIDATES}
for q, letter in answers.items():
row = _MATRIX.get("Q{}".format(q), {}).get(letter)
if not row:
continue
for candidate in _CANDIDATES:
raw_scores[candidate] += int(row[candidate])
parsed_count = len(answers)
max_abs = 4 * parsed_count
adjusted_scores = {}
adjusted_fits = {}
for candidate, raw_score in raw_scores.items():
canonical = _canonical_dere_name_fixing(candidate)
if canonical not in _DERE_TO_AVG_MULTIPLIER_FIXING:
raise RuntimeError("Missing NEW Dere multiplier for {}".format(canonical))
base_fit = _fit_percent(raw_score, parsed_count)
adjusted_fit = base_fit * float(_DERE_TO_AVG_MULTIPLIER_FIXING[canonical])
adjusted_score = int(round((adjusted_fit / 100.0) * (2.0 * max_abs) - max_abs))
adjusted_scores[candidate] = adjusted_score
adjusted_fits[candidate] = adjusted_fit
# Same weighted ranking rule used to produce the supplied NEW multiplier result.
return sorted(
adjusted_scores.items(),
key=lambda item: (-adjusted_fits[item[0]], _canonical_dere_name_fixing(item[0]).casefold()),
)
def _score_for_hf_with_mult_fixing(vision_result: str) -> str:
answers, methods, unresolved = _parse_answers(vision_result)
parsed_count = len(answers)
if parsed_count == 0:
raise ValueError(
"Parsed 0/100 scored answers (Q11-Q110). Expected the numbered Dere questionnaire output."
)
ranked = _score_answers_with_mult_fixing(answers)
top3 = ranked[:3]
dere = _canonical_dere_name_fixing(top3[0][0])
top_3_deres = ", ".join(_canonical_dere_name_fixing(name) for name, _ in top3)
letter_counts = Counter(answers.values())
method_counts = Counter(methods.values())
lines = [
"Dere scoring summary",
"====================",
"MODE: WITH NEW MULTIPLIERS; original matrix only; no extra score; no tie-breaker score",
"Parsed scored answers: {}/100".format(parsed_count),
"Answer letters: A={} | B={} | C={} | D={}".format(
letter_counts.get("A", 0), letter_counts.get("B", 0),
letter_counts.get("C", 0), letter_counts.get("D", 0),
),
"Parse methods: {}".format(
", ".join("{}={}".format(name, count) for name, count in sorted(method_counts.items())) or "none"
),
"",
"TOP 3",
]
for index, (name, score) in enumerate(top3, start=1):
lines.append("{}. {} | score {:+d}".format(index, _canonical_dere_name_fixing(name), score))
lines.extend(["", "TOP 10"])
for index, (name, score) in enumerate(ranked[:10], start=1):
lines.append("{:>2}. {} | {:+d}".format(index, _canonical_dere_name_fixing(name), score))
if unresolved:
missing = sorted(unresolved)
preview = ", ".join("Q{}".format(q) for q in missing[:30])
if len(missing) > 30:
preview += ", ... (+{} more)".format(len(missing) - 30)
lines.extend(["", "WARNING: {} scored question(s) were not parsed.".format(len(unresolved)), "Unparsed: {}".format(preview)])
return (
"Dere: {}\n"
"Top 3: {}\n\n"
"{}\n"
).format(dere, top_3_deres, "\n".join(lines))
class HF_DereVisionScorer_CustomBatch_fixing_without_mult:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("status_out",)
FUNCTION = "run"
CATEGORY = "Salia/HuggingFace/Scorers"
OUTPUT_NODE = True
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"custom_batch_id": ("STRING", {"default": "batch_6a98cb8c3f8c8190be53e868d3c269c9", "multiline": False}),
"Result_File": ("STRING", {"default": "dere_score", "multiline": False}),
"openAi_key": ("STRING", {"default": "sk-xxx", "multiline": False}),
"hf_key": ("STRING", {"default": "hf_xxx", "multiline": False}),
}
}
@classmethod
def IS_CHANGED(cls, **kwargs):
return float("nan")
def run(self, custom_batch_id, Result_File, openAi_key, hf_key):
try:
# IMPORTANT: exact original _score_for_hf from the original no-mult CustomBatch file.
status = _process_custom_batch(
node_slug="dere_custom_batch_fixing_without_mult",
batch_id=custom_batch_id,
result_file=Result_File,
openai_key=openAi_key,
hf_key=hf_key,
score_func=_score_for_hf,
)
return (status,)
except Exception as exception:
return (
"ERROR: Dere CustomBatch fixing WITHOUT_MULT failed; nothing was intentionally published. "
+ type(exception).__name__ + ": " + str(exception),
)
class HF_DereVisionScorer_CustomBatch_fixing_with_mult:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("status_out",)
FUNCTION = "run"
CATEGORY = "Salia/HuggingFace/Scorers"
OUTPUT_NODE = True
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"custom_batch_id": ("STRING", {"default": "batch_6a98cb8c3f8c8190be53e868d3c269c9", "multiline": False}),
"Result_File": ("STRING", {"default": "dere_score", "multiline": False}),
"openAi_key": ("STRING", {"default": "sk-xxx", "multiline": False}),
"hf_key": ("STRING", {"default": "hf_xxx", "multiline": False}),
}
}
@classmethod
def IS_CHANGED(cls, **kwargs):
return float("nan")
def run(self, custom_batch_id, Result_File, openAi_key, hf_key):
try:
status = _process_custom_batch(
node_slug="dere_custom_batch_fixing_with_mult",
batch_id=custom_batch_id,
result_file=Result_File,
openai_key=openAi_key,
hf_key=hf_key,
score_func=_score_for_hf_with_mult_fixing,
)
return (status,)
except Exception as exception:
return (
"ERROR: Dere CustomBatch fixing WITH_MULT failed; nothing was intentionally published. "
+ type(exception).__name__ + ": " + str(exception),
)
NODE_CLASS_MAPPINGS = {
"HF_DereVisionScorer_CustomBatch_fixing_without_mult": HF_DereVisionScorer_CustomBatch_fixing_without_mult,
"HF_DereVisionScorer_CustomBatch_fixing_with_mult": HF_DereVisionScorer_CustomBatch_fixing_with_mult,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"HF_DereVisionScorer_CustomBatch_fixing_without_mult": "HF Dere CustomBatch Fixing _without_mult",
"HF_DereVisionScorer_CustomBatch_fixing_with_mult": "HF Dere CustomBatch Fixing _with_mult",
}