From ad2137858d446d19526a70e60e2b4d2d8220cb66 Mon Sep 17 00:00:00 2001 From: CormickKneey Date: Wed, 29 Oct 2025 10:37:03 +0800 Subject: [PATCH] add terminal agent with openai-agents Signed-off-by: CormickKneey --- areal/dataset/__init__.py | 20 +- areal/dataset/terminal_bench.py | 60 ++ areal/experimental/openai/client.py | 18 +- assets/qwen3_8b_terminal.png | Bin 0 -> 193527 bytes examples/__init__.py | 0 .../openai-agents/agent_terminal_workflow.py | 202 +++++ examples/openai-agents/config.yaml | 2 +- examples/openai-agents/terminal/README.md | 124 +++ examples/openai-agents/terminal/__init__.py | 0 examples/openai-agents/terminal/env.py | 477 +++++++++++ .../openai-agents/terminal/judge_agent.py | 71 ++ .../openai-agents/terminal/logging_config.py | 30 + examples/openai-agents/terminal/models.py | 793 ++++++++++++++++++ examples/openai-agents/terminal/prompt.py | 264 ++++++ .../openai-agents/terminal/requirements.txt | 4 + examples/openai-agents/terminal/server.py | 222 +++++ .../terminal/tasks_to_parquet_converter.py | 232 +++++ .../terminal/terminal_bench_task.py | 131 +++ .../openai-agents/terminal/test_client.py | 129 +++ examples/openai-agents/train_agents.py | 15 +- 20 files changed, 2775 insertions(+), 19 deletions(-) create mode 100644 areal/dataset/terminal_bench.py create mode 100644 assets/qwen3_8b_terminal.png create mode 100644 examples/__init__.py create mode 100644 examples/openai-agents/agent_terminal_workflow.py create mode 100644 examples/openai-agents/terminal/README.md create mode 100644 examples/openai-agents/terminal/__init__.py create mode 100644 examples/openai-agents/terminal/env.py create mode 100644 examples/openai-agents/terminal/judge_agent.py create mode 100644 examples/openai-agents/terminal/logging_config.py create mode 100644 examples/openai-agents/terminal/models.py create mode 100644 examples/openai-agents/terminal/prompt.py create mode 100644 examples/openai-agents/terminal/requirements.txt create mode 100644 examples/openai-agents/terminal/server.py create mode 100644 examples/openai-agents/terminal/tasks_to_parquet_converter.py create mode 100644 examples/openai-agents/terminal/terminal_bench_task.py create mode 100644 examples/openai-agents/terminal/test_client.py diff --git a/areal/dataset/__init__.py b/areal/dataset/__init__.py index 3258c6087..dfa90c665 100644 --- a/areal/dataset/__init__.py +++ b/areal/dataset/__init__.py @@ -10,7 +10,14 @@ from transformers.processing_utils import ProcessorMixin from transformers.tokenization_utils_fast import PreTrainedTokenizerFast -VALID_DATASETS = ["gsm8k", "clevr_count_70k", "geometry3k", "hh-rlhf", "torl_data"] +VALID_DATASETS = [ + "gsm8k", + "clevr_count_70k", + "geometry3k", + "hh-rlhf", + "torl_data", + "terminal_bench", +] logger = logging.getLogger("Dataset") @@ -24,7 +31,6 @@ def _get_custom_dataset( processor: Optional["ProcessorMixin"] = None, **kwargs, ) -> "Dataset": - if "gsm8k" in path and type == "sft": from .gsm8k import get_gsm8k_sft_dataset @@ -105,6 +111,16 @@ def _get_custom_dataset( max_length=max_length, **kwargs, ) + elif "terminal_bench" in path and type == "rl": + from .terminal_bench import get_terminal_bench_rl_dataset + + return get_terminal_bench_rl_dataset( + path=path, + split=split, + tokenizer=tokenizer, + max_length=max_length, + **kwargs, + ) else: raise ValueError( f"Dataset {path} with split {split} and training type {type} is not supported. " diff --git a/areal/dataset/terminal_bench.py b/areal/dataset/terminal_bench.py new file mode 100644 index 000000000..bc351936b --- /dev/null +++ b/areal/dataset/terminal_bench.py @@ -0,0 +1,60 @@ +from typing import TYPE_CHECKING + +from datasets import load_dataset + +if TYPE_CHECKING: + from transformers import PreTrainedTokenizerFast + + +def get_terminal_bench_rl_dataset( + path: str, + split: str, + tokenizer: "PreTrainedTokenizerFast", + max_length: int | None = None, +): + """Load terminal-bench dataset for RL training. + + The dataset should be in parquet format with the following columns: + - prompt: The formatted prompt for the task + - task_name: Name of the task + - instruction: Raw instruction text + - extra_info: JSON string containing task metadata + """ + # Load from parquet file + dataset = load_dataset("parquet", data_files={split: path}, split=split) + + # The dataset already has the right format from the converter: + # - prompt: contains the formatted conversation + # - task_name, instruction, extra_info: metadata fields + + # For RL training, we need to extract messages from the prompt or extra_info + def process(sample): + # The prompt is already formatted, but we need to extract the instruction + # to create a messages structure for the workflow + instruction = sample.get("instruction", "") + task_name = sample.get("task_name", "") + dockerfile_contents = sample.get("dockerfile_contents", "") + + # Return data in the format expected by the workflow + return { + "instruction": instruction, + "task_name": task_name, + "dockerfile_contents": dockerfile_contents, + "extra_info": sample.get("extra_info", ""), + "data_source": sample.get("data_source", "terminal_bench"), + } + + dataset = dataset.map(process) + + # Filter out sequences longer than max_length if specified + if max_length is not None: + + def filter_length(samples): + # Tokenize instructions in batches for efficiency + instructions = samples["instruction"] + tokens_list = tokenizer(instructions, add_special_tokens=False)["input_ids"] + return [len(tokens) <= max_length for tokens in tokens_list] + + dataset = dataset.filter(filter_length, batched=True) + + return dataset diff --git a/areal/experimental/openai/client.py b/areal/experimental/openai/client.py index 18a2579d8..6dd3c321a 100644 --- a/areal/experimental/openai/client.py +++ b/areal/experimental/openai/client.py @@ -15,7 +15,6 @@ from openai.types.chat import ( ChatCompletion, ChatCompletionMessage, - ChatCompletionToolMessageParam, ChatCompletionToolParam, ) from openai.types.chat.chat_completion import Choice @@ -277,22 +276,11 @@ async def create( if is_omitted(input): raise ValueError("input is required for Responses.create") - def _convert_tool_output_format( - item: dict, - ) -> ChatCompletionToolMessageParam | dict: + def _convert_tool_output_format(item: dict) -> dict: """Convert custom tool output format to standard chat template format. - Converts openai.types.responses.response_input_item_param.FunctionCallOutput - to openai.types.chat.ChatCompletionToolMessageParam. - - Args: - item: Input dict, could be FunctionCallOutput from openai-agents SDK - with format: {'call_id': str, 'output': str, 'type': 'function_call_output'} - - Returns: - ChatCompletionToolMessageParam (TypedDict) with format: - {'role': 'tool', 'content': str, 'tool_call_id': str} - or the original dict if conversion is not needed. + Converts from: {'call_id': ..., 'output': ..., 'type': 'function_call_output'} + To: {'role': 'tool', 'content': ..., 'tool_call_id': ...} """ if ( isinstance(item, dict) diff --git a/assets/qwen3_8b_terminal.png b/assets/qwen3_8b_terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..236d1d96e0bc2572cc11fde9fc5c366b22c06798 GIT binary patch literal 193527 zcmeFZbyQSg_cn|ON+}2^0s{r&Z>_g(8@t(kL}Id$)Q?`!XU?R`(6qP)Z{ED|gf6qH+1lFyV-P%tG? zP*6iKZUCQnhUh;fDgzTJGaj9guU4M#Ks2*<(GmA^X6jhewPB#5u-q@Cic}tp*kxXWV2$@TVMjHRj1; zO@rJI!ihuu{Mh_F#HCXS8VPQ9ooz-6J&Dw17qod*grqGcFwD5sFXxNFDTyPLX}0YL zx?_7_nzLg_+l5d2=>>BbwI9pCLtEK?YF{cNb~8Fqe!F-uD{`dC>&3Q7Lb@R~qm#_k zHo=1;ksl)M7GUp>&?mKz9A(bW^0)IB7x~L6N4TYP25hm&d7&h_SRp&5OHXW?)5KqT z&5@Of81yy$j4A=d{m%G>l!=@i3L~(Ofr5681O*+~y9T@juaW-U7r#c2a{cmiR1}l| z2nyQoW8{JD#a|fkx;W?8_IiXr%1z)ueBkAhhWh7dOv$wCfA&#BfbUR5ltrbafUUBj zow2dCy_t;zLsJW&4k(zfBsJ_&Q0~!Oysk+pQEvj*AAzX6aCjjn%V%g~#j0;)V_?kc zV)g3cIw%4ze88@iv4cLy#mds!p3enLd3gjMuz&F}8zt!S5C;n|82ZvXDY;4ZX&aBRytTuM0Z0x+eyljs+*f=;? zfFoG!U9BDTU0AH`seWDL&vl*|+Z)>I1c7go6 zlePWtVF3eVyZDBUo%Iph-)jS>3S4~3rwDN|w$ykAu>xcU+(VFun@iyG`2Wv0|L%C@ z%oqR85Ie#!arQ$Wyy zSORQ+CruEG1oUnjn8yc@X9_C77SOVbziSe}8~v{>uz!tHyoxG)0R=@EMe3P|ip#aN zNz8iL1=7!3a8}lAv14gc7}5(Gr4vpQuHq}Hxt<|L4@svMV~C=8hc6uyMh)pJ6wAhk zWPfv-JU>p_O5t_x>Y6m+wTB};1t&S}_jkY&sXJZA^@zk0$Uu%jI!F=6yS`pvtkI)x zN#%UNAots-PTmAH6;*Q$Yx0U$e0+R&xr#0`ksk^w2I%$w^6fwo^ahQAV(P9u<~1}t zVU+*Tn*pV;kBaR5G0gwbEiRwp;Q4Db2BE3D-T%GKe}(oxQnE{B{I90{*KYoIflXK# zTHfC3NT;6HDNQ1e)w_17GJ>m+NZ%-2W4B<`14c%j?vEE8@CVMtwBXvd7>`^5TEgeA z57OVh%n$j8yuu{d2fUtdEqn%JShS4xR)(m>v$vXivZ?-}^MShX^=Rp6Mk#su5H|UU zgD*r?-jP?Z7?h8)CHmr?3FY52K(B@GUu*h?>-Nr8F6`)NwJ3T1!**Rv>~&l|MMjkm zc>aDGR~`x!Ch0A7exf{lR0~i+5E|!f1{I3dYw9Z6FOuo6INQ#6w0?k?-o)Dlf81pX87yM z`Gdto8~45-!mT%<eI6Qsy6GEX;g7;cJgLJ9xvk&HW2NbH> z4Bq{l6%{bsEaAB(Q3u~r*WV#u4_##xc303%=CruiZHndH)UQw^-&=abl2widuibgOkmAMGsOXnh1S?E}bwDHhP=xUog&-^O-%A5Z^p-+tBgXdYzp-IZ z%Uy^Q9VF5r{YdPJ!rekY!A}bVMf>AY#Qqaqm>n?F49=bZp>z?nKVuKRpoW2rY2vQf zN0>jVC#Irgm~?YMsLVgnN%CHs4T!2UPI~lrY$&Kw1e-D^FRw|4mEvDjJ4w-NO=$Gg zlG!uOZ8ZNxmz4wbaKm?RR|h%u~0D%lz&;^nY-W3RkyXVb1lIhM61BS@O&U%(_Acx^N-qVY z`}!3{{tk>a-0FqT-$w&n&LLopLZfAr|Ex6aEo#Eg?U?|Ijq+&PK*A_ccA#hiHNK`i z??RMPICaLzy@UtGMck8-SzjlQRq7GpDd%6cWuz)AdhQI20;X~S4^GG^@WX!&4UY}5 zOqcdjy(@y3PQ8!-lgvMa5x#>~k4c_@mkpjaQ&8g3mxhZm@$gDVN?DeS=eVJUKjxJ# z>d}ow+JDr`Y%mo|hRxG^2(EF??nyjpWj-tLo_8jc<@m2(ik*e zVmH2R74MBJus{RACVf1G)|Gh=lfMAoeBLX-D~4B$NtxcTFnZ`Wrn*!)HBT&uFZB&S z;XyX0kYQVhBWt!?B2Ra*9nVAhNqqgaQ&pUb;%~=YF7WXM&m@@rZ4ZALyTV}!f_$aD zL$>xkI(w~4(4pC3_&It7DG32jlKx6Q|3ADOCGaq74S&&7I{ovs*EtLDYFMk9_AEU= zJ0@DedTc#G|4F?l=KAd?!uylX(@(8-R*RbWz@Jq>|rFIs7WLu7vZU`U(CML8^6W7P9>dJt>Y_Xtw zN;$H`&MC+@)|{@}Mq{PMLHt^gS(+8*G__XMrH-p=ris1w16CZIoXu9Vovu9XG%=Vg z+37dQ8g9s;ZQn~gA>eb@u6Aq~kM{4`=Pdz@KcRldF`mrn(ct$&L{%R6+(qC#9n;CP zZol6}@X{?Z1}hMa{0nRpVHfx_PPX871%KXNS|8N!5B>-<;P;~C1rpG`qoo?l2z;Kf zM#zB4pzSkQShvO)%iSc)walR1XY`RlIp=$hT;jyHgtvED#-s=`x$-)o)(Nk($Fr0I zO?@QIIOG^>eECNtF_mV9S>$%sIPsS~VGH{S$_(t1k>dco5$&W*mz%PaO)3AN4+Na^ zML;sU!7K6fpTyIu0igO8ZODIRV)_*>qKRm_KR6{F|N6E*a-$CMj#d!DyPOp{Kd7t_ z>=w*4JwPu;wP`%c8yx|8knQIM}x8b zKMSi-bvXuj=vxt)Se`sej<=wp4RDI1b5t^(=L8?>7v>`sx?`#DT9fGr^@*|^U4V1P zarx%I&<*997Z#q-!A|2VwJV5p@ccsB6~MuNVz)F5R2~sZE`|(@x>1Pgx|leUl%>QZ zH(2SVI3JDV7@QIY&Vqj+bTP|J%8)bfhvo-nX?p4VFDMZUu6!=Q4r>J=;IlVuU1GMBF=p})Eef#a&8aWsGwZi)G zTmzpdKC=>0Uq4dD?u60#I~H?tg#E5Fl@+TwUk3#V`XaRIN0L@X60wlfZmIt z-VuT(h4IqN-@$cErmxIdi#U*v@Pw>gO`ja_E~4LpE0GMn&uh8Y#h7=*M!9A`^6-95 ze00ZlftqcT_L$JtI;z-c^hhz+8bN_~Ew|N7a^quli57!afdo72dmmYW4J#!3v#pAN$fjr+{qpbUv z914TvE|JyxKj$`*+91BmFp@EY21YnTI9K~o?tnqIG*k|?q@5~Uo;kX3TKA1(FCv^7 zt1M)aVqg%jxThRxDH@qz8T!0$xTL4vIoass7p!hwUs3A=3_z{hq8qNNHOZw~YyBB|S`?-wtXyi! z^VsTetBt}q6ucj1KJ>AD3W^}xV>~}b2x*iW1?Fp%iSJL?E9B`m)T(??e7M5nb#|D` ztXVF3cC^aId+_tk!i0TKw2spl%?eM``B`24;bLN`e)A39-Cog!B>V1gfEo~+Y_-9r zqQO```Fc+-rdZTsnWyqGadHt@`LO0$?b$21W#n6FEgu!L?MONQ5-Fwh&ITl3k}~6u z0n;Jc-Ine8p|#i7Z@-O`B`rD9I^B(gswOxnHhFp=q1CuW4f_d^yDwwO?7lx#s&leB=3e7^-i8U?r0&Lv z1sM?}y|dGtdU&+9Pc3;UBqEC_k$a65ibXGJ{z?_3O3O+l&iR%$+#7G zj!#`qiYJug$Q>*2r=gu7dsBN=4ja1*rru0%=#z| z5b|1;mf?V_T+|Yxq7v1tv}6FPG>41hY_&hKq#~X8y6>uM-?1fK&o?10t3q1iSj%e__3F{)J(UuKvYu z>G;pP%L5CcLT9gvqyxGv`m+L$!4m;_%DEk_!K9O7$7^MwT$ZD~r{o*W?}<6*fe6!{ zXdUoFcFWnZwbRWWF2FYF;o(|o!#+%ggxlPxDW`>NlHghY=}|+wHlIVv{)a3v__&g0 zWajQs|LNH{R9iFWS2V(+Kse(4_t_K^(1B=VXL12{#RlINTxLV6|Dq9sgPk5B0h<%Mx^t1%G{6n&6}Pa6ZFbMwPdffryxFsE=f=UXxrHmdB%tSZ296 z#t&8bz3WQrbq(ik$*>?5=67V3MO<_--@Y=CAO8wc1U6WYpZyC`K$Sp{QnF`cb47uo zOfJ1a+#hd1?}s{GW#jB--rtff**x#3>ttKmm%h)HCk@T(=<33mG#*8K8>3wdiQpQb zecNL9wggZIDdg%%TlW;(X%cJTbGHJ`dQ5|aUWd=uZlc|bYx0x3y|Z7L9kb7msl;mh zRq$83rC*hF{Ytu}XbD8&1_LDzuRtG!?t)7B8u^b(QAE3z2+C*&@$lwgBqGW`PNuR- z`k~!{Htxf)6A8-({19>a$XLb^ie`n6D*zJUYH%Gnr9MmoKo>M{ubV)v}ErkD7(5xI35lMp$Qs6C#7YkJi zh~zF(C46hafD07DXZQ?-o>aJ+hBmQF05KJQmdlM_h+2%=@@c?6WVrw0|7>YhiONJYVX?#yh^$t?`+lFtpz} z_RONaB;c?>;i&ahZF-4+gaHzHXZjmYnS|dP(#-PHbO#o(K_x)lsGfOV#j#R<4Ja%D z742bQjScW-yGV~gE%J-Zld3*^fDb&JWEKiyeQRVf8GP3I;Mz4b$*@`CUvcJVEWEiF zfd}$|_Oq*L#=BF z*kHDqU>xZE<~qI9o`?d1PkROkKK5;qBI!=IEH{%Rm~RvB57a?{q4ov*1n0)B%^}B{)Y#iqxu`%D*;7KhvGMc4#M2e4v9*)5KT1vOfu%$F6jbK zT*C4XmJD5;PSD1FP|H5D7`+7`Ba-o5CpIsmujpEOJOd&c?~UJSfr%mQ<+{YoKC@3 ztw<@mba#ES(>kGl;4Xy+b;HpFD|DkKS3u}YfAu(xgL^H;`z(&KPu$&8a)x@Yu<&c`sSoNf7NJ&CZS`crRMJ=D4w7Bgwv}E2=GE z5v@^qY_@juns+}?k-4Zs_3sYYLxh!~@6q@NT!J)3drki&udIPqa)Y%sg`uFFlJC zR1TNs6y{edzswc9Xk>mK5He}|A@OZIjUxejrjZ23f&4{e@AoGlp2mKLEsyolVYB&L zu&2p;OLnjbnZwU({aMd5pCt! zHEaDjyC7BiimwIFd{kzVUI3ZpES2yH9)cM(Ss=B%5U029-Wx)g2mYG`f zei;!Fvv38VEdRe2k@=`0)iYA488NRxb&|(od^g<8>x`$_OT9=#!mbM5(G{DE9`Vt| zCRAK4%a~g7ZeURK9br%_V7KfyjGZw+VTylLs+S=6Rq^mTEQsf2@AetLjSP^K`EtK0 ztEkcp<}LT$1D_tF6U8ZTnl>=9HEh3A$@I#5K20)7Te>wR_XdG! zps?*_l4bvglKi7nlKbCI%T0o{Cq4w)llTD@e2eBAHN{Rk7hPTTJchN)t}DVwj&etd z^&lTZzb1}7F(9?csGI1az`}YO?9@Ma9$sOQtju8PqY|X1-qFF<6yfcRBv`*Q%;ty2&@_SR zm2}gQ;H(-q2_1Fol1C~E{_PTGDbv7o3-j~OCaU&*%ZG6IiLA)#Y|DRUrAgnc3+>Gd zY0?4QLGKv*`Pt07yZt#HH%)=eB`kr{rqKa0q;>m~id2q*$HQsf0^QR$+IA35=c$ZC zd*dGP{;Q_g-DVT}=??K4o0&WzuHJ4ttumjV9T=S8pTZY+!^>LddE3 z$II_0Br|y&**M}lZN$fKAM}!)dv>zg!FPbrK|1zVW+Odf|7{2qwt|ZOjor<|&%>0J zUX4NCC#qEurYXSL-)V(o^Vu=kCMvr#h8N0g=`)yiOc5p(nfpX^br}yT&+xOsBgplZ z&Yo3D+?{s0&b>{x`5Z$=4b!P{+)wviyn<(p?^&pypD=9?BnXUoHGPL`_lE3Eo|D>7 zE;!#Epr<`?JU0t@-tAj(yg~3{s`#79;S62bObCayQAm=z52k~QX%#h4&<2G7TK=m| z1y3za5wYlZlH8Owos&5v;tS^GC8!hd*) zHomCo*EoMm6%wW_&nLyFGxFtRsV=mx5Gh*zlX}QVVj$;Qzh?!0oLMLW*j%F28ap`H%G)`S0z%ciOcfU^Tvlq4oTbI$SHXK z$WnxV{|Ei+xfWAIxpI-tu}$#ur@XsiqFJ_?GV`#?kvQMA@vg}jU_tvKC(P&mhT`^3o zoe}Er`Hm%1`-M+7mHc&XFNKNov^e9evhFgAN?1nw1yyw(O{0!)G-~RFW6p&j~gcj-m>q?AQ>v1|7sp@ zH$DmEFLXH=SwX*haTWsS`0i|mzD6cjOU=n7J*3;!arv4BUnrolk&z#J5dXwFUnSq= z1GIk{;DD%7-K7N&cR4jqn{m7G2#>720$o!Zt3AQaU_^kALG)Kz$>m~4w>9T!s9xjQ zB56Ahrjmqu0V6!sxlnuH$ecl$_Mj2SwALmUI9Brdx<@=@lemG%z$u)+0SFD`Wpg-d zC+x~d2_OO);1O0Y#Pvh?v{%wzXrZRq{wTQjQRen@2`NO%#^yRKXfv~+9{oyoC~vs$|FAaW4F;&Ib5+Ibqco?`W6yf!MFWa!Omy+CJD zbELQZs~BhQ&L7ZX7VK#lD^lRTJ6&U;RV9}N;roX%jdhc*Hs`aQ>!tg#pZV*o>zw^? zAVZWgFrDZIXa;1$ z^Wev;Aukrvu5)A^{}|&id0Oax0@);y3bXru@CF4H6CdbR!s7}M0sTN*vl;U%FjP#L zJ?Yl#wHfGJ0o7M!J!$g2v;dE~NAE4I({iInu{=RTUJv$)~lu8_+r+RJ_CCxV__4$={G|;d}^B z{aoh>mmg_|UKUj(W*DLm<$&bI;{*dFLB0fHDyHC zxmBYv6v((8Mia|>^gB;`&?U-JPLSuMyF_UUwqZ>CGvsov9Z;HxM^=3<}tiG>7vK$D}q7Kkt4ZEL^%j;=J1+XFif&GcarR zMM@<4b}gq>yY2LK+>XIm-6_a4pD5FPSXF~KpW|AwvYqOj$qz9m#d4e_=h#TadH1m` zi8p>X0|eLH`46)Qj3)-XrQgD7WutET#_I@ezT2kG_`P~K`_}WuxHeWMUzaiUhm)<-WdYQ(mef9s&5DiGwU0qK2A!d zdAv6|ON}w+A)l%;46!PlR(G%0ahs~)*30(SQOHf8cl0{vu-nk^7Pd$X6zVRj8k5uR zPb#T0i6w|cEJh9GM7Pnf$URo&f5+-&C|>L{Cp4fnP~*rG|B&E1(~>6z1^$Nv*Totu zj_r7D=V>R3sk~ulu$x!6{9R$4W6Nk)?CZ$&@6DXXEnysXjsYyzUt>4sx>Jm^H0Fe} zLui919T7YOb0VKRpzrOBIc39$lN4~3bjbv%6y1R85y07~x}~xg;#_OOa+wa)s;Jjq z87W{Q<8$OeNO?Q+tuO4ai(yYtbkhk6jk%f*Q!F$`YjRtB*Qr8I=9#J^Y#r))TOasI zh4r@-j3ZXyDj~l7PSG9F#=W1<7H^(+9Nuf%KU(eo)U)0#V0E9v;z;H&W;NFS)5t4= zw3Pd2ACm0%HM;rWdM^gJgdeeBQASCE=giUN8*aotVILy3H{q8u>SpbN9{V!Xa2(9M z-0oW)=;&CdO^3wOr7u(N7&g(KTJ*J~kvm&DdVX*=@jdCgU2eS;#xhcQ`|P0T?#<8; z95wRtMoO*P$@|)cw@^(Q$lpT?&0<&@>Tbje2^b^_a0*tRmSx(_XU%cSQAG&!IU{=1 zZV*I1m2W$AYlXyb&lkTG#YjF}7;){dm)orA^CyL&G;6h&q%M=0Z|n*qT1jsm7c^9T z-^Tq)@ySwNyb%dBd3>G3TzvRixE^Sln5F6Tyd!CI4Pn!YKExh1@9c@Ned@(I!jD&Q z`)O?K{3z_DYsX+-Eh)s3gaz5ODi1F^&6E;gDKfWe>e#NDwj&YAKiQUn^sO*DdsgF; z!73t2`EPoiSFP@@3MuBurLwB)_9?kJt(u$G4Xkjl#@%T0;++lfArkt8S(F?~ zFL9gqy9!5Kh9hwSKRQ8DdC9ky+&96}4qmV@SWJh6314v8y0g_w57DZh`&DuCc~0u4 z{El}uB{-Z#3L}M0MsSnOd(ZbSJ(1IX7DrzP%%n3pgPUAmzi)u5= z%_qfqY+%fKMViK@@iB9%vKA4Rx#+G<{6~B{4M-ZU(H*zED!KRlx|!t*hgCV;{w!{( zCOrw^MHz4kf?h5zcrJ^e6Sod5<<*3((%5cN#qfK0<;y-wxbEsc?44)t=W9;t-CQu& z1+9`|y{~8wNrX=fs;xS)C!;H!Tvqo=b27xAt-2*~gRcepw4^E!ZO%>BAMxf>GlkxM4zChE| zx*4(Lc!1dE*Ti_Bll^YDjx)^NI5c-4Ru;WWCx=oTx}1ElHu{rtXNo)ldp@phmD$Zdw2a1}-uC^-)o*2;d~JjNw_#7+hMW(3My%n<=AuDv z6{xoe9X!o^RH|1Co#bP1ydH(!=NVOrk&T^@>XvEThLKhA z8u@t6%n|3EO2qVGLmTGl4{an_!R?H!HdgDe;rm^%Is}<~M7UK;0Xzdd1`DT1hzkcf zP`~TVxJ6Vxwa}L?n&nopBT6JAK#;Xt&t}!vPljK7Qn;|t#d!wj5fS$?LJE)4Wc;)~1r9&dLJRQ1D4w?S~vp}S~qh+SA-BvwH{|NR`@WSD$X zt|JjiDg*-i@O;#zRpZ5|(6a_CabUdFe^9Vy~TY>YGWb0 zyFXzk-&0#_J$G zTn}>sdLmON`U?`5@bEObW){cT72vDw442GRb8e~!t-8)Yx7qazMou3VC>0fj|jGURQ*%o}~9mBEzAXazOP-F4(gW##) z{MnZbme~d|Cc)vtQ?WwxAr3v%5hhMk-hy$*N!iHYsG_0jc%t?C`eU|T_#=_GlfzEe zpc#;@0-dFmVyNbgxQ7C|F1!c*)5V@Kk@@Ah#4JWIrkd^V1YbJdlIQS`e`3Y8B!ZEg z4zHGF7Z5)c?IgQi)v>Go=6KLmz4CiZO3twcOAk1e2^R;OR&jaLJvS8}<7tw;MUxm$ z+{~dvqOyEP7+4lpZHH)~qdC;3lZZLk{fR_3kIqlntXx>>PHER{*;yfZn6(YGaeEmJI-WdSx`}s^4S^(!O=*;ZF(g zUS|Wa#QJkJsN&E(y_UoGK-wQ{v3jx@5%l`&&9ohD1VE0agyBaF80GS&@3ev+V4>=z=V*Sgi|Krv@gE4w&dds8zUI zQ^o3v3X_d)GFS_inBH#FxaI#d17JX$X8NKzv*mxtDnrr{X&UmSk(I0)lWYfP`OWgH zkMQ}1?sendnx<_!C6{#QpE9H%e$cr6l4z&9&PYNukXs1eE*6UpwdEh^mRK_E>iP)=J;Y?Wo%UlY5mcN<~gF+D2!e3?fR^JV=)?rAeQS+I`Hkyl==z zW{G(r)Cv*th6D|hQ6vs;FHUN%=;`w2{U3#}q@tNt^u;5XUNVi=t+x5R8V;!EX0k3) z%qQxu@de`xG*U_Wa3-p9NA{SBS<| zpG1G{WQ8}|BH|cPzmD?--i>jXR^oHpqd>GBaw_6F(92ui!YQ({$f{#JlNgjNYHoNG z9(v0;;PZGnB$7CM?oRynY|sXSAFbq2%?I!CrIVQe@+cA_73UEv6a1p`Yc)KNyq5ji zH#{Bvwg~9jPjPSA4uc1{dMVDnEAEU}AKFetSQvfOEiHw_?Rph~ z-QMJVb>yu71}-wWXqA4%5u>qdwF79|%;Yk6icf6eavLP*egbon=z9~{BL-C~r77=E zM<=)5?x2{DXIobj=dq|7WoJ*8aL{$ zcg1Iw9Pnu66EW}3k3Yo!x_dvi!jeYM6SNq&;7Y!S)7JEePVw9s9l9#~HBSi4w zeqRS!!F*qYA&g_)7W`Wqm=pnBM8$is{(x)GoL86O`IA! z3GJ?TLlH}848iE; z!v%=l7H)ujlvhyi4qQsbz*JJ#Nnrt7cRQ(QQt3Jvr%8#Cj-BH@m2VX$R6|bWJ=( zzl^`4GiG%hF7#Ngl+UG2k*GVQEA?(_+(WiP?EQP65PLg%m?;VX!yuR8=58k6_~aQ9 z=?f{}JXrcE1N|n-@hkvqIy@Bqpj1Bchy2M;cXqGe%a}lSfY2dOaq&JzR_gE2f#x)M z8k+Q~NlxO^8~WuEf@BakWSfxK;rx0A7396Exyon&qb`7cqgK$~FUZ>t{#-x_;i#`I z_4|<3cuiS7rqb#Uvj-JGm;SxrFZa4UCWCDqrU)`qBHAR-3-g*F_5Gu}X~se8+%io) zE|3*@N7c|!w|>j>tTHzCKY&Dwo?A=4O-7Ae$-5)3EpCq)N%m0Y# zYX;vXFsXaR9x|LcSW7+X@(g+2?^~Jy78}D)0Nwp%!L5J=1IyY@Uhe&e1w)PdV>^0* z)<^L?wXzug_Y4Dsrvg~Ywow?Is)${5vnZrp1fmP=qzfq+lK-~PJcdx z04nLcvR+!D7L0BDme_tg<{7Odq;%M?V)bV@me!x&0^l|D3iyQ5XaCL-=#-=hCZ9V> zw2q;%VH*m?hv3+orXTJA4Qz?&1Wk`HRi~dwsoJo5%JA4=)<+4wng-=*@dL{fdt|CX zgBxYN0~jKbsn*8x+cMX`ym8h`^(@iRjGs!d(R#-kU~32((fPZv6#Aza)58e zy}MWNst+HQF+r}LD-@3vOM|d<#xHL?qE%4yjE=SWAPl&!djs~-EI?N2D2Kg!^9CAY z%Nifw>He{R=?f8lg}NCJQ(a!uYCQg5Z2{i9K$E~lKZ+aJS(^pb6AvT!;a6^w-kk?0 zKSeq8`**q-0!W`~5-mZ?8ig#z>_frH`lZQiiwc0TPB&!INdE9L^mbV88yJh;KjLen zHua17k@&b%1Frs9DC6c7cdl#_NcEG7lbVc!&!9A?gDX&?lHwhB3hg7^k$r>GgNx9| z&)W4kif%WaOn&!T197UuGYACMJw^uH}>y(H? zaaWe3_PCOxKTAKxB_MRkMXvHa3Bik8$4<*-C*?}t+aBPR@CUi3YNoj<6HnjjylFqR{>RX9&-hZ{B0)05}@Jq$o;A%mg+Cw425^IAsUn`FJl9x&S#ky%o zZA4#+C#yQwbLpV{rNJub&#E(AAr8?U9g6&s-skKaHJP%F_ghKl%oG%1*Q2X8o+9&B zCwd=xxu@?`m?eL|$%MM~L;Vw~vdx>-JGcwqU1{?=V}L~@l8zjMaxd5cu0HpovEh@# z?=GHi_vqr}svp;s9;ff;F_o^y4dt=Be&BVIu z5H_(XIh3L@2v*~y^sSf%q8FwIZ+PtzAY`7p4RY3uabV zPlvhfKXHQPKaZIhV~Rt$n*bWxiQzDbYdndTs9itt8_$+WeTm}u7P2R}>i5%enU7jB z4;B3~K)JBABQR8(9ku;lF=BFG;3+}u+R5z^XQu#ET%>OPst58E*_F0;s-zCADan#)f{9Tb;F<0Nsd|Y!t=}K zF*yleHsaHYd^Fi(Cg!6*u(lXEpP?gLeAtWfrJKoYh0K$(eEYK+GZ&;{4Duwcc^T}k zM+_O~-Sl+-S`yE3miNGQsn3eTS}E=8Q&K|a6dhJQTm`KzrY1G>rB?(^`8}z4cilYg z^;nsX*Gb<>JSE6-dfW_QOG!T0nXsO&|8ehdd-Y{K`_Fxj{-ul)1QMwDumJ0@!*GCZ zR-+I0vS`vx!up<^A>R-u=wx(CWY3sGlE0%doUS*z{eYP)Ej7vEY(*ux_e<$hWVp~s zf*tn+$yO$&-Oz2P!mjkBdEL&w=Vim^?*aDnYn(n(@@^n2dO$LT?$#u)pE14J zV5A}%xz$Pwz-{|RJP{hjk3@I#xLaj z;h0L9sl-&xTUpj(%hnHYv>!dc;vG5dJm8>PCm`o-AT{^7Z zbh%3Hm;S5OA4MNwU|K;+PTs8BiB=VqLhOqpjH|YZO)$c7*n@SSK^sc5t1*@M>Qis; zn0BU{NQTEN`;27o0G^x=$o<)VcifKHw+&JLyyks$~t2z%tEG zWk~808%|~kc~6R)lMv`+ZM1inJJOr=1OzOr1vssf%2Hp6`tf6Tr3R65+Xi4aW%gLv zjb+Oviv`Mu)t-qDV6XRQ_b)6a&L<43j}KtW28DqtR_X*GZpIfHbDxjD-pQ<|$TTSi zf?r|>6cB_n(aF>dMo)A0>?c1v%lT^>_-wi1am$0qPpy$Li1o1QW^9ILy91il@1Esm*!1f7}D zz(*yNzp`U>XQ%B}U&yQH$;>rHdc`7vRa=#G4sw_SDY!XU}2#8luq;*pN(mINYis}5f$?Rd{88GNeo7FW^emxt1CyQ0q5W=J-EOb>qF0%<0&y!_3 zQ@yEsLmS;cZUP6D^#~}7huq&_-KUkD^nkAe?ORmN@t!U~4bkxYEmPCSlMKvjKkqYnppMiy{(@=k7b#n z2qt$d>@=_s_v;Wz2v3pNP`Fe8p@-*HZDV6d##N%IWEd z#ud7)l2w1GsgWDNaLcLO%;NoxoKF+{H*PeJ$B>#VIpi*FKIA5~cJiMeAkKNio$H*( zlxQkbV@DYZuK7WBBpOQuti6SP*rtW7E3t}BrDzTFrfjHVV=7&{c}5v#{rguBs^~&j z#XyHZXA0gv&T;$TzNwfFWBab-pR<0}Z^C>+2v|hSTk|#gft;yZpR%HU zczDJ%3EHS^o5mEvUDC=xyVwhmJA9k3uN&=msV5yy3BlEh^!**q+T!mPR!t7{2#!ov zwMvpO`7vFrcriD55+rBxy$RkX!~}aqsmB1UFsG$TR<(#+Mt@R#bKQ3N?96!k7H+c5 z^kV07u2I6f)Sucm3*Kj+i@@y50xXwARFzR8bv{e!O5JDZ$!ME>Rw$&eKvdTPGQg-+ zJN43>%qdEWZLuTmA;0|hu1}i0$~SgdsM=J1ymEUDu7J_jedJ`Z_ECXbf1zfX>?=qv z%U+NEveWB&=_s$EMF0!*!kAq{f`G0mdRekJSW&yqXMh|onN&bWxa$uL2JD|Vxj)|9 zVQUPFQLk`WspqZ3B8ysUmkB3hGWyou@+L4vbd$So3vt$ceYp>g1LJfEIzf zd+rr?>UsP}@cGbczyUr0Jb)3e;7EOEgyIdOrzvf}+I)~kb?XzUN^GDIWPKi(H;>TMl|87g;HCen=f za!Di}7;TR}sDXbekPoe@{Y1)d(upgy*xs3Q_Ox@W&8Wk-?~6D{FHwItL5IvTF2gpq z^$F~!RsecaE6y^~n;6y;1Mol3AM2yXQ--BnF1r%^-O5L(m6n$Fb>qOD)1=&K`6G>X zM0g0UPMbsU(M8AUDh}y(n6_d=!j8uIuTo3#oYku5*M_du6API0qdBIc-q8M|4z>OQ znY1>hQ%}e7{q4RsidBi4y&TePtHhUS89U4tZ?6YAo}LrqFm|x zG91-AlG5}OpQNDD~;OWrtWudzF)`XZhBtI2ia zNo;A*8Us7f#;^3d%T;Orr7bT;)@xxAcHagx#3o+A1NY)C7s$uN6QdDg!C6e#?d9C$ zB3Ri<7QA,r3jvlS~HELrfo>SwA4Gj}@*Vw3##E}-^!7*{5i#a>fw)%4W%ya0VC z`y|Bj1V|a9S8ZxFh@@&tjTe%urW^#vg<$w+KAMh6S+u}nQIQVP06!?Q>R5As!>+HD z9Ezxj?eaRUNM<5=7Y52c#vwD84=8P%E&U#Sb6;SMt9<)sZRJ?TU3|^*yOou#!|7Fy z7}hL&X#1Pb$0zhlDgyGbyE(sn4e2jHsKwWTiVM3*kk3MrEJ?J?*m=zXDKV_Ct3ewo z7F0O3k@q9(je}@IhpY)LU#9Q01#`jee0Yhb##jh|U$=Ri39Nm2U^~W*3Sm|p<@1j? z16nz3>k*Ba;sT5B<6hdxNQHudb}tUMt{0}x7yTUX@9)2ztl;!;K7`u+Kjgh-SX6Br zHj0RVN+_i?(jg$--Q6G{A>BP7Euy4=gv2m(42^UQA*~?YEuBN>z`$PU^S;mf9sAfn z_Wrw%H9r{Ez^u8~ebsrL*Ex>Roq2B{|H(RxJj1Hlpzy8V@fE9vyQDSUwqe|{u|_Io z-1oEnGJs<7>#ASPITYyrH1vRZEi3OBq4svp?)V$deqB2|2JRtP!SxB4|oa?N&mfi3Tn&c>~Ig*I9ItSRE52hXK=6BcK z-ohJ?dNKk97kSz2kT8CF-DSWd@vY4S2$&{Wo~)zmh=bNV%UX~5lDVVX5J z(;QmWtjlauY`t&Kcng^1q#LulJ3?Y}?U%2s7y3eIs$NGoL-nc}6d6gl~ zaxz4b+mr}b&YotaUn-H#3zG<=*g=#TTKp6W$`o(SaB;}@XyG*+;1|(yQ_dk1IeiA* z0PAiNyEW+(88np+gPzCi2Vz!pI(uj&j&d4JmfPdyPNYaPv4-mPe%aT>5S^Xa9zZ?b zm_M)UtKoa#TRk!XXBSST@!d^dI*|s=&Kvf-Q%>pDF*)ICHahls8XiT_ly&T4b4z({ z^1Sr-f}Kz{socyG2opWjERvPTJbCK;_)Bt?UQqv35Y0Y!;Mc*>S-h9a# zp(+h}8l}O{Op)>0Mg+;FeTT__6;K`O1N@cl%aTBV$4;-3P93}#_+9@t7hf&GS_NX! z#~YuvxO>F1Dx^i63DCg>IcOcXHrmmwh; z(7&y3wFxsVezz~HCI(ZX@TUGUlzYswoARRBsXy^}V1V1zB6E^n>)F_S!dY{;FYK-| z3yHvlDMP#95_U_5vuo?=Z5QI_;|toF)lJ+=ybc7!R?0*zgvyyCYySjRyt}~4Xd4|Y z?(>kKHu^v8Cnqo4t~v_cHre}A!`+h%CN$Pw9dUaZ$wGikJsLk{&4ow#FU&-HiX@-{lB z;7bH(M^I{MdI4M=^se>ZearRY@@cJD!oH-2jMI*OqsHELzGtaf3(-j%%Gp|P`;3qn zKa@uAi)>J6)f8#r>hRLuS6F#jx>x}c{xpBCx>`augrU`*OO!h#p!?$_tVeE5!9*wGCKA83yXK9k-*3>1sxAin^ zKo48ItSLK>%xPc=Naf-c2Gw}keS-L!*OQEy>&~|v`P}mtbms+gma{??=LKulBEPb% zrFkVb%x+h@Jk%_1L8^jzowFj-+-3@)y=(oc0usIY){qxhDQ~x{@5VhgD@ec}b9|MT zHFD#C8dBxG&sCZiWOS5>)ZkvOf{`=35i|bS2-@@7kM#0UzD`ZTCoY*F4{qmP7hYif=7s(BjI0_9>j-BRv4 zL26u`3%jpY!o@t)hztGSep|cB2wYRZ=ITtR7$hPOah^2dn)}{0Flnm*@OX4;-<^{q zQqT(3pSIAb3;x5H63k#Pdvs6iF#)fHVtE^mMpnkDoPU(LD&PKzeZY-=^|Ck%OSKK| zH^HVio}S;v9f2t#>x{q`dT-`urj32Q8+XoH+zw?<)AkV?&F5lY&L%;eBBT!jq{RRP zC07+LUS2h7{VQQm^mcCktm|j|>W~zUYu&s9GsU*?nE1{04!d=FdJ>L|##=e8{EdCq zCA?gp(=Gdu?bMZ$eAltHes-??eW<`ga~`&)@AFMMJG`pS`pCU{BQP%xmi-dj7_N-+ zmyv^#@ft&bLo34GYsH5s^V@tuJRpQ5(vrv&sU1j$tk9}^$2>FeHchgnYirsHqMs>l z>Er6yUE8Tp4kF*;>f#9Ph{l$l+@x?xgF>e86eG78Dr}bFcQwdpSs=R)Kn8GO=32? zX95MvZAG6Jwa-3^6qG!@A`ss7p^LY{g$!U3>oUK~alJiG>%*>CL>#`Tb(pD+mqAKk6OA}IsS^(8sew_Jh|0Z23pGEF#1af5x4U{oJ@2KEL?Mzk4QwKhAijDx z-MK?+@m5%_MhE&p9Z_jn!dH_3x&`kA5PZXeLB9Di@nNO1{jrOs9)5y$p-B) zw*XVc#x(EV-4M;ES)HYILK@6ra(nyZfZ*CsDw0PO?@kWLE*A^ibO{?@0@i3`(sNyA zpx^O)x)9W4v=K#Yse-3#N-i`g#8#hX4Fzg7;9VWPLJ^F>^yXgGWSZexjd!{g<(aru z<81&qfsS4s3r&nx-rMCOOq}VT;TEQVANAa5et^1(F-ai?lZd|~%21xKntt^=*UsW; zAYcF#7u>a-!ih36R^4H=B0H;O$JzVEPC2DNYa}}o8RW#^SUaUVAuN{ey}jiA&hbhl zXe*Skg&Z1+Bxrg-cR)X1sm_Wdv?4_Ji472u3AZi0GCQybz&CNnI;0&o_P!#mlg8fq zID(s0FasBZPBf-@b^K%}cykxLf~UUfI-|@vgEejDLHx+>h|5=u8nz#CFV4{oWb6l~ zA3SsBZ4O+!J-l@rHPp&*y7a9zICjsB{Z!YZ<4ZQGP_gxuN2|q{ut|(g;fulHp=U$Z z)kIiDL|tBe`u&HRXR3fWVvsbT5JD@N0x(kpP#4bEqUl}NLUw{Xsy^8Ptizijui2r+ z{KrMXj6W+*1!t;Z6L93XuMrq6S7dk~O>EKDBeM@cQ$5o8+LT&nM=s(*vRgas8*Z~+ za0V>X9u8FPd{PCn=&eRNfkmJ_VCYfMIwZ~Li!<~7=6vzi#djGSu+!%@7b|L0;~lC1 z%h_u*xh2t~sn);cLLro7fBqcDYjKHJ^n0gsIe1DYzBIxpLZ2_Ixm}biz_3MjG;8CV z;U$(gGOtqTl$x;Yu=ea*vB+;byoAIu|JvTBPnSC=(Sp=k`Jzd!Ps4Z>8=Bxmm)ohP zWcX83zst|;)*S0K;(!&W0pS}PiPC%#{Zp{=|68y!wnb6^Lp-fNM1uF?>k^yN$~WlX zUkmkOoHXH0$L;BTGoYQ^l~cYjPrl-{k1Nsfhb!Iu8K;9ImVLSQ=f+2atwCAyWw!n` z9u1fDUJU_5L}MNOL%3S`s$2?Gw+(&OHL|{=5t*ktda}E^I`{#Nr@M4JfynV`HWn_W zXt5jqhLzd{%^EvGFU;?Nuoc(>B-aYFY|EC}b3N@CFEvm&FRSaN%(pHj0qqE}c5M6t zGhOXdZt5^E_6oJo3P~uctz0s(|LBOpNdorrqyUR!R9UIG$B$TCy^=4pdzfezKpkXY z>t^8iC*1!S=c{wt52~Od$tRl*TdiyVN(WHSC5a$7) zGV;&d=WU7WC(H{Ks%ke{gAWNJ=Slf$Nph6FbB;Bo$2)v<|9#?5c?AQoyZZUz-0_7>+qbnF9}d`MJnJW|OHI zm0@)aXjC@oM^svQ(ogoLO`JoZ3|6cgUI+jcL`3Dsbr6v^`t22SrDXwYoytD%drAa1 z@@yn#7I9#b1-y=&Z#W6SGqX+($0j{y;M5Ud)&eFO=uYje8}^qL%tMQXH{GO6ZG>S9 zDk8PL*>smIxaf;U-P2H`9#zo0-C1CWqDClWWa3d6eoSA2kndMlWJc7B`X70JtN4i1GY_*ThEC4fm*IAk04^VN*RQ|?;-oaAI~LlAT4n(Vs|)#OA`39z zAt9|f^PSpOaO0=4{OC4bwh3w9Z(Z&zD%)H_uRuNPyZg}Z z6+qB1Au2zW)ZhEGwrC=%X6>l=tBIhL!R7&x@9xu<50b$cD%B%IlyVdSAIbb&l8FZ1 z4#=AH-c77Cd=Y5->`y*3s-3Lj3C=5}Zb2aI8Qu>QF%z;-Y?mBw?@p=|hs>`10OrkX zwbB=Dg~SGiDmqUM-12gWDYz69fPd=>agO`k6_6Dkxa z_hdPMy&EHnPA~NlCV9rycO;jxI{1ZvP|u^TDfeOCOJs~N(B%?_h86;x>W`(XOfwWT z<)xF?@N;j~xT)|SYsH{TTgzjHGnoZ0C4eJ3{*O3=of^(Yq`1#xo1B<(m6thDBqzw` zSEbR9BXmyYUoXasq=h_Mnuz39^Bo>}ZPO3VNSjg#E%??$0gSgX++)#n{yrdTez0)U=Yucj zm&#z3D<-ggplnUD<+s9PB8NYjUV1W*oXX*nHni`lvcWCSNa=nyGrRLNl+fiRAROV% zGR23AxeE~0ero_a3}O!nHvZ&_Yy~*&#(}QUj&7OGEZjk3|2+1 zMqkcoYkulSc-JvzTQR(q7qAB^E>KtG#&+{F&q!IQUI0dk6v-*e?>eLh&Cq!GnKhm zNA9LGfSJI=)hdPSB_7w^jT*6MTi<8w$A?3W409b{HzcEK*An1I_dhuRx`20T)7Q5p zE+K%nkT`7+dQ@s}j2Mzuin89sZleCxwH-%+FlMXydLwF66^y_S5be0Q(;OU3{G-K5 zN0`iEpLo(-m0>HdA(O96MhWr~L|gEFHsLWzEQC-H08|0z_kUi$x{Z1;k|(FOK|n7m zqzcG7B&Vs$;b&BbG6^mJ2oBzN_X(M??UMWjcs$B^=?ck`y^`#lIPJM|ows_{&2gAd zHWH*`9G}WJ{?RY^ui6c*ex`A-k~HjqoOks_L#9s+ZHCG+Zn>22ocrQ$YVy~i=-w+| zzds3-nfdZHl1==HrUzH8IF-yl8=V>#3mOi{<$T1nxtnI*db0X>qnW}rSpra%m1e^+ z0R&I(GsC1&g46*odso>3Dzi6FhgKO?;_Un_S+=Y~P-1jFE)HPIIgW>P&Cea9)yvr*7g) zAM^BtMbIQ|x{4c4^Jae+=i%i~8NmxctWx%gCT#^?iSiRoRAFA3?~8vsI!EX3?b9!S z4-$)J3=vd8q`o*)6<$K~2B5!KU3|P;>d{Vzhx@yKgbb*j=mY9TmDqrDKnb#VJjUCx zQ_9yd7zH4!5U)Lfw`x!+=zyved|d4MH=EebFD{F*u%5a7pEn19Z_BGAHdE`9z@T8% zt7&XMp$3%@FI>A^`iwJs-y(4>0Ek7PgJ4_Gv8+Y0alx%B;H54Aa9czCi7Zz}RfTi} z-&`td@V=d2e)MF6Krb5pBLCs<{*H4w7N7;BqX6mVE~UgD&rC`Z%wq)T&FDV6KL->!GL62oy|ZRV z3F`u0@))?+uNfy;YFfO(y0?^^Nu zY|MOodREQ6hQ=uZS}oT{ESJaStq}JycM(9qZ#58_M$c+kEx-5eeJ6jow^Nw=pJfY+ z|8s3XtLs1F^#AX!?Y}ZULAjWKOj`feB{Ks915*#KueurJ+e?lN1qi;ore~Nq^@D58 zThsECf1)t3;ye$4Tj##`|8W1l$lqI-T`BJu^1Y4{Kt;v;Le<6fSNZupAysz;-eDTv zM(1QwGtCn~DsxP1s*m$@Dk?fv)HD5$`9Cm~+^PQ<#8{y%Sva<2n3Sw@Tz<&``WUV+5ltNiC$KcM}hRyX}p z`Fp)1;eXEnU)yye^_`U%9qWJI76mor>8r>9brjPtnzZWw=e0g~m@Jd|oyxMUKa}Zz z&!Car+YEhg$cgp*KkM`7ov~<4iIABn->LHCGynHmzi|gih2q#At0^m=JPy6S_G=X# z9MAo8jczrP{J8&fHKwwtL%9CfMnsz!Qim%`|GNyvc<%#V>53JseUMO$703SHKe9!A z!r1+Yz_0D?)+BB`#($UT&po)i3#ml^$Kt*EjB*lOfskMo{=eN%CH(i|9J+TO`v3dC z3i?jx0uEhx=@J?f3oKi5FbY~k1?mHICif1Pmh8`K@N@i zVj=%7aq>stoB|PQ2TIU);GAhmfI18Vok<3j2XhmXKs!C#x|SmAYfq%DqtnArm4{}p zC9$FuKIi$4%xGxagq49z14`CSHhI^-+PKf@zm_#JYv0;EV=Z(pE)?5QNaz+Dil*%1v&umoF0-A&%K#$1nQ98fMb&ipy8|3|+%$Ep-)^U5C_yy1Fv) zLe(cW{lboc+b6aL=~~KSlq{>w!=HOdlxSuR(3pwYKbS}xmD-wqbsJ*i;5N5YN#xfJs8j_*zE#bA6?ISJiZz5{uM*6Q6hJSL5 zSt(;Yawc4wD&6S0u3SHEBy{M@iD+`On=by!8}hBbo8#CI#b)}+-|hH3m!#6(HAWv( zT#Va+E|YT3JvhK+@JBq+5boWP-o#|H!g_BLV-v&q9BU@{Jv9n4Q~sTvlvw@71LW`Y zSO(Ti%DY3@TWhG>5U`8xZHyY@zxMtcD+uHe)@b(AX{O(b@}Yb@#WNYRuxVrU{F==k z^z6YN5EM{qxSu%PvizvgbDF{ctYyK6k+R=EkG~ZaYb4Bhwdox2_DaLY2Kj=y+-g+j zSe>~@Fz@sbW(?n4loHxX9T~1vw7XrCwHsK^9wXvG|ETEcu$G_~8z#lHf@e5XXMLKP zyuN^>reDu;#!1gDuT&iY&zY5$BcGi@2OLn`b(8vhi|pNCl*P;Pfz^^Lj}$3Sa8-Fh!( z4SJ2VIyk5~BB+;X3S7MIrHSgOyk%`?f~y4=_7|Mp>tFi)OQia$21~FjMAAQe_43T$ z<9Us;bEz2o%>D9^bLQmW5wnGRLUQTFX^tK5u1sxAen3o2fjR%oVY{p#w=X+sHZ{{r zUge$EqJlW)qrQ}5+y30IHwWr=LRmSp);pxy2Z*(6hzdi1fFgq~1>Mustw0hnUpzHaI|nlgsBjUmzH{AQDH&a4np!Ax&0O!rk_3J<0M z>7hFhQb^^S^^%($*fXJs6ZDhdLouwsd*pKhdp)i>Os;`-A$z$ z*JAZ@ftV2cuAC$PvIbS6#&O-~41U*%`XY|UTQ?TFXT-8xxa-3vb!7P+d0$WFeybYf zzEs<&{gOkR)@&MTMem!E66HKA8CiBRmG5BiOCwSAd75Oas=_xA6ibslyPfeW?q2Qj z(OVJHF(;Fn4Bf*Er~O#@a-4lLule~lP~Z6ohOgQbPqCWIyV;u%!mG_@k)AEP1brBs5BL_-DmT0^K9^ z32T_mAZ)gJB}gqMe?n>&oi-Z3IC`X8tTZ!G#84y2st86Ao4VeU)LPDB(!X-UIq%FT} z`wxl>#SayZF!!dzGCZ3bKSk;G+pLCe_z+g?9MoOe&T{R*C-OvtwYQec3|#whJ>fg} zjG?W4*^X-ED~x)qOz^fg;aVHPi&Ki3s~b>Cw@d4 zP+Ux0Y;2y8|32y|{-4*9qaxLBea%*N?tJBR;1I5C%cnM^!zPCLY4D8EUx)bq;hVvA z@cHx`_Z;c_B;z$W_zFr4!+9;Mn-Z-%dpiet_xV5Q&KKy(%;i(o#mei)6&+TUygBNoqT_0GhTm%}Q4)??_6R_@VH^iW zUGjCR=kGH-5uN?T&cv$lnI%gDge{5DGN&SRTa{uqEzYwW6~24l4S%997eA>$;bG3~ zY)WF$G*#X1(Isu+I=U!Vhym{5FYOn$>xsr^WL7D7)Dh%EBcHbJC98?8_>^Ag zrl!I;sB(Kfqkz9SY{S-hZsur;S+M&Y*C*g*7j8*ZE&bD6Gr=xR{?vgJ87w<9?HwN# z_H9ph@Qr43#@Bl#D-Vnmepw=1{3@OBWFB=jp$QW*j+3z-{%#9SNJ}e~k#Ogwxtt!Y zFqhjND~#{Uka{IQ9Um;wn~o8U8jNys|0GzN!ZVLAlk#(5OxB+udFKGV0SCB%boOjn zcszvLLLYH+ErEi#2OMpb&ITPB_x`%XgL4e8MuxwM6$@H$0(H>L5gP3$lx{w5hfb4nz{hACZvjWa!j9#ny3xJ2j(CLQ1U)Ug7*~I$hM)*G@=KA9wM?+;sS`-Xm&5+)* zVku@AEiXY0=Ho2*sD0pO1DCu4TKT5h#9sLExlF4iSh2Le( zoL`V2aXgV<8jzE<77)gkz`}j!=uO4C+zBwdHmRi<;+--df|^B*YZ`Etnn?a)I?i!G z^GR_8??$*Gsfu30RrNDxU*M_8S9fM^1&&Ei$+%H(1+)yp)|;T-=tT7lG2A>)YdU*) z=VvX?k_65DO+$v|*y|C)6cdSgX9Xx@bbc;7Z1G3t2o0TqVx4+4cdjCF#_YKUJ)y;_ zo$aETZ*}%+By2jxITvor+hchRVyz+hk|~4VYt&b`Ohh)6Ex@yZsqWbAk(sdLtHtGY zNjxabzw&e3!(-`%)vLLc6Hgh4uJgF#2c}wYR&c7PjfbChb+9(v9;4*#t zQ#C5`T1G~yH+q9_=tXpdjtLr*e=D8{%Ss^+8@A?y*<+jjw8&}xi9~FRoYOkHtvz0m z%D9=T6qYmeGXS%l`A z8rk7i2HE9STCC9)C9#~%GtwL%*N-{m4xUB%qu?yNjn4sW*d|4kgKO&K$kIFEODUGjtq7)b}0=-_lRIWC6WqS4g>Dmp6`4x{-*uzY#U0b5r({Ka)! z+LFSet|-htaBC2@#lo9YnjR-Bn0d4h6P7Rt}FwW@5KPsy5?>Y+`)r|I@Rohy^ zQw=>6`P3sfl+2!A|M>?sa_`Co1MY22O4l!+W$ZPX>#A)9Svbeej@GiDEVzD0%nDl; za^x4;+m%aWEB3#ob6gx8-O#iMwPDRmBRM`Kl(X4coNyDY)gqV_3+miVXcf?l+@-Z5 z@h)S=`0F=)vbi6A#dEJ8 zGa2-X%|;pfsoO8J1SjN<^!eH1=iTJ9gIX%jtfR3MN`s&Omi2cjo7IT0RsLnZAid2z zqa|MKbQwMqy)W-iNxFANY-1{lnvR)JQu0z0E}SNzsfh>LdowAI8Jr*@0o3Dz_vQWu z@uB@En;0V4=NVa)+ngT)Tue~`Z~Xfkoy#T!Fo@Hjvnc%}h6Vlaa)3=Q zp_AbBkgLHVToAE@82_svAaQV+!sGW)1>8D1(}vJ}YgW16;@)&1{jWcDDHan_XS+rX zMTC6jr#nb-h8G3EqqnDUc{Qwp+ndGmpV>*CWH}7e(kB~Su^$nG{h9q_aHe>z_S4^E zE=9S&=9HIqBjV&`i>F@GgfQrI`?sbgt`Q6mD`9AwSww9qM6KX9&kR>d#T8zroS5#z z1tj=Nrj&5=>oyA1d7irFQd)kA8`Ses$qdAWOFL47?Vsl6A~p9G=OI%&;y{iZo7_N4 zJY9M0V^7brH5yO7g+O;_ZeD?y?eXv`7zNVzFRY|iZdCw=djK)!j-@dz%*N7$Q+HG| zzHw`TFO$&8@PxakYNuwmWHb#R25yZoEa|4^@A;AgE(o z&<`k*J-gBJ$_U+hTLawdG@nkIh1pRO%8nAja~ndBn1W+Qr%xuPvTrJ-TPaCrp)1JL zJww$sve=)`=(3HtPXi}J8)F!d>tzD6q!h1JOA&NKzXM~6&l4eZ)76*5Is{_;vFIW~ zvi8LTH9|ru2w~1K&)cO-{c7aaDfl5XSgREK;)-#$rU@iaebZuTu*yIvQ?P8&Kt#vL zEu4TZECM!z)r`$3A*!*@*`aawWw^nkuunA+Ljx@4(PZ*IJmJP;KJN^vd4&{w8XN~p zCb5*vyAD=qEi)$`-pVQ4!4`gH=7>>Llg2del`Y)1D$gJ%bnW_;X(P0y%?K2>o5$-| zsZ-7~pLNfGvZ&=xuL0n~MU+(NV&h|OBa6U~eH79n)e=!zqrm>H4W}bvBxL^n#rH|{ zLJ?=@@ob+wG478u8HRlD#W-U3LU4mxvQWP(W9^Doo{ophy75eZiJ=R)JFDCsEZbZ) zK2f=c!7-qa!2C6_ys;8|R0sLZ?c2|oI4T*zfzvgIHuo^0y<1vFdEWB-+eQ%0B;d+{G}udAtUf?)&L}9LKuO;A!Dgrd?+~su5EicPWk7&* z_|@5CoK1|4D?IC+zlpTbJ*4{i{J{yJ5E-d;M&Z7qN>HX9Sre4IU0 za+9wFx@M{%W-3^ctpfNm3L|rQ12f9bE(E!=0n=)TF$X**=9}YFu!I$I%%k)Q*+n=O zKdTf08{dFxY!bUC_?$8FZd!bM3jN(Hv7OLQ1>%aiWTk+$$XC;Z`*>)$u^GpX>uMr~5w&@4>OH zd#Tp8iI4o=js*2fM=|YTsYUz4>=&c@oqQwv3RjETK$_$f%!RqD<$NPU6C5)$g)M;{ zJ2&W1CGFwv)h2dqO_`<`1VB-uAq!H$dhO$$P7=(%T~qgR4B#Aj%%x~mPD8`pcS~G06bz85G6u}e!x<2$Q5kbtF5R>KoP&BWruH^PIGRp1 zLx#brvs9e9-&{!{0d<30{jRq!DKN04e#;O^epECt(V?x1XH)r|;X=L{H?3A?B+QSQ|BscVOE@o2E=F=BLkNQ36n2@xZ zI)h?ZR2<$sxs3N?rN7|a*BIVIWfs#=lrknWVJJ%ClyC4+?FBV6t%WR9UQsuDcJS2GnacM?5$Cm;+p8_Mb&E!jbHNL)5ND?UhL!o zZn+Xt&WkM~dAVcP1DS&F0ob9S@bFJ6)BvEHPx6puFF$H7xpe$T3m}uy?(E@7@XmSH z;iHpanpm(ou-)I>*#=}E3!-zE@E||jM@Yb>$MyhZO%>V2<63<{{atlNgTJQV)6J*4Z2nq~X#Jrhq2ZWk8H7%T0XRrKGv)k zWPogoT&cm&;bO8rIbZiJHBy`rjp#XTZ$!1#1cK(=Rml#C&e0RF`g-I_r2A>kfZgH; zB1+a=yl70qXGBGL6Qgo5akafvgnfliD0j8E2JHfF9;)iqhDPZwQd(6#fhinMRjrA`Du=GdIFD4dydmQ1cnWP8jO`q^0DMaunDRi(Wv2Nj z#8a(RZI!}=BzMo){rydIFt)EPd~3Q5o2!3wV`=-iarW6L6YulO?2p|ahksAfzndQq zV)q;!(BpOI&S6XN3@lX`(wAc%*MGMoPtvz_p{;*)MQ0$L5qm3=nk_Ub&12{@?PZ`? ztqWmx?!zy(9Xd8=3NY;#qJlrJT^p%)W*hpYFf!O{|I8*fsk^^1GkREt`epb-7Fz?o z?Jjy^re3OXVcyT($0E@()Hn*OFZA-$c_6*2*}UOPve~+HZirf^x?KBp_MVn)p+wPI z(VCE=h+;-s&V+sq9wz)Qhw-Zg2@sYP@NcX9ZDVZ@VZQtzx zL&}IPP0|--EgbyDfVPlb-%9b;)Up(Ja?aZt67}KmAX=aI$=@wZ-Whl{cW*rso0Z`(GK`bX-u9Cz@@&lwo$DBeMN~cd$q|(U_0LHQ z#H`0>JQDk^L{J!#P+!lls8-G)U3~QxXvNss{Y{_B{@@`$_JGs}89nCp0MRBuz8TsG zSj0t)ekSF%6BeWb=roz`J|i2#^_+c*ar)#BXaWb~!l07oARS;nF2L*D?y{!>7>i4H z?BC``DaN1XM+c_7UGw6epfaqFnM4F#LGE1Kjg*A)860G?{j8Y*!7zM#S{3tXWoo+F z9(U+?HfAIP;gQ*jt31M%&90ezkj&>q&kZnT*}agDu+Fk2kOl{xxIK zFf)F^ej3QgzB6RR`9@#FmQ&6!E_-ArujX_11ldwog?ujbK3SG+m$xlX3j#}X z_Nb51e}IT&P7pki?tGh9(&DUtpW`^J&GrHG3oKMcbFgAbv6qRLExe(xLOy{t+<^q*#T?ykM2wOma2ltPQn zzIDvDHyZECf3`AkuSh8T>r;_aoXGin?;M`c!D1 zmOZau;c_n(Xrt)mjK_#*RGRb(9{ND91Mi7Z^*cUL6!v5`X7y_Gr1si#QaRPK{zHZH z(hyqB*{UW!_qCTI%Bin32nJoK$Lgt0yPpj&oO`Js zC$Dp#jE579XRTY$M+Z8NeHKF)8XR0`8;C0m)`6pRmrC1he{-2L3X6U?;w76@7P>Ta z_uebDrGko#K`*q>nZiTHVzEu4>3kOgW^P_W=^aRAXcmOzl<<>9yeP@j1HV@=$rX_3 zZ9pIgH4=){pA~XB{5#o^!AV8}8O+g1!=3xqV;psbo|WBl&nU7*_8mq)y8xwyQzXH< zQ$Sq1y5vJS(C6XLp6#+<+m)m3Jg=x(`7n2w8U-k z;|a|7?U%|9iHi>%09y3s4}$12M-F6A(m-g@zl#_@D8(%fD8oIHM^Z*83P?XaM`WG( zw?RYhu<^U}Dl`Oc$g=R@u0W~Z&!V&o2k^+sJEH;&$e;3{&=!b0&xPQ0S?L#5cwLPhq?!ffj{T3pkRBSGjW46ZCF((rb>J?+HNvn6p`Sy|PwW$HVU zZs^#77vT^{VbtKk*Pk{OoKN31{9HAuE)iSYTMw%`&mKScSFupcaB}DCZ9M!XH!L-r z-CQk5!-IT7SZan&YZpzwqH9;m}WQ%U1=P?t+3r?;*Fw{{ixwq?O*hLo{UcE^*Qi? zH6Wf!BP2L$j3$ADm;x#+HJXSZH{Q&o-eVQ`kMnKQeBClO!RPL1J z$_3RtCVG3)hb?AD58#)onR7f4w*8C4$U`op=Kaw{i1r2Gilc4Y3(j;o#+d~71HB!_0vdU!~WUcGPOu+|DXjnV+$ z_j7E!m&&1R2Y9zwX%-)=Lta7N3k6l0bw$WVxsDZRc%FAqS%%(6XYwWEe}n?`0CSP5kCE!K zSM1wrY3A)ay9S$~g7VissE90dzLQ}4V1koi6O*5re$Rmhw~5PlD#Cotny{@~?8(#9v}0Rn^f z^MM|ZQ8eVVtA5YJc7wz`gaN$}Nyb{d^Gju{N`@AmJp7F^c@HggWp1vfHE83AVsOon z_k+ro4W-Cta5KYZ_)O~+?WZl&K-CSR2m(1Qq{mScKFS-6RjlixxjT@K&{%bXgOR9ozzGw_OU6v zJz3G1>uloPjZ#1;v5A$}hq#AOvgO|){xUmm)5+|NT1HSM*z7M`#98HHbh)r3&4Os8 znWh~z#&-Ei)gc;{I?367X}K90Ax#s*KxBnPB1B$i zr%H-V?GtWd_)_p6rT+os&td3ZuTubmN&mJK;A4|dH|~D!1)@yBhd z{-^gSo=uMQs1}c$qLHPyUei#OI`3R}04I(`Sg9%3*i&~WRZknAVuxd(0s98)4nX-xgoHe{f6KHDF$QhJLQ=CY zMF7>}$voF7l5n3r*Tm_%B05W9vHS2iE^3K}H&dIdR~AWuVv#%g z(L|qTX=9vz=y+K<95wKZ&D)WU`f{k|>o@ZSPY|_74MpEHxl%tdNW0CnRSEc{BM0^M z`F?hZzFqK08uZx3@42Bv2{J1Y50dC@y&_jT#s=69)SDQSCNAPYvroicnf(v8QP%n! zXa)Y7{p9=UZY$kiKznkKc=wYOz4fXKVGt3bZI-Y19DWCTZz~OBBY$6c4cLiD{@G0d z!T)r!O_zox1%c#nS?R*mgRe1L$&s1)-U+XqE~20PN})y3q}_H=)+K?lX9Tov6=89# z!LFJ?|1==6YFL4ggG;18_R~%0aMn$_YqGAI!l-6(;f|)#Om!E;crQibr-zA8skdTT zc!fg5!rW@9gx&E^P4LmEs!!Tzqsqz;T?F-{tQ)xgXnQoFOQTv)b{ZNoTBno2cPMgy z-D-%6_hTtPd%u$do~8XklVf#hW&)?cBW>Jhbh@rIK#}H>@EsAHSuV1AzJ(Z^5&jcXR5&V?lVla{jHs zXO#lI!*4b&KAVaV-S3UC56tZS@Mzo5n>lY`;5K<%m@!V$c~pvwkhvA&GdG+h=t|m? z?RB|-Yr%l+=fhn?F{5kmS(F9m4~q<++&GQ;g`A*iEbBo zklLc{O4%hXH?}Y?OC2?(&A~usst<-X==7GcevyqLXDg)hJY>$lPtAbvq)hy@sGV1 zY&ds5vS(}n#6MV*b7nzYo~!#(KwUaVEM58O_2jW;Gr`TpQ;+gAXl z(8jl~Z|)Nm@H=5<2zTtQqP3#0>9^5RG~{oB$ErR6oCXBZj?xcSRHi_|b$6Khk}F@{ zH5ju*gA*JKHZinX8t-v2{%uSH4evW<9>CO5J6p3W9O;boa0*T2ZgG71^Y&Zwg-tAx z5i>Qdw37Y04Vxy4v-$mTLzo-Cj0q!j)Luyqr+|m%CBLTl#!a#Ut3bB4d-TgV_i6EE zWfM=CZ1t$66Wa#J2S;Y8Yich&y~0&e8$IOw43fEcW@rBl)N2@=5cX{=(o^%HTmkXV z*rM*k>rTwm{^(R)Ue&#uBSfxE8F@}L6mpfM z(sF{#n&xhU}Bv-<#}FS_%Ur!ta8bT%fr$J z|EFoprFZ>I4f|+69UX0j9wG|ba4;dsG$f>1G?sdHGT%oVorxp7%d<2b2UPU=eD095 zcM)8CO4t?~M?0T&BT6dIK)ITd!tPva#PpkoML@grSShn0nkQtGmu6ODHoRsZSvJX4 zKwbU@2K_ubpEl?Bp%X$h38kkt(hjvzY-pJXGpL5XpSX1`Bx+zu(VG8x+HqXTPx9I+ zZOm8S!2&NQsU)!Y#&PONlK8mJb(PKV3+Ck?ulp~o$9`}0sK03iTTi1K4~8m6^Vh6< ziD_+RnJKmuq)u3GwVBNTZQC>}_q^P5d->{8ABu1#QQH5w_|Q~hqsy; zAt&DbFbyR_4({+Q=F7M}2f-o^BX9dJBZy^6p=tMX4S>{Co#0RuSU9Vqn7>z-@9Utb zd7fxNI8rvrxp~n`vz?&J@Yx0`v+TwVXADkgsdyejjYq>&X~bP)jvs$b=NUHeCjU-0 zj1wvV%O3zW6os;`WkC$R;i&!6d1XQqtiC;?Ks_~;#y0a+4WO1dnm_Bcs#i_&Fdu!> zXe1=?17KU8wZK%kjJMcpl_Q~Jizk*V2^;H-pw{cW;fdDrygQPWZS)TYnTvd&8DnsA zvb*td`*BU+L-EY#EC9dxXg{E5)K3WokrkyUewQ2kU+leQR8?)*1}F_8Qc8D&bV!$g zbRAL}Q97lh4kaKUjevBQlv2{5gd*M2f^|&=j>igg%clKDkpsg zut7_=F#yfr&Bc5#uA>PRbh$ePPN_q-_hflJa~;q;6$r7q<()G!V&gsWl;t6(hmqmA zOklIj`#Vl|iVVrVuRZBtW?R>=hz$FIGtm9ED-_#AyukM>OIfDMw9a_aOCj9%jq3yL z5gk9N&K}VC%L~ceF+C$CGtlZua6fp(R3jeB) zCuh!jZ&>r0OzW-WA{l}XsXv7x&F+Upu`W-Wro3|j!4K0HWz5vm9 z-bH-cCTwIm^Bkm^vpg>Vl70(-jf8L0f&lPQF`3@w?V}11b>V#Q=e-9Tod8K%fru6z85Q0+8`3&aM3nap@HWXOh*Rt>I8w#w?bdV2R(E)z0CYys!?9GgKnYRg z=AQ1w5j(G<#(KT*ig*G-nb1@*_Vfsdv8r6@ii!au0?sZPLg>q??-xs&2^z(sDl|vZ zj2&t+MuJ#AuGwreY2uv$7NB<@`;{*6x2}_5Dc=h0akhjM9}UJ2eLSA_rJGE%7e)Qro_8(H)+7xC99H> zZlV)OUsp{M$gg|O!4v=f@yq-B)dw3PU;YgQNVHykCHe>D@c%LFK=knXs|YCm|F8N( z#Q&Gm%I_(9>hhcWl|8mx>es_As(*yyr%ORLIS{}Wpb#f10_kY*2@U_N4m@g3HJp>XUr7IkST9WhC`lWpb`95<5lhv67^^Y!2E##?dVeMWlHGiB+yS9>t_-YVbud(o&e{f4^$AXp5!--% zAe>T99PutAAyN%E0^(qqp~2J=NkJ{20rbDNJ-Ak&Bi!F}D*l;tj_WRbq7C-MO--)k zY0H=6+#&%H9Hmw(U+^^zv?Rg!#vmo?t8C#@EbP@n$m8ug-!*R`1Bhj9q&AtXBovVwS??Q-byX(+D6aL|nBlq;9JF~dyL6N--DAdRU) z5~SBeH7fD1)da^O_DZBB5gpqPv2Q0(ib*J9?^py*xd}la(qG;=RqZa28qTiSo#jk} znOtvqFU+5|KKB=Ac7J@XTIyKpiX65T)P3dVpB6$8~|NXCa z_4+5XUF;4Y-p--dd(AT?jyoYBOSIMkU~1EM`By^O*{n(Qvwn0oFqD;ijXDW8A;1O> zcBs>Vr~zUDYN{QvbOKQuaJlmRnUu-fum}BAIB+uz_Xpx_zo(PX6%eCf@1w=Kr{$c! z#~hS9tqfa69?<49aovEqI7XSX;N^^@*+!o(whkhEp{gy3?>jv;46~k1?d8q!h)gRi zDGpoS>d)1=Upn*?11h$pus)aH)V1$rHLT$2w5X6gE%-oS&!fBF8(-wO&$QS!6O67KFXj)S7i}4%b0=at$V5@P`0qjTztVqe$$t%GufuWilfxp9BLARUB{h+P=7~m$NAgTdf z?RuDh<^v^V=c5#xM~>Q9yakSf zpf4mUYW`kmON$LR`JwM#KFGY*N8*mwF*8*KKaS0}-(J@)ZGOJprB_u*$==w;u9sk# z!-MAa(|JuKQhJn@-G90F!`LJIf|ZfebGwl`y)dzY2uD0%9US1zRa;ZF8b(HTLMjfZ z1eBDjOCRJ@^q++5#>NXVjIL!;}~JKK8e3yYDoDeo@3yq<3jffpYR^zTa^=38{BylXg4 zeRG)yON~{Als5@E)z4HL_g<#Leb`ryhEalEc_o>aM*`u!Ds>}K${K0)&nC z1;c>N%jGT;!PAsf6oeeWD=@e@GW*FHjJ+l&wR(Bg4h*GAiFtL4qp4($eUC9Z)ahd= zg@2!`~I5Q=79bMEV z4iz4yRW+x=3UZ5M#ipBM+J6iSH)Hwuw2g|WsIqGDHOapyAtmruwgcEHrtJtx`{3&* zJ^VlGN>dYdWmTF8yKwV0)p2w4UA84JMewPMKfDjPT`6g|oW4b;Ufap&nn&2Bnzy=a zx_-ahTJ@Id>1}{8mx#%%Q8r1ZnJrfRTHSfwzhgHip;~Gr?MuR)Zm&YQ`TUVE0sedd zQp4K!yO?sj3Un;;uB%qRRwn5W#>T4Y20y|*k(@lW>q~pdPm}b6kG>oYei7Ni--5dk z6;PsQzIQJtf|MV^M|!V`IV&q`q(%IaHLB{e_7H{?|I4D-#!ieF zZIO*qst#;R#3LZT@4`>0vud8#iNB%AeZH$)%}EP|t?`6G<@*@K#`jBOl+}J{4)5j1 z4ZHlre!;_8THD|lO>aFH21DzbrI5-4H-{&>Jpzn9U7V`^Kr3w{u)Idv@Fcn_Z2RVWBJh1ByZnR0J8H*F_dd|PxrLm-espR*&^ZBXH%z_@N? z#i_spbwf$L7ut`US3{E1uwnD(jG*PnnOy)A@!_8y3$z?Jv2;vCIEVl`o2G++ca*k{ z1TB2dZI5s`!+y6Y3ICbT&cHqC?>nrb-bv~|JyV<;90~2EosBQ86Lk4L9aV@gi*_2C zLtb}JN?=qEfzYW804qfP9;)Hat`;DCkp!^^Lh6 z9D^vt&L?;X7TC4UVU^J#L2sVd_#RR-ffMnANuy;${6V;!W`UC^BOT&shTzB1C^B*@ z$H%gIoy#0tTXLS_^7jgro)lw(iNDcethBl60=N1wTHdsIrzg+qTdM`XjfB%an&G#+ zRVlAt>!EcvNmYGMJh3n1u-RPt)bZz2*G?LSLJ-hoz4bg8zU6d^`Z(Rwd+DG9~ETzeD_G zxi_V#u<&z^WJi0u%FNBF4+M{kk>;?1jGwEP$L2J>o!=wGrU#{WNys;)rL=A@>a{7>BI4a+-lw+p!uH&`%dsRm|&sdt286yWK@9k9N#WXB5 zs9K2@jFV2}2M({w3kwWscNA&pkw}!{U#R*3IDS~<^sB0i#-xd-&(JRMI_Kgsl~??}hD{&(M16_HLWlGjt&0xIU>BIN@0vQyIpoV3^V10V%W}VY zx$maZuI22r7mR>xP%K_j0fMfeAl60GA0O`{^Lpu^7v=G}bq=xLI%$X*CA9KL_GI(Sz+p6JD!+dm`LZnb8PH1akuHKvsx6G_7 zGCnm`iJF?PNS9m|3Cw8VUN;$1nb2QFK9OhSr&$T;2U2X9-iQd5f0ebe2^RoZCsEfP z+CC6kNDpQ*zanNxwEj)3{;YjK7eag-4U(s9Mf>dBq~t1(K889`j*$HRUoSz3Z~5zK z?PPMQkfE}a;Cs#Ia!jR-3;b1SIgsA@Ld|mJR7nf>^?c&jVl#C?V+I}#=1`KJe$w`b zg_rL<A zn)v!KPSUa=(1f(Ked~1iC*>+aq?+qJ+2WzZC?5V{W9TpSaF)DRrq5(6ZK}47{UUXbjiEexBQT?NP@MTD!hl*t3O!MnQ#$i@I_9m<_3}*Hk7Ug6{T1!zcu?vw zT%Jc!ibuw=2sUVBVxT`~Q5(sj57Sgpx&1mmUd_s?$W4faB`iB9hnb5j=CPTXmPP>| zh-G!JhsXejPci^6a1TDEkg@ScYLt&hJOBaHr8}M~GD0P=sn=ryl`Ygnl<3IfS5WDo zv4lACWlAJ*09X%qovA5YHL!tI9e`)GumdbMk*dUPQ=UF@+ioYVi#j)qrfOF^z0>YIWjQh-NAFC{L(N}W;}S@+prjDxXD5dY9%44KPsJUVW>Bf z>!UHAo8As1|8b#h>?B;hUXEKjB? zcm)riz@8uRb%s9i!X~?rpxRc)rkmCADXp|%T~i-(HMY5tBj2IbOiiC_DgbA8MG+iO z6wG9A{2NgMdDsP*2vz$;cG^}asjvzFtjnf$e96`x5N=$xo455ipmm}mj!}WDD*NC` z+t~9(E=3$mk>3fMGEj>8J5%q-lUgHQ>mw=0nO>2e22;v z{Mp_;%v%>=g+coPx9=spkh->lu}YJ|#)E7Ptg}tN`kHYGYjWbeyvnol^Ly1Up6kRc z)dN!v=X+2HszKN4WBX+-88@?t^aFk6cERu zm-M9~o_@i#iuqufJ7eg1t^#{Xn*A>;26^auWo*OXMx1$}V$G?EI)bc{XGq!RU}E!3 zi-uyg+fF7|&BkMMykVt=Rb4$V))egw`Pu>!c<}$jVK!%>TLJEFQvKmR_k_8= zG=P_b_YH{;wm}$>5jxD2_q5UQ^4Zq+yW(oK38hvSLF3;Hon3b?j<61XtjoA4_ahuk zyp(tAJ-YK_&_#uwus^sWFLCy9gLVe+3%&9Oh2rJ95;46g!>Ma6`6{V3AE$-@1IQVp zhI!LfTVA*wa1}h%w89Zz90`poF_6&7J<4l|6p|$^u4=MsQ}VR_@r{>Ru*3f3+17iH z42X>DUADR(1lFYFX6`fJ{ByZe>%4@UI4``d@rmthX!4om!Mklq`?p5YjNS;$xM5Mk zakPZr-4I3jGjprtfb2Y1o}dh}`Q>&1bazwq!9ARco7arlbnU?hSMXi^3-s~qpp}8z z0KBdNubqwlNs)2lk?9_cl%`s%_9)_QS$cPsjpS${+I{_+)sf1rs>E+5|1AuaaTucz-z@oAxGHdeKj15@U+{v4l`L)RTg*cWpzH<&h$$3k?lRXG)$ii zL47Z;pv3vOC%Mjow8?jy-c7xrkx#A(*kz4Nwk7mOrnbn6*{0{d0Nu4+?au9qV=VNL zL-%pqe#%>ACp-0F9m#b#*Ld`-Rsy7i?yYHMS59`+#Fw)<*rO-v!513+nNJK^Z0 zFLaP^!HXe$!|_R9@LZOeYtyn30H_Xf;OpwB^~h02FHjmWuvP{bt&G`dJ5<01wBk_ic*KU`f-|nwpcp zUAo~!k25prV~`y#aEqq#CRi%qX~}0}mM)>Ivvfxr=(`?J8ScXff5uRdi-kgGZ2XSC z?TM#M+&c8V=bKwv*JYcSn-SiSH<$(s)(GJ#TK|zRK@54M&+vwNaU2@?0;fL&L^i zSdAbs@p>}Hv)WpU z?E-AZPRm;k5NqkE%U}6SIb`QF4CcN?`1o*qyT+=%|$t)kY&e=s* zW3uo?!ld)Ir2r%}@ca+lPtp=Hhy-dN4!}~u2Uuf`96o!1xmR*;R0Nx=RHx#I)bAT8 zoiSZ$nKg$T7A&*CzaKEl6Z{|eh981I8@*U1=i|~+_;O;Um)^Lx@j>}B?^NtrGRnHf z-f?|AaMNzAr(l}iE;H#lT|j)27ub_cqCw{~fAKLc-dH_9Vq8B^!z_Jk$1CU!(L{U6 zvVA(e*6XU4dAhrfFW&!{NU%k;`BIY~oVfUu-X(fmxkp`8@Z+O9vR@t`02e9#r^I9@TtEl6^_zXHXdLlZpIECpqUyp%#+K4d6PyktOd{G z&|Q8%lkcD9V!FY8-I(mC*>D)bXfHH^yH*#KjNvA{R3xeLr{o*mVv#0?{n zTfis*I~5I&J*DvNIa~n6aazHh5ufjIx&X`+$MV7N+f@A}om3`3KJ1MJQnV>VGkfb~ zbde7u9i%$Q?#c}ff(09nOyGv#XN>kw^NQyVnfa}&8UnSN_RU3P_ztnls|m~9oybqa z-~@=EIZ-OJHR-hG5izPKNVE|)i&eSmD^L}@Tz${3O{B3f498hIKl~L|Ao`!-)G>rd*%1& zAP^CRLRSU2>-}+8zggv4S>Y?oFzv;Zi2t+YjcOi_*Bm^!XJesM!2U;M>~3&ogJ=Px}~}9uxRK z_6&?~IQ-;#e*URnd|dqPE_5E&dgg!p=`O;#cWXb|xwqq?8|X-9$N}@!CbypZ8tmYm zp9R4D!54?zLp=HAmw|oqYkqyp@$!suHlr2lYf$bTXTAg!+zl6jA)E<(UD{x26IAhyk2|1n5jbC&1PCYvHc`L;Px0<#5pNTFhT^ zo5&-EN0MjW?Wzm~P2JWEmo3CWK`$UgqSspSap zhX2=%aNs*JHU7^x5=12a)wLD}o7!3?@9r!Fg-zSon_0`f57s{BYAV-V6Z`}0%hMJR>Y12)S}L^ z@^3p1r=`J+zx#Kt8hKVRc#n>ye2k&z057241Q!h+dkHA>BK_x}yZHW{7tqxLQz5ZV zU>+jA)&sMCgYQ452DRSly5$fd=R|afUYKYoO$To7$GQsu%(_mZe8m8y9wyBVc-qE= z{qOMs9W0T7OMlq!aQ@WCSOECM<(^}I$9ZM z|6m#06bTa8D@pU0hFeQ6czL@*&Mw#F9r|M>kPlsRiOLUMSqMU(L6f#9;ZCt%x!@ru1fG5bsVgAy)evUiEeL0nt|OA(lkT|tJ2^6p;e5__X!0IC8RSxivlyM` ziigZsJhaY14wu<|ocx!|2off;rtG~!RpH#ABP3_EU}9J{I(fSJUfMWy#~3@2`1P9E zz8Msl3Dz;5-=AFoeXebVf0e)*U8K_I_15v}*--FcsSY+rY*kPj!#hgIke891aMf*4m?^8Tj6TF##iY6%`(K0^j zg2Sx*XOAro(-ki{e)AHYAUyLwyi`)TVH_`DOm$_!Qp=+T!Iqp?MhGI5e`GIH46(K> z*`eWld3WD7gWh_xa!PwDatR5t{q4KuF2aFS;2_FU#KYAfb46}j)(+O}CIgwQb!F6u)p&?bKq%E_^-rO9 zNfBVi316j_zQz#C7t#5eZdVRX%u)K>$Jr6$4wv9+k}Q6=VRLk5XiK_^Mrh&)I=rq` zrS%T70delgyb??n4;U>q<)MDQgTRnh(uU@Tb35O8VWorz`7yG>wRv6jbk?ZMcV%v( zGGi0o?p0Eia6p#M+zQ<-hqlLDjY;f( zq}%Tc`aDP149Cky$YE<(kZpC;rbW?E1sH^+hG!9en-)(`<3iFY#zPLgC?86+u%S?Z76V`+=8@o9 zV9f$gLkNmsY`UxgcZaW12{_d_sS&(!7rtd*3H#DF&tDC94+2|4ui8YeRV5v7;F9Y_ z!-2KXLX&L4>mrc7RpBA)mBP%COoY2EJ~6-cA}kw0;2)h z0rkP0I~p*)B-uN6NBkeQ4wB`~t$5N>(d3~_c0X=3XUv=x^;pT3JJp6a)688tavu09 z=L)$k)wajDxk(Iv*%gZZIgzgN_EvUJfXGy#2<&~kTVRLSZzB-_CItS6Vc67I@xaHr z$xzg}MX?&vxrbTP0LvpBNgNN$FpFoB9#?;&hQ@->C0fQWTO_y^;TO5%XB`NHu~iuH z)K~n#UC0aVaC{FWIjzoD>}&tcK6=1D{Qs~I7r}dQu%hG2kQbY7jE}fWiu3ZB9{k;3 zJ)nkoI(ON`CHH0j;JA3jYbvC^-x^5vsCMJUuT#)}5kcM8T=mc;?Q|SNr&m>?rOQy} zXt#OcHJ&AHUm$wd;7BN<*D=*j=ZfiMs(j2)@+(k;Txu3~kI zjinv!e+`yb?&Z0HL^%S9wf)d>RyrnmmYe`!TB`HA^+&#|C|#e0`e;@Jpo3c_5nAKu z`}f5Wn)c)$ZdhNg&!Olru|x&Vyw>5^O0~pkoOSTND_Lg^N9&eB%@Ky-@S@u zEw63X1r95LWT;iZAGem|=l3?=lP4BZq!T{IK$P_>^6QkpNQtUPgeJ< zjU3NoC$*lC6PL|W1+5BEuu~-yE3`WKj$7Q&L>k@MKkd;h76pst{YelbcT!Saj~#-h zD?emT^r_`N?{31^>%@5l2}8GT!Li)j34sznnF#^Q-wR_NjZ@{J>z3GC1ACYHAA2W> zeCrqDr_8$hhi1JXL2-z-UJv55x*Wh}FeJK#`Nt0X*&FiBJyp>^tJq=G zICC7d*f$3>lly9Hpuh+xw|i($SB18l502PS4p0$i@dtM7>ZWEcv{eU4b1VAq-W{Qu zPxh-UR3Dlo4a{mRheShFKlxqy`HjO}+DI$B|DF}XUk#$AhyS9bRtdyNW4!}gVpX}K z@m&-yc+y(@-DV=*{ed_yz2U)Mb_@_9dIpZ9eCoGR(s4Uph~nCG91#QsgTVG7#$-JX zUj10pMSbG~rct@M*%XeJQCOj5t1)C2vru6F3pUh7wiUWG0)ZT=O!0~ZF98weK6hwWspzB}JhUokwVu)5MR z!DBFE|34+T0UgrJD~F+t z+T(lrX02CiV#SmD!AU`J$yc>N7alEeHiq8O9cqZob=|1qb$2ZJ`g zf=)?CgoJ85&B=JAvQqlBwbhu*pr+NxWi?@9~ zmmkXe1A5s2Qfv4KnB%=GDrLiJ)wi|R$;QU|1*_;ge;{WtLNK@aKLDkw7p8;bMK zhVJqpbj5rAJOLq`dO1NUOeA*RJM{`4TAnr@z+N^WeD%j8m#DN-qB3_(e515!e{sKH zK|@O~Zos^kiIpy1NV;>NygpxDK`FJKj|p^BXBv#5sumX=1YBTRsP?LJV{~zJHbwLd zo8WxFMHZpToEsmSPH8ILF>2{=lJ76rLCTP%BEk@8HskIJZnl{H8DpUcw{64vFuvOb z*j@5CoJsJf_z1_vOk?Vc;Nh(4zi<{xH^0tN69eh54Zk*O4&$RbO<;d97@Gf^@a@tg zG+9}Bc5U<03XUeujISwPih%Wm=b@iugDNChQn4W4dmk|6dl13i9jk?5M^*7**1%a% zlO2(ws<9RnEFA^m8Jr7W6$-m)or57j!cStB095XY5ho%^Q~mSiLF6B4&(9?y&11F9 zthx);*NMxlyN(U#%D8r#n<>3f{UvDNuVYApH$3a++Oq&3>-`@en}IHHhWL@v*!#rF zGvD*o6%+C&32NFT#3$6|0f>EV$^AmnSHU}nT>{P#x=0)N)g@)>BHFF)%kfzx#!E-v z#Kst0SH~b<`^a$L_8Y26D=5|>Wu~0OC}$Cpwdfv=o%?VVb_A) z<|&C!OUSV8&8AHLSl%C)Pkda+jfvDnr2WmQ=41V4d*~t>`Cx(@x^s+2bvzFQ2se>a zWg=n)Q@EaG3g^VXI(x?+3d!=5fFT1{VhU^G|E2}UfFw6aa_=C7k1A8^vIgS}Uqi#d z9Re`>r_Q&2dwn~v2mJ|qytK@(J*zG4ghQ~K>92iIyu&**B?kcKRcaD3{pOQyn4dao zk-koE`S+5uXEgB|n`^Xk@^nRvrx93_hX81u%(EM4_bvrok+y#sBkd3!E6hc{j$G%R5|mSFU7Z@!{h!rF1HNlB(W~m#1i%k9bLSQ|1H)Z_d~o(? z+o~ts?MTU|s@i0FB`l-7PS7mW*Th8@H|NAxgzk@n)GWsNo7> zj(KHR@$jdgwS+q-U-F}GPNiFApi$%@vRe+M|>70*~@tCN)(4v@42CN|? zD&4ui;#hC4x=+ykoo{vjE^cT2T%9}i%-;C7D)X+I2N~X|Mfp5oZJKu4W{EXZ<`DD|AI-#QiaoSZjmO8VmNYT zigB+yKd6m6iZ$-)PGDM?ukv|U5DPGAFtohj7JFR2#IRAzo);RQN!ij1&~X5 zyJK!m7#%&YVmDe9X__D2yXhKlwxYa?LXireF{Z6>qo3Yg`wANJZbjQ^CTXl@v-R<( zl2ge3@eJQTbN2;w;68Eu3oo@&A)x2p`goChj~V4AGGy6or$BZD8_M*Z=yw=>10fEX z!2X-WFhLKSi`6sKq6Y(v-F{56GWwWNfReBHb=pY2ayyJf*J8!dlWbI7G}~ zV<>wh$vIEPk}JSWgOL8UxF{EGzA|~m;G{zeG3HJ|%$Dm;b#7zC#xJS}vX;o0Mf2>O zZ7vhL;Occ5Ky1<9#MS}CX8#YdlO&P>u_v^+X0C}HhbI%931#}|&+#|RMnre?*L3zl ziXay<@YC8(@;hxy(>>ecE%Wpbr(Phru~N?Vk*YAmav`Y@xiKK}!?U_%!$&>Ned=7A z8B<~>6@G2V;}0x!xxoIX67}wvxgEisH!$TtsW-J%VB9EybTOeYWVZ(=*OI^i1e3hM2YYu-IHdiqiPx5PiT_%S_|G@IW`Q!@zG#TJrN#7JIV;3SwnZjgGAUH|zX9GY+<9M&vU;TrE#lzmyTcLj zh%${53QFxgBmt*wtPUVv|4nYSlpBO{h6B7w?3?_#NAdzR_Yn&zA4|On4PGcDJjryN z&PJQ9aShCT^BKoIb-vDAZ=lV%|EX8H^Y?&m3igu)Xt7VtS+RGkg*XEJL4(~dJ&58( z=-Hdrsf3!oebTx#TNeSF;p2@bqH$AjA;u55)(l*jjHTebpW=jm!HWS@Mr$eawj(v+7c-_nIa2nt2gCt6gYR(;tnUJI3N z-=O3LitpWa3cOyJxxft?<2j#sw;O-yK2}E0k*n2Mo?T{ ze$V;IlJeQnRt|PTc_zK$Y!07|Sng~S>Dp?JexdPPem5ii2S)3**i0+SfB2RYBGIZG zBSP*SCLFmu{7b#2Y-phj`q^K$Dd9`Zm?S@U@-3tc^Z0PVX5G|^#{4yuI{RmbpR;Cw zKnno^`>J{uLrhn-?Po~(zSjf6LBwM%C;#YNZMV-;<&SmkYnbe;NXDbLg?sb(Yn3tC zh7V?*%o+6+J50;QM)?62c-@5Ckw-PvO^U-iKGAO5(~l;Go6e|t&3oL}KKmoxoGLCY z(XUN6@9};?z@`Ur(G9sxzIrgMntzY|61B)RE~5}7TwZ_hZ?VEpADMF?Mx_xb$N$w+ zB~9KBA+VcG+Hq6b;IuBX;k64GhGjt=h0f{wWQzzJKgQCb1~wsO$sedAqL8Li&-Z@k z4I;08A#)!&XupI(ch_j#>RGM-+dHIM(<~S{nvI4jT*U?kC%3GYkOP+b+GX)?F$6;ZD;f0u1(xc6CSW_|5sIMtRExf#~thWz@)~rt#$@J@4c8M@yh_saA zR$TdFO!gj~5WKiy(bNBF2T6tY!j<_j@aPsIvvL3%rSF1OGT#P0iRh`q>A5^3!BOMM z!o7ouMb*L2;e9zVwEY~mTAB<}xBS||JSe|Bb#NI`4_sT`o8y>F zJV<0hQ5>qt^7*552(=iy{*jo;sKY_9;15{s(Z`URcYw!15i#K}q(nsg^zU74q4-36 zE59C4@`OY&m6&zhLKwj|7nmASobYTDmU#1{2g|_4AHhPOTd_IKyOROKqXR`BZxaJw zvTvkcw9SjsUykM0;gddpP8Kp9DEV80a|5*Rp3jb1p&9DOuYh6a#QrpPmd_Rw59}Uz zjieP`2CMtlq|EpnXZq3RH!1O^9odm0je?K3`nC2sJBIv`o`>r)!}+rmr-#qqYL{lE zbk3>xoKZ+>Fw8+L=h_t}0y2o+f=ox%Q-wDRLP{uq7?O>;9z$ zJ}^Msl6B803n^JDot#uBJpD0AV+r_n5iD-`)hFfco%P7L7rSE6pr)@tY;Qgg+Z1gh z#q2@Ef8lxFrH%iv=#0ZCM<+$F-|C06osneJJR-B}&hlKFFq86Smm)AZ9osi^kCt#; z28eF6>XcX(=rbr~Ek=na*nhZ~L=qNzq*30h|NaTF^5xzgYs*co_bnA!56-MFGcI@& zFMef(tOa29td;*Zy|^cnC3|okL^_I zoyE23RcbM#ggy7yEIs+w-0~nl6Iyr!HRZ2eg3z{ZpuV+$J2kHV>C{ZugLq$%5#2Z> zRwL7nyFvJ~FffkbWR&+Mxk+V3fN_rGn`bR|Z({`T5hV1KYvao>uTa0bnSdygLA{RE zSvEF!ejH+ML%vhkf^8$IVA%rfQ^QJ`gaZC9>q@^vPSNmo&akR@P+9%Yl=LR)W0(tS zZ^|))aTNQ$_brpbV9I20qr64_^5<@xhus((|0O;-^rWCT4ezX(6Gd_JVW#9? z_Tex|QSl&=KlP4llvnVlF`}Xue2Fm`d`d0(;h&MF;`9&Fzdulg;=I4YH_ksWk@X3w z)*n9`R|19;VLaA%TZ6;VUtWz`S{Lel;|`DXi@cZn&$n4f@_&wVc%-j*?#}JhSy8Mk zk!a4h%V2k7r+BNzd5ttvzY0^RIR7P*5%YD4IB2GOhwt>F*W;~Q;H>Y*$%RP|V6=M4 zfhsS=Em2yuJmj$uQ&>qmra$e@qZRcEh3@U*AN>4UxnHSR^eF$_dwEG(z2)N2!4ei6 zQFXXjdQjKz6I|A~K&okMys~nT+{N^L^k@t0IV!lV+X6KkbZYYAzvEMWkf&c;mSxPW zTcm9J?`?<(puvh+<vprNN^~{ZD41| zr_|JH5ynNYy?~TS)clk(jQw4d5pQ_j2=*iyC|%uWl%Py zb|8Jms}M{W@-C!aMl~m;J|5a@GzYb&1903M9Q&)wqlO0Df%ooo40g0PD9$7I|ZEj`J#cub3525q-o zTDbHHS`6_wUp(zM$|mNa%=%WjGVsA^Tzz9;ITkj0zwk}^$i4H6t?^6Aa6*n8XW3L4 zd|)*K^#CSKZ3p1B@v4v zxq%`kV5QgCde(UhH}<_;4V%$m#QOy762;1N?1QQ3l~f0^kI1v>VG(O*ud2LXT4TRZ zSH}nz&IspT{J{S4QA}y2fW{JWOO)5~H^-eSHX*pCU|o|U+dXk0&3#s?AeB3{jKa0f zf@4s0m@>0=HaSDs9FdRQ>G>C^IO)j7F2l4C{bhXvvxYs!Ok_rLV;3@;ZsW?L@IMbu zCwT*jTwd*PJpW1yeD}D0?yRE?w+~YOxkiEyv5>TQkgF2=Fp#*f<^V6(X!j=J$$Tz` zuN{75l1YGAG5)+@PE@>@V;|b79genRr&H*g=P4}qLuUETibIekT%(*sBk2>@k`hi> zqf-C=56|Tm$?h4O6Cb4GS0{^9dC-A$Nvb?~@An3^_M#EQUiYvlM4}m{xyLFKEjK@e zR@rZPdd^=Ijg+Red`nay`C@mH=8V{z1^N3cI@;%~Zf0)=(n9hkxz2 zMCL7oTQ2ebk1gY_o=yq@dje}c#IMy+;P2CABj?xjX33=RFP{+&3%$}?C&Aysj!49l zfT?x2FR@9kqlg7+)U|!d75s@{LHdGLeqZA3HAXys$V5EPxn3N5i5!q-H?^3AzN}~W zlV?83(9Ncg@^QZN%{bpUXgu2$g(8ZCP)~&RC9b&D%~731;G{WK6XXI#e+U#Eqo&by z!LZ8vmqIeM|IYg8TMJWFuQL`B-~A`nc6Rwr;vdQ?nufZ!XBvuz?&(oH-(BIg=-W6D zzwBZX+H3FEwx&7SlDwHPAuJpCPq)?lZy-bNwh~RS=1KTVbW2o7x%i(_BlD*IY22WS zCIZPfTToj#2s~J9v7%qWT!u6e;jYosEU&sE$1L`LRr$aJ=#;zdASI zDPE9DZZ`3(?|h0!qzAIU-Ec2@2)UcpeRD$69ky%adD*n&Vy&_}GCBFcp+z*q$jgqR zy6RTeJF{keUY?P-%QNEBnVpO1Ik!30?Y{o}K7Q;km{}J; z6KgHMf9$Ao-!ZB`7>aSZ@GxdElYEwPPQd!*kzc}plq6n)0U45K|LC2}6@i}0@GkP> z9ZH=m4T65$YuH=jIpP~UxuCA-^*4o-kPePAXYs=T9T$j|$de?HESB*)HPj1&$oBlo%Mx;9j|x|(Qw zK?_^>&REoM8Zs2Moi0r-w3(4?y*252&~xdNVB`y9o2<6c`JUR@Y2>ok;j3yau=V5P zeT{q2n*4`cr=Qx2CT>S?gDIcGVOGJql`O*&`MJt!!cd=vooqZkA(Fx!?W9JI-R3(Q zp(5GsLS@c^1*`^0PEPWb2hVQhvbhU!c)Av5|9A7=R8{7nH>e|mW2Wr=pXG3Ah zg*J)ocOz-^s;#jOw`U~MgbBe~I1FL8?Og2fl03Vq8jTFGrlNQ6vZ%6)Crlm)pWk7{ zaE&nk4`*qB`O}n+JGp%o+J}Y+X`&TUJ}~_x|JPVx;OKsMilnU1$v^k(8hjG*HyU`t z23)x{?lT+TQeYSqSh(@b%|3s8MK+0cYxZHoY~!yYy{}>D{@6bi?;x#{Y4<4q;94;) zRvnE}ri6+sicv_HAaw#H0t>frWSGg=x<+G5*v5%oBkzHgkkz{ z+N15w<5m$7!*sObyB$MgI_s}S>=vQH3n z&Th=A+B+|QP;JMzf`>(@AQt!-^RyJ;qPPCxq7pd3vJYVe!~I>6%YG(g2iUooHn+d> zJ`B(0lpDPP*3#|lEDlM20>Q8M_`JoXj5%vEpAbK);?d#pzif3G9!1oWJ|Jw z_Jpd1&QvD(0ol%B#?lGeSAuNbMZ6L1#K;xF@H3y?&--W1a~o+qI-;kzS}3(Wr98mF zB?|#C;sxzjYm{&dPyusXxQ^$!;@^;1A6`BI{W4$mbYgiDW{>Hj`Pjkgq0O}hT^{9jGH_c-0Gy$vOnWHl$`G&dGCoP18A5W2_Fnv) z9I#Top{zsT{1(H#7pdmNX|jWi-+FWi+nRqg86}`x@gkgyEGff`$;CpaUSf2Cq(ULN zpo+iT_cCt$;OYz%=7w>Tlxksrag8Nu0fkBgP^css6Ak3<-Qh+HH9d0w$?dOCL;jvX zSP)2bnM9PgX>MapBx`Q0h>YQ<|IYo7-^GTKG`?ehNE#-9q*-*ao<7EIEH|L|1^^2< z#N&(%ZSCBqCKd<3;8>=NL7<;1?XFN-A3_SSqU{S@1)kIQqG=3v@+X}!w9BcIsD=jeomX&JLGwLH- zUM|6FgKmB?%wTe_^U$1MB8_f2M}56^fcEiC^`1suJIMqRbVB^^B!>u8a!1;BJXJ15RcMv$K99s?|%W z1gUS=i~=v-CFPtQf&B{35C@-bDgoL9#QT;C*DNw%TfJOEAd+f)tuROmeVpn4e~+^e zc$^>pd7M@_!oSEVcXP>nyVE=-%;PyXtTv}gDRu1!qsK@2 z1jR;gN|%9HMexMsWU$osr$F^{HOOt9i3gW^joo8>O4lwC312ukpC(pxP>y4^!kwJsDvT9svX1;7Q6F7b z;4A&!63p5p8Y*y&_od-CKOyhoRokgK<4pBls&2j0D&|-I)$ZfPfOk^)!VKF6#?Nn- zXS>B=M&XHOhqd4uW-%R;3pj#pXaIU(BXF37-Sq_EEXrvRw7Z z<^)*~Hb`#1!Y|u;TJM3Op#WR`jSN}rg+(j>Qi8hW-kWqwkq7tP^Lsi z036ZQKef_Erl_ohCD$ zYSNdSDwnnQHd;(NdVA#i#U*%r8}J#>yWbazS(>mspOD;UGG%FiE`~CvO{JM)mUP`LKdkvky2WO0{5;iEIA#gJsd7Ka=xpodmgp)rffIw z<#4>6Yv48;?XcyvS@4d4AaObWuv?$!Y3h5Z^zkrw5)E!{=EnM_PiYMHAtuSZer*fQ z=7an_c@NSar1ul_m2rp5Sr?^D3GO>z35z`S&CAr5};M-UhgU0ef_s`;yfv=&X zF6$nFO?DeS5s_a1MQHlEMO3{^31Qk2$`hDTf9{1X-pslyb`FLzZkXHf!!18DSqh;Vcv7fn>xxNOwge8v_lKD;r8r?^BnVf<746#;)%gr{Ykye1B zjIQH>c$Hb^7RhFS!IjqySpD}%cXB$Q#pHw&B&pjS;wlKru{P}E=)SmQ;M>SFu+{-7 zk*|J(lYV$+u~07g>w5ta@K><@X{lJZUTt3A-JxtABj-7_{h z-RdlZ$pA4xby3$|Tu*WZnO;tK&rM)dTg{VIUw>iIYijD*WxCW{F_6`7*uGj*8Q*o0 z4-u#c=^~w`G>?=RYCiWnIqtRu`==}IqDk=#9l=g2;e1%O9*ds+EFB^=vs^Q8yKAe% z=y6%2+7C#h4tmfYji>AwpDX)Km{Rc%<>IMjvXdZz=aG1V_i1|ru)yMAEf#;UK)LH}W>e0pI z<{;!q|MxZ6OJY6Ip2lN&OP__;s%t><-?43lPsMQrdJ6-buhX!MST77pu10> zIqOuPg*pA9j|w)1``;Qtivn~4kdnKL5+dl+3l}y{Hs7oO2XU`8CAn^{Z&WBNq>Jydq`lZ{2$1JB;n5Y?Jd9Z0{d)bN@#M$V zEE{g_#;10fQ}vm!OyPX3X7`jLf(l{tCGYmPqdDMxv5w*+b^NdHCmPhx6!oRLhGQ2L zsD=Ej){AA7)|VbhilE@wex$K$U(<^fKLl{L}g7bls#X#EzOf<0W%m!`j&^ zem`GvsMF%x-O=;V@OO&xo@Dl+WC9sgn#v#ZzDcNus>q((cPerU+A0&Mz?04tt?It< z9j8g{P(%}&6E+IKDE253mxR_zCTM;swff~s$Z@Jh$nQ)pS7Tz)+iTPaZg& zEIkWNiJW`8W*%0Shdziw&iS*AF10xCwNtrMXlMeK*P9NPy~UmQnt-lB>cLZJ_JFy| z9u zn0!^|c#!w=B?ZrxHtn2ve{6B)#+htyqgIh=n$PK}D(CfDFF=RME_>SO8rIK0l7h`s zuihIblBexKT}mGsvKeFzEU#a{vlSXZG>a=#JWBD)6WaA zIAzpc3BhBILy_CE0>7MT(usK8wzfV!=l=K40UHzQ>!Z)r^+jk?_zO!sAeZRJ+xE>_ z86kubUUK?|0jA;+!qN$EHorWoYp#iLFX5FF!6x7me*|lrl3w+QVEx(7zK>M`1bNvF zXMMi`WM?BZq$BU^m%=4idT1o0)V;seFM+MCq?1|Q@6Tr@Se|Jo&0c)r0W(Z>^90Jm z;{#0ObgbBS5hJ?t3Ju}DliCltxU$3$y49X+dIE!#%d(V?cqoiy@UYq z0Jw<**u30>Xirc7E13e%TmuZQ`xZro{8^ZddPNGO17Q@bT_iM6v;OH^<`&vn^9 zUFz}ol+v~6-lGC>a(h^MnRbTT?z)V#NpVoV!1I$i_t0NCG8Xx_`j%}GsH(_prncwBs?aDa zsb|C`u@fES_pa1}W9QC}BXtowoc)~P-`nwz2?2?%tyRg*$ymOX#<4U?2sM}Gp;JKMdtXU1P{ ztVF>cK52HN7uMGluM4G7DE|NoRQ%`KQLdfIm$^UFLwZos&)+`}{?96j-698h04NG=XM@$#n!XSp!e4vyi4lUF0N}NwN-eAkokfIRvCQf9Jp` z>o593SfFUr58pPhdt5y~QYRck(N z&z5iXRt$guTo`WK7Lvc)|M8y6dt8y7UN? zr@jePobdsr%?tpmaL6nIJJs(x%LQof+4HITRug-2u~7N+%Vq03xTgEpHHx;yOQC== zif$8VS1$Y#wus(te_vcxsIb@?ui~wEdvxt3IS{emU-k@d0C4;W`$QA_KyMzynT9tt zAzR;&1&3u$aee0LChGD#t#zuexcr_{pNeHU`pZ7i^@2k-cxtEhV~#jvWiJ|;H^7)v zNWozMPS8fb>mtAS0(!h;1lnI3R#+I;J(W_X_yNi%ij(%cN|}smf)2{?qj4oS;>-%>b2>;2q9)C$Iz$4KwMhl&LGy?gH-$o2nao*{d@8_HR^#^Wpc8r z*Fm;^m3+ws!wM|I89pxf%|z@J z;wxyM9_WN}>^Td{43?9#EltYDyu`ZF>W|PDd7a>IR34vVXw-hhHw4zAb~}d*jaz?| zt^&=o0Qo8rs;~fUtYXOkz@_=@mUIsd6&Fly(Jw|_0$fwK_st*h;cX9pF5Eq^NSSz~ zAJl#u*qO#JOG$upbmA`%-D8+o@As#4*V=r+&86kPBe}D=JR~6o$`ujS&)=Q+ZnVCM z^sALZdA-!fJ|tRc_S)tJ%Z#N3ZJ^FN+ko^{!~zwCe%WJ26rHgWn5Ri_<8tK-!>Y*W zm>p@e@la}Etw`8hA43G`OWMHbDa1raDPfdO0{~J{%+XZ^9{&_0;}qdFM85QEPg(kv z|1L%uKrur7rx>juJ9jG1J&9&NBYLrJkMGe(Yd=08K(C9POXltyN;qFJe75oC2O#o% ze1#m53=@F4M#D^Za7klXg%-)=p8b9eb9rF72Wd`k^umIjXSO(V4<-A`SGW&vV+FkU zG>NBZd4*ZN2x)9G|E$)0QP+MO3vF#$I(Q3sX!h_&NcH1Y@WRCg!bLj2)&{{*o2 z7(Dxn?}nNxK!gK!+nCPhEcSAfn6^K^IQ>Btmm{AB5i0%-* zdc3!-)cNh_0lK4K0}sF@6GU<{_q_%@pD!YbjaNBzlYo-O&yjj{$nD;89dK2vT<%=bG@=LQ{eoGCZ({XN|mN5Vi z=XOWeKrb65Gk=g}6~uE7P4VvObyEoyLKV*lQjX8rY5*C&o=7fg$jk!2IA z*Og0Dz@otB6`nU+9`-tED+*1KwmR<&n>U74O!-w? z+mWk_>yH|+kbNKW-KUx>=$C!Rk7$T=TD!m+Y`Zcki+{BYctdX3A}4lFA}Wc|aYe3m zzC1mBIL6%^x)jf#@Cw@P_}$hYvpR!C*u0nn+E*U$#IQGNV+R@BS*x2%a2fAQ;a5Oc z!{A+vQc4vjr>3Q?q0Zhyn&bX)&ncYVdMaUqw4SCF4vq)arD3_UG3JtWd=ULWq4|yA z+`v?01eEna3X9g}W)#QW1|ukX17=71z}C`@xVB}cC!dF0?m&E&x3{>lu#3^^&FIWB zEWJs=yR)$h$T}qFL^e7(18S&2xrO>!8@~Q%seOZ3sxrc>AdP>5MDjLBSpL6)1nXda zFi0{>ealP@S_(91#2=i9DH-ora2K42M))P^ybX`x@d4+svsz(me6v`CMR&B&A~ydC zpt@u<;BH;yO;zJs#SOBHV{4AWz`pNTt?D)^yO+(~LJ+fhQs~WAIh!%-y5+ekZ!TzVGg6h=9LJ(zj*|Ho;>=k-uaAlsN4;0aeYSD`dYIok+n#M#0?w%Wm z2=LOd)3-4Efw`anZ*^kTcc*lg)G4<4F{#oil#YRQB6`NaSV^!{JibY#-a5(&t2e#%|X0WtSc;dS-d#-0f(M zW`r~p82eyYaqR5aSpa1&{sTCy{|fR_7Kl4bM+x$YAEzvG&1WLhwe~ruNpUX|miUcN_^OO*iQmq?;b8d`ED(#?6|=n&@=svy+nOHTZjv z;KXI&LHgPn1?P1((Rnu9mdT}12}iZsJFk&eq^39~4KCIxkoiZtlRPTVL;C@ktAq#}B#zhl@oa=#_TEFo@ zIRF1Ho2&(K2%EhEKJk%5|Nc6z%5p}6K*xq9$)?RTPx5T{xQobo450GF>d&^bXK3a? zkjv*A%5UFWt>x~0t_yKJv&uwcy|VV-am_LvcD>v_=-w=rh@nBl7kr^IM(vlo*Xh@H zsqro*uZ-4^@*YY|z?X5pHF6}k>jFB14zx@QOa~BH?gu7?T!-18>=7mfJNGlg7dffy zxEnc_XeEn#6Ul{5YAl^;5>4?XN#)LqT!Gj7i`xS)CL)e&vP8GRg`nw3?>5d`j}8J7Nee^k7bw8uE(>9~dRf4~~3 zda}}?{!djcBy(=Qud1;q0GJWcmja-3|If$ZA(=6bx9Xp0Fo=0@^R3VoL4L`)E-QF4N)`-7i>vxyPC(9#d&?hmO|60+`of zd{i!NW1wX-+LI-!yu}#us>th}C!Ivz|C!Qut+oc6S7HfT2&oeh#gn%DV`bDVCRoCG zxeudkmJTnNT4cwIl7>dsqUHfIO0}?qv>UBRYG5CqGwpxb@(iO1W;zeUYX(Yp*)_A| z9M}cV&{`>R*(!Fu;)5w#b@y;1rxmHvOeBD^1iuo122N>7+SI+yd zdI%+n9IZ7K{xp5% z;XzVaU7WrfKr9N?W&{@nY^ zpw~!pH2JNUB;us&)Q<0W<2XDeiU%MNa0U;cWnS}upU3kaDe~<#u#Tv+kmNyFnEs{vcsgy* zN^#Hge-*rZ%OUsGox@{OIp9%zoPZm~VB7`h+U<<=S6)*Ql1Ggb$y0b~AuUlDozl2%2+YQwHGBD!dTi4Jg0(xL%ZSVL zUfW?HZAvzQo^v47?U&@Bw69(-aIwO1Tu*TQ>h!rQkZ!W{g}~a-iZU`HS-k?DZSp|M z6TI1KzJ3V-pJfGBUz5jt&1~=owYJ8o)j91D=4R~#SAUO3nj1_Ra|Dr)T!%`k3V1p0 zPLb5wkFP7&8r1{CR7ijA?c@pYdb2eVdb8un?{^FJI*e(}k{74(524h<67p@L@Ee}?2 zc>OxVB5{3dUFR_>H~x(X7u#dBX}$1jQo!wO^aF~rUGc289QQA0fy#;~Y)n;Nq4`gy zHy_c7`W;ovEVq8_h|gT1rnsIyq|wlVY=bM-Cihmsm*e7a8~bQNy5q%X{SkTAqurJX z?qqucWb^lEf1AQ)FJf}z7c)2oJc)ThOfr!n5_ORDf{b{Gba<~kLIN=O^0p2j{^zVt zUG1|4#a74rK~y978Z3|_VL&PX>5UfA<`u{1Ll@mb9B2UP`8>Ugd*{fbZbP)OMVwLu zK}koJLKoDAf*mF?_pID17)Q3zJFLDkUvG`)RB!UKsI%-MVou7Rai zq=^C)?=C~qk0wxTksgJ+WxJfzX@Y=bcx9byS_l41+{F%Su!$r}*Iv{}N-LdFG zCIgIdB1Cckmsrt2*4rpg+koY7?adNd&nLzJ+{+#~eBw1i<13EWQCeVux+%lDi@XX&^j((6L zkYC&6(}h7onvmD9!{e`B`s8;8#W=bkd6o_GW&mC)+U{sH;Cyb2!TsVa)*+5(o>JkX zUEFx}qFP{}g^vtRZKsYdeUaZ%d;Nfs;3jp_*YbL@tQtQ>YdxnnrF$sbO=)!CoG9Eu zU-NOs+QI43>}bG!U0yV{R8 z3+y-I3yM=C6-d>ln%<7QcD;!H>9F=BXC#aQNe{i+u)V8+$K2AYD^I4`9XrYEn%CdH zfss+YC>>08{^ZN$Q1wO+rI2og+i8LQp&#RyFO9#8UeGGVvLpqc(q43`w6=B1dq!6o zzEqVe6a9NH4`6$-T7)Ipb=s={ohqCvOsUc@pF$$EDMHX|G!UWUf3`dY-z)&Py3@{d zw9v~zQ7~XH%L4?;M$bLB$zPyuVveY;wKw?@`#=H#?LXrBn?R&sby_knzknyk(5fv` z&Cu4?bc3|rO@eSY5tTFjPW&z@9v2sq?MOFbIfO-G%FfVXEuA8fXsj3Zp;(l8%1!N( z5j>#RuLGuv7^F<@iZ;x3- zp$iCT(>|uV&TbwH~2Nru+%o$B?mcg2GlF)JkU3-%8yorUN4gD`fTc4$5*4{+3J41gcyud zIQ*q7V3GIdJNgo--Z(FL07M-5?tY2P5zNlvZ9v{_2d-c`Gai<))g^Ay0!;k~A2>mp(GBM+F(clB&`ecsJ6%rCR4 zb5i#Z@`T6cfd{y(cnv|rH6br65TN!lefeLuC7TA3{IibglRP;Z-18Co9&-_3=tZ2o z^0oMjU+jjcBq?suztAW3wQ&kx&?sb3%fzhe&;R=Vy#Q*nsf1SoW(o&y6uWBIlYz0q zP3nnw_NrmvP6wVi+^xKP04s_tYndPx%w;{G9r=Mr`nd&o&uV6eAi_K_4w%BiF8sl@ z352$BSY$A5CdTxo6wfXaL9Ys5EZVbiHZHJUucUgF{UQuO&q1P%OKQr|fxRWDhse`o zX__AkTad-WVC(vMbEKE^aO9ytkG)LLpvZQ~s;BWXN6G6PYeiXx{A4Y`D{JFS5M-L-fjBWaspwW@wc*)A!D%4ed?j1s&Q(ATk>LID z5`xt!n27dAr>w$WXbyzeJr6sj!yi zpFV)w#Mz^9;xN_3ycGi@NM>qFM7xvM{k9G&!x5UjYDtAWAGvmy3+a`FN4=Hy(CSJN zHpjtA5aQph`gC!%zjj9F*sx;CT&+5<06CAVG=h3t3ING|hi6sKLvo@*p=ytnN2}p= zy1{|qE8m8k=;p2GXqYLlFVFwJO5`m_AJ}Q{M#YbzH~||3u|0jqX}^}?Fz2G5UaZ4+ zD6AqFxCiL-zIvW6PM%Hx<)gB=OnJ>6QhXTHID1j9pCrcdMliy<%q?_RB?a6bggIH1 zAy9wz3YhcRsN0fIZf=tEX9-qwKOWvnkxSqrNYdNrk6|Vw*x3v(?;p-F9p)1XxZi+d z6VzhQ82i1sSW9dFVlVXZOM+t(opaK&zUN+^Y2R0bDicA2j;9X~ZPcXf8H7=5?Y@g3 z`M=;;*sHcl>hQt`aIuU zS*I&R901Pf7QSF81`+>bz{Ew6!3)O~;{Ls@9O932FOEgSvonX4u|JJUKsWPR^x7_! z-H!BS1@hv*2c;P)4mu(g#?V+BqP$ekq-c|F=jF9HK$gGiqCFwo)=*d=DM|J#!diOJ zyWsK)SOtN5vNs<~Gj|CWWJE)6t@~K?ogS02Thuh#h@)b6e5?tdwMA=QJpABN*flfS__ z#BmQ{fNl%VXZ+t|*Q+?c%|GV+Yv_6$2c*jPet52t)37aSmhnt&OpaSmftguSm2dp) zlvx|&YWYoNB$wr7?%VgpdIG^s>X%CD0nHMH9f&h`UEnNL%grlcY|!C^YkbLIb0cr@ zz#xACobd+EiC>nFOpgWbGCImv(tJ%8_WhyUjs*JzhpfFMIV2>!d zY1Znx|^zA@rFi1FLKEc9e?vSU( znGDJg6cF55+(r@52zcl-wQPytSqU^uM3RU)-V^`LznZkq%!iH;2;{CN+|xJ;Rj9}T zRQNPZ>%V8Tt3J$3+2Wf0nm##hfhgwz>+k9bmH0cai6}~RJc5r3JuW@qh5!YAc=$r6fS^X> z6DH?T>ogN8)_F_OX=pHu3fJZiJ3FpghlqE#Ze_Q>n6^Z6!^aiH*)?aV>i-SWQe=+@cJo^U$6Jyze2-pK^MBfb^D zuKODI4`XQ@^dP0Pw*u7r1~9Q8*2cnYBGr~kwX;tGLr5)oolkY#Q(qhOTi}Zv`h&lD z4AjvvA*U3QZhfhDpa|>bvEjPZGbpD?*Ld;t4>H|S0A7IV-!4!WO0*Z$;@!(neb>I?z1 zmMm*Cd%_U^GAALs@r{1sBd4-xbJ)CSM)eU0=GA8jkM$XcmGG1|rr3j?jsP2afc%{6 zG`)T|5u{re2kuV??&+!I0m2`$j$Oh#I3w!ao$Wk>jg=yd{o{0A$SWb9N4xPubsDVB zNb%9afV_C{|I&*s4`C9J-VR_%=MVrHmda;5gyhBV zqS?=Kd(G8}@&YHBv!4~2k56ZgHYz9A6lMCc2w$w#LpS`0wv2yPCUY`H>Ad!OIT*Fg z48Z5!_n@0IMBQlAB8tdl&%Fizf<1KDtLoy-`IUl`n&zr&AK!0I_NSVHTkQm0hy&-KPTm>u zs+~@2x~aM6E1Z4=i^h`Dgqrd$=|1A0ojxqsulxh!HL=LtQ1+WsxgoCDl;eN{i1W3v z8SQOU(uh}9xvmBBJEKyO#;x>!N6o^XD+*GjNs2CGe zn7;_ur^3O2r)CT9UlHWcdL@3ACu*(^M`JT=iD zL@PlhYGg!N)gL|an?!g2<9G2X#9Dw&spwD)qONW~eyC<>)Z1zDwUCH2?t^FZ##*En zFl8>q^=C&O3$W}l^{bJch;Vu*`?NKI`;Vr&x^XtiLKzO>az(eyp}zJ#^(r(YX-X_l znQy&=~bDB4@?-T9ac$wBfgz2Y3b@|-sj=D)|o z^=|50ufh(ufRWG`>*ojGfR}qGw;p)ATS`r!HGr$~vk=9xu#=7`V|N?cduT}}AA?s} zRN}6nI4-9ivthCgLCYK^rYeJvW#a(K-zdBi-vjfmoami`T>WO6&FQS*Ih~5x*o0HF z&Y;sPHWItNu+U3I4vlXYHq1LyO_A~O7&&!yq>2d-Jv>KG#NM{s{M{{pOw;0Z#=v2U zK^7igsXVjxJ$;bq2qH!vt@W6Ff^9QE)}Nz{1Gu0$7%lXXA26nt%_-DpB(KqgI<|!* zx@Y~bFbJ6eIg^1r;rLpsZpRPvs7R`kjqZ#mq!`#<>0xY{7{6YiBBK9oYC3+{{YODV zC~x>!E3aV`j^wJiQY~PSJWRs z1A2KtXqJeHCqixlUt25>@Ks6(N=@W%&QpQJ^f1HkPhf`NEHt}3@~!YXo2Cm}dN{VW z{+3YPk?2HBm49f*0q{Y<4Kn;@LBXuptHLOG@jkq3p#+Co_R=Bcu~wfNt`>iGLj=R) z3e{H;4%z2V`q!4nH^1SVD*+A5<=i*-u=wK2bKo$NH!f_H!Fdg5eF%uZ0(}miX+l_G^4`sSGU>0B>O9OkCWp69Ds7V;W?HDEVlUd*m*n+9` ztof|_m?aw^f)J~*Zhu{1(JOZ2=I@a-4>WLD4fISF_h*84$vtl!B;$|9Yw(Xe4H)QvMp4Io6#4S2TrCVu`*nv7Yw{r$ zxAiG;E&@xd6ugCu$m8XNNDZ08PA88IAw9r$FX+xZ<2Py%Cw{V_hJWSQ547aAYm~^D zZQZ!0F|^*7Y5WB0)pa_oxjt*GKi+p2tm(*ko}O@^Kz|BJeJ}k`_)Emew?z*|VA1W*+=tVJ!j)+c`%( z8CrD}LFZ%~fq@Q$?c#(n@$?3zQtfV!8pY=S(prwyAzxiTud$R%7Z3oana^ZTZd0U0 z_8{6@LLJ53cYE+ua94lc&0>}rEMi9UAQoTvNj6TZRtAQWz&me~Ta{D3DZzoy#9c`L z$&<`B3K>lI;JRLQUEp~MY;3J*ca{eTh))C{@h`>4M~s=uz#;ruN&)jx%L5qdC5`g~ z=qz!K&)jze5h>C zd>-VgGLBnFF9?3-E-$|wQ)uR);e)xR6stsJ6LZLZ3j?o1+mgbDr8oT*9iYXxAEE<7 zKH&ag`eLn89$841KK=0sC`xU(-^*t~I5r>75KVo(iD8xe1%MZ$H=Ies_mTol@$)d! zINCw!OBvwiop8b!W*3}(QhktOM?0U&nrA~~2|HxWmmUqswUog>Q9b&yUOon!XC4N> z2X!&L{1e`Q-WEqcgal8970}a_OtmV+9yMXR_CWy8fF6i}MELWg2WL?{z47m4Fgx-| zS?>mUUl-XQt3=kJ)lGkxMleoWO;TmZ~*D{@r) zqzY>zQ+q5s9j2S+iHyRMk+b1yxISuG@$RRZdE?UgXIIo}C=N^IhsonO6n>>d$jah$ z$QUMVepNrt$Ds|a|8M=Kkc%fdB8j9X<7s zpDbPbBR=pm(yxz{gN6a_s7J(q1-07W?ou1F)UPmJ*3vJfSj2>Qfia} zAilA?ExFKPIV&ImH|J|xidC8@wH26qyw-Psz3TS0Lia_KgOvg9H5LB6TsVa&(|csy z;42GE0;0(%wMo+Ir!jt8Hhic$|a-Uln7NQgLfWi5HMK`@SCKC!a`6o zu$QZsgKGqi>>1(RLJ&EJU_o~*;B5s);3zfdi6c}XVZMyNao~j)aA9@mTGJLT2Fwfu z(BQ|L6>_~~*iRPQ#LS9|Rwf&dHJ5=!F43inU=avbvM&Dc_=zucV>9;^6T7bh@CN*Z z?p`H1ST%YmLVLNeoUG|QBsK8UT=T=ExLSr$w;%v^vJq~PV3yYT` zc#YGNjVh?hvXpsdRMp4VJ?A`Um=T-ZewW6J0b+J5-=W!%8up4eHPmN8DYC>A#V2Z3 zB&VibX(gtM~@ zvv5Xd#$jvwLRU``vS4>C<1u*x3v8pZC;?i$77|#%Egxr_!+F+V6f3d?FDQX+Qdl=$ z=1{et7|qWZY5BUyA)UTi9XJckzs@4~cnL>8FQIWIp&=x2M`kbdb6pyG=IdM`tmkiW zGyto{IW>@RE{SJlI8oeUB5u;>NP~Jc0O1Q*^z-#x!t#0EB}Kk25AaPfa(>_>uhMhH z6V8C0g67z-KkVz+X7j^t&eM)JOU1>B2kEOT6;o<^hAv|=uIS3>1-}w<4y=LbPND(i z+}nkDi`gL*XG~ZYoG=>D(o(F4K2KL?Sk(DI_v1GHoF&2n7B;3V^ijJi9K;5eGMp#% zRe(Im`5d4_!FI@eZy%b(0Oa#>#B?Saej^cp*b^TggTxqKgK8w`n_eT_<*@QiB0~iUf`9!pUlIP&iBmyP68c{sAUz9gD!LyPO=%4n z;(G7dPpNTCMa#Ae;Ma^GN8ys~ zTRKnZaW?U2W5As3;bw0ldZJ4`dNOU0(MPJs4Kksp?H?cn2%|zhCSvSwIAwHLQa@^j{TYi{W zE&}Sd?9fj4ee*4G8M@j9`F0LY1~}k*;DDV61|z3Y+Fb|VM}*|}jZ%ph&#P2}AH4|= z3gys(g^5LWPGK`c&IC5rlf$~m=w=|lXnUOYSRUBR<;8%g+m0oBO8n2F5a5W0maQ(p z5f88ymi&N~bt^=x!{(JDn~ZHs6y9n&STxH0MD*9hDK_BZ=OY z@Kk{j$IiL0KS@!Zn&OZdeL8%DDk$<2pT>8ABSmWP5ldLx)>F%R4$?b+~MxCTZFJvIy9h8b~m9@C|2P)K%jt&y*K8 z?fsZ8ghj)SmiT`-DPog3RQ`(PS!T#$zP%D9_VlR`U?gLSbRSqz+QR80EsAlyDPV&8 z#)^&P$uR*f%=I3w)FgV<)sNN58e?qZEH{kU1o?^`Rtx#Q*k< zS4Mv!;vP}hb^8RY%SruR0{HB{b2q!tdn{H&23IAVw{0ObycveKJ%)iLCy(~Vqd+3V zjAn3UBM}qy5sNC7nv+*rUZnYHW0sR1U1J^_ z4ux(JC)~JB$F#=Pyf)E(@ds?n1B|(>0syvaL{ekI{LtDd%$+D{%QmTR&H^*H5VbwG ze$-u4ulS;KNiRfIp#v*)Bvbz%yx{R1!ryf3le1?pjHatBiF9pRYaQB9QnE;Y)sGQb zH^?dQUp(BID^IXWaHc%jn#7xJK{nlYg(?0S`~W8hNVgga!hJlMcrU0B#$R+{{ooSB zeyP^QQ;RHR8_xN0$kK0$)#67Vj{*SP&8}`9Ja}+(qrNHn?C+N^w;NGrNyVdCJVixe zjTNTKu&TlylzlEk?duWuhw*9o3=Vp)arFZEvw?9a7CZ$kWzgip=b9Fs4m8x7od z1>~@76k#RQ^GKTBg;b(FHv)UHoBY;|jVh|@j_U8#Z#?HGe@#>-9*yVa+sh~IR?o-( zAGY55A?ojm7nbgpl2{M~q)TGy1}SOj7LZQKr9o+=rMp3TsRg8S>F!QxkbV~ZeDA$K zJpaM&ywA)lX6BrC*3)r;8^whgpJ$q&!gqW{Lh4pw?ec7wE-#ntnyV3EfWA5?UN z@9Nyf`gaZ`I^D>uR24s`1UHNnX6SA=oJRap+T0p zUi(D}AMJfxQB=*RZNzWvfhVI!dCHuI01xNy_hN*!1qN>_T|WZ*L|WH1esJ8yGFXkX z-&yOadkl3iZo$SxM1al>50+=6DjbdX%1yQ%H7;Jz7M^UG=%th>Qjsy=95TtZ+~}+F zVXwm~nPNSZyKMD81Y}=~MQC}5C=n6i;YaZLbmGjH=*t#HnrXRLQT<;Zg_JpNeU&=; z?C4#i?>t>`XFaplBuZ%M5-CQwk8>3WVtd#ON!_bx{++F+#P5$SXtYuk0NcbCzCBg8 z%X!;NDW_IIPMF`TUuM{r0b66yod44HWxOl>%VFPHC*Z-b>F&44Xm%(hN%$@$+Evim zhh_nTE%UsgsDJe?G7FU$_RmETaQQC!clI)7wCea-oB{aP5;JYr6j-9d>D^$VkPG}h zWk-NZ8=y@@$XO~Q=kV;2f3Okh{B4$UE_d++M@LNsP08%*#4P*qN45C@Qxi-j#h3u;g$J^!% zuT^A+qy5;B-=oG1tR%61b}$wJXK_uE;-aD!QD@~W7*ST#4FJEsVx-ohz!QB6>JN7V zCa3*5d6Jy-p=2ZR?m{$dcv{0-g6OT@6Tox(>?PO%10F^r<#Yy8?ICi7nU2`7UG6A| z&+rzvQM(_*U5Q5No_wKVJ|2zqXao~0?Dof7DX0tUCn)F##PO!Vay;*w%gCI??#tnH zt$Je3Cor#P3h)oexs!|$6%`SYF{r(km}p|gKyR`DT;?N-J-VKS?-=yr?dyd*G;ptW zlBkik9c$XpcZv%jCTi*YbVqxUJ}0XsqkLn-LzE5$)EInO?oDPgW(~}k^@{Ot0}1q3 z)i2|k!buoOt$Nsyn>pxQVed~ZxB0jEiISqOw!G$f=YTDjYHc&7TOSBHys};Um?X-k z>;6YG`ft?E6gh8q9gRi)^jl>yQjKCFs?V4W?HnY1`Y~qeFRzBkE;>-R@V8R05G>}Rr5;zu$HZs=zDK??XOCy`1C*emPQHXoW0GVyk3GQd3?oA|Z^4SP z_t5fi#^==@Y_IxB{awqKIbw7q#GB(o3r@_yLK`IM_+|L+Nc{t@2082VFIB}AU7}-( zK{(gc#(9ghJ8D^snl;=W1ilb35>+Pb`F8BdzdrYsO?GPvNB)&n2j~MLNaAVa$Ggyq zWJfFtr#`WZ8v^0Rs*{kN8E-E-q1#@#W1haY7${(eY7ic6PcH^UU-#o~n)ptPeSGo7TYu5c5l3Ws zN>p}+p8U>lRuKyfTG+)=J_xb!HMF}$w+=T`jqn@pvI!hKkqAc1s)Ez-o#5*yZR*|JAT>URg(YvsjI8ngONqogG z5%422?9SY~1;eEEN`*a2>*E*?On)Gd=da6?-JmF*K5tM-PikxR!~9_PMaa`Ipd~WLp?!i>ii$aw)Ex%z4R`&c* z;Z)(jg{0ZqJ7i6~Wn!RX>mpHyz4#c6R~Zn3OZ6t^=a*@$kL`Yp>5sKCjzw=_ANhpT zzm^}cLzV=W2%b~fURVW^r9iH%DBMp7b;(?AOtN6*gRIr64SV4#&<9;2@;RXx;Ym*N zN*1z&yN=@V*eeDM`-pLIQL`%F=QUQ7dE@Zm--p*6U)(BXwpZ=#7>Vr4wIBQX0v>jI zdB@`4%8bREMjtYIcSiC~Jl|e!$hURw(hhg$A_RR8KJCdIvp;T0jpAEpznztdh7MHR z>(wK~W7Kn2q__PrxO+T5dX2y@x^LKU%qVR0v4#h^`2{N4GitEl{TaQNy68=?UT9gW znwY-GpbkXea_%BvlR8Qm15JYIiRFFkV*W%&kI6(?Kh06ub~J2D!l{yzzk8H#o-djX z2Yv);M|Gc0*@jGLHM|vmQTDr9(A)hqIZT*k;}8~IcbwE(%qT;Bc_@Am?4&u3w9zgjng!DU# z6^9`ut*Ayo98_}sOh*WsVYzc;y~jfbXt(S4QNv1sJX>=USC#Aa@-_MFqjsyz<2~{1 z$QOt*;C?mx;#>5SycQ&7<{<`l-@fq=;3fCG?Tnm0bON8!_g?Nc+8QFGYsdeC^@kge zEu^uPEuqgapMV*d)8d@0^icTJgwQ zZtIhyNgjw|FJ_V9!Fcqw4T+VqC(Y37$n{opP}JjhBe6h+UHu%@?Iyj*BIozzrC5Wm z$RjLl7ht0k4>jwbDcl4qa@O#;qM}mFZpm8x-kRd?{%2}3Z*ro1DBRIeUj~cLw*)wc z4%VThpTlC^vcnIlN>dw-XO?zz?E(PDNHSQQ>$+tPavmHn0 zhAkIyS>1!Hjy)Bcv7=i9lg=JOJ~wC^qX%v@X-6U-Ysu=`(`C? zi%D~|&F55den;P{*PMx#rQblbtumY+jJqvP^IU~;@m4(|cG%Jpu4xQiv9#Dy;3;r|_A|Nuz{&Aj zE*dd;Lc`Oh+{N-%sVt&g!l_(4U&ek+$U_dFK$b=3F>o*Q}URY{<{@Ub*>W)5X-Z@~ysz4PV>McB;!Q>CR}A>%s2Aj2`UnU(BygWjb{~ATINhiFjNApY$AK zPmj%@84uh(8)x=od2$4MX*JNV`)up4hmFQ;+amf68x+%8Jn*sLRGV7M5&@C=OAI!L zYIZCngP)T__2B%0G*nQGvz7p;J_&C`xpFE6Smi_aux#efvU6~MXX-y~F%kZIV7?Zx zs6zU=0t5b@6T??nh};ghkL;yC5KZl40^2rNjB9eA2_B61i*5RHkyGTgMmv-x}oZIw%Hq&$YxloF$=!-pNAKH>tFRsc$6iW9X zCE-2ii{D?4iSwOtCv6*^ez?t=sA+Ok7wSWb8V=_cZoM{+_bKl`Mcok;=@hSUTMCLyP7zS6Ka9F~r1 zts$=3cZJumWt=e>I!lEUO^g~#SRM;qQ&n6KH!SZ{Syl?IjG=b{^XdZU-bc+nedV+e zismdZ%!5GzuiIsSF3@$&>MxgPmk`~Jr*lraUkk~BY`zS554?MIO`!pbEvl)Vr>Hkx znEsHPm8{30TX8+Tm3f5$r>Y9D*t6G$=I@6!UBWccJxmk5r=m)?82%xlH3Sa9=HH~c zy+_N8&%oC|KT5gS&GpT9%uc|4dYs0eA^amEdsA&qv!2S$yodkx%h=@F|vp6fk zJi^`!+$WJ4k*x_n8s_sgjioZ!hL|sp5%TuOmge1E}kC$UB9R_~+9KRpC%^oM~_m zQMgcVX5Lmj{PrD(T@k(a%#j`+c(a9cI~1OID!1imd(?ZFB2~T!Jh0CzuV4Po+GeA3 zm&BamZbGE*F;z!#vu?M14Y?lO5xzSG%P5UkA0l}eY#RkS{FMc8D|qdV^4Ei+=0_3( zci9lr!ygbqpQ863Bz5ua{RIn_l{wzrX%inJ@Y8Uqpze_Ybhr)KFvZSzZDb7&6->7U#{9MbY3?LGSgZ zLqe@fv}bA`NL^;QLvM$1ag~0axMh)owDWC{DUFO!8&Y{!8N;tmV6Tq{`nBMh3|df6 z7&$BSyEa2)7Je0;>Y6L)U#Hk#qTEp%Mz@^yywaJSkl*Ny&L2j`dS6!7a83A1$deX} ze3n!&;bT@FiuM)!t$hHrAy279rKY9g9M7ifm1{|8S=M9U=9`O#pHj|_o!F}kWN1ADVdOEY|HHFzZ8+Z$P%UURC%RhqPLv36L*9sbfFvJu!+ zxz9x|U_}zt}YL@4l8h)Gyxt*+vU4$RWG*t3OW}bFf-7}MK!@f%(Z-%~D>q-V0o@$aDB;6GaG`Fh>fSD5%pNci;0SP7G!6AANRPG+{^={M68*vCaC_ zx;%HWxDR=E{Xo+@b|(`#sBfV{=1Pg;G@3S(+U&I1 zjjvwgHi+RReF}6XR})4eEj|g^n4zEd$(Ki(DA+=~l9=!mOkZL4>X;HtQ_^tP(L$7@ za`}mOFy9F}d9$hT!4&_`>qFg&TvW1|Pan`w>d#0@&-}&gDtzJ4w(@^FPh=|#uRP|{ zB}TURyUU$vT+GVl(Bv&h{r!l9|I#M<{)X)${n$HG_)mw#9s%Hqc<=4l&;dvmwieB< znC|)NsNUM!NJ%Rj3oTmYk@lhY-AXP?1GyrxqCoTI!dmJZ^QrfSZ6F~Q{o5a^q#y~T zXfT_^nzN&j&Bre%6LIv9VuA%im0tEPN4F+h6N9^rq{_9aq?{gzNABAhRft_> z@*$I#mBoY=!O#9LMox!5jNb;_*g%QfJa0U1FLQPl+#e^V1x8~Revd*}&sVrg4GQgt zy;>B1by3so)}}vZD>&883txWo%DJC8yoBt|?}QKYSZkit{`2OX)y-_(!397>*+%Kv z=v!OJ(LRdIqsrR)K{5#yKIk*FP1ydmlp`5+1<$sAL~+h7W*hv@#B4YJ8$^AgH&~yJ z5bruL8=4YTtB6mSR9#mDE38HC+KDU96_jJ%06ghn6$;Je(|H}t~Z2y zH%>hu7YeGWym#0>yo)oekdq)dRVi0Sd@04*bruPciF|aW^D*dplE^7l=Kd zsRTT&fz`UF5#Fce>zOu?a3n>I?aX;z&`ySzKkGx!RIS{BGvZGA7rID*)?w;Q04rgj z)aEeIRK2VeN3Wk-Bg6=A5M^)PTJs#C=wo;PF!IXK z$`7Ovv0)496oR+Rb+qn4`vh<%?jQ$`aCJj=6|crH-f1GiRoSt(g7I?JCC0bLx|7HA zJGe!nmT;-e!%S)hk?ykR9@v~KP1shq9krWU6~2HgDdE8*82g=%{-UqbY^ibSE%X{H z%CaL5UVoL`p3xLdTy5KTez*VBU!*y`MUIoGb7i=`HlzSMb+=0zJ}7rP1Y07 zm5k(H638bq=W{`u{M?p3P=HS@HF<@S7K*JgiOdG?XFxk))5KYm5W!maQ~V#GeqO7a zFv1IaL&4Z9kC|otOQM7`HTWI@rcd%N!>)RWm|(*8@!@G}ihf*Ili z8rY6DKb7;F)3KUgEVhX7JdyBtUZ7Rn8S-7WTxPcqPv)zbGsizML?^c)GF|$&uye&en|5sYOc+&P&;@ z0!8Z@;ZzTFaW4qiT{4cF4k&N?v3MtpLmy~-Zq)$aC_(nyuw@a1=+S<9pQvS@Rgj7Y zc0kk7mEig`bj*Om{YDnE)z zBQQVr!w?oLySU2N{%vNoK+Eq$d@j|4B4d7WHnSJ($8>62?4?rbbl-1zOQT@S17>W~ z!Shv`wJ!NP2S@q;2#J8gWZ4ZYPsTl~x-6nDcxT9A#)3P5DxtyGhb@!Jvi>kMXkRVP zQISD{9urQrc**pL8bZy$ShuA|aQW^*F9;Oqk44SUw1=-n`2@DhV(}MdCOeQLymV^R z^4^5o!7~h~H(M-Q*_pyAC&<$@{t1|LaQgiOWfIZeBAipNBOVPkHD%SLWiu;9g9rDV zgI)rJ8iu057@UM+c&tuTRD*Ni4Y$v}V($?*_Xf1*Db2Ato5OMx(|Z%8)3vz+fOWFx z4-wuk>wK5G-eCv6(5ACN^hy z$h0Ofply;U4e%MTL(B=gUWM6nH(SQ)Z1bDTbV}j49~?r?cizWq=x#$>Yje8Wa09FL z)5Y@H7Sp^)X#;E@{T0v#^{91*vUWT2-Nm-9z0}qplmAH)n&GX;l8*0E5~LWIqZBk&SJch7CI+J&L_0AjZ-5k=1N~%&p-v9h zbs8F8a62a3-34rOS~l?dZn*8O40;AKZhWVI5mMfsmm`_5>$8Pb|RJgWa;`V7Fg z-4_&N+)X0BC)^9gv)vZj$PiPx*olp6xlh!lG>_iv+vtr&sMv&Exzlf9DIfw5L$`pq zXLO4hfxXCFVBL0?$?bKe=XrP3Q0)7pXbIJ1pCDGc5rl1+|D?HYy?*=#a(IZWotoAi z-%3)zkXwIL2GcPzCL_%F^t5sq`k{$$ zBf*31`pEJudD*L~apXebFgMqnG!mT2D}`IU{lS{u(_^uzO$&_YxQAWGWaU#xNz8#k-^)%6M z#B^gpBx9eJxvsP*TMd)2ZM0;RToz7QLvx=p;3;Hv_-Uh3j{1NWnJuYss9ghfPYLm+GW#p>-ySqX{u0xq9A)^Qw7t!RWcYVl3Lt4^J`c>mU(RiR z8=?%suzsGT#0U^s{uOOKxxAIFnNVl9w5F%VOL&*AP{nBG+UCr{wUzV*W~2pF=^E0I z3VGvpka0wkB4@yyR#ps9VY^*wpCK8-9x}V{n*Ph& zH$~@M|9On@bJYNs#?E(egG5_q;<_D^9ba8S58M&g$&KLd+{+ZyRD2o4+d8h2w{fX# z=V6Ztu|5Z>oKPF8u}~^cg%dUPdOL*K`yjxYDAU!0@-VdNB9B7Ywp)8uVCH9ny_i0FK#M{ z)k>V-Y92&405r8Zd&3pKu^Q_9ws>pu2v%9af15)gX|9-ww6fkQn-jb3-?-(dKcMBn zw!iWS`qASs6n;iGJ~F;FVLdr1S8_2zpZ9hsL)ZD$=n3nj=KzWR#H>ndZB=m93WAK(k94e}jI&m5 zZ?Z8TkQdDC#AD)@gt6+nY5WeP)AWt3<4`Ua7zf=@W5SYhV@g6qp8@Fedscm~j%n}3 zm)@x(7Sp#;f3j>Gn@a5Q4p3)?C0}eY%h`Q^d_U`g4QmAFwGM!tN~}ndOUp~43%ret1udJS3iPk?&b5`(lgrqJLv%hP{>16wNBC22F2}c0f*pz2+O*Ky#D` z%GSriG{t!deY?NOr5ExqxtfsW$(c*gCG@lxtQ^B5_a$GEA8)t%b7tjY{;2(`q`>krw<0Y)c*r^Ij}?!}K$adA^gQ z@{6L}PAOvezdgf}QtKAkpT;7)oB>S*4kL4E$2GfRUgDdIo_pegI2JyLqm&!t@Edt9 z4@BZ<(pk()aF;I%I%ig$3s}gfWT@YIqX}KE+KVvBT zw{2wEa(l5+<#t@>iraINL_WD0qi?0i^zItn+!d5$R$$TacG9VDFX{JjV@qbpqy}GH zITIwHw=O%TlBFju;sb-n8ej2LKY+UNeh7s<9qymmXu=9 zfJOU;`Fub6edH_@rdif~{M45pFI&9>Ea!d#@Jo~wqEZ*e#ZDOs#R+sNy=p*&U^x7k?Pe<~FIU->4`mUe#FF{MHLN_@qi?{? z;KqZ~TW~!RsihzlSUOYe5{R_WXnkFdZJ3#z#u2G~o87;-mT%g8va_ZI^pls(ejg>hO@RXCr0)3rpH(Wss$fk4g(d5G{>0;wd- zoRW0!{q#c1z4T7HOakNV6UA|_u55*qa>#Jn|32QfElEhr+?X)q%WIg(Q4;(C zvVckLmh|4F375lzA(4tXD{C)&L)aCR*h{W?swb{)wxF%iOPO4~;(_>s z7sV}JX?)Y`@L3KJp0UjEL&v2@7b!B6eSuFE?4rIn4b%{lj7b9nex(xLyt_cZX9YwH z8$h9b<;27B(ah4pDO#6(>z?&^<0XEYdkqh#%URQA?q}B|YJ{S%#f+I6ol{z#9DQP= z$(xwcDAuzt@GOIC&|yMlmjv7K!0)}66J`84?M>ofydwZUZ$Py5(e{5*a5&^4Yml@8 z&C)(gxTr>Rw>~IPmF*GRq&|lIfaH7q>8!*#8eD2m+--kcmkN^Tk;0?+gl1 z=$DvfJW*rDkFnI=d}zxb!FOslXXHzdpSr^iltwJHvQHc!qq6dl@>wItHA38M)I&)S zG?x+p^RG59YMP(F*om&@)sqwnbr$?r0~sN{14?zU8hGLd%skAJ_bQl>)-jmlpgAkx zcb3d>(3BC~!cghZRO{t>TgYb3G2T&oTVefXjdc7$N_2{{eHcAXah!2?&4fU{+akcU zM!|>T0d-A^$+wqOmFN&fl149e9)pj*bVwU+Z<~s0nsa;BtW`SiCo9>d@seUAk!hXX z#!GVvnGrFaJB82ukm_~sKIx3QIQC2sCfjAMr!*z+pj5yGfqqO%9pv`ocd=; zok-=ofcFD5XCcn6e_;dlKiDWh4)^Qr{YF82nqat?@Hmq?h}f87|7ru#Hb+zv>)q}z zb;nvbyero@LJYExC6oRmsw2>-0n;AEynYFpe4<{bj>Z&p4R3On7OBf?6y0(R%6!Dh!9h%rk%tKa2)ZJNppvTs>qu>p5=Pw@B z)098>$(tAyPMM*1W{3d~F%gOcnaT4QCJ>V$_3bh`lLg5P$MR2)wk=uFJajWDFHwI% ze~m@@{p>j4Q9?2ts)|y`iYPG&s4rJ+3kcI+TFNC;!wsyh3OTRC5E+sV8>)0jE4>Q$ zu%3IPQ8ipqJkS3@Iwg+prdJT=Q_Mf4OSi)EG>hZEJbm{OQ4rj>%Yc>B63)ds@}nH3e}%?;;00r*LSyAXvT(^`LOT{_fzk|IL4e-#7=X`XBi5#VOu8;pRR}v#7=T#FU?@V(RRS zGN!6Q@RdX4!c%aQ}&+gx;CU&z~(K0U^IP1V3Umy~ecTQ>}sxn@`my%)9 z+H}U1>9PbfV8ITh{PLNGbDvhQ0cQM#&~6#`+I61sKbi1NRMu>>4v@FcX%%0~B+}ut?%KBF_W9b*wc@WZalSy8KP2cB-~IKZUn5r#;WSKKJkT z9@M8NT44CQ#Mnf)&7sXZZuy;pYqzgzuTLR2`#02O*$Jy&N7(Abkp-Ja93qUB-;5_) z^Tr+Gv2wzK${Rj@_GJdb(cODm8`}h7x|nke3$xusFoZ0aa*nKJFwEkJq2u}sUcry_ zGddI1lT{pqzNj#**n~5W!mG7ao-Wt=sf=h3ImAHsbb)dz(|PI9K`iHHvgE($fhK^8 z?Cn(>pS^odi++TFUdIV**vh;E44@{R`qOF5>u*Yk;Lfla_+be15?kJB7d~Te;s0?T zyV6P@1w-B_4VeCP@X$l_QKfV|ZN8Am-J7mL$VE%sQxXW65W-3yu>cn(6(eSoC(6mt z1E(Mop?Ryc$7D++T^+95<;tUPGPhRoZal!(0JyYH#fab*X97EP@OQNd1EXb6`}7sJ z*{?tco10fu!Q7lnkv9SA1`jGOP8}Bi`qXDvmB(8Io2-=&F&}JgIDvcPE`mu-MrnZ@ ztSpKCF-1Wy<_*tM1D%jMwCQMaK+)D!%D3?2-HU%DQ-Mv?g|Pj|IsAI~JhR42jfQU5 z_&Ww$7DAt|V&Ud<;=-d;Ck*##F!q+V3PBEh$U^mAeq?EGd7bKUh;*^z;7lh>EyXEO zk51)h-84u=ZWC&JYLz+IVE5e>L(DLNLn7m>k8~gwozfB|d==NfaBpr>%Q(OvdR|vq zn|=IVN?Ktni*d^VlYRKUjsY`m;0#A2Sb4`~dkQC=fMn&F=wwyEf(`Eq_r0GEt8;FaiOkqB)h8=Ihw5P$HSf~TXj$;b9%$o{|G_4F8{^3p0UQ7@RdOBY{c?#XdRDMJS zfflc?zQe#E%Kn-M1U)q=3Vc>Xe{!D1)Rp~mTpf0x5!@f!Z_*Cb^{s#UWcCt_e}izu zW^A#YQuFnryh1csEBl{(u4sXsWqoaDO*MAg*O_2puBUpN7CvTkFV1KY&WcB1{@Nc& zNJ)hDkKbPfKk3u(FuKoPF>TJxNMgEd>u+io>JcS+WNzHYZp#lWMA>oEFz{U>g#viAi=Xzsc>G?d@d;W?U?9imH>e`Gz+-}i>nWs!C)I{M^qN+?O zC7%BX+!9M0G#FKB{GcLwFp0zCAtpmRb70~OzwlGPEk-W$rBmv|L{O6?fV0wLn@}&) zWfP89z-F*8@{zzfeTX2cw0ikMX#>ac(pHTp@^^vo`F~cR(Q{~-om#;~Yzf>c4%}xs2G_zn2oa5sLTj|=N8S?ZZeGOE#Fz(o zzLnKQ|I{3(6xzoR20qiaV|4f}W~aLd_K>!F>Ytc9k+4E*OhdFb@S9E3Y*-ua`1ZA* zwPzxWB?8hU3K&A=by}2T*Wt#yu(tlwJT&|^jBg>HE1sJ0Mr=uaib$yw@nGUqjS893 zg$Aoh&{R8VrcVv;Jd}|QvY!9GE!-OnV4JCgS*VSA;7a7bLbCkXjJ53Jha!hUG76#L<9N6}eL zaiFh_ict+Gii#-1%p%OyDl6#z$&UoBtJtsi+SasDoz*>5>jw+OK1fk(i{D^A5Af9T zR^Px&ETgUZ(63-aU*>V}JKQ7<$&4VVzu+PqL!d?WaCFBgx2+0N_V9MWt^Awq@JCa? zz6n+I6mv%SzHT~*A&hP;3R;WyDqJ}C&}4yH6n(5{rS7@J^85q)5!0Vpzlxe2h~U^* zx!ckpywB?;Tc0|0`#*o6?hz&_EgCZCf0*R6k!IyL`qt=1Nd5-58|zi7lGciu>IvD% zKI$sph^czYbYS(KCwWnBH3$6w-+CY!sqpxbfDaiSwpPTyC7d%g%JW&ACi1nkqk#@nGq8T$|ij1UC(eIh> z-I@kF%7F)1&RW5zw5DUugZD+IqfuY+dG)e<+Y8EB8{*syk-R5%?bmCJM7XPF;ymh& z*^+Nvz89k_mjwB>ghUG175?h$t#4ETT(_blNPI@MwX3=guu2nB?2Rd5deHoBO(u+( z){H1^6_Ep_t~7+Rwe}{ZD)#{ul%fn?QbQpTNiBl?g7YmhEhXFrHYT`Q!~<+1T9-K8 zQ-tFs{(og7)BlYt{Mz8J(4`QJ;Q_Ya??Wm1C;^2A8)jv>3VE73HK9fKB@A9tKcl%F zy~H~Dr1h8mR`I25#Iq)*G*kKL9n-U)NQ3t&>M=>;QPTMbEGz`ix`e{L*OD2nX=oJZ zy>SHPb6dYb|BZKKjwkhkM%Q5L5^2Zd`=SpkA(S!OWtdmuI?6>N@|}F;iO;9;6n1xx zqgj|aSw8BWWC8V}76DxC8wZLQguG;di_6_I0OVjPXUBoUJacJat`hG~;g77Y*5G#K z;S~~Stt=FFA}>x#yzIzerE}2AS#zn%hS68G6PN@^QA$yU%{|d5zid3 zn-9$R@BKg22?>i(7d=REHdfdrqPW&SuIm0%_WK*UO);s?!pC0FwF=ys9M5JXNJS2|lKR1u*gkJY7z%L($3hA`5cV-cJkS$juGmGGquW4UvV;-8)z z&;ILqSOz&{d}3Gpb41utC+ckUuBd@0ytz_qZ!m><;toKJJ%qa40*!1lITPd zHCYL1_45?cV%v5pE_TqBKvXysC1$6h&}%DE+z%i!+?P}^6Le6P25nX00cq^y>D3)av`SCffU|!X^B>5Z|3SsBV%o;QVju1W zZF+1yE>%L%cagn+TMd8|(f4PwI3Ja#-@(FZ9q=4Z6y)Qs)<&YkVA{>ei5hmL(zmOb zAxhU=E3RpFVRx!Ac1oxGByYZj#u$*pgORgvPcvCAC#q~_l92AqkG;uN#a(oO21Mv_*tQXzj7Hun_pv6?e zHUIeQL968^+k{H^$a?pwa+$vB&oP;H57ccUsLz(TfHz@KWw5v*+rOq6V` zhnm|}&u&YVLRxNfHBGNL@BZJGx2gxM<;`ds=QyI4(kb<(2xWT=AxJIcAkvxL#1DH1 zZG=o(>{!%maPz3WW$Uu2+|3U5Ex27`-A1$uj=oDY?(a~GhN{O;^_HSUbA8Y=LEF1>8}+FO;MsoYo9s;( z&6>4;?v7^}?+7A?Lop&hpi*YckzE%3HnBW7PsD?J*L>3WWFB0`+!?&LYTBJPW4Y)k zkiZJ(#Ews}8Q6)JUP)&dD{h|QvHtk!O&rcjWED^Bnqkn3S13F^<#s;cxiwwCSiwrE ztU6XiO}<3$TtF4eEsIMRqh9I%JkVEZb%P1LpbtnA8Rsq+?#!t&I3KEz943}%+hqu& zf!h)??C10WFJ0Wbg$Fhirmbp9Jbb3{)Y-fJ&_<2}v2w+~^r(b;mK)m#uh$t!DjMk&#lh95iU>58s=qLJQmEtu@E- z&$}En!1(ko+c(x47sOXHU!u!sEh0-<16dDpO6;|WnT})N(&o!D?C4_s1pLqz>D9p$ zrFjTtezh?$2upxLbK9j{%%*)^vRchxC9;+fAUwR>-(!FKyyF1t1gQmfU!&>qb=ZM@ z-X4wLUAx)uv!2ntmy+lTQAmLG@O@RNowG4dNmc^`Gyi;ST+68J=7JD~R@{Gcsvp@k zd+B<+X(LHr!>V62gdxLMkPzA0E|6A(*AA*#`}4~~R0{h8whzX7hey&@+73C#%vXMF zUQU-@6}RK$o%U6QI4armLKv9_4+?U(^> z>Y<}+aIGC;P-~B}ItAE?g~SXtRA}Y`JocG(1in>-NC<^G-{c+8s--^!T|PT%<`$Uz zoOoX+9voSTofXFy{jFubvjn)+K|NVasBO&PsLoPqrC(=CMZY6IfV5svvt#bq!F(^+ zQgED_4a}*@d^4=A*wducvH>DEPpBnI>ZnmjJ?Tsklh?L=jcj$lk*qkVMFd!6-50OU zFbikZXaNh6fsiIWG`tCRovWslfr1Oav@1|zQ5zVk}6+oj)w7lpx(H0iN|Ce z4E=nfCg9v7q8s)}nyV^$tnn@9pKZ&o7w82l3BvJGtn?8U+&wP6!ZQR>>v%D}WxIod z@mbzx{cuqP0A2wzgOJ}q&Rf-2rd`f@$A2!lO|NKd`@GlTdK;8owqnbhk}m&K8$XI4 zvN|ILclP#&icofU{Q{(G5%^g4Sd@&N$1<>O|92OFLk4bECqofn7m-K|V?UpFd(sur zo_VgU6gJT47;U_YxI#<;?y#K~40RpC&Sfd!m9jZLt`3y%t9{{ zdPZ;BaS8?l?-F6;hmx*zrL5CJpU|WUN%{Ec>pw>-O ziM9K~B3{a_)atBgUW8ML?g~5zkrW0~kUj43d76`t&x<)+YKVu(neBinjASf1ZHK|MPhq>-^>e^ce_yFYnVw zAf{H$I{Nv9>7VL(1V3`@+mbSQPv`d%@$l?-bn9x2;f}}=A`K%)>Ykb&mTSV60w zGm%;hky)0K@q<&-Nikt0euX|Aj}hL*z}YNqW=&LtvB3(v_`CyKNAP{oZsH>AEkNOl zTJavAZ`o#~w9lE;=Qxaw_{{%Yj`U;W>RZ?%|LEUo@6o^W^;rC=FnK-Ns7b5PB+j=8 zo5(mA(?66F(&Mo=!eMpS0{|e5cRrJEo_JNb5fi2C^ty~Qh7y(uGZ8&7>U=65;cjBODZp7YG24HmySJf3 zB_p_u#+R8s?e|8AIBHDEGQ)opI0I47iJ-WcR;ZS7ZbH%I`2xtWrRbW1oxS1Y36pdc z%@x;QE59L-IC3~5o8Hjr%{Wp>w?^Kv589m+XI6>CumUcX^Ptz(li|V3(dRXXpR{HAtd3GL)8>HyU=u65Z3D z<)v5o=-x>xWW8lv(|y?fuOgr#AOa$dpma!gNlQsLC`d`ifB_>!1c?FC zE!`m98{Ld%z~~wZI{R z&FHz5zTwIE6VS^z;P_6!DYg=74<^;=gj*|!!J4bs2OB`Z?oJI_bd^EqS`(|A#zS)% zuKzxY9@u=5EOd<(Ko(86Upo86Az`K7qd0x8Z_Q1CSP~`a`4VuzvenA#-KQ|Juaiw< z#V8TuS~`}?5{(6MoieEkp{qfaScgv3B7Yp@Y&Baz$>q3VS8%nF!S1I0*Q;`&VcpZm z;SHHZ-TXZLXRLz~tR)LP=u!d)46B2n)g}rTarSQd+CJ*&%k(!pI@f4Es0EE#`Isje za_ux&O*$k3NNV$T*o#td!%Zptb?vpv@^BTj&)L{^F;X_=_(3pd{s$E{uFQ z`XBl5zel6*^4I)F&#ZTC?Lv3Rv%Im9v~AA5h7}^DcBe;dxyz@7<1NSOzKy{K1wU<4 z=0<1knjPvLvi-BIo<@VT$ zhg5n~3?-7PmmS;P;n*cESOOX_~pIue?p=>r+}W|7 z^(Ne14;v+R6Xu#fr%)Js5kPbl-3$K}#9$u1p165+G}Bk5Ht3VBggd~2)Yu!$y;>cQ zLDHkCfo#S8YZq`OHas?J3K>(+N2MkwIO2Cf!Zp`Ld3*9#rw0nDPj}Tj&nQ%hB_MeC zRISO)J>rG4m{pS+o4;3d%JIl>4ERG~DwFB0)tBGDcvj4bRO$3s$b(#Fh&)y;*b0#e zmwQ!Bcs%k>D8I|l$ofUy(Fd8`ujYD#xm3BIZ}^I&GE>Yj4JnT~dAQNOJ+q{;=?cH~ zRK)~nHkM_lCD&d2+>SzQeTeU5jpC;k!=`}@_ZEvS3$5@7Tz-hTza%Isd&eW-f_+E1LDX7S z<$X=eH^^8&cIMyaxz~T+Ae_6{X-L3{x7<&*RD1^}tFM)xO1m`TxD0EoU3(yr$H3%C z18wqn{(5FZ|IsN(mSgDbzsW)Wf zNmi=D3)J}Q9X3=%O$jSSVJ`QAs!ug#d^J5uN3oFO#OIgkl`de7E|T5^W2OPSsh*S} z<+;{28GuUului#XAI$!ocsl(x8zhQ+3_3690*Y|G?ecS)2*Rz>RUr}dm?M00tA~u7 z{hGyIiaC;h4S7#cL{JKzN?LcJHen7&7;l*}0K`3ZrvX=;3x#Jj!+@4X4j4Q1h7}n) zf(;Ke`q*{>{^@DqM;K_#{{xgnyrbq&FNcwk`pwTdYvBrYX;bQN-o8OsM_dI5=~u_z zUn3hD865CCD%b63`yqvVcQOZo*|stD#$K$|HQR?onH(m z>vl@sTfSoQH4P=5U>qOJFAonS_;nHK+PJg`8TzKrcnn0L~lvE$)VIuBJy{%-Xg#Jb%lqnlYg83O!( z^Yaf&=VcV1m5KFmbKhEaC2AXackR*q-b~<;3vbaEvzo!3)Fz4Z|LX9hADHq(X&O_0 zkinvi)#Q)@E-F2JfN`VIVyLz(Q)p&cKXLCT>sF132kGhyYo_=WJ?5NYMg0yr z&cE}2JjfiW2WH{Yx&w-~PHVAr%^x9s$%!wMD^m9r3tINy?|K7`YDS>!&yG%F#XJV1 z<&wW=BMIYTh0?>(r01jVXE67HPm->woGwyBv0hdb_SAhwM0>N@sVM17W((h$^*2r? zGf_ZPu{n^4ZmNQmY6?pFn%}u(*cLvW_zLCyw>{(gzgiS*CKg&!ctb$GSa`(lTV*u9 znSF>{DA^i!?;Z*I*fFV|6_1uxU??-}d2lM7@psI&t*DT#y<1CSc_%H?>yW^(X#IIi zc)n)B|-73{=_y}SI}TlgmU^-cwaaucf3G^Z>1%k@78(f;ze7Ii2tLfn9|eb zD3CVua4IH`apQl!i_$UcG4Nv`t;{g7k7-i@qBqv4;ki*UR_wc?*lYUCo&S`|x@5NP z0s5D1ojiqKq52vguB~P9l?s6hD?{I-ptOhJV)woicq{awgaE9NYw583mHQY(1~FAU zw1Ro}!tZxlK&8MpIdcU9UK3?lm3>tu&choXpGB*lW~B02Z=aPNTn61N`w)^UXR`T7 z>s4?ipjM4!{6Eg8MR{=pSnEEF8PgF;j3)-;mNzR&^p=Vg|Dm$%&&?d@d5nmnWGhGC z1C+lOZkJ-pXJYF!8!cUfGE;=m_j-=CJic~F(^>D77nOjwzdTtKk@VK+wl*{wXMB*` zMV2c-K9hNN(bH@qVDM$(uLAJff1gQRAEko}p9a&H+5IH>A;y=}$==`oLNJhtMf9tm zaZUA67>{LUjMWrG;SK(_)A-0PY1#TD%gg(MvidG=8QxjA1n}Q~2A= zUA_S;jV1laVjV~T$OkFqsROoEN}x}Bp#(QGy|%}k1D8Wua5>dDYPtoSrPaC_;wfSH+PQN}VBG*pp|9%N<1;f;i&Jxa%DXdetT;@5wD6Vvtr%je6l; z#NOfB$ja{9pkMT-&+C_Y?*(^;Z=s`5EiZi8Oydc&h-0{`dDAY9`+%#c)~y+;-qYu* zHdQ-b8d!QM;Iz?Bxbc26N3{2ct7S$EA(`7J!y6$hU-yo+l+-uJUq_ax>cOT|OYj^^ zr_z;|<(+BTn?fvA4*x8f&gY7fdP{T3!fD5BJF6)U)O`cg)1C6-`(id+Z^er@s%$C{ z$FGfsUu4dBs#f#=e2cl8@9%g-ge%_!ou>V(SMd~4KHiF(vF5Z@|C!91p96`wuUYKI z4I$}u>s{8O4gL7c4(Cic<2I7j!~NCn{c_XMjA;^cCPa+o)?ujItcMDP7lK6;ckOTZ`Ug8}B#bGi|WSJN&?BBC^+iQC!+8a*F;*h8~Dhs5wpMn(%h# zZuhV>QK&B$yPR<6)Tk(pJu)?%&N9StVgAyxa^W1=icdb=F}L9s7bUW1lJ}|Si0FK( zF#pfpwf}H_wyyjKF{Eb^7mstxzL$eK_i+ zrrb(Jr`}?V%W+hp7A~bmekt$K%7FfRlz$u-ZEplDh3(>mMXDI=rfFc2*XQw%&P+R| zwR|xFbJrP)!hizl^yYq_MaFmJ7IgIV@Y{tk7%~{5wBD%N4h36jy4`jM_9|n#tqI7S8+2jdw?b znUM{ob~xa?D(EN$5tB*MMeFaUdYC1WJC5Arw}t&|s`^>q?pOG^UYKDnuk}|kT+{*< z6gC!>Uqu*rnd}?SQ$8DK`x8zJ-$Rtro<7SGoGFFwHhs|RL)CX58pTnW-;qpN8D|zP zhl&&xbB6nc)+AyLblgYJPhCjalcQN-OrctfE-#q;A$k3F_a@*EA(gb#oCY%L={Iz( zt(#@c1`ica5$?nkZ{6usGp^Jqgj_$Iv^bLeT`c(jE>?T@WPLZI>JOaKnN)tvhpgyyt$`GuA+ajNRUBT6uL-%!*w zyDDKU0B(zLBGogort^N%ql z1zq$bYx%Z+)(&3FJd;MpS2w0rT80GH4GPj+%yx$Hmrk>j4YE!8Y<*O^(D{mYo1fet*I)GSV_Kb6%pBzP(z0dOdqNGKO*A z*+hBn%46X=vSys53&!~$^+U%}og5+_Mex*S5R!`|G455l#Z9_ojry^IsN)@4#`yJp zGYh$+l@^oQy7l!gJMg2HoJ7)kZg1oJKK0n7a2kd(!E=fF1lNIjot6=g5M#nj2Qm@@kY1mJm@h62O|%qxL=V z_^ybJoe83*C(cyKny`W;Yc!_JvfOham2@Qr1VWY=IV z)m;7piHrtkxaU(Z%|n*Lxf#pV4_#>{jQ-h94rwkGO#RlDIq6k9ZGO-B{!slwj}d9q zsUh`feS>wyp)J%wJf90z_*|i7r!#{kv^%v{9QBxwzmJ97_ch%%#boqMZcGfDJ<68T zaR?W`!o-fX=UhS_YZy+;sjG8|pCvMFVW?aM7?*ry`b{b?XZ~2unq-mR5zF1;7~{00 z?YOK6a+mjQ|7C4?``6ec{NO%;psi<#4EZr0CL78^gJ9I9Xga8OoS^D>H6Scz1dn#L zV8G4T{}DU6tZ--<)(pjBgRZMH@wb)ZJv$*_Q{2V@bXrn|3w%)0{WA;7{f zWS6K6$Be?-KEt%iQjfD}qI2DlV_JapWRV*9D=rbI?r>rQ-z;z&>O2}hP*cW}I{77E zd;wwgR?(c>(rH@uA6cpfRpRuDP|UUA)mNAPc)>Lt(9&DG2KL0y3O}Px5tVv!BO!J! zc9Xmk|0udcnAbKvJobJ~{DrWiHd)bN&QR(M+NCA9X7$;lGZ`0sy{tESXJ4016(Fgwe%Ww%MY{k~8E7wVAhL@qJ*G`hfZBfqCrKhwK5x1WX#GA%V7`G_cCD?u& zq&_bU*kUa)K3|aZ`YCZTGXUQIoz;cRDhYboE7$~zdO{)JVtpEGA@IIzvG!*W?5;_8 z)L_hn&jUnq(oS83#fGlea$1M3u?)7Ve^_{DNa+^Sl~|2fSU?)KT+WRc;;JQ!T|I&tHu9753zSbJ4p zW_S0|1kl>k`0k-%Ki6Qu7pA;<%-#_1g@$K>q1^JL#qK2JVndrlUcZw&#da zeHqV@g?|<}I^@NqsPU)oBIe|-8Q%zxXhI5Lc#=ti+izZX1=_M&3^md)Fy~nQNt}c? zZI`=@=k?0_8QeCg(HyUoXb6m!`Taw`=$C(}`2P}?&Duq8_C3$XqWoQnRfW$KQ+PHC znZ@^3BL$~NF&yDp7|+d5r}1pLa26H}w$|@Jxd0TucnCJxE}c#+_7!r+PbkH1QOS5q zhbh0`ny|+>ZH}G22D^8&$Xl+m&Q0fW7fFsD_5o6bxUf}qM04IPzq{lb2O7LS3q@Q{ zBbqO?e3}Ajn_P`wXBIoWqA|uHy(?xJmm3cUalJhj_hJHDkqWQyqY9cie?S^^H|5nX zlAp%Y9CQ7L09F@BqW`naI96#Xks3X5E*!<8t-Gq5ya7qn zjG*H!qqwlgvjq{4kCqPj32zdz#Bg_n%dserMh&zLYT_G)*;NKGCxhz6Ow>w=$ikx) zW0&m^`3X~8Z~>;6m`c}pg^erNEZ$eulQ%9Ye-1;D0I@Z(%tY%(&RI^^AGCFQTBGkxGsa-0sbWp5 z_$a_DymFLaJUJCsq1%tE(SqojUY4Wm{gie8wlm{pcAI_?3;&rkw`i!4SoPM?p75&n zM&md5_Ma){rQAVD@4t>@o?61_)sOiTBSIn5Wkw21kWE*Lw;uzR({;-X7oL1fdCwpp z@rzu~SoY@XG(4lo*!Og|Vr-C$>;2Q{ojp)6Oae(Svi0j`JC|6pL^JPLtO*BCZB&4a zhcbc@d;R0W)`>=zq$9n*)sWOx*G9<4+Qy2QavsunKKb7VIxnoXH$P)@>={tt>-c}i zX~7G|$08bd8v1)do!LiB!g1a~=>(^ue2t_xC0ye7u#Yi_hh{bWaT=Lr#DPu^E0PT* z#n5v)9|Gdq^rzx&YWFDT?u1On>u{oJt7*$k0!Rk&2AWY4Hm*1SDtXjdCTnT68{1BY zr>=tS0>$c!XPz6h`~s(L3=I{RsEw%pj^7A`*O9tiL6*ne27Nia^m~^X_6ja*Sl!c- z51TxJD7+qFtlSSHMX?UixPDIsR=t1PKYlv-Y>)JGig_$U$RglL!J9GmBf-50x9(e} z$bM3iA8r@;^;~%--gk?Kq!yW2lSgwpN_DscnRd4>^Vq@_i;4tiGN)HVZ@MwZJD2>_ zdu6R1325#)g?+z!dT3D^)-ftT3pwFkH6Tfl`;(DNJ7Yr$AS)lqCv3A^00I?M)-HO; z>i_5&tj=}cE6Pobb{?AT5&~z`@KG^TaWR$|PxJ2DQMy^^`jlrfsOh9DV?^zy^GM7Q ze-P>X;vW%&_ZM2a+K7KW5YCKT|yD*^}ysrmazK17&6SLj^K_m9XFl4*OhnLZN=IVhq#GzvJ{Q4LnV031nB~O1?i$O9{T>#h ziY*=Ty{3inB#*_06%;S89-q1LvZ`!-nK9IdtO0 zbGCA5XVeRTY`~4m6l>GxUNaejWp52T-~Tr~>&UZ{EOdU=BZLGL;pNcK)muF8VmQuU z!?13rFHOmebb!mgzNPlq>Ml#gH1p@H1(&{fDeuS!?A)fu1U~Go)%e;ZIH`jgMY1^C8bqtTs#`VDSc)!Cm+DK^vwb7yjRai&@{y4@?`4egv zVo#YonW)o4N^7w9;*hG6#Lk9jugMV$J){j)%pr2AHh`9RPk4<1zn}CUeZ8_V42XL# z0lz8e&`S!P-|_P@GE6C2 zx?f=8mAynRma#-2R(a#5h8coOzHECgClOGu;NCifV`M!woNcp3?U>VF?0t1@s2u5L zOItwNYil0PE?c~(oU&g_{~e2rox6o8-PfnD=of~OTWUsy^Mr3&x`^ln|6Z4ZVC)_E!m4{U=BD=gEr#f>uCZNw^XXaJkBN9^Ll!3~r`<*!hcPfnTo{Ki=0y;mvFf>*=l;2A02p-hVPmmcy&2C(I{*32!|NgU{aDoLAq~>&ByuxP zr)Sh^!KG$JWUZ;J;mjmIOYN&&;h;cxA?2m(n03|Dxj&b$h9hF4+^~+QnYiB3T3*Ha zKCuyG+|y}F5l!NZ#X{br1P$}iMM<31a;h;?8rQLk^{;_#t!D|$BF$fS#C-c_6kc(5 zU-SRR_`~dvYYc4gm(M{f^aj9kI}iuM>|XyZPp7avysdKhRZkmQ0h~h^LF1$usb^_=qe|F%hRO)dPrtKR+VWnc9JJl+m>p{+ z7%9J^qwiViZkf2HngV;t{wTv+kD7=*`jlK%u5(c-N4eVJwp$uSc;-DsCtzWS+Izu~ z0wADbz*Ku34GXoXQClv~iZbA1p*M;-PKxgW(FB+LWil%G1QG}$XGzkqoVJvs|A5i2oG#q)VZKs*jQg%s_08|WE? zA~^J$-M=QZVNAhtb#ri#Lwj? zLYh#rhg{YmOQE$kXQ`W0)2WYl4=aMcpz-Jl=Z zRn)d5hiO!xOwtUDPJl`)4DI3N3Vq(%JW1{Er{q^pKj{tK6}N&gHJM(X@EH2`hSwAW zUi2>aciQ%H9n3UvoeuN!udPU7c6;>}g+9_kCWPIH#dr-H{C`*n`Pg4ZFv6=uK; zzt*i_=4~UNTMPGek3dk{r8A8po8DQqTs>6rx>tdUMsdlltWkEzQnY6C_p36DPY(;7 z6)LRmcu-8}Xv_qn;$^z9k|eE%&a^7EWJ}{WL;Nu2?^)pmzzs2#;=kB9>p&(y^aS<~Xy zQm7{}xEFe921`WJAe_4uXs(VD~k9uFIWlf3q@9ezYnoZG3Fs= z@V~_aAw`!82>t$ixa#V?llw!Reu<}komWt#rJl3kkxr6VV*D8-%~}KoI6+mBnHZ-6 z)46GWHPl-~HKRpYQx~-@f+@hgAgV(m;B%o3EFdvm?_sDKN~l{Vj$gcY%*rrv*tJl< zn3*$F>!%iNe8X$G5k^9ZsbH^7ohxj!3*CYWu#t4hfB{Xcl52>XcKYyBvtC0A0cJs$ zW8wt4piVs|qBEBll6^)Qc3sbwaLyfuW_Bd0Xti#a7*7^Q_7Jx%4m3{z1FMV~+j<1Q zM`RI@ftp_x1pj#scSdJ%#I|M#S*O<0dM{(AedC+7kNdpaEG%&9s3VL`3Gy|{wAP7J zQ35;90lJr^CC7+yLU(i^V#C6c%`41OQ^FXZsH z{XJJRut)5y41BXjPTAPM!tFK*^pD)BwA6D8?#Mj6fwJe~5`K2#jTzBLqzERf13{l# zDJ@}^0on_`6cN_3;E$3?=V$*Dr|^P(E9)`w-`=MxruTUQp&RB>hF*+6ND({$dT+$b z?UB`AZrB?^wX+7(9eiGuflkOtJ}KmmnWK}3FU7+J((Z&S9N1Cw>JJbiNBSh{%>F=M zKRGb@*g?MaNl>3y$xPKdvyHR0v!&k$#)vABmmkfLQ%jYcnO>9a44h6W_FWrXB9Ys8 zW|6EEr`~l)%sv?{!|aoJ>&-C?bMg(f`{=Fv_>W9dUj-zF!IgkP{_3)gT=IDg)oC;h z1ltCXz&Rksrx@0D9D(TWv-r)@^)YUJ=l1?)FN0S!tkNN_y1(&{+VPlZk5W68uCs!c zCJpdEdz#QYRGCl4H~xC4&8}mkxraw8S3&mz&j|-DtTgQ&`jZ-Mz1IX)bKyFsm$!Yw zc?63n6tM^C@olKJyY!7zQIYm#zV~ZvrQeS!sFA2s3V%zGIdkApfid=d3Q8rXqT_eD zC_J-HbudrSC989)5)K#obJgiO95P@SC$Hy~5cyxe(S8Y!>))+%vZBxRMO;ce2pM;G zyZ-PBWAL9_x0-!!AeIFl1g@pU8vrJ^feCB6n>wXaX&HJnza2~v_#2v|eO0Tg4vGd= zvOc~7_%oeSSUNW^5f0=02x4_EpLk3jRV9zT#a+sle*7oC;19x57_r;?qGD9cAGGgf ze0PogyCppgO^h5~({hVl!w?K}Sj!9HjJ9}=q5k*;zWF{M{rVM#&;0mVH{yRoAWWJM z$!Pvh;}cd<$fVQwSqX)dGUD;XZoe^7^$*5sGX$y6&(bKuZ z!f(scvorgf3WoulV6wEk%SL^y)uWyKS6skTpM|>cAu~Y(+2r3Wgf5`34u@`9pq_jV za)p9DzFab(Jo;@vjjWO0necLwMT*G?tw;lAJH@kgjWE#w{wNNFw{WZZKAm-@X+rhA zGYgQXu$*1BR>{PZeEiq>{No>+S3RcOv*$|#PGi>`!$e0fk?C(Pz_9;A<-`!8j`uUM z!+Cm)*B?hWU2d9jRX`57u1%{=Jr6VBS@TqsUET8UOi`DA27lknk+0m1g#}n^-?! zy|K5ls9Bh81gq5ZE$8YVRYD^7sg2$L5TDG{Xz5@qjKgD7EWS>bX0lsoFLC0$?gd@< zX%@JIGO2R|<@(DcdVMAyD3{Kpj2kyNaBkC_e1o-BCP{>?+JX+nzy$+D_N9PhcKFJnp*;T(mS)-P_wN-*$pC=k^9Y(a)4A70a}z zqK<)_^IG3V3oN+bQ-xj_Q}7o%Y4Mev{Y{`knt5T&WRoi=<(~Ufw^x%EQR33|ONW%J z6`OOc7#O*p?AgAqQ5#=78 zQC0M`L|yNtyF+`+#)OUADZ8rGXaOA}=$)Qr)9S8$S9U05q8k_aQ0uN-%xrrgoO4(X zsW7W(mkJjr66(G*##ZNE#55Od#fdVK>Ha34*;If6NO-;5gA?mWPX9M%0sEjVvjLUo z21{3K-V^wd@3&FIY;sD^CKV>>Iq2lnMV$y*`?6Xu4-q+ap9APg`kNDyaW1`}(tcT$ zh8bkBT*-8Y4rofqngC%97s;%TfOCn1^x~{fNY8>xTlxWrnVM)+X-6x25U`gR=hel_ z-7EwX<9jj32Sw%qRb@Q4$%G#BjMUeg!nAcu5(;7semX#DVXciQ6xR;3g(?x~Hg#=! zEPM?uG=)FU@_^Q8W?8=T8@8;%{M^z72Rkvu#dv2m;$>*9!U^rfYK}_k$8|gM{}iC# zW)}anRhs1PhwqtQWsSL`W&$1Bs_h@go7xtG*(?}vZ&fgMRAJ>_@f|Vq{e!+=K#M^S znWl%21-90jo-shk&a`OAJ5HB$-eOHqfL)2CD1Y1D46Rw+a2#*RM3vclel0r;6(8`6 z^rxQG{3S>=@nZko8W@Fs(5QI45+Oi4k@JNue+-PWAiuAv8jGxf6pmxOqg&_G>W&S` z`xxx0$VMFTRvXq?Bv;y{%tl?w>mFT}^ms za3*ZINGwn*w6-u4^-r1ft(xT@Bf>4F4_$fKF5mY*yjG%?iE}GcbwaT}kJ5d|ky{G~ zi(KzBMb8bI&LQaS(eu_2Ml}_W&|GXq4&xc+rN%R~`zqA$yT`l>y3i5Qa2FVYa0-c_ z;Qz{ba}~^I^C8m>!VsH7sS|`vX2;lFAFRW~9I~zOCh$Oql+(S+O?ahk)6NK&Qz-w* zR}RAlnl9FoCBUJy92e)@0wXPCD!5^jUx52j7J-u?y?_A^(-kQ+uG5z4HGku&o4Idj zTizM|wU75kNzJAcNOHEjKXngsINZ`WD3~upRHmFC7}auLONglurZZnYax5^eyDr8w zmk!(!`CbUR5VBI~30TG*X^>{cjHS8|?vC%0t^K;h=&qtB!q$Tjc5^G?ftn%rSp4kJ zPSu(D*m&MH9p4UX(2lVPCqU}JWHlzFZevhy6q8CHi9;|yS(9SCp}mJYC2v0rEZ+O! zD^wuHYefe-+8Wza5IFI1*B4OjCA~Nzci@X}<8_bQ*%F({_bpQnGB%yBZfWqcc)l%z z^q>)ad}=;LhWjvc#hBya2ly9Fv*U)wixL{nkBAdg4OS%6~BU(zk{l5r8ht^5)zxgK` ztAo=Tf#1Rx7xlJw-8i#d)_O)Uu>#6{8;rAI1=NyPPQjKn;`FxH5Wak zPJWXgo-;qfc%y?Y0dP?o81X7gs&=XPVu{L_P$8T?kXt5!$B>A1J2d#%r(}IOFMdHKKa@EsNdjZR3>VCy`LB}2|4i1`P5;S=6%?W zy(h@cDdfL4a&(B(z2JE#{ZS!G{7lb#Et0?H0CEuVH4SWYW4!2rh|KAw^{>Lf%CADT zn4f(E$qp??CvG%`UTWyhQ~&(TyJLg~KO~a8tEb01oYI=f zWCA)|zu!a_m zhcXehjD>}tLc%xE3#z^ytoyZ3CR!4j@KR849unuVedCjgO(yaRoWtW+Y*_2$tj3cb zzB>04X+hj7QNEbV*Cje+q{MugPP2>Kdb^Uy1QWe`KId~Fz7;V69o0$l7jQDQTMG2Q zF<bL_nh9}bJvzyyuMjB_f*yDO+9 z$4{k~6dLb_x}kUII|ZNGosA%Wqibu)=E%R?MjHFnhLo=}FHaPjC4wm}oxQTY z{Jtpt{0Nu4^Rz~;{^eMmqJ1tcMk82&QI9ZQOZIzfW-$RDOk=RZ9rVofO700 zhOwbcsW?$ZHug9wY>W}rvFR&W6_2>7PP{kcR2_MPR$oUB;iG%)65S>bP z(?iACjAIcg&4za2V1Fy83annO>5(=TZ3Jvwn_6!<$1W#}eI{*bf@<)q?gH;P>%h)V zStuc#$XYb(>2^y}kR3Ft35+2h?a>9?l;yb1+O}R8ej8a?Np96x;}nP*gPjRWJ%Mwl zrpmasnweH=5V{bBhIoYl*`DvcH^F2{Ry14H?tW#nGL{LUa(xE6{ig0g3fV;P8gVIH z|I@?wT9QKQFL{fmnyubeV1i=&%Dy7g(Ux-<%C>uPU%DOu`pQ%0!~Lu0_OOsq*;k2u zA?1Fyu~H{YuC0t$!aGBjN9M$drA_Onu`VS$fmK`Y;O{%+62#*VBWiS2r=!iNtZhBm z=YBL~eg|+v&r^jTOFX|QtXe1rkG5IibG4n1~o#ySz;(UJ1mHnBu8?Cx^B0mYdU}nF)Eyv6a zk9!O$L_LfsMBEiofH7U^?xRZ8&+$WhM>&F=@OE2FOm_Ks{vrR} z!=>VB_1)7&9!YP$!xqR0tvXH0K?xNVRfptWZ_Vc$V>83wB&=vgw+as7@OxG68u^{e zlF(D53as2`l(7jw^HTY$K-W`i3N00CuyW;V zr&*)+LPEFW@TkrOHe2HI&1qNCIwY$F&%>Jy@S?w9s?LDv+GOMY2Ofq z)rB%TU=4|%_WDCP7ECLNrp6C>9+`H|(mwn>f}%41x;6_4&`u*LtCH5M(f-wHqXq3f zjREWb`&7H)FRAac}4II18a zW@)j4KjQCvgYS-sDXNEZcAF)Kf#YudYe-2>!_BV45+GL;WrmGmi|vdg8foWTZJeHLaD!}VF1BSklvCf0|FO~@bLdn>uHsTOy490GB=q&x|n z#cLWDzF#>?U7-<-ZPLf{-PbxMz}Ko@LDxjp7dBIohNw5?xhu(VTyWBtv=n=}k)OY63jC7 zCR|N;Ayf229#*=BaX$MH;A(+{Y+lSIxPWzE4mr^-_(Q<`d$Y4PV=aoFI=#6#Ras!` z_ZXpp`|r*~!t@H|z2iQ{$?8{LZ0dB1MAw#wT#~(zy6wJ1#E<54(9lc|jv!?h;vga% z1;9q=%ofH9CQoeM>+0jDb>W%(#`mu-?)kUCpky`4H7dh;M`{o{Wl+?wDHUpt&YQ>| z4<%x-=^-V11NDvhaIQCUOVN6Lls)GwJrHuZ8-`P(k&?ewb+H2xS}CSYda;X_F2%r^ z3g|$~0smF`;eixCx=6$R77w?hI z5tSY{Nl>sR#Cm02Jh9bby(L%`i8Vd!u;{k`w&1?z%V;YtzhuH5Mymb46AGH1|3Z@L zO;eIwwb9*=8&B{B7ganLf$Hc)P6DA{2XYd^QNDM7qaKDO(ZU=?kgwCNwcs8wrA&zb zAOA7QI*6tvD*Bwreu`~}yzr&so1-=0a5bClm?zrTDOHu~oss`1dWo}JifwE25LyTa z{@rq2-bCweJ=a9l9Z)zs$i>pPnt&aN$7R5W@G>QX#6olRk(>%BG_xF60wzd$o~;)jm`bY`pt16ap89_0f*y0dD@({It;$_Xj3zx zWji9Tv}J~{j(X6=0p)8FhGlpmWZwLP55q)TEgn?Cd;(y0716g7=S6yS3Zj$ycsL8~ zv+>q55Z^IL)u#2Mdso>FIZn<hELAFZJcWQ0>sA zGl{hmF8BVP?g{JB7U0O3j-Kf|1_khzcSgbo$M&9^$lTK=`zmm_ zM_`!WfgxenJr+{$Z}x>5mRF5bC}Sl_B>2oOVc{E4XIIVmJoS&8T`_3#oA1kyns5l& z+R9hSieH4{)5wOqyIJ&euh!z7#q_|3?{E|!r36a%Nl6uIN@*8jReT|3v9U9%tk|i3 zWLR?=s9hYOi$gkwyBbiFjeT`xKlWt$K8Z%S;%i-IoCo*&ov}Wj!m9-ThXo+@E=kGD z6O;%zf}-~?cR?_TrTUSB#I)n|>X6!7pS@bRq0fSwy@y5L%$nGjalf-XQh@q%Dp;dj zwEw$a@jJKg4URp1sTRh!7ww5;aZ5>$HP;14=S;yTk4Wx5@%13;9Egz_=d)viA^z0B zu9eo29gdrBuI_F!Ggi~%6ulqyx3jd7EWMdC5_>i0;cw6!6vy-KL6C3KnSaomP=BL& z(0S`BphEop^>!wtU%UHqmRMbETifI2D(1B=r{=Fv|4_|e8`<=oBCchwIyYu0f-c-L!aS)YaSp;I zJbyH7B$)lEhKZ@}ww`BLWTZOgszlG#MK1Q+OHP-Y2o{gFtU%Q~g?plCZ^YZEHhJD~ zn_RS#_AknQ6>!w@3meqW<;=mG+qvon&;>gL^na0+DXx@~NYYED?dl1BaZ+h~t(JM3 zXv>bgmA-fPtj)?F5NVetT11@dOf#&{Z*biYm7EA-6Kp01fJ^PnBe=>Z2w@;y-{WC~ z_i%ow_^;ibt4^*Sro0hiETo@p&IMtP!q4ZqeYDY0iO_3jm##rGdfoY2%``U{_Npb9 zs7l(OME_T0#ALpk$E(vuPPClIMBJWgGe+OGco%4e*X%1@)?`+9t0k%CVJ}yy92@op zh4?mf9tsXO^PL-*aaY$RJxa7Yif}PPc+X(_X?_Z^E5I+%=EJOA@7v(WmG=w->0w~w zE%%LHZM8h;=Vv) zRq__&-@zKfuN?Yt-&D_E{$V^UN!1I}IUXh%O`2Hw$sUjAFUtk4A85?uDI!SYJ@F`K zs2dvC;R@6G6hui-U|er>nz+mo#9!`@&54rMOSW+Xsl3S)u1yUO8lQStbk}l-TpcvK zP4T)x`J1(DL`6~a)|fT!K5^;97d%_!lygcx64qo??Dq>KG~u)?P^G+K`5w7sz$S3h zJ7+qTT#?CSb<-}EXqBBHd=NOiN=zuh?g;tpGY)S#=OEI0p%y+qLU{oCaP2c65wcf>R7uffG>{4_ zxI9`N0CFp&dQPQ_*JN7OZn#2Grj0J&_1!nlMpW2aw%&41Uy%c>pHR^>qlhZcIyS^5 zxTU{jvP^U<9TSEQ+!e9;>DHbX$#IpH%Wy@XvEUVi0}l{fHATzb&Aa2YZX=`ttC9`$ z{F;uP-y>J!v_>$0-CTds7otwBbvZc!Bcc4h)NyhdF8Rh|&bEMi#Kh@(P12Op?>aW~ z$K5G(QQ-Ql_{wdt)@-(76ja$!D-L&kX{Jbi;GEZ6OVuN2Y(Zb{(90S!qnXMm(%h!b zZmhjWcUNRFPoiKd=8zv2VR-KPgJIB>=^%>#2Y^G0nw4R*6sql$*?=43FEydVuI-z; z{L_;Dl!|vuGlTTulLx2I`nvr;a=!mc!oW>uO%{SpMs1qr7O0TOot^^8zmmghU-!yg zhFj;*96nOg)*|;|@fxU9rLvgqs~n*g!G#kbh}n zs)J@AVkP|Fi|OW1h~x`1bb&unFAW;UWtSG>7xCKIWdq(_3%^xNb6*whRyJKdpG}N= ztZ4^dl>Dwcg?|<9CDs#3d1J-w`yI|}6|u-X|7VZiyt7_GpvBXtlg7MWP@gBLvQiO_ zT)Z=qzH_by4P=v>k3bhC-xE4Dp#2VGIn+<)u11eNiB6oJfw%8YQ^SUbx#pUibWOyX z%&YZpX7?Ubqd^kGo7CX$7%fkdCDUhT0yw0EN8+otYyPD~QDt6UlR2nc_Y5J>Ro}Hs zd`PC)(KoNofjzg0@5uA%isws+-^K6)J5PjGi$~p((T+Tkv7h_%z;L|fS@kLlO}v?xB9+8QTt! zIB2mx_joU6d%Y2JWvkAj zvysJNaEJa#E4n%4+yk4o@yuCXu}me?MUj?dRa@rpIprTIsE6X6bYD{sq$?3`JNDlT^d5!3 z@1-Kl-&5^7wP#thWOeZ(G$|_>Y$>~DzIypAFiL{`0VP&aG0yxg+Sb8F)mF=%sX0!Y z7Wc&;zT~k$v}fdgr8fna?F}oF`q@GKJRjF~qPkiX?>` zMweyCq7+jz%ipp?8ayJ22GYX$SFOV8)Wq}3wQm8U1u6oq0EGE?&uNn&laXrNM?|Da zL?lO>PRkBoC4v2YjW{{ovsAB}pBudt_Qb_mmpUr>9 zO+%e=KW~AL;ulOhBywx1dn0YNs`p%!eM>!Hc6m?MHZ97qH+p{`>U=_dCw~Ef`@Cgu z-+*tF6d2P!+yU)N$*(8qQ^RxaZA;y*FziV5V%~0OQI7iTx{%gcTL#QNZPidbm}#kDTG3{a8$u&N6aU7yLn!7qqw+@0Aw0gZOo+ z&ch)RmE{(e_YtGbzhl3uz*eNml1@q&`Db<;wlW@=@CR=V3k-{@>b$HwuBoRjRN{Mm z;`x#fayb-kbEu8@PF>B;>%hh&UQSB?{-eQ#;L=7^>3=HEB}TbWY@i_3LxYJBJ2>(-tk z6T(8Ry*#ep@9c8#!|mUO|IkiHhBglBoG=N=3?KZS9}*@m69$q|9&6iyf74>fs7m`pM5}$c;qA| zT+Cw3c9Y7y4IhoT^{M&Ta<|Pv=3_Bs?zjkSh|GDsBL=&jJadFGGsb%GOeH_wRc_p_ zwzqvd&c_^udwba5P`&H662Fx_nbKCj0L&t{;RP7Knd0zu|Jisp()_&j<{~y!{N_Gn zUENo}9r@)?VLti=@m0K*x)U|6)zr35J6ld)s;eQ%_Vp{+TE(Bu!S-LG1G!PLzbZzZ z>u)W}v*zrz&n=K^pY(T%*yro2(M#>x`_f-}&XW|9qEH>!SWrP$Wm)6U7c(WPv=2nc zPn?KQA(4Ok5}jZphSn&Kv?)SZW;W;Sh4SA~iXFE^Oof%i_iF3&iwqdWNdwsn6@%rP zSWPK!TH?HZbL#^>u@ulrnKm`J$kj0V0}|ccR0fQffVNw4R*E2k#Hz!5-(O?JT#x-$ z9jXR21yMJN?)%$!yI zZ%^TL@PcdM-4vMWhdiLEr(y+sAyzT0S|B|#Fu^+@>9d!B?HrSu&;=Iw7~mNh>$Nq- z<(K2J@ZG!%5xsGibDK2xWkAg$TTg)KFjgrpU5Mw`qZU3lqO-@B)X3tXl2q8#_UFU5 zv5?kHM)kMj%yUqhxyH6T{x6vI+x8k0b2WOuh~zmIxs?=*fV3*LJo93WE86nH84g7F z+w!Y#{4h?tKk02tszL4sEn{^k%qyJxtm4M4OvNegyPJ1ZkX>`@_=fj;A?v~^vx7u_ zQ>6}u7RwOWH^*kaEf-usXJ8`PliB$|p*wWxrL(@{L-g2zZ}a8uwf8xfWKM@co0lxd z_b*77aN{=$?w)sq=joPW0eS0s$RiOF%_?zuUM+Vsj*2M%v8tG@T%~!qSZ$+EVQbbE zy+5DS&HB#s4IfJNtyb{##kCiB%2zjy!*;YObDOfuq{_w2D+=O=Su~Tp6gx_GxJJ1@ zc@hABlWay6+4wD-nkpyn3ZJ@{p_a`^%uu9MWvF|b5{l17?}MRdym_lQ^9k^!5*28+ zk%g-kH{jTkCO2C%)WED3?NM{s`d05jsVP9h3Dx#8TF{PJf0BbS>Wh892&N@>&d zD{Zb1xEaFyXi0WS;tABe2<0_fbrE-|*ru86(-Sw^-N^lvLt{yL&NCvaS8m+;-fJhI zM&5hG+}b)z@ctM8xFgw!?EXUS)t>kKqCgWvt0si7@S`Ad%u(o zZ#LEj1Y(~tWYZN*Uh{Ytq9iu_nmJy5X|o&(tqYz0Y&{G>P9Yo^1n=p53Qb(72bMTI-aGW3)U6NSnO8Oc z^u>;X^d#Hwh){6ZV7T9yeRVv5j^T)R{xL{m_%MU$hUR7y_dH2r>TB&)s z`p+SnJUwO8Ut4`=e~3P#y__@cB)EVI)9L2S<*+CO-p^#+a#=<|^D~3{dL;Lq0;WK- z_X>tSJ+sKqbXukEZWdRx!jYT&npEx%tdx72)!mxi_LXse9oa86Dtf_T$6>*kBw=(R z;@~_H1ij?TT{|PO+^zvv>RJ5y?f8lh5K`b$QG%0gOR+~q=hPI*0zkF@DT4v;y1l9I zTdV6!ir(HHVn@Z=u4n?agEhc_J16pw=O{eR8w{a0LBhd%1wxUopIF7BTM9;h^Ye&7 zeZ;Rjo(J5;*5kNqFF@_F^1k0TBYDe4QP=)xY;ahqh50@^C7rRhRymxO6h|s+LmaD< zsrhWLdUb8(V{3x7y_0e|-SsyLtK^Zdfic3~WmVgkT9v9x>J__FaNLD=QIf189DIi= z_I+f#*wdqC?4f_m%DP-hlFh{Ny_G7IUBRO0&7#LTUObgvaz3|?2j54X*k541eAaZ0 z8};o!fNSgg!H~G9KDX19%>FB{qH(rc9rN~T>IDE@y8oB^OKF|8&le9#Dt7$<=wCW-v_;u`(u;U=5OP{yWv@`O>LXily&E23GmZ-?Dn z>b@q{|G~Q)w4B;5$!u~LDoOT50AO*AX(!f{oB%?d{;K3##w6phP$(^%aV!?q@335P z*6lSVM8{s=`fAl@Y^V6bt!F2Gw7>Ub#`w>arFDWi7g=^lY&lx&lyg!Qi1fF}An>#fCYAR#uh!aHQ25N^-w(cCOn-{q|oi#W-m zS!TO1!uE(xn6t2zuXAG+Ftm<65A$aLHKCBMLLT#&cPBYE0Yn>;qI3*QJm=-5bs-4y zTBKSS$iMN~2U*#j*PYq>qU-sXd(3wN~~ z4i{qsU#+DGqkp1>a5#uMQz~aenQbMz^M-A9;;l946~NRPw;yiI!@Pd>HmqJ>^b{v? zyf~-$ccE?m&xIbdjs*@)2a^i$={((q95zou{jyjT4ktov$awLzgw+LiSfo++d)_`z z<;r~<+%x&voUouGW2uUC+7V6Mt9O>DaoF$GI}9cZ9YFyrOlT)Rdijtrv4n~gh|SI` z?SY%}M!8h^(@Z=R(wKj=?V}?6fzN8J^eX{+5y@zn>fW`5XPFIFuIcZ$!J;L97}D|1 z(!7e+B!ky!mIsk-+arKz5Ucg%%i)+Xm+ylicyp`Y%X7&6{~3r3(e5de*cp$e1+PHH z%NM_AQul=_=+kPGq>6J_%|BMEobNnf-9N*bOSeYO@Fs>dM4{E5{g^$aCxQy@&=bgi zJd4d(Hb#b<3^Y%q(=))kWu6W6fARo+;;2%;HU;;27g7N!ZrGQC2sWVX;z7x^;MmB* z&Y7;xQi|%{UHgi**NqFu z2}eHn8h3r}!eSWmAf=>&48Do8EO(5W=YSs>Zlj0M>qGlX?mG!jNt~k7y3gG$Gh$}U zYfS|40YRV35+jZ@JajJ7dVNy(cH*xYZ*F!suZXCPE=-@J&-Y&MU#4-t2TgQ7KUH1c z%*F@6Nqh|a8eR19`d?qNbFsr?I?vfa zqKROi-4xZ0se7EsS6^(qq?9EhaXGE6x~NdX()PyQ+!z}bD<%AS)2TsqR`V`!S80{`&bil6f$)(Jp3g;Nrar$kh+K$v99zgPxF-)3utPa)WjkJ0Z$a#b-e``t z-yy-FuJ5PBvk$s5BF4GPArB6EpC;pnwaRHU@JQwUA}jQQ|5G%`b7pimPSX?^hcD2} z{7*)pYrSyZ=y+8Tzm^cC;12)hfECu9U&DQ}84Np8<4$|F>!eOdZSQ>UkBVx8{jK$o z*0PS%fpV(h>)og1n@&xn?f8U?a~X(DAvw!2#T0mM`FKrH?-Q=Ned6z*@1i|Y`M-au ziNjs~n48X{pbq2ujBv4X@z9P&x62&x)eO0Z{+3!X+z=q*d<6C8EdF*j;Ev`!Uj!k0 zG}24buC{#$jD}I3b5zh*V{^chNIk}r=FGc(z8f9qsJcIih;l5eQxBy=P%snB;9=^w zx$usA*JA89*plQ^jyu1U#Yj<6p?O336K&V2FyZp^THFQa!IKFE$p} z0ibO9dqffz+CpOBy`pqfQ+A1n#6OR|w?Mp~JFyOL8RLUbzjQ9oFd(>usZqERFUo%1 zpDt1PBXw{Kz8Cax1g5Il+ePE4O10GP+9?3j#{2?W+@gaY#$I7!eZ=?F8JP9ol)~nmo1hMYVEg_@eDt=Yx_y? zL2HjlB^^9JOkQ2ncGyK-(NQ|{obZFSdiN#0W_=jiDmdlvnY-%byx1Py+UIbL&{JEH zXF<0#wtM}TYv4zI;{yOMd-Lc)US>gzT9XeAKjf|S!DAKSPS_m6qFDx3xiFQ(tNt*0 zno+=@(6m+|jRLN^;S_YOHlD6qUATu_nkfg7AX)S^WOLEgTb>_Ty_jVg<`75_g^F3u zzI%2<36gNax=DbrB|i4 zka#H*U8*{lyz0e2#^X6`*ed({CaAE5P&UUDB;q+hR*;B})6^=XD*@ga=x>FU#wX@Q zYFOOnjk4BuJ`jTc+|``A!s3Vr4Is(UT^sFZVJqsr;OrSZ0FU$5NpB-18H zvW(`dii$%v{&01H%GI@me$Dg5Cbi|7pLSdq1rs9tS$P>eg1xF{xf|G)G0 z1e^Gg4>m!+)Tvr!1|jxK+G1*Ek9yJQ#w9aW+S(;G+LhkI!kl@^^e7OI`rA3XlAMU^ zzSB^rptgjO74W!0w!N>j9*d{AK-A-U=W%2P@|?o@By$!Lx1)0E9OV|X5xby zGd%=S6-csZLQSku80#DMqvTb*{kg#P2PRv;42$JyE*C6nbiFn@g^x7VEEYdP0E6CQ z&jerFdTQ-waOmsl!sNXJoI|fO>Q^gT8##E4J|)I8?FL9R;Q#fBh?M{Hi4wa4o|a@j z`^?MRCV8FI6)yQFtpoIGSdigdw5UMt&@8T>e}^=);rZjZU$KvaMYhi%y7|CU%_bG` z2f0o4P;npPJfswA#JS=qYCg)BVv_01#VL8i+4@U$hQ5>vb4#0XF)DmN3gUkeV@DXg zD0v^~jK)tj_b>1eGWRO& z@@V*a<+cuG=#*MWED7YqqfKGoiC)~8R2Z>TWPnR&u@Ud{?jdUxUo&>u#GFsKG%AbY zs9!|~x>o)VjKYy%G-VRdQy1dtG|pxC{#(l8u;uXdxWN$=xt33?cOlR>swDCzUmdSn zZ4>cvi#LXH>dW&(z{AiAS-YDIWvi|7w5@ndqHTjJl~3Tzm@b_!FR?VE-db{GfP`eO z?mx{t=zp5`R#i9uhPhP4)59VZEpBSn$$${e13%K3rr|k<`48shV}8Y+!gSe#0@2J@ ziCe?#eiSW zA*#fnsDvy(mE)wcwB$Ls#pWtoVjRgPdddo!eAIRs&Y3TFouYmGhXn>D(U5uf6NmMz z!Q3pVD^76W;B}_zX>n)sARqvj9XyyXs4a$idO@%H#boE!%BA-5P=FD$$6?R0fi@%pzbMvonJJJq{QN4%=eygx9 zs6kgDNaNbn*NpqVDV6O(`iO3d)f;pd{F#|*F{9j~Bq3oSn0C*A1ztKuMnEA~|j8unvdi&+;lC$2cRv`hm4(G%G}$r`u$K(Nj77 zamBl72V$2VM3^U49t<@4A`)y>8~2#d#jRGogn~KdC4dcY5n0tUpnQAi(spW)g8Q z{ygnc>QrfHYK0wEhQw_Bv3D(-ZMac=J(z7?4Lq$YCVx|~D`@h`WIxhVon)88D(Q1=ZKN{E^ndIvFJMmc4xM~=!K)0pZtA#n1h+V1% z^{k5CDZU#lL(*OP$5+0ICod1$*$hbTRXuL!Zc`~|JPSxHwmjulCAV|>4)Z9-cyc~i z;_!zJsf*vUKS+y5fA%(pU1K60*2#c!`x0HfENMsrVPO^3EW%Nc>DQLuliqpQTp9pQ z|KM0SxG*62O5gJBsc^x)ls)JD7$3#EXYf9qX@w$^K7oWVAFdUS+2{TN5?B9>_O1UJ z?TK9pHJU+Btyc`KZTcLvb4%Aq$!EEupV`iSGBsP9sDJ8*CK?leRLNbcLXq|c2sAD@ zVT@%n-``xT?Z>@}uActk_asLCfQHxnO)TH$j%Tl59h>A!l!K|*FC(`Tw5gSY?_iW) z4Q6gjc>XkD<5I4Eu%J1!LquEP8MNu#Bi8VhaAC}9abLfV&}Db~Dt&*yzNmiX?JI~?8 z191~_M3d;io74HE>m&$h=+HpHAU~g}4iz7(H9zaUZ^6 z+FCv#$q4IT1>z-baR1G@l+1i_Nf%#_kFBW8OWk}uVWf0PC?;4AcN)b`i`#~{NC-OB zRk`|g_JqW#^VtRaq~)YoOHrI!hR3q~&rvB&Zv!KwbaadU)UW|I$H5-H6qVlczWLz< zb-*)TSFYxs2N9&8+P=tb-9WnvgyO}eI2qwFy_otZ#x^niC&sGcV6wM?uo~wy2A-O| zl&kYLrQvulvD8f%qskhn%P*|bi)P2r=db+T4$Ezw#~=XLd_m_o{=N;I9QvN$Wxk(b z!S=dNw!LhXUF3rx$JU9)3Cx3op21k{OsGMJqK5fG6b)4cA{$C=Ckk`Hfzi*9)e9v$ zQM$CO-51?LX~Msrp>JpHVZ~!~XA%?|H$b1^-*VjHOW%-#tY6$ac?KdxNYIg#beq|q z;ju%83zn81J$T>?*;!aKq#Qe-(0oD$ z<7li~v1Qi3(m4JEEjs{t^a^VskF>Tuq0zQA`qSDDsY7LIk>IB8i|Nmc4q2qNP6peQ zxKHPO)>F_Wi*r>7R)xCpNFR~XD-kN}ADzfd`cHmi2z`zsF7zCS(}9@hiIG*GqlKTq z4!qXtXJm*bYu6JhK!$(tQ+FW;=E;#5+GWPwIPu5ag-j?!)gr>X!5v6$K&I?t1cgreD0m!7SVzoRGE!+w2nd3fY5ol&n=JUQ$`a*v-Q+L|GnrR8v|BnNz;3!wf z#WOn;nep5Ju{0L>KbqqQZW7W}ZGNqyd#G1^;({ea`x3>JVRNZC=R?}g1WpVad2?76 z3z(J!5s#Df4ibB3+LT#2juDO@ryd<1yU8j-&fJTBgN=cf%N{47{Hdu!_2Wd3UitMG zT^T;BxJOb81JI6}zq44#>u>wS+tNP*@d>|<>M=Y#lpTlbp2;%_?ax2oSW7*COTDk< z2JOeny%5lu_1SVI2mI%w>3;KauB?m~Nqo#LooF`tCdOHJc!o9mGBt>_7R3<6x1TS6 z)m60XnQ?zfT6usB~jwjczsOtq~bLz7E}uN7wa6 ztH?mCZwL5hCL)jB{EZv1hg48p1eu;`Rc7zH8gzHSlep#nv@B^5t)bn+53=6y7{29( zRJ(|ih;+$p<^rUJ-e93A7Uvq`5Ud8Wz=Mw^{!&E$<(Xf9Nun3B#Q#WQR#!lzUD!zq zL9ejnfsTur!;+;l4H^{~MPx3UnJJ=WA>q}S&{n&H1K$YO-YQWMS1-LBMHyJbjI5w? z3V0rfABl+jQ$`Wh>!vfhD3Hk?md#>$#_Vz}7^GTsogsF1e8-S)9JmwdN9q>F_ z_(MZDISV20403tH&wvg_2W{O52Ye;ppo-g~@c+m>;&b;s>G%z9{dq-He2X&+2KaF|&tbI5R57mazzLC=A)T;JTn2Q_>cIdq9tz*cB-KM}>no={lcu}nMA`Nfw!nc2-%!N@Xt-$RQfyI`)~+%;AwH!Yj?(a*wCQi zY<^JJ^oPkFUu!IwnhOz(!^K}HtJSx^-vAn?F6?xb=U8_>lO{J9tw!qySL;Is=DH$3;>s5Kf6@9d2B5Ij-IpfjtZsC?`k>t&nAoo?;h5LmHi|Zc^G?AML7p zRv9Bbg@!xDcq-Yh>Lfer3>YJ=tRV|OOSx9{4Qbg{~6Ub6Jo6 zh(?w#fhIXpCaOq!S3HWH?~Ldtvb z7R8pY6Ns|@`k9pozbFn3v~U%7cAkBqVXh+P^C%BJfn(13;MF%jx)_ph48*4BS(c+L zI#2CwCNG*Ifl-)3ejZacXbg$i<&5JFRQgLjndLH} zz`S~Y9Xsm%TUunab|zo60cdsoM?k@H+~*{@fPFKl{)*;gd7JB9EeWdOKmPCWKmMQf z559KPB>7vL;9Cc>^G3l?UyhGe0c#si!zFn-2?8&yGcz7M@{Z6Fj!Ad4jQL(yq-}Si zjK|l!KIy@52M^^S zb@G1Iy`2*ACt)f6H-Gqwu?>MZ#X;LL(4LA?=;5hv#=y&2C6*ls( zryZBiLHU|sH5ES9u9RApr8o-m8moyz*(7Gj@n1~Y-b6}oe+mkbK|U(egw=?&AQC2? zOdXOpJaaL2lQX(8-gC07`Z6B0<+tNoJWiZ)LjCnEY7r)r}8 zzSx%iI-^g&#whWh8h-PdQLfbtqg*m@q{p58SXK~;zv;19pS3=)n44@t;erPZ-5Hx& zzgAM@r{XQ;xVsejXGA&wXGE1de{kZ9_`~5P%}=C-W20c&$}VPYI*W187$bnj0i<|p z-96|p=T(FmgwHP&`)wIqGYzHT6jFb@jnDYWLW`rR+mwfy#0=whrD>++FSDD&Sf|Ll z>)TX1UG9%v)KA9{81WVG2mieUGoL-55!v15fr>Y zw%UT1isB#C#|{}lzhpwz_yUd=SArryj$&Q6r+oX$x>`gjT^DRqIg(Vf#MuC!rzf%e z8YNW0g1s$rO%7y)CMy+Byt0wma9{EY^^rk9L}CylEaF_SFlgE}Z+ta#bDGtmoT|ZUfLAe3YTK%l#lsIp>jQ#U~JSQ7og!chBdTJ9Y_5&M~JwgnLOD{fsn+sIn}F_X|(>MjfKBM^ZH zeG{y@YfdugwQjg{x^mzBGsmYiMfykdeUoju`)q*o-te5Z@Ufh8}?ww`1z&Deh$ zUHgB+kN$2|uI3iSmI4M9x>{*0QOBfkEgi7*nFR}iQ;>vwuH%Hi&c8!>^0S|<+fV+r zlS&eu5TP^J0%PiNDvz-GHJhjI8mD}=W|7kbEvTyVcWtcnp!XXFFiOi3dX6Iw?=p1v{Do1q%@%ZP*Hp;lwvhegbU|+O;fLe5@K+h~&w34t57-_~f+OLs zlSSa!e!_T_%xi?o zQc=0Dl`ZLpykY`_wz&rWk-PtCCx%uVMo(IFw$mfT{PhR9W7y=6wPs60{h@Xxk=^)DyXfk~U_C zXt@^3y^S`bz<8lpp|gaIs1d@4?RcC~BHp0-aBgI@m`X{z+(S^$>)G!xjPZ$}ns7-Q z5_w_SIKf)gB~}bY!JY=@1Ab79;J3%X+BbgAze>T9?k}bnKZa1gHZ|=|%8i&S*^>d3 zAQK=*Y!$CR&KJ<28@*!j#lgGuj{gty6qoGZp%d0M`tXC%%B(x;WE=1+%b-^Zj1?3Q zk;7w!Om?9o)~g|7QouPE-eg74_4RWZ>AmYok2*KlkX%BcVK1s>*fsZ!^tot{zYWE& z;oZkrY+ZqG$F+^AnVQRceZf&Sdzo?bJwj5WQg;Mo_SKKh;&Gp;v&R924GOK>NJr_b z4;Po3z>&2*;?E|x8oMK=u80ERa2MgK;7`O*Hgkv8bjqOE0S7-C`~A;>+!hU~e@@FUgpGm=@F_F9c)hg@SCb-l2#bGoybAJuzr@{Fd< z(A>#rvwaL6^Q1yZXRwDFr%uyFXB~i*+yNrle`k}A(}B!{YanGEB2@E#8h{t}|Bp`2 znccvOJU!?EcOvq&1q?31$BU zTd3V2i{t>vn+}mk1wt+!4*E_p(j(aqkSsg8-Z3_zV>l51vnh6&HkdlV1B?6skF2H} zRHZhUnb0|ef)5O`Vk%T~e>|mTg-40szGQce3jPGQNaZTE$@viOz~pB`(U#>`W{lXn zduw0mNb~bOes?lO#5x>W1;n?{#|F|C@fGuo2;>h!A5sKH_I^n)BP9c=T39b7l#5>s zz|S?_hd%K9mkoTS#)RV}IlDMazp&fS{Zy==vL8chDkOHujG|;Wel@9$ry>vFa%;YA zgj~n8w``XA@-Mdp|Hmy=O_1Dj-Zo+#k>^2yi$CDXYE%$Yu)0kuis~@z2)5GjF{htf zheSF>URn%*zWtGFbuj!_Q*x=?3hz0*?@j{9Y@N#N8iNAYGZfd)q1zU4JGi47n%;;U zKAJk{X)S*!-s7oYj|mRUt?NlbWPJ4DUpT}G#K5eOYZ>8`M>y039PJ7oDcBF$kk2+# zue|-|Db#>7^UFH=NGeW=nihN069dN9jEwY z0YkGcku2%I0G=W$~hYC5_jtlTwcZgE9LtOva{FbU(PVc za8Aj5bLv=#?e}a3+9($o`Liv?3v5E6RD^~6zzo(34-eN}fSX1o`W)rh>Gaw|;9iJCza7|ahW9KCTCaq2i_P-~M}G`nZF zXuxp^foK7=I~5E}fSf4#PLV32-eG$E&@<=LY*J>PO>8gnJ6HBs0#s_uzooieB3!&@ zT4Os55=EyEi-!??1Ttl}m$SH_vwNaB8A{5f$>X8uu91iPmhzf)W z3}_7GwwKvMcIhY@+cb>wNuVjm<3i9Urjr)Rg6CcF|ARjam(*#(iy`PEUHm{4J%f}R zC2u(ose3ei`B|f8BX_w4RiCGIp*GpL-h0{_BC2y8!Z|2F!hCv{mJ++DKGQE{s!ZyO2dLE7b12ZZGd3jW=r72ERl_!F~?HmN5zU+OH&Ty&`|_D%Dqa<&#>Th z{=Ie9+d?V<)j8@A4+jEH0pqUs2k{`VGIK3hVn;tMUxto~8ur_(P{os@Z1wMwgLoMf zZn_I}uIVaDcgfA+(haz3fjUS46v(^aYV3Xg+%xhfNn+WuTHSkt{l-C$Nio?1)|%%-x9u5;4n^98FQR5 zt~sfnWlt+7pDJY8mZ(c&Q~Fz}43BeGexYeTiujn39H&rRuiV zuG-(!_^9f#m)(yLzLbltFu4K1j2A=j2A&TC)$)t@9V>2YnI{ur0vm_iCfeiUb*}&8 znh9MGDB4!aah;$lwk-MD86%Ul)ely+_Hf&xf*3og(VJ_Ll0XcBoD*pD)83_eW5=d3 z9)zg1NbbSbM&wxIqYJE;3qz~hGwhxyLO;ucNm)f;BvPYXb2m3jEA)Nst5Paz{ffy- z^<`48f=3DONrS=|uUpzy0jrxa^LLRJ_T*OGVlyAOfqOiE&}hY-g$j;-2o6*WB_?=% z=AcJo_ks=NmOF4$rGm1ktB?I!_`%uJ`UPpTPAf4IC)D*8niYsEY^^q%(DK~~TP*fx zjXQHQjZ;p6hm*!bJ6Ehf(c~Hgkej4Q{$06T` z@S?pM-T(a;KsqgWH~XyTL76ymf3lca39=0F+-@~L+mY|a#QpGwL3N?ym)4e6OGgK? z=if2U9?slO5g6@$B5Fb#4?C*IG16$o@Ys4nMYhO}j;00jXQe(E?{l5+{ymIqWLm9i zXvE2zoQM5t$&@QuS4>(Igz>4&Lv>YJJ{A!}_1i9;THBYq65=imG>vP(R`J?~h6)hM zowLd?-t3VZJ(5;!G%#!EPww!X0x@;=5TMr8dKJzrK(+t1gSYS?cQg>P=WF|Jx=y9} zOIuNXUgY8bZBB>YqZd_RVrVtCO@Rl~c1d?XTmmOOaKOCu{CIMp8Bs%RQv0mR-=pws zN0kybC0;3?8{=gk^QmITNAW>>Vq`q3RxXFg_~u}11vhb+u;BOuu!LY#$6{@gdVq2| zvgxP*n6Y4dzW#mS%_``N^rPss)uqFM>S=^mziTmvoRd2;0R2#8~BMZ4n`^mtx8OB@ed;Z5_1Nq1-%HwCfA@!q1BEpV^IDB(XCx_p*8PS5I zD^yLqF1HBGFC}4%EEN<})yh%CvpT7#j?C=8wu7D^Y#9u&w^_RZj_YK%xqNrYR`wNT zJ+)asf`bR|6@sa=eRJ(Poc)q`~ zCHec1wAy5a#ja_)y}R|Q0`}_h{Kl8#^Kkl?pXpmdBCICMF0qZr{qeK&+IY4|#j*9l zi96y?rJ|~2?OO?V-UvxB!=uOS^qAax&biOrBlw<^a$&ZnIgnMexvW!i6bl`RhSC+{ z3Z;B1`;8E#lA(>AvEpJX;`_>PRQh0IY5k2S;5w~P%tkaVuBTvZL#Y6+ikA5hT=#EA z$`08XF43j-TS)g^g<^viF)0(cug~uM+|dbRYTb?7_p~v;ZDQZ4VsT3-MMXf-dC`0O zUs9o-UUFId7WZx{i6&Th^plN~LFtHfVsHfq_)C`z-X!_<1P82X>JqJDn*Ww~7Ka*w zTvc=PO}}PK7|?FnzFY9~b$QwD8xa+}ie~5%K##J<)azK&$bI8dJKIrBOMFjY&|(e2 zU_2!-RTMj<>AvWH`6hXo`THA}ic&YZ$IMl%w~Jn79u-#(2f*rdt%f`{%Ic@#r@!G8 z0~M;`kd0u{t+&oH;xL*!Y2{uNX!;Gkm{Px%GlMAeh)G-RH-nYTXt8fi_S0l1mJr?T z0WpzhJ-$^IybAPQcfl2F0m7r8<==G-pnP9F2g*;vW1x>qI>{`qnPZYFt}+eY|*# zmuh&b3+}=p?14T$9Cr_Wx3h;gZ4H{d5?FELsb!bJ!O&`VhiRVFMPHroZP1Y3kCE9F zkXrAdqiu5eEofYgEEm3rXmX7SsaO7k71&oqll1Pg@%uWAmB4npXkTDM@$8iC+rbQG_s@ZPciio;y*@?z!pndi$x9tOh89#4PU(V$uZCJ?DWsvs=a zRO_1c-)PVK*B$Qvb_c(+Z9Oj|!*9#6-QkRF(v%Mg(q)Xu2RE@j;zkb~8*}1bt%8?x z0yAa2Fz8_9?loyd^4M=}J1Vvz@{a6aIqzFrFe#ZD8Z zprYl?&pRX_a%GOqK&_{tM(cA4S7T{2q?GHPmJ2P5Ejp&PTWY8jtr}Oo5VD^8R28>h zpQVku4fL+Rr$i~0iTrA4xTE(KV7QZ_5{PNr_Fct{}YiDEFR$;TibdBzeDtjM|gu!IPeTQE|v^yY3r)>;|O@@Kp%8 zm0i!#PI|oEeY+b)o1+=Cm;KTplY-rqc7USp``2Hoz?URB8GDML!VL<-eTxU=ci?k$ z?@JFpXNphdz~uxcz6|I1qLz(9{8t$8)O3*J-+%E&Ui@6s5xHe0=B>gor4L&{9z3a4awNjoz!DG za!n>{5olwac!ij6=){ZGT(W2i4D~EcU3JmwNBJMyMWKFl?MM6#enw@vq8lmjx_x+sqN*cH% zSzMySmByT-^Rj>+3k(sxbpq3J!V@;9m|@0K&FuA0&cfh?%NC2zXUSSdFU+&YU2|Q5 zExiYeb!^>%k8uI8Q-ea-K;kF901fqlqv{|uMU z4m}U4CT-kUnur#gOqsCjz3Bp=T)~~?#GuzSu^_@%nlt7xmAJ?Sx}=r4q%n`q6>1=66%Y{RfmtVcp2bH6&v@ z0XDbMWdI%mEE0dc>Pq|Amre^GK)xne(5PPl1C8YMwGX4aKR-F7{d!wohp8Npg!U;D zYS_(}GO#_^^bbnBl}0l1v?xyJuD^_Y`yV5pa34;mJz0;mVi{qp3B zTHK`E(7p0{=($y-(YIyGA?N>#thaD$`tQTORZ68nCC3Qq1{n8I5#|kZyR!?{{6-b07EpAM7~x`M&c!U+>X~J0t1zxgEC{*MZf^ zyeJPayNi|5{&z!&6=ffki7!G%i)f_atm6}tBnrpP1k1--C(ntJKOt>&EiRn5hqL=$ zH#VT@;8P?iPVOdOgiXoI*4wF+pMl*rmEtLK2MQKywHf5=TkpV8`fL3-7DaoE6(Ux) z$;^lwU9X3(0^{SGkt`iWAyO)5e*`_3!-O-+!hKW)Zo2@)>{u)u+p8DQ8&E$AwAoX& z_wRn#3Gl1UCG+;B$tFf#F|R3_TC6;%MORC7Hf_$nnNyg0Zz`45^y6JASv?q4b9znrVOCIaeb5p0v69iDXzWP}rh;S08@5h= zrstOld)@!!kCzOy$r6iG7^SzeO>@t6+`>7-J41W(F2jeNwREVm5ZQ}eSwK?rf}ywj zah-eO2b9Q9mOr z;>i(lCe8kz!4DeV2>#r4RXR4gG5hTdpO}V#qpy+Nn2UB~dSdCm}5Du$-MAq@)B8qIL6)q zXAz^k66eL5`CAwi6B*`*n#3_O5_9zWZn6H_bAQ@v=2&-{Y7wHgl#jj{{Zl#6r=v)E z@$Vfyr%L_e12vR)Ye#b$Z;bX$X}hfbTw4*IZ|xFogevRc9~gF%hr#|cL!b~gD@U3S zz7mYQ&-@2EvvAMefqK-JRaXhA&&eKu9za_%)lzRQw zaqM&fjdv!fXIK^6;*u0x%x0fi{_IOnjrz}ja9xFk@=}~^*pZJGa?9$Rdl4M(3!h60 z2w==ZcN5pEf0oKUcS~i=Ly)$=syjP~Z)P#Qy;z{IKVJst=E?f3mUFfd3eQe@|9sMD z*|Uy$1I*%#NS8&clar)|inFl9tvw?3WYBn3H{0sG$DaREo>AI%mFqFS8-=1uDsAqZ zPDG{h9@t%yal*8=p>sO+>u7t!=}w-7^RoLysOoK5^{MR|dOgIzkI!DE?vQShOKSg5 zYvW~r==&0JHIw~{t>L_9jg|E$b-zGrv&;9&_S8ldbx$*`blP$Kw!LPy3Tyt0tdf5? zx3ExZX#CSgwWL_jE`{NlNnP$o_eJ?2T@=)G7K5|WJr$!We0;RoK8?Re0-vj@>zw-! z5{sP2`G>N|Td5`kAIIaJMxN6|0h5#$xA*dPbsOq*UIAXMLJz0~q+MJxh-4%K#0>I! zqwHdK$BuWLfa?UkVQ(6BESpN#+#CnNOb0(k>ZGnaYQ0a=;_t)p+(RYr=YWa+F6mt3OJjwkK!(o2naIabGp#a7b<4off}Q5!voaM?gQ zj{1#OcP|y@S%q;T=h>biy$ki)U)bgMvUeY1#8XDDa)(QS{c!4-+41)!*S*oS$6!MXW^4+)g#_^)Ug-Q1K3%y{j!b z@Yz)4`L3f;d*L~g5W1&c{Z=$ymsxRtR{k=`m4@bf=VA8JG4i+&9;v1%#73svnJ-`x`qh))T? z{n=hZ)k}k_&4}Lxz16q!+j0Km_ElPB(@*1g5=)MMUrpP&r2KuhlnTZ(v! zTLGV!8)^o@e*(SreRk)Kyr1W)FDcr&oZz$^u)8Tx-xalCnKo@sj8M(9z!W?mjc-zh{rfo z?}BIOKfyCOq6}219f3Q-%K9nbD5Q2d5T<0L-x;JvaFq<|DudO>`S;iN-=BS{^t6rD zS8jfb=`eOn*!-o)J}%y=qJ4CE<7QYd$h8BTO3wh*7@?>}5_%oTK%rvM2ebLJ!nW1x zUgQvYeuDl@6W>BOCuSclYiiO-=NXYLVj&iO+x zj0SaGulSf%>DnQP%6zNc{b+Y{gJpgo$z8AgH)cG6P;FqE%3Qvi1oZsi*qmB=6VA6Ji)PQ{%_6R!$JW$W zhY&a0LL@{^r#?zYZK1xq1F4<3!VbT0Wb=!Dn14{_G2Itx%r&}_po<;*kNbmu2=AZ% zEcUKH^Vn@TqG*0XVdhid(GM|Yp1-j2doKBO=^L+~?ySdh|5D}XJa61~jrCozg(0gU zeD@Q#OK1HqO4^xO?bVZdn6aq+3wZpX!4lPu5%+%$ebNWi4^K|u^f$9M$x$V`y0GQYygfkRMY4-)WV&FAnu8btHlY8+7?l3miv_mY9>uJ8QGi3S7s^L2t^Rd>4n5 zOu0CTY4xO#--l`BpAp0Of=LY=jwu-3sEXN*S@VY}Avnl9S1|7|b>BvXRo-rV zCk2JdP*&pr+Pet$RJ*y*${G?|qZDyfHc&G$ZvvBo^NsfFrHty>4P9w2)zSVt+vT}a z1@$T3se;nPOKKsxBI_ymZM~*$2_lQUx3XRwZ8+)Y4YkJ;GeNyRuliCCulNU5g`M6+ zc^#y6w#micC}haw2tH8pY4;6m$pw9jkUg_W{$S+Ld6MGU@e;wFPvH!vO?~j)G334z zieBI_=AiGs?5DlhMtOzhj>r)@0lj@IreW==JrQ7WT58!y3x7!%nB`tNS?t zCSA!KwZ(-&uC|na8XTv%G>CPWHP5pgoRH6D4$5lF$ea1?ZKIMzTm@EGTt#JwTMnCmtwF_mR(_Xp$*0}WOGc3e<72=8 z`-e7AO+IZT|8*pPa{*xVGsv{d>Lf}JRi-Gp6j~61=YvR=*6&!S?gU;3)jQ(b)`U2o zEK+&4VzaBc$WD`zZ~1qeG}{>(G!`)G_{A6xW8j^uNogD z_q2?ZXGm&g&GyhQ>KcR1NWuSSkp{SP7$pSV2~8y2=S$ybi>!_bwl!Vua!Va7YGX25 z%Kr#Z6n?L_F-LOl652`}ebp)+8t1xqrz(lzz7YAYs$?s@15GkN;tvt^uX;>hyN42);vIqJ>Go{iCW_JdJZFn}8yia8v=_fzZ!kF|}{q>1z36P3e zGPv!R_J{kPiNI}-~lJzA=GCk4LnoVN23zO zcp-(Z>@KQy5h|+8weRo3c_JdXH`$SE#IN z9R#DrfV9Ize@*smA)<$hR>s~Nr}Q}cx=jz>zr>rSS*MeHBT;#aEcW9dSq-w*nR@54 z;PRJN6hj~i&W?u`gkJRM>+&z*3N2|LH2Gg2=i(CYN-*BE{wp)<@K^0L$h;!nyZNm&wpL^!kl8MGRp?;g zR#ff|KBqUuYKopnu8Xy8^|6^X8J~$UFB)8%KlJ42CDB|HP{f-8_FEl2X1@FS^Yp(V!`I2>w z>A(epn&1D}G}CKl4B)|_!Mu>?pNa5Ul{7Vd!eYZ@GHj%JpafiXxMNu^#ucOlv{MgK zg??(i6~=M@o5+*D9V$*8%}?MtRU4bCsp8~7=)!(K)MNB1Czn;0Nl?Ldt;P^?6U#E_ zVt`$L=Ebu~U?kxjs-LrHbVX=@6Ua&daL+TishI5wm;NXJ=E zG}5c@3CLvG78 zN?)HpZ*Rj}WgS~%0{;EIS4;w||8QwmT~lCW;p5OWWvfI*{4tDNTt$j;OSf=YusPpn zeo5Kx**$*k?$`B{9Go6Lmlxs)_cUiw|lBICsRYV-ne&N zX^18bic9<2S0#p)$rW<4f)rD>wUiTpX;j`fO(ZJJ>0HWAto~|^kH@Ql8 z+BLohr&1TCXPf?zHp$lY{Z?g0{Jb4TQ!($fNl1^2`)iDre^*kTI}uY$t6=msPna@9 z%Ga|^YOF=rFw3#9lIJS2(@ zSIpzEb90?9V_Y@zdUn~n_AdH-GFxej+sEh+wp&6ACrfGOtrdE}>fXsbd?K#T zeL9}IAOd>jP9$U7rtanhBYPhuoZ9rK$-)1ZyK~p^DBdhZ+AxC<&Jzcs!Y?ZrPS+9m zwi;p^-?R1{2!cVDBhr2@?Tx%8xAd{7f8^z~&GfIEoEER-e$tR%#x4eAXU#h~5gGfu z1`7Xq@WCkEc&5ck3L-EOQxXmG`fz|>s%|FE@4_wfbIJm%%g6Ox_@JnK(;+n*BcR%= z3d5d~gwmFmSbPK0!1R3ocA78N=Xtf8Puo=cnxtpXk9Ic`5mVew|>Q85sK;aa^paxjH^pQxMldlf!Isvfk&2Y29bi~EASa+ zvZb5e(T2@lMDG9ARxZt+yq8r?)cgmb!njHC?(L5c}Qo;=RO5C{bxAX zh4mRI;gEjTd^)E|D)@d@q^$Mqk@?8k{*tOU7a8vY)bzLr$a_ccoViP0zs^Hg(@55R zmnxQ;7K)zn0@XYW%Pq=kcdBRF2mO4*^cy@riyX(sw6}$j|De{pIcyHy^Siy6Zw%v+ zD7pB*p;ULv2$7X2im>h&*5lpg-6};wdx00MM;oQK^+!_sW0LTbjfB5?yd-9n?(XuC zQC!3E{l7T4<6GD|W&JEj{@@j|U}YI{n%OZB?lWCnz7u%mC=9Sm`7Xz)Yy~?)=Q}?g zwe@pqFp$z^nLe|CILCyQ!djmRf%x;IU{HUMPvZ@={P4+-YaUE*ls!XYlPvAx1fvCw zF*yyHtv#}A3&0wn`6!nMzFSpjQY6wECfl_-<}{gi*K+cR-Du4df5*@1e(F~=x+WA^ zp^BKvspxO|SFw<+m9a;2jmKg6CMq=d&GD1XbeDGcuVZB^AZBAJEt3p{-G@b#sXO#n zPt{usCTxEyYkc_mq$ED|po_)v<{(!$M#}ZaYrxN!MMHY;KtsD3HA=Aa!Bg8EL2nT} z=t1kzkAW_7$>tlVcPKFttM95y*NYx!qIJ>X2#-G!+MD<4b`Y2|)ayvhG%79F4CoDX z)l)Imo-AVdInsX0YP*8fIinZf10BEShBH+K`EBI3>U4bdJ^W1bn`I%Q7bV(z^ka>p z8jjZACnur0blnGTQxa-D!qh~p5*qQOCLEU>xQ*7WgkSopu>`egtkb(zwIVWS)w?iP zsgjI|EyY${leXWAS*ToUs;!=@`Cw|ZQps_tXH{mAm^?`GPV(hJgU6OinMcQ8>CT!$ z*O0+#o9p9Nns%$*t9CYl4q{d%Rcf`U>G!i9{5F{FCK5(Oh99`VHeRB5=Xf!?(?x7v z-R(36{T6|32dkWI)zF!?fJe*ipYiDE)x`Ewb)Crd{vTG>n{b+EAI0qIZ3@no;(5>} z=$p&`;j*v*ahK}1uH)P|ndY`hZwV)Ae`OS^YZNQQYZjW{8+#TIGLo4^`n~K&J*!?j zJsGaus+0A-3I6A+Zl0uTm*xCvSk=NBaTyiMZ0gR)tqy``+TPVzSmRK< zkI>5M{S?5gaoMBsO(~~NN|GBET8G^PmvFSm1$LrC_5=URS&AOdBGI@}7QDl5t1(fK zU?>gaUYPKUp^oSstHciEt%7uS8)qaAM$ua+?diB&R5AAp z@bm1o@v}i|#o=oRQ5uJoTUVu~GPQnFKV&qeqMtd2=-2*>sY!OvPSMx)(P9|O;lmNm z`qqvTq-l>3wrA;7qu7PcsiTj^>6DM3u8|AzYQCztZd&UTl$bMoDtlfjZ&X<%vWvBQ zkGnN}@~4-z<(^xg<1gR8hV6L86@^%vL?*(zJ4CsxqFrCvaMZ!S)b+9X%j-=wl4hM@WwWnZg^}Al;q4&|` zIWC_gS(H0IX&EUixSqAsv*HHWWZIZBwEQ93muV65sj9QRCs=}P>5J_EQZJXk4{?%t zfw>MKM{Y%HHWNrX;(yjd^UgXTKjonMOYVjUDKF)j{5_Tjn=jztqEdVDVFJ7*VW_T1P@8x47++kJFURoTp|7MB&Or`u}PuXmj3k z5pGBiX^YA{hT^6ci&l@HxjV?|>X*omRg}Xhrv+wMj2GOq0VtJ^t(X~Y?WG+@ zc0^XWU!w0E6-t1E>5{14%cNm1^l2nsqx{F+QlQddMmN)#-Zz_9mj^d`(&~58iV~&B zLqp+p4It@$VO@Y_lrZg^!kM^|&nIa++*(~>k!JNJVo?hTkcR_+E!3J|zV+yw;M8}9 zdVpn9@qSzOfCSE6knn&g2Sr5C= z@R%W#Ubu&;QaWFz+$b`!hQ24+acCfRCzGu$Ucpf2)4J?dRp!&R9JLUXeR1Q@*8<<^ zIQ__YTwat77C+Be7FIX(D)v~Lf@ar7Eznnuo`o#+VVQvnRiv-)C@m`gacQ~fr!}ne z?+@iRZ#wqhhnn7_39tP)sOG5hotCfmD84&)M(&a4NpH!y%`9N|P4NN^-lXl|Nb!mKv5% zy1(hnqzI5kICVk-3MG0%coN_TK)I5jzhA}F<^vQ=!8mOu4w{QoZITpM8ViNiUZX_l z_P%}f6S}JRocY$}bFG9bF)B0Do2d2wcJAQY&jo1NFFN7Jf#<5O9mks0#OR|CbP~Kt z&MV)KMioK-Gs2gE?Z>v65zH$Rn(u=b531L|J+FNK^+a(rJttqS4E-#yfYnKbc1PP$V!7n#Qb~+Wb=rcP){nfokpDM7yq$M|W@{AWuH%xp zyO_{>gEc)2l6(zYd6&)D|0)Jb;OI}iEZc=&)w&$Q567T*V&7yb3ezF>bG%kHm^7~N zgn0bDP;0y^U&0!sGKk*DWH-bic6o7#>naOcwdh7!P*i%h*0470f=Ra!d_Z*-5o!y0 z`L?`>M2ekHu}`aVPXL*#LW`~RL8n{xS`1kzDtnkFRicR3d_Eq?vnZ#ioKVgnbU+JZ zlQ}PM1v17g&A!5tu_{<(McQT@Lzl0-lpx3oU*1`t47nE^4LoZ(~R|MVWe zhorF%K&hW~Z++y_9{Bz13^sx?HcOTCqCbeXAsch9X{r7b>36vxrtglQ!UDp-hjR~4 zuY7KSm`%M35BWgkJ1TTgiT+`x$~^2G#WyBhN{#fl0Ow9dpOwXp(Ww;zcK; zV1Tro_xHU;s?NDw;-WBgl7coT238c*@aorjC+ znMVQ!@X~8cyBkb=PeFI19m$b|+XR=3>aPb@7kzY*&Gt?y(AI(Y4;<-+!Wl(9&V;Vw z!{)76GatSBLMGryQ|X=w_Yhb5Cd$@d$9Rsz-&uXMfFhmwyx)n2hf|ZPku?~MC=&k8 z7_$|Yot^(}*s4!fH59uO46ylFqKACNP%OsDz-xP3;QFZCNr!o59r}50>%M+e$3}CpHAA;Yr zE@DATFylTOv*gZVhnz2MsRVsxr*51TPQ}!+xZx@-d@dlDFqV_2P7$y!eg5h(BuD4EqH74OG=@eW_6-(ioK4ar4qO_vM0YIYPVBD!#9a&q+=z!QkQpgyxL%qtgo}K z)yd4=SVO&bi!tWrmuTSUD-AIdheuHHw~}on{&e|y`Mk#D)?Axs8-;i{EJ`LA(OS4Y{|&Una?IzoEjq*~#8>C`MfHpoNgG zI-ORtCfjBoM^`Zv9ijeQUy7bHJ46OxK}VpJOD?lKys}s7;lOv=^%@D*f&VbJvTfN@ z8~E)z2_>1OT>3i-6T)SVGg|Od!1aUMi)Yz88?RX+f5Lf&t9bFQb)~A2NHi?@_7kW| z)l}0hiPrfDo?wsKA3Y}2MI3;HCq~AuZY3a7`Y7FSN?dr zI&n8`kV=v#bzgw#RZSmp$8TDE3Wax zxsrJr)g@d9yJ#5r9Aub>|ENG;+2+}KfxXpfSD7Ma7SS)%V<~OW4 zUve(%`gY1>L}mv%<&6M&%E`dHT}lC4j5#>M9JVx=Lx$B0JhhY4O4qcjUnX)~jabER zk42z-MX+}U@{G(%i=>52Wf3~4vQ9%~E0asjJ2C+#iA=F7jRO{D(U4aW*Lc3+kF@d+ zbs#t>o278CCS~z5B75tgcPHLB8g*5#ne z8+p?MT{@Tc$-pzvb|zal%?ErEkZ#geaHpJA6P(iWG)pWVfsR2+w%KB+I^&W6BOlcF}i}8 z>O@y2G)TQ`5Vh%SjaW9EhF5?5c$E)pi}L-^p-u<(gk^a_0kw8?zM~@hoy~sM*_io5 zO~Mrx)p`nFWdy)n#S%s?ZM%a`>rE)I(AKM&wI{xBU`E1R-k0B#bHJ^|l)03<>-DPK zZ@o3yD<@u9r4IgwV(*66u@@2TK{&o7thB>-gt~?6X9V0;wbun5-f?`PUOdGlV*Yg=-5f z#qe0*y`FU2U~tAT>!(i2fE50yXVib!eJwA9?G5xAvql0qanUZzU9d4p@F$R~nX3)= z@y^2xwS#fVa=K`&TQ<%EG1=I~*~|rg7qJ65=!Hny!BuvK2(T-xk2T&=5^Tg_Y&@fE zK;(LHr-wNNsZoxnDx(%DZKpv7#>bTxhh7K}M&tb#&J(WQ8g*2USwP--sv=stGt@B# zjwdExbdue$Sh9MP(^T9et&Ee+(W=Q!F(@10tSvOTRKaM$;x=jemsKs#Fl@^2SGmbg zg?Zy0ktBTa^s9={@v-V)E=7iuv=rLhY^v)Cgip(3rJyjhy&)6ARN{&va}XdVbtalN zc-5z>>-1!<1OhjoaV>Ei=t=exxnU3^xF#G8Oywk2f)HGrZ&4g)NSM~LXwn1_0!mW& zb5BpQ=Hcr%f>r*j<|fMtj>%1jk6@3etf;lA-$=b2UWdP?ehe=YHpHKp4x>gmO7~ zve?i>_4#(4d4B8T9ReQkH&Pz*!|(*5Id>H)+)VCo6tj7jX|BtG^+CNO26}Y8D^3vy z-7j)8(o2t(6Bq%F7&jrEvj35?lmTLcP)m?yhb?j%roNvf>U~IZyd#PUN%wjFr7TUo zue&`m)1&Qep?$%X^n!&yIN)<==ujW8#j3(elkLFG5&%OHU*iQNIRx?zDOBbYydxk< zvgh1lC&j9>hD5hg^0FF3Fw3u!1I^nSi|mPQP^S6@?~PWEH$ig}3UEn(~w@ z+&dN}-HOq8waPE5KEEOZU@BqKp;(o)XbqT-f{WF6Rdytt#oCl!S1+3XSZH&O#TV`F zMy!|epsi^ki8uOS-Wr+<@iqORktlHEzr{eNOu(Hz8n~ud1f3S|%GWkyU^3)a2j^u2bxgInul_5|zJ${TDgRS(A<1r=H z`(pGLS1gR4+u+eiTDIQpL;tyN;+~ zAAko^ZYtxgyj~gH#>Am9ZA6(c+1adI3a<-i72U66^^=%`E`MJ3eY}eFu<0?@fuDuR%FpWdoH9=6 z)7wl=rg4H)7QalY$48ABJDVTXH;wSUzm(Z&UkMi--;{hkOY9&)(aN^#RI`sc^yj=f z#9S{K6q9+~!Dr_p?Y;eFYB69v=CxuRmF*&)xYilJ|Y+F1|bL-2y_Cl^=4>gb!=5SkJbC1#Wy)PKDGTVU;F4Q{JwaW^3y+#?X;^5o*9lF z`x1WVT7ix_?*vy_w8j~X+tj*bFA9>~Rq1a2o;e2*>JcRU==e5M;M^g#g`1Ba(%kBh zzqejeBBdz#MaG+eei@cSD~uyM&+-YnFSIpHl1E|l`xX6)c2!Hu^~}IsR9#se%Nhh#VTXjlUafh*F$D>p_{SCZ&CTD#NzMO zcBK0X9tOL7zQOkUu=j<1Fc^;XD&1nRxQceCC@LJt!Jk0+%vd!!02V6x{i4=`3Sn18 zOLuI=<@rkcl7uZ)?ZOMGQ;Rt>)^3vh>dgZU$+gyGdjn5fO8l9Gi<8m4)v(*4J*Mr> z^lr1i5OzD8F`2P)?i1>o)1Bh;jsBggo~IZta4?i$mH#xt-zmLpKuH|4aGFuQ7-&M% zm|GjIgBMq3h(@|xXJ{cu^3Ltyj?|TcTV^YBdZs!8iaSD!?(}m!C=SCJL`+9Z9N#!7 zf@gAq+aK};D*0nZFY*|3;)*i)f3>k%)CknJrYs*E^;=kDVeM}>J`t6S*)*SKk&l}b zuV^SJWiyHbydYnT3hwl)eWWt36DW-9W$rsp|*h^A)GEFQc=jqU-*jY^0|0$2Lim8~$8{6bz#O$~It96@(~Y zRA`_?ELjj_oL@pqM4etm%=JU5?x_2GfX1)mnc_LIOG;Jz#i-BV(>Gqf z`zLjhJCv!EhCKcuGrs1lB~7xp{hYjn%|1unvYI53?TebKlsd{lwt9&*23h))U;@eY z@`Zx$LWq3|GaGvtJW7aq=Ey{l6g8<2+JW!~vJYmu5f`@(npO#%8|4h~;zu)ACO=gC z-psKa2Ev_oa;jtTV^SU4OCl5x$^5Zfpj0|VZ5^-itq*gX4Ai4mO#N~Hfo1dpW7R^m z_}eF6!=-fl}6^7`7`b)}GYchIr0y9(wtVm3h|^ zq6DXtcuqi7VaaC)^Wl)@f}X`QE#Qj|9{c;R<%z2};jVTGxhs;WY8#amVK&lYSJwwy z&)4R3g&oghtvr1-%tU*bBH$Q_V>(JPHZ{e9C0C*cx{e7!CjSdTDfGGcAA7^R$U`6b zgZM$9BuXuvk7GdY(jckZPxCAi*Kz*UwJ*m_!V6$CE%y~o9Ps?7>#^xq%Glc@4p^7R zVpHfru2{h$YFCI)Vvn@y_uG7Zko>i8j$OQ3^7^$vKx_x1-ox&%uP2yv;s74ALt5Q$ z3eWwCyt?Y!4&fd$*Wu0?16Q&|I<*?jP+$Yq-~YnFqFdAbT{8(+yQ^6=%v}D=;M5Y2 z{BWs^X8XFVW7Uut|Nm=iLNKPdNDK}fazEaFDI6U+fN;&lI!-|n73YQJ4Ls@WGt1yy z@{!JkhM{RlUGC0r2(PJa4L|$15QsfVMR4@h7V;3aFABb70KwJGRAULNyeH>dgsI91 zDZ=ckS!*R1JcBO$r~j5EJ2ce*gx?iW;ic&zs{_Na8YM&F)t+Fnjqn@MYXqTG&ba$k z0ioE|MojdxfGAftZJK-eX0RRQZ#1uyyjp|&+HJ!}2FF#~zp9VKa;1;a_U32Vlo%l| z9(A&Qoj+wX-Kj84L%93J#?DngX3G-e`sA7WZSyWJJY(U9s(P z^zsfbiWB&TsHopToG4enk`Mv`ya%%14Huo?y?yG?@h*y8DKJ!As8PhX-q(@*5jkzC z-5jJXa5|J5-qzZu7<4NcpYrDmB=ueqAu&PU4~zD<%@Tt&_s7KZ^Nb5VNXo8?YJ?9| zZI9(8NxPoE^VO6-?9eVc|5{snqx)=hyp5_8mpsSVg=Cb%oJOPBC7&#JqsfLaYrPDv zzYIu5f@c1e<3V44Zn8fC*1ocPq;-JnkUyt@4!@!ZthCc&9K&Ef6R-KLc*v8uz8>Y3 zLLC9k|J(w=$g5gc9>p|kLetX-@4|kD-z})ym)@;c5IS-N% z4p0nP0Nj4OA6}b$B-#@JAN&l1g}P8lP+7oMytSC`C_+ zO#{0_r4#v?4uXA}Sct9)Yi`jBV|?8ZDTkGkU)Q{@b+hV(935uXv;B?be7y-Xut`Y! zUs77YE?{{=j%y$%3`(L_aQT?8K1JX9oNoS;)pRM+7~b0^RzX@MP|uGs#Kd+7n#6<} z1(D-kXyHU?K69NMqhk6^CvCqP%89EX_n?#`$uT=Dt@EQEMuLclNTz zAqf2@Pcio!dYLjDRg^8RDVw1v?Fpnu@tK{5z8B@ znUpeJ*53qim-#fC{YQhxjOn@W;soD5qTe&Xi&+QW1>)W3C=se88h-yeE=$bi?D?DA zN@&3jesfKh7ZdLA$qxKh?eEOxke$vs#aPqsFIS0N`;coA?j|>QwG+DT3dWqU&48E~Vd`vz2#e8II$Kl)kh8`L^SSvcM458>ttBv$#NZ@x)* zXY5U_3zeT8)}Yu;S@;h1x_6gES(UxFX=mE28{GpLSPT&gmNux6d}SP`eqOg{ub2L- zD#iYmzjxWV$^1;v8y_V(4lxzx;lJEsEKOxAhZ_PXa8k%T&i)tfuqIqaX& z)-;f})X0(QP%xE1WG${xgy@JGTeNysXbD1I<6)(+)A7PXtR$e%kX6k_t#fae=iSAi zg#%}?pf;v5-$FFVlIN+qiH%;G^DE*0OMUx@k;ROy_@2NGE)!t=4WU@x&dMU z&;@vktXFCd5%vmn8VRtS+^|7=ViuBz(*Ir}sixBWpFLy|UUUhKwrA|z(*6ovoT*o-#_+OCZP#VW?sgyaAFX%a!TblVr z6i&@+)S-;_$c&Ao6}g?0^YiM+<3Ro$B&X3Rh47tlQE%A~8` zN~5cA8lrR+eHtw4XTHgl!~fySlL2O`_-bak2}s*1#c3MPoi$Z4sR*okl+~)$$rEMs zTS^WS$p~kJdR)f#3n9|e57mz({#8!YFDHA$B7Dkjf59=^BjG?fGTPe4daE&3h6^kd z1w!6(;ZRUa8z5%R{3a91c}dh5`QWDYm`K>G;|Y;UVLB48GR>4)!P-$an+p~Aw9I?TaEqwxSJtktsW1m50j+lV zXV~XI9owSvtyFKAdsF}mEeh&$>k@m0=Y6Iom!&+1pN}!ls)s4&K7N-oLG2~+3+xj` zRAk<}{ASzDx90q?ix;l`p%>YYrA~d8J5-mNbRlgRJwuxe9$jf;F!*}s>2IXVjjTT8 z_+#e04H(L&39no&B3G4oaZZ3riYjY66CVbQD6lnK=Q%W+VCa_f;PCrkHdd)&nC=02 zyk4sF>5fIB;FJ=_O^>7>_CsggZzO}_x6fKX{z+MXrZtkW#d~_*Qky@J52^193Kcfs zePwVi`u_pf;Z9720R*IrJ`sn+Jw-OZzZGjUkt!#;R(wxkGhVAC8ZMH-T)3~ z72a{MT$j!C{4?g^0R08nz_0OGFh~gpqTy>0^kF4F>=+jb?@QkkQ+5MfyZ&IWeRCoO zs26_2K-|E?FUeqKP}4E_x7{_;`T*c;EbYU8(tZymQkYyXZFXVYlif_R#rv(%AFO^I zI4Po3!et~TJrYa#v{y}Q%2^50gT5V{kN;()Y3!MPI_YkqX(0ddWd#3lEpM0gSS`*q zS-hjWF)Nr4_myTFj=Q2txL8&Qb^AG41eH*)@N1nHE%`0A*z!*pXS}x=lRtf0kUaI| zxiu;|MeZVZjjk^nQAXVBS3kDKMDqrUi9-Fw?6CcOb^{dS8ax*R1!CNzsjJXU(GO;8 ziN+dJ`Tl14FJ!8Oh$&oc{q+V01&Mg-*3W)Hr1)rloXrYP}Bc8LmIKFZa3^18g%HE{J6C#^>lkooDw;1imj+Phlbt?W8NG7&t5yH`A1s zZpl0Ak%!^mSbjCpxz8#k$_(i9=DGjA-zx)%f!0RmN>4?69~I?}d=IcW2y_uQk#2Z* zU)A2H`QYQx7+vH~T|F`n;|#jBfX|8(AATz4GH9o4$k4DC!RUhgSPjRlwqur>V#Yq7 zB*urU_P8m9^3r+B?P6G7iH-3C8|r$rLeC4foJ^2az~91Va?9rRyY)r|@?p>pB_ zK{BTnSdE`VeC$o^Nq^*0K#8x4=z_)G7qf9b1Y15OVQJVD%PvxudyCGRDWR|Kw8Utm3}bmsb%fmY@Bxatw*LUwWJRePUhTvWSUYR!*r=QPINBObhpF(Bc1+ zBZEyP22Xtk(fE^B%$C_Y-k%vp?n&Y+Ngsaoy5005-Rz!W>|;{$fxWR0nmw*P{xM5( zG?#HP*gL0U1T;tzin=qYBJHHZNhaxsfAm8O-$b1_Ig5i&rAFb;mW|SwGkTPowfa@-NIp5CfVB+H#2DPJj@{Y5h$b!-^L;gY` zfgkL+p@dNa<=#3{!`x%5k}n@yCjQL<+eMeJ^wqmWy<>N~0@>Nfan+m$3kEG2b6Ixh zeRoI;V?1=g_+gq=DhHdnwjWoe##nAHYzPH=m0xCK?1y(xmOg>p zHM}1mD%N#~`ERgNzRI3h&viDJwAn|DuX9A;tB)~l%wo2y-FTSKge=x&U20!5CAmqJvWpHh9e|x^K*l0NFZ*@Xk zxm;tYHoRASP15ugn11kFHT6Qk#Am;*^452u@X6mi=|)+&quHbu@;rv5_e7-l@DRIsSkRW5*<3 zl(;U-AMp}M-a%H}s;+w6o{jSL(PW}RCf&#EYJYxtr&9cihBDp91;@$jjE(Zs%(PyGj!id3Q$=Maobw@Fc0(Gx> zEsFPvbiz_8)`SNHGu*bxcZHY)rYwx41X8x=6IHWDkxK~1T(jMMdp)uVfh>oP9v zv0$yYBVIg%;H#<6>(pyX-y^N&bt^NUunDdAf}$TB>(*@s;BEQ6pC(*oCWjw3GYR&{ zwMp7Elzl9UPgflFD{XjDqG=zNeHrYF08_BDhdD-xCSEFlH)D2v-ojtl5^SHU!Ax`6 zcO@q}Epd)b-hE;m`6CopihSWASD%9XnkHw6L}YTmjgR?jb)uH`)R%%imi@geA1#ed z4>_yK8C8#N$w}MQByfb1Rlf!asT`i&{z7l#GMrsOyPa;l+eC5lI0w9u!$53laLHiI z3EOX{aG8R4kopnrS)IsA250`Pz**_*1KsQ|NL$B#yOy6Ud$x;|7|5fI=dm+&2A4GhzDnXcsD`w;` zMg@BWPyA(b@R%vq@v|Xq7l$$%eQRuS9Qhg@#W#_3n)&>;O}_xnB-{ro0WU)wAu!|PV{E8YIcXj&8=pBjqQ~-ENi<8}2@L%Vh>QWhfVAFC31P)=`W5$?V$+K{g zzl}zGIJnh|5(AzMk+p}$ac(%(;?rcNS0Ap`@~*kPWM)lr#-vUR;PZ*N3h!w$c^QHw zzk0SB|DZhUrmeD#O}^p@9$?$)+>BX?=sgw3UYBvDv3Z3?0Hj3+srKCS7IRHXFEEy^eot=7CVH|=WIpEOalRdXUI zvO>XQn*r%qc;&r&E_5+$8ULc!XSq$>W+26HBUYPSwl$vB$h73qx;?wV#f8*13X zKUAJ=7YYb1=S7@e0*;^5&bcaMEfWhn3x4UF3%nyJ;b= z9;=hAjR6s!1SI`|=7#z~_yK>Y3SknfGF?_&gj-UKZ6`=DxADtC=<;{z3ipfsxn6?` z+8`Mlbxuqz{+mJ4d5x=ndBA&PNRBw?@c-lLE!d(Aw{T%PL`q7O0YvGNhM_?^hwhY; zkVa~dmhO@Pq&t-E1|6hRx+f^*$uC?x3YfQY;aMvF!m}UF>Q)Er{ z+jN|#@U{I;R2}b_Dm{1U$iX3ep{Q@_Yv>Cri{a{m7&SY{UEXaA3ixhbR&@4AmMQK@ z8K&YIhPIXC3o02MkeB2Z?Gg{N>B5vsFBcpr4@t?OJ<);q7hbqqAFkY5GVugQyuaZ%-k6z1mMfy+6< z3V_N!=z?o+ZFMC`$!x!4xZ$UaN0qRQYtkw|(4RUiNCY1wT`YftelU$8Yq_$-Ec()Q z(O9cQ1>JVSEaDAbY1Wzrmpv}weE8(&B0@KY-G$AVhz6r8UD4HaQ95@R;Vz%w`|WwM zLC&#b|Ox@#0UJhA%&Zn;<=rR$^&N8EQCTsYxl z#H$>3aVJ^gy}<-{6{1_hW6J5mC?}-Xgm>I)?Hv1`Nb07jX_I2@^y1Oii+CnNLADx1 zrruvtGv@1A321!3(>?Isd-?tCHJ{=c4#-x2c=%~M`tbL)w*6uQVrQlJQwD3T_37^D zj>aCRc~^)_hb7JV{+~jKSQJ{hfFmTdK=($k`oqmxh1k(zW4(!AuBaChe!Y6c2&Go9 zT(aBN4@$j+NB$xh1zp9a;0D6lmUIsI)KB$w7DJmjk?1Io6$^iU7ln>Olvcv%Ry^88 zv)DrZA*ARwVmSHcwNOdVo0s%#3=7P4Pd7&6d~$v~*%LT^!2>;Q>1|bAoPXCbUGxhz zeSfN1K`ZB0A^8+IC3tQSPS zmiCuFhUe1(2B$8QQ#V-XQZ(PV?YkL2%zLo2#SHbClLWY$6azfVxKQpW>77?ly)kCq zsM;K&ZaI=tF<_phd5U}J2^%73YyoV!xKhp$T`38q{CX#={gix*N_exW7IAIb_Vt5g z<_r2_FE%ig0~Z89qs8jQ-sEI9jDq zuEAQy0e|%)c$&F4n+{O%!1#l5nEZ&rYu|b8g1zE|){9$`dLT>3_pr2Hk4!py8QtfbmhONqZ@i^$Ue24K5nG8g+{MKrp! za3&C3@Bg$2y?P5fI_tNi!D5}M&xh&YF@yw*6KNNMG$_Z5{=n?sZ6`EuVyIQ^VC^6T zea6;;w`*sp(pIld@kg#Q<3%4S`u<0=`#v5ILWu#lH~Xi;;#Jc`;mK+00q&1IzH!&{ ztc6$_)8lzgMk)zhA8$ZR(_e>uwVc=RJG&3@{MWjcz+oGiJqMPm_hZ!yMS0m)l6ZNI zFXwIk}p}$^^i%SftC6o|qthG?lP~b5=nqRnS z5q&y|U-<2+LilGOiP)R!cs{F`dv*>$izev+K6`_;1ekJwn$!27RRrUQW*NnJ_J?TS=0f#wZLEOvSz9sFHZIBb)=e)U=7y_UpE*66KAzBy#Gn+Y(n5 zS9e%J%8QJ&E5N+bz-qK$Bj7fdI|0BU8$Ef{-8}KzFw&HiHNJ}UUNm! zxY?!H*rl`m#u9Ou7<$Paq<1vheV=pKdHD)>@{{zewluKTurCY2w|OGA z^@@&83DyF-k|S&;jswqdr*zmR0^DGcAN3@3N4%l%{9F5)SKWi+&Gzax`YT0?X65QVftzC#MxJJO2UU`~(YJHl#I+_%F`lHFw@cfB z@v0f0X=Wp~%zw@aNEC~ujH%x-1j_9Ku07kvZpEU`{2Q$yQf|!4szJ6WARzqbzd!(q zp^%Bj>gi(Ps$c?lbu$IWJ#YS9cbz0OHX^eZ))m@MInB|T%?DEZa=tg9SB;y~?h>yn z$(&(iYjmmpJpZ)`L|Yos+W{FSO!&5gtptmxR*uP&m8>Guvrp-v>>6+9)B6NP##-Y$ ztb+$4%X_ti;|qZ0gP`9r)NDjRo0g?d(j8tVg%I#}T`J_Kqbm3B(v-r&mU=NXY!|9A zdHd`9NYWAwxLKX91HNA^RCSC>AwnUoTpBSd3q;k?m-IHaA3+DWPZLj!7u1tqr-m|M z&V+l`m}i*fz2Vjp98*Hk3wnR#ly{v_N3*yMV8!ql3wT!&S4*Q7gf5VvLej!3U5sTbda%mbWe1mg4Rn(|-1X0a1i zBR3tX-hVs(olqoCJ!dI~C@rW;Kdrg2^nxXbYrIkn92uy35Tx!4iqB*l@BgW^EqNp; za)HjARbiYia-Q%ePh>O=nF(Jiq2OFzXsFj2(K6Awz45!TJUaB;`pncVm3(Gn+SEu_ zVf-1h=&nh~drK4~9>zuhC$ zRmJM)Z4It3lVrj(P__Maw{2$|My<&+2C%|pluloa+?*>;hs`qQ>{6|Wm4G5$zQ&l) zF6NgY>kYSR<6q5jp8kC;1ZbJ%J#x9)5c}n@3AJzbOa4?dbtpjh2Gsq_<}0c$$_q*< zCzd67(1c>+Hl>`tW(zaKRm>0R6NBq$O(Y?9isYQKB8spifN?97+7l#`QP`&xFsaz0 z$5&k@fa!SYSe+nzOaj%i1mmuL82Uql`xgF5r6rop*4|=|Uks`o5@H zrmHB?k;x)PlZwB!#2}16zT=at->`gBzzeMJ6XNl%9dB4;G~?H6o=8(VJ*T5FcHnfU zKb(>WQSFo1>2fLkaUZ(w#oD3WVVZtkN*s|^9-;&qD|yZ$C(cJHf4*;tji$G4a0I$N)IP?t@!KG(aO9En@&M*DTQ!op27u(71n|$^(o6@&6 zPGh$s_u?vEz8d{v3^|Ulk?)l6Wac!|5vhE@!#Y<)T?u{#3mEZ1u^Q~)Ha;lH(C7fq zi03b2#jC|}LGI)+1aqxwJ)kA9k5>I4d<$d5QTK_egZ`p;g(<5dvg`mjRVbSsqVrZQ zsRl)$)d*y8n@blCo2Z215PmzJk9A1*{k?$S5b)sCob>H{pthfT8EZ#jX0n!b39kO`itOGx?`$EK+K)L2Tdfl+%&(4m_Q$>VnJ zQnXU5Q9{~5F26Q4_qmB)A4}72^PSSAv%bvJz~HS&rL&i^5kE*{IC|-8IHKjkEVDrJ zuyPGWV_t}FfoR;W5DHY7{TEc&nXFr+_dgWKHlM_+|E8CI)s~GP&XG}VZ1F0{q$}e! znS;^?hIhEN+HaE((68E$vI=gR%q_?X_r8!NmCN9>%ILa9{X6?7TVXa56W?L5FLR?l zlhA&*0qd^`4wdt|2fOP7e5Ip6gD1nEXhixWE)-sSml>6v@71ekwus9DMdE;F%O`Ta zHSwK7OhS6m_|Fu-SdV;^F4A(1CC~*A=7@qEE|FvlOlAnHI!e|`E-}p#d3=m7?>eNj z{>z$4=U^yG-&!8zrIS7$CpAh01J~15nkP;gm$rqbGls))ASni2#1D6}w#5M*4du+E z*FWNxhln1eN?Ws+`}skOFTKpQ7hl6W^wEJk1VtL9&e2=pUQ^R0znRS-ZlWz3CIV$$ zG|vYeI`$2kc~OlsU}!c;2OwKo^Ah`IDltTo&~M_2UY9dZ^(j-h`GaXMiuGNfU?N42)+vbj7Hx@J$)8IJyt0T|@HbjmURfc@$|S1T z$ce3VtgfYhxu*oXd~zu=`o?$iUn%Q*?ROAnY~LFq$B(GVE_0mcO2c^Z;)TQVESbZ+ zY2?HLyS#30WT)aLnZLAylz;GPnCVA6BjQroKUOEp>%(LbYiGNlFUTLYDB zf4yERd}++Ce@>BH^q1sm0VL;!4%i!tqiC5e+$cxBcz0rP+ZLDew&Pp=R#O=xzoij4 z-AAG7Z`vUBFzI|P7x`a*iJH9bh5+x|;rn>vbNvMugqMT@BWJu-lmVPp!C{1f#{W4= z=IQA@NN&)^T%zTH!CZdOoL-sYCaIaoPk4gQ8IR2d!}sujba>)d1QOsm|7|H`od+}W zW;FL*@wg57{oDVjMz$CH4Roqc60HK`eGC`tcaYFh_;cyG`jZX{QDpg5R%e>=(!{iR z>U%8&9{UzFD>bNM+2iY}YrKbFE%~2)y^OKRs`X6bM6PM4+#*ljrI!cCp94*&2FBvWsQ;MXMfQ*T4Z7ZV_tAfMK{14RH z;C&wO&zKkGVz8Wo+`z+xh7qpNkvv3YhihwDI2tSD<#U`=QLeh@w=FF0TFgDdrEoJU zw?gd$Z4h#hHw4Myg5Kid84(!2``o~pD6vB{Vx;HvA2Bv!6Yt~{?vt{U zeBcYvI<9WnEbq!;5>cbsuh4VmsHhpLi>fBTrKD#FENj`~8rZ=}daM+nf$pMl}JRMT(3K<4*xS`Jz6ZF1;Q|B6PpWS=ZQ zSuA$b7Rg?-ddn5@rt@e1MK}Ek7v7P&`z)(D3H5F&ceNdxgvrND@Gsu;LeaI7>g=Xe zr5bk54>Qd!($3ya8lpJ@sX(ID3xc(VPs;VQ|CzGVY%6hV2@WC$w^nQ4pQR+Fh2fjj zElWJo(M^4sQ}d|SNg9H@@?!KFLi^&;?5M;!7o=5=%#RCvjT@lzB$&VxQhE9u`4z(G z^ttobX$0_ca5552oin5DjGx&`(`TzE-|TXCot<`RFi; z_?Nrv@CDXqIO$XWQA7w3b}?udX%15-8~gC+xID!)W7kE3+bd4k zmFQCYixlDE$ecg_3&ec19jk5t-rWsLKfBnEM{81B=(5(T)0yak$G>xcRcigszyiye zesX6w7f76Pjb67R!%@8EG+DnoQP~^%m>Vkn87oi55FwPLR_uy@#Zv|GWPcka)0&%b~v?2Jo(jWrS05v?Ow8!SRzx+SS7Gb zF2--sI$`BMOoyPekv;{`dC?rcp^*D6cj@xm(#OttB(kGfkjwA0&gyPN#gJ#KTS`9H zFM@mF5noI%Y8;WO!{9)^GGa0>4DKA34~1J_rPICrb#7w5Y8*J0iBhE5F?YFkoZi-1 zkCm(5R<+=Lixu$wUA83(571mrcO1)Ny$IFRYe(1WK-;y^-@fl`sB=e^B`}4M6k81+ zH6~HAcSi*o_i0|Zl$_n`>Cd>LbfN(dU*?EqmPq^UKaqf2H)%Y^E9;ALRBsIkv@mt z?)d`Lt$)VhG{aal?kLrkp|1Y%KUmZ-m4;RugN$W|I~$u+bMsO#hAp0L#oR*A3DVPi zpky7MTcbNTZcv+9u0EfM`ZgBm_1)WBnC#K?i38!z0t<+652=EhF3L;W^f;A8m1eas zL%oZ-)(HL*{B60h)|5wGsT!2xk&t3@x&4~ER%{-1|2c2|G$nyc&cQd9SLMlXnbM3l zuRL9mp*+&moFDHlXAMi2Om;8C>{q0{*1qi^)FW?5GMts^l%EOh8=HB{C;R0ddV8{K zoB_c{gOy+Dcs6~&z_729asFvTZ=4qE0?~F(ey+_-e{T+4B7I`=)N?uq1lyMBaIza# z=JosH-Ml38rlj}tOswLZ7H5+<6y!9_?+oNrWepd`?zzvqq?(v!*V8ZF1{8goFY{&; zEeJ#e1T?FkP4{+IObM?J%n}0#)E@F1=`gJJr@Hcjx`HVM__fv~I?J7x1Z%>9rEk_t$fsUA7THrS_M}>t`7+F{ zfM>;3EWR@ml&LtssY(cQcGForMR$6CTz6)hZKlpksL-O*Tq-SG{_-HH#i8Ld(aLlq ztF_G*t;O56`E9YL-eEpG`^bxY`cDu7AE25ahJg2+WnJ0Wwy-=ta5dR=U+J{KHQ7#@ z!c?jDwhG5Y@wx@I^51hJ%Bxkxm$YyIyXEc6jj9XDA=svdo<0WN^3_&FUAJ#>j8wWE zWtwd*582R098SWtj138H1M|j(4K+HAD`v8Y8ZM@>o*pE8}4_~ag+Klp+*UTJ9deRtU6;d31%Ks&r+! zCpbqO>|899xAjkK@M}ESocjTYevrTWO%>V~P4Z8Gp zTD8Y3yEknPq>HQYbf+-PEe*ABo=eW)2|bnOjN5zLIhA^joB!v(L(!Z~-M7ByUUh}j zyuWLK{eo$1QlV@umU~fO&Ag=CMXp#00cTbKmv}Ssbwg?fX&#+hI^{Dc1H~ z9!TONL`_jTZei+RW^KOXxAI;Rs{F^ap(9jhkFpq0ge;eHz%6f^I;U+0Br;u>&)J~* z7?rS=^F>cu)P3DazHftri}u@(i+DG5ACl*~8ja+88scx{ z09m|DVyuKO|A}FH^Os1g!$W6jib(5{=umBKZ?g5I<_pyvn8m769;UR9%_?p8Stl2U z)7y`-_GcX?TyDM|Yv=b|r$5_(3?c5FFa-h^E@aJ9ja|J++GhF7Jl=iZnK{|-1Y>Dx z$lNOhsqLnI*@Bdxyfoj{bYbN|N1q;kdU0e#Jq@}ENA;r#7tA88s23u8yat-+vB337 z^sJda?x|mDG--4EZMa*#!{?_N$!#$@d4LPH?sk{b+#QdFAo#YYdl*dZvvs?2HbX*1 zsDT$f^X%I4BK9$1!QHD)@K}J5HS~q7OEu}C2$XfK{G!^mI9w%;_mzqI#365 znOc>|^A!|B~fcZNG+; zG2*Do6qLR{qZzzxgE5r99^o5TxXLn3omSO1fVIzWB0@zzUxt_vbiFx#*2O(##R%0r9(^0JKo?C(AAWFq49kX7 zsYnzH%>}4)B9ZW(u>*M3L33fwQd9I|ntRIlB5|ujg$^Etzckx41%29OtMuQ$Kq@se zzhmSD(Y=}yqQ}Lna(q?-CJ<@4i0T#CYZYhjTDdF6NU;gDPrp43@cW(fY5U^IQpHn@ zQQ35am5-Nj(qbSpc}DdHT9+?AUOw2NSZDJ8*-zS@QtHynWerSTmyG0>3ch~A1h)IS zu7lX|9|}*nd|{{6d=t0hXozJ6xxyo3DF#O5(5#ddZOeTwJRZ0wmaH!Yc2C0{cOu-b zOQ&AEZ>IH}sJycfa2X;8=EZD(mRn8f8UpuwF*25h{C(vfjLG6f6WL&*CTm>9MKzem z>~|CMw>Lv+J=mI&z?bvH9qPeupE0Cn6R->(ES}!@b|K(kUHhHe!4u-9J{(TSA-dNj zZPaHbx)F2Vd`x_5zY!FqqwA#oS;qlZrpH;<)ex$4M24$1F6*yf%sLhKUrSxRe$;)U z_h<6GQo0&b!*eC^bdWgktl(5RPk&dI@b6nPX>px=Q?kK}lR^wH1=UI%o^{dYy^c}p zFPx_X7bJAdX8;q4(sN>jVnJLM@uXK6a|7YzLyxc4)b4St^gFW?3@_|jPbSOt*>n)% zT}R&q`V8}mM17&%Oet2ZQesIvpkcw!SS+{uCQ2l3wTR(H{z*h?Lq%)trw z^Q?iKV)YlYoDJ@-w1Z}eG(e1^=(j?6v}ShOWIJX0xeT5CzG2tUKB}_KC}698_PPm` zoR5;SP>=gcz*n~M3yDCikc;{=)@kF{P+=|+sKKO45+`RZ-g+iaIbD`;Le}CCYCZ@_ z_^)B4YT80Nn0zoeW~})QW$^4Tp(k;M-Z7C>9N!ReOW^XyiJ>kE_XWdW3W-vzo)>K* zBB1M`Ud_~Ms|V! z3+JwyF`bfQjT}IK-<3u`R+>_f%&KiMv{g~QDwS3g^fYB&2H-zhF(GL$t< z{Ue*1*>|ciJ^;FwT;@wG;#xjrzVBpC=N0}fkuz?JCmLiJ0+(savdenx`qxCx2%&F8M}rIX=R=`EiTHV4aX{-GLGRkCHP;0{+X$#{@2|d z>INtj;hAxp0jD2$$(12owITRvcqqkXXK9=?I$piBVIkhcn>-w4%!~6c%4Qx2^j^+c zkp7K}LvAVQvDpoI)f==x)vvl-!MEld`;BMNsje^{;N$+L^$V*P`El%4#I;AiNh+Jf% z5|d|~LW!X`igM1q?@v*G@%zu@BU%4A!A9d$*TI|ViTD+^_Um;TPHTfHN>tdgn5}Z_ zqz0L4lDk9$N6A=ShGQ@@{-L9)&2+r=pJs@LNkxQ*0F z=FN_P9~9?GC`(8)-Er9#*RQUlq4pY>%SAAo`RqEY>A9UQ5tJ({`>$c{5;@Nx8fy;0 z9|MJ46hWU@I;Whg_noS!qy_bnzrMsGtL1~LCKZ|?V$O)XA{#sp>96wXVy7z)LIU~O zR?6t$U-j!$OWN&$xTiWrn75!zuR<}4&(_(9IH3ATPh~NWIF>4eTdjz-lM-J)ho%&hZmr!otX%6^+<4xh~bV4@4h^sF@${20tP7Du(T&&rTo|kVi z|170T*`yxEpHC_#%Yl{VJsdf;A${1P`#=%aiH-HMRRZS5Af?5s+bsPcO;|w(>haBe zc=>blZMV7PN44t&GI@bzR_0VB zdC5jU>Eu_b_XS7CbgMd~HpPFS87sL<tMVd_$UHXd<5&*MQg}q0p#ip0u zPT7aYPdlTxq78r_XV}1vF9!uNH1-+8-zSYX7jVR6S=`}f=wM;rdJy5@EZ(#{WkzVyp?CRsXtWir{ z@Xh6(O8bFeD$wWfje$;HH^)jkW;)Qq6vPCqj)D3-H z^*@@X$`;b^-fEw^jn4s2B%0y~1YOShH{XmJa7Xq2Ve6zqo$Ci;Pz^$vi6% z|0znY2$6WICJh~DAhhCmBD7gL6{ZfFrQY9$51gDvP)&`IF@S9o0Bidk>6mUwb`FrF zp}H%pTi-Y`&jAJv;8S*vTD=-K)LCk93eKF{O^M5mrgtQOxy@nYMEz!VO&qgmVuOUA zBx$&wHE-IZ?{_6($ErwLTBIFPjE!J`yUIvqTEDh5oe}W2jthX8S}itpY-@){tH<6T z?7&AkP}PkrRLed_*8(48TwZbos{JeQQ~Bn9`y%J#LF&OE8QQUF^|tCF_Dw_6w^N;8 zKF4&&nx{6&0DQSQi(J1_s1$mi2Wq8)EJd1q-6SdmiUHt@(ll;p;D9xUx}MF>`65nCFOIib-IfNGKy8b}Kkh(O43HsU7hB+R1c z<|aSW!89)~QQU?TMgfJzLFDI_LFcuqQ{4C3``Ajf%qX|MGU+Z3s?o&)yYLCR%o>mZ z8BGr_!@V1vizFFV+?EKuZ~>>vlV0&G&h~z^IrqP?%s>%*;=C@Z-$ti2pJHipjrjTM zL0)l|U!1qxSjvCSLW#7u0%Y_8j&ln9h{Iclt)aL+Q7`yJu6#mQsyFRK_sQ?0&1nt& zN$2LrFiLJSBPAn!eFYdK@C6|qSaT=K#KiM>N7oDLAwf*dn>l29{yHG_suA@pqT(ug z_B?y=?G*_mV=BKXw;JYkroI^f6{i5Q1AfM9yTq%LZ~7V!XfAb(6Zz|2=^lUCWYHV> z;!a0pZqm_XF8Z1iX+_yB8_7}9IFE||ShH1vx73Prn}xwY(ldnBf9N?;o0`3AzrQF6 z2X*OFed!0%xPszGwnC|3KW-pqWZ1Khoi~_cvgK^Ivu{3hYY&U+O;i*uHCDp9{n|P{ z61Nq6^a(I+ar__1pFin+{_Iv-0ag6JmIZ@UIdL!)6J_6WMW;Lg#4G*I+AK;aP@#0| zlFF^4LgW&Er(Lmmz2bgQl>N880NKSNnyH};R_$+q3)hLS?1O9lCU#?!=|<#po}sww zV8$wA>pa+<@2MR9xO<$p$l)Trd)hR@;4bw>Rm9r($K9rEh{|-Lox`T)S<}uUI~v7>7KSWe@B(4s)e7}bZRy8H7DJNCv=k2Y9Z-Gg#1*kP?W z^0Nc@xD2wDxy!2xm& ztY=%n9x=7!i72L;GF!XzE6yA(&M6%0wjlR9GFuE>4?EJy9g)l6%0ayJ2dlYg|AT{x z3oK;9T*^aUJ@}j~&#w)WVp$|(Ni}+9#YHz_WyETq5H#HecX}71xcTZI>gpXrDycn~ zvtOd8m?!@nG?9FXH?Wi?geSsKJoaZ)W*;eZNRA9FEDS<^!`mUrpt6C$zhRE7%QZw~ z+f8txG7Sl{;ZIH0L2H&GAM%oYm@YW?i7VWKom1yqW$N_DU7QC{Adeec;h%ol_z|S> zD9|DVXf$<`Y_|IkX+1;xVW@L~n1=qmy%VUlq7({m%khg~AlmuzHOVF(RV*@ThpVD~ zkUOX$7gfym%CygvU()Nwl^D~_32+yv6;}TKKvZ!w^Z6Cn_>3pqTUX%4HStsJB|g<} z*)FZoywPqFTla@&qxU$Z+-T6=Cbk>lDfp?DPqJN7o(V3FV*Y{VJ5j(A1m?MFQ(K0v z<>Cx^-_6_T(5C zrVU@03#10S($-Y9=r<*w9^G(|e-aCZy<>&MpLgtY2Ey@VTupQDTjW`sK@xanH!^x2 zO!3A`Yk@BgD(6wJMaCKRTEr!Gl)eJbZQ8CjJsckIyBIsB^B&Hgh+kaQ+Hdq{V=$K=ea->62mU2Q?#pAcKtuE>>;?P^Pcx={gGq8o4S%Ttw%*`(9=YLv|DpbYFM0!Y6fiK#ECbXG2HMUbaAg&?-Bn? zx{$9rprnstw37cX>GDM~4d%5hfROOJT*>kros}%<4aL=C>_p{W;3*or)6UK6-vHkB zHw}i?ThH1)EE9UJ2j5PGQ$bAABr0D5aAK6(kx!}loXiE3>is?Y2E7IfWJW1$7)i2u z%CDuL2am)kOzYLM2StSaa$WMp8yOfxEtX<7qPdW)ffuWZJB(#Gvw20h5$W?fwDojR zmzo#UFFEr(!z_Ye2=af|S+H{LP0Na9&p5##XG<|;buWSD!)U-vdES22+|o9#-L`Ja5JI&ZpW(dy_gVk??)evuV%e-m3D-_H@N z#Qjb*I^15oUe#njai2`*^roAN7IEYsj&c1!-`uGA7yr*2!h&>8zzv)&eXH1gjUs-*=!?d z53%r0=V`_?CjG8iDfXYp5=L-O-4?amqNC6 z-lMA73l0~ot#@$Pdh}+CM zi10;fu&97+^EpOhg5#aneGbU!ix24s^Omw(n+X$7`gsriU(!=h^}atTykDnyGf4EL z8Bl286G!%(5rVi&3Qx@%_Lv)^Xa234JDmfcJ$~vtbkc|9U&xg{5Ub)w3Lv@KFsuuv zlJz9Ev-Ui1kClwh;5E3IJt6RKEZU((#IoGTgx5jZ8cRa__w;Tj#l~!dVF32DSRC`R zUcN>W`oYoQgHd-?jG~E>w=I8Ul6GyGBiM@2qSOr6>n=j6bMOzaOITDtlOv@e&0jn) zPbs#g42|AhM?I&n^*!dxACL8&M8&nfF^NiA5|6+HF20NXlqt#}9mgEK-ltCgz>HP* zSS)V0Nn?B4sl6vG$%m4qkx%I81^yE`StoS;_AXZy-Cn6aUEKB9$pgIgYhSe!|rbKy<(srOTY1QHhU;Gde0rs;9ce zF?pJaikuAMK{w}L@s@ar&RVuG`$aA6rbU*aZ7dw8tbq3ZLL*UUi$va7M)U0OFbOX6lRmW&2i4lB&VGGhKdJl@ppvbe_cuAk zsvld4-JuTxV*1OarSRA!SHDsiU8Y~r6cS*R_?`X4Mb@6%LXyW4EaT^CQke5|$7UmR zvR7f5SM}eEkzG!oNm=`4hG)b(M&*E%mPV-X0jxmpu2~Iii*J?h-Nty=5c`#7q63o@ z%isy>Nb_tVQ>+P%ae?eA_QDP;Bd>UTu5nsQbV)4Nj-u9@aF6HH_9w#U{cjKcJAQe?II7>ORn>ELCR~%-_V#+_}t&T#YPoxCi z6{K#YMPhbz#Eaww$zP7=87jHMCNSCOk%QGL60aY&wxEEv)W;cq@a2a2bwDvz;sLR?LiE@-D0M=wo+aQCD%yB`kyc z8%&>rh05psCD`+Q0{DBhgv+7U%_%C%%1d;(kPa3?r6_U@;5P|++P9{_t-^P;j~(8x z0Yc3PviDj^FP%?T8~C8FOPputS1@hV3QeiRg}po8H`m-0(dfpP8a7X?`{dr!)#@jk zfUn1P+#<(wX(g+8^OR(0G4);6!if%B4p2wz$IkegqJHyS#Ug~h=WK4?{I=Ue?E8*X zMuU73{VZHR0^Qr=bseHTt^Xrk{odED$a&R`CwYW&@z^=OBqNS}rsK*ZIhMdt)X|Yw z?~c>D!4!ji*QEz|S5c^1cj7^5Ez#YeRZ_!;OK_aP{Q;3k&rrLbFShjx-QstsK8eAE zC+NEr^`OoE<9u6%#$NHteBlYHARW&5U#^A#ZiOUFhZ#BEjn=Z$;F>|FUe*MZ0cZRB zY;xhBQM135q+ayu#yVLWk)a*Kt$d?{X62IAd`LbNv=Arp zGHt}Z9k5*fMXQN60%hmm98txZAWqY>aLa|&RT)pBGD5B_0iS1LxVvP!1NQa#-M7E< zpR^C-t-b5m&~nf&V$~2+;_mp%YwCXN+pq@q7xL#O{&ZRDrms z?DJ(VuZ5?aWap$^#!zy)qz@o^=cY3FBskDf9{wR;{Dx3%~@`19MDYOhasA2r|- zx*XBCdW>x94g>|5G+NJ})hPST*Wl`}P@V5?o##yko~~Bb!NNTlllsGr z)JgyLKMc1kivRGRF69C&XzWZra%F2gwg-S)YYP3&N(=pl2s(VdMsa}NV@+;dH!m@@ zHu}q-mF&!+gu&Tbj3&s`JHV2<4deM!8`AkC+^;D;7{ls1SO1#b-vVndz zmhsjf=6c&uG2-qkqm+ZKOqNYql(FCFEWsdvtKMegsXN9Kz&km!-)@^L2d9W8 zhWFNKfk}I#7bq0q$|LH%G&D=chkQ2&#={F(z8D1tNeNDxt#h1&KB?V*X3yXdEhdDQ z0Jft@DS&D=Sf2cRJ??8Aaj-WJ30^BaB(YQPZDk!(*9pdN#wU}OO2c%(4#cvNpS`Zy zCyIusWnf_}&`qDnZP29uxk81dm?od2L-|q7REvO){(N`sLH%;|mIUCtjdwFY-NhWB zKi1Jtc67$?z0b1fwU7w)IJX*IY($}I>yfT_M+--|tc-cz#*Tk(Ke3DUD1q$(_+ql~x11Gf$QFjZltYTi+` zqp@cx%Q&h3J)qf9he<6>7H+bRR_-z1Tm&$8S`>(rY)6Gu)uj2vaG1{a48LH@J5i+< z3Y)*n@mRk3>@XfwA0TkwV99u2+)#)LJfK~F(Q1GAcIoYDB6MWRueSC2i8#t(KZL6& zhu)0lI{^OVxnuIR*)8X_g1E9Y=wG<}T)mn0vtQ!jk4n7pNy4!WL#RE3T{UNV+C^^w zYtJ^|N;v;$9cm5n_xeMa)^{MM#<0%JJz1inm0Ka$#xFnD)Habcb3MveD##8TizgX< z!uK~g<@boYneqz;)0jU>(^>DYQPk|SmAf>~ix5qeX7uFTpi=UQ`@1hHq~|ga?r1R$ zCr8jDyQ#+$B%R!9;}qTGU8$5v&(?eUVHf@e6-2Fe^Y2XS&WAs8bkqB@OWq2NKSb+y z>nMW4KY#ehB>r(xgu`t9-!#SbZ<@L-6!APonazW74^^nlT$}!yimd$)LlyIHmHX5J z*vDsMxV!I?$GTm~kJXiZRzyI7YC%_91ZR8ZkHk4%bLD5;uZd8}q(FL6{T$yRR?n-A z$ckFB_5`asEJk?>c^7r=r^71duEK;(Tb4Z0yRA zOrrZ+!iN{a2O1-;EB}eg3}FJ0ig`u7mnXl&__&_D6}PlW!WV-VpxY=Ljp6I2@4a~4 z_BP3V2CZEDg>qh6fTQ#E`OyVZQ7E8N`?{WAZqXOrhH7MzSkl<=4{M3yX?VkTaiLch z|DjTT?E;LQ)!o^!eUYMe2p+-rZsIEK7+ZrK%%NbN(|}G>aN<=p6NX>0j*5y z&h*M#r&vI!y=kV{aUVN0Nx&`x0xQt;hlAJWb(Y25P0|BKr#!rSE z^4wmvQTiZJ)6Jsz*!~R)AG6(K6zSamL`sI3czxC(g{eB-GMqG5GxnQqK%NP-C@D~H zlr@#7$n+@H_O-pSkIpM572bK50O(#|xB#kPP0*z^+y^&R128v?S5RGsg&WmnP}iXB z&oUhyCcdHbKYZos^Vt!z-N!QOa>8EmIRebon6^VbN@FIiho(y_B?|qIT5}egoQ>1A zsk%Qw%zV!UUI-u=Zgz#b%+ALUyT6$8^4A_We1QiuDe$sFA|K`CpY?S(Dh3Ri&qc7y zu0)U>NLlQ!)3ZO&WFY-d0(J9QlSyCTQq`i?Qvj@JngZ{TGIrjnfQ37#kKa+lI-=2< z=)o#$L`5`n|Djd9NUUQzku?9PW&qTd_C_f^VnEn3BLxAj}v4sfc5%}{Fy?Njn;UqqZsHj`Jr>;yrDkXo3i@rPQyvqk*oi4g1<`zcc)(0q+)*yR-S;r|mzXg3u*9?(9! zBjBE7%z!A|9<=)~QXg@7p;muP4yk3snNwWy1)aZAg06i{5oDa|!__D^alLKeJN@|1 z*a$-QA?Axr4RU#PWijAZAC_8KU+G*N^FWpxabYTbx?DeKkg-yUO(}MA5@7q(O%mVV zRjP2U2IOM1X^_G1fbFRi=wfGLp=J&0i6ctMEevId6^nvm&SlIB6UvAJp9@PVhZG8b zoH6&rE*Bc%xBq-f(>v@z=mCWlj8fZ_?055G{4cab#7aP$=k#Gw?j%wAp(!SXmc(vs zkk0T;^<(CrCx<%u4}0%}T46J%IOnSz$oYObzhZi`57pkex}@JP-AJTbf9)t*I4m}i zKv`1GAA}7meUB9=Pio4VLjOh{aEwQDO;70UcQ`soyDtk$ez(xvMzK=S%WWIJs4$1S zA4(sSz`fhA+pazLFblA?T*5!Ma1_!hq!N;P;+0_Zbg^2#Uf8F~{CMmkAfUzDW+zg> zQe^v>DOejKWdAoc1)MR|CT;NGI%s+*z?Jo^*{c|AOxZU_eW=SV+jy#R`TS8i~x|4v7KAbk&JGF+> zv3rMQ-3;wYy=v%@@IgPSwQlqi7kP|}bly2l7Ra|?`i4Vaigvx&>h_ea|^SN>YqwId9#*zkxGE0ZEOq+sbV;;gP*qrxc*X*FmwV zET5$*y36iqPhSzX@pyZ9THm|Xu3!{?l6IHr&fNjzhs*?TnNHyXE|bkJ4U3g1pLouv zG@sOsSGXGsahIt^B$>7$@U&=wowH|hrrSLt8B3AGfBrBax=<(Nj;3XM{-awL&+l4( zi2fj1=D{^jtxMeVqad9$0@hJ{{-Zm27aQia;`ec?!A@7gdUt!h>B?r7VX&Gi$fW;A zO2QtDn=@}Zwb;qg(;rN6zeJqCF>iVnR6z*(KkU6_RMlPgHVOy=qI5{32uh=LZV>72 z4iTiJyGvR^Qt9sQMjAo78$r4|H=MQczMtp+p7VY{T0lfasI(nqR&kkB3~g0aP1i+0MH$x*d8k-agW2zqKSsL?z1ZThO|d_l?udP>M@x1mSGa;0E8 zU7n?3cqE;`p5*_SPzpJ&SEJE@hVSl1a;)qFa=pvk>R5$Q zxDA(O3si?oAipw5sV+=g!doS&!H#j4h%I5kX@w!bpd^E?>n6g1ls!|yl88X?Ni1ij z6V0JKK8M`F3#z&0g9KoduB3$I(!Mx7?2p0=m)}yhmhW5Q@4z}1e<(#2`kK4me!utg z2i&p{jc`S06YiC-$T6{n8v^lCbuy#k-nQ?^NS<$;#GcU}m6XZ(mp4W)y;_?mY8!bF z?D%&8+{d#b&NE-`^EUzVYr6C=B|KQZj*H?A6q(FQ=AO?uKi5h~zwP8YP9^YL8Ok0Y zf%ru`RfK4|p+_1G zt)$P!NYfEjy63%nliplY%c7Jfj#sVVUilI}Fxy_wdpSm3#Ob)TSF+Jib|raoYY0le zNXFiI;j%P<@ksUiw<)8@@J}nd$r?tf2vR0LA?F~Iz1pQeo#fbD@;%Qv zzT1^*&39WHTmw{*>3)oA&S*AB;tXD$?MmMr`l1_HC=PA9lxS+47@^bq_hg7)_^>%30wT&N>$PZDUf0K_WFppwm z;@%eJhxQth*gcebf45#Ah4NNNw5E9QSM4rQR=f3{QgxUObVkKJz)^WwD8d+d+a?#&ns$(OoRZ% zOh}Bh%4WdSqd$Kky{ce}abki&qc6S{qgOWb)ozlV^Y3ti8`ISvMRbG~@De#=oOa{W$@H<(0ntb z&Daq*2RD*=tcyCfn1`>ufseG3V}Vy_J6KP#Ocj0-I#wfVX*xUM-1h<9N24~?`|504 z+TLD&O9EAT9gr_WjhAS-uICnGS(PkG6~3N=C(0@dt)@Rm3@d*hhmJLzhmGYac4zB8 z`AvU_{o$5oY_C~~j#TMP!o?Lm z9Bu})s$4GU7?Uz~LJPOqetG@kBKdq{lp|KD?&GB-{HghUkHo88fOWLb7m^*gDSxOw zxH(~IIX|2ZQt|=41Z``R9@#AzJ=rNuUfh%fChlmrKhkxxQI2r01&RvAFKTS;gE<9d ztdCR_-|w5EdQ*5S3P(Vy#|DamVk8a(sqfKOc_I|1e-?EriFdV(sv@Zj(@dHvmTlQv}7v zu^QX8-902c6Ct~F|Efxqg!(Y<2QL|y812sQ$_kvgubJ(<1Ixy0w=bGBj3RHulbm@o z=*4?41PP=6Tnq)>r7sR-`RA;w;HjSoqwk=#)&5$wu^jAt3-g7QaCzz#{ORJ3i?YNV z7KN-%0qDCXcALj@JaG=6pSZYPZPM27o}~;ecS_Q!Ux+^a-<2^O8#Q9||@bl)ErH%;k^ZbGKQ(Wi|a+yr3iqR3*kOhZWNQl4)O6qQpz4N7hWOkNHZwaW| zO2ny63fZxZx4xCEv|j;N1^$9~Ffb_O0=8shD%hdLx+R+x>Be$50D;}~actub(wKZ* zaN~uK{*N>&x+}xTqhXV0ta{HjFz68=@YCoqkL?4PA-}#fTr0hLZoV8ZAb^nL@31K% z#A9E8n#>T@DFC-FqwVp!(z*~Grz8dDP6_^Y-d8-O*MV1%PmS=RLCuZVovAMCyprt| zE~A(+>Ym?R9h?|FJx;*)Zv;+m$qHU&Bl^7^G{8SQZb;Rdp}e9Ok3N9l3mSPnLiWg; z3#+n-O^oe7DoKB|9)|a!LS149-BQM|46Rww8EUsbFFya2=yH@yfYu;k&zUTp`r5 z9?=?Ars6o3jq&v{4n9Xb)t{!{t`sMWNygG=DavqtMd$nDHFEj}#t~QAZQ9hIjG&p6 zSb8)mJob#ezCja-am?fUeP4Zmw6Ap?%GW|&lb6`(5z)JhC?-z$-VR>MY~5OL=rS>u zjJE27`$RRS($4gAJrJnyXmG{LT=#_2o30+ae4)L{zG&__M%Wu`g)FUQG{gDrSJ^lm&J_WC_6~*LCSmSL76Mx*<;=X2#|J`VcPFgqF9bV|o-fI}S8nbRe!1A$C z`ut``kb9@pw~t3eyk0njXmiVqdZhGy!%)lzrl{^pms27z9FflCd_G|K+lY)ZH3`s6 z22glhOK%^xW+YP&mu>4Zc)2tD8Eytq{MTwA%hr>d#vZiaJvB|$D1Y$a%Sem(qGiy6 zpU9(C`%-8~9=ExE(9L-AWV~6)9yzbtoo6J1+*4?YW^Q^0!_RC)?Qz{STE1oLR!Hl6 z#z?774?TCuJ$JYVNo_ZrILG6W3%|oM-9KQJqKv&k;D+iu+>XpfNtKa*rUTP^#KwB2 z`}G9RsPuvz-P%6PY?*hp04`+fRJ zF-4m)hs7}%b8RjyY(%$;Pi--|u#q!(`zUVwtQa_nOPRzHG+)M2$p5MUeu)NN0gPEf z(muVXI zW)&v-(E(hWUrgly(q)10d(tWAedi9-zY9nCrijX8m=6r45n z!xHBBWu!-Dcsxl_Z-eOx)s=_5@+zsNZrl<_YxOac@X1F((O86tUHV{X9L1_QQHO~3 zrx?~Vtx&k$ubjOf?DO8$2F8mTITvvek8%0VvM_wlJ|lHaL5GFg*@$8cT2lg(Us{6B z=-qqwMB}ta3b8B_onH2TKbDQRo0P7cchF8~>PCjto&?*ZjziBErs=A4M%NcBb*zWkcbOUC{E0Grt1swjE4DLtkPUjo2_ou<*KHe)QPbDlLfMrg%Xx}i z66-2u|H|ePYSOdsya)|=x~5zVCWIw5cU_<)s3P17u;ik9JDbul&o)j};Ho3i zBVX2iL zKS=6Sv7~!l?Uu_UC7k5ct`N{bo7=!tw@XBY7WRHy;!V?Jn{l+u_|cA!DKoBb?~Y!U zxuoCfn*77}UP^Mvo$PzZ&Qu}9Z{0QCNN-iF+hKvJ2d}9FOxJ#?+jS?M52Xc=iLEEO zx>R)4t{X*}bbWE$s7L)^^26&EHn0Rl2&0z-U($DL_Qh)!9g=v96Lnv?w^>STDthE3 zyI_;LkNsX<-)}s|h_^e3+I3(B$gdA1<))>5<*u}pg!D;B*D4mCL*@EG&NBfA=8CyIPNNmms7&Jt{1fDX|bs;cjTY7EpAVY0yc;CR71NA zK{z+0^5jbHouAaF$ro+#_*7G<$78{RZ%FdltAu|%mor1z_y*a%$!eK|d3>XPJick^ z$@V0V{6HsHTTCt2hWwCKy@qurb4fjhR_>3^PoI!ON-V|-`c{6Pw-2g}pt_G*2Og$B zLl*z}3@zX(C)K{x44Z^;Tg52Y6+OnP#8-wC+EsQ*(S-uj(W2eo5XJnam$~<$53do9 zeQYHboGVT^L}Az~8ZNIz$}@?~?I6zX*XzaXs1`^$p&kb0jbFBOg3f|=u;X_d9+g5h zaR?b-@{v z?e%u41m{yfH)GthDXe}c45}vII8D_U8|PTWxRMeKGQ=yYpXd`?o(yG^4%tPU_m!fk zfvpo3^n%2+gFYEjM~@5YejTXEbcv%|iXdC-)kLVKF|?6QNmW;L_u4W2ik-pK$I=Wk z_KpSlBlaXQ|8!-TN1eU}RmrK4YHFa}S4U5x5zA{IPXf$4%ACjL#7bj>N0*BFim;Et z_jaT7I+l&LfATA(`;;C>a}dzWrFnk`9gd+sMqdZ{GQA-3wAQwBab&UgnbeaxkFbeJ zI&(L8Fl8T$RkUf5cTvZFZj~c>YzMu`>hJlb7=d}z{^fHPDWnsJ-I8U=bO*loQ}i=y zJ%PGtRii*o5|8fV*ND5Xtz4mCoxMNv)~1Ny;9`3 zk?cx68i<7q?mJCV@o2Gta=Wa7VYVD+bG{C<=EilogOEi5t@V_9>f>$gV-O-6FI3y$uL8_w)59d`4Rr-_Xa$cU8JtS=;@S0x=}j7!;Pb#q5+dIz**AID2`9RAHp7D6#GjatrFpza{~SNu2cR3?rwzN-32OypTqnk98Ff@N%KYWUa9QV*)T ziKY3M#K{(qm>)0uK2BIjK}cwOt_N@g?gMzOs7D3idODPo70X-UgluI1%a!VahI}7^ z(4=9#_Y+a*5zE|jxX#Mr>U z$-DyQL{OFIuj=<}U)&Z&-DV94F~v83#Jhuxjn$}Q)i zr0o1Rt)bjGv#CaK-RlZTgZ50>OLN5ypU5Y6%tE|?ZPLSHehOdqY-di-iqhnH{|rXF z7rof)@`WGpC!-S<*TdN{NQcBvPp?YR8V}9FRegS;YDwroy$>-_QZ#SE6Ipt|ooxcN zyj|=8FhfU-vUKy4Bw$#4Xx(s~$sO7W&I)Jw-;w;CdRPvlC=qW3KL_(ZB6MS{oVH~} zWTptUCY>oucoGr((6HF%cd@vX(~#HmsP9SyBHvX?I_24#R5hY52jbbI``_b=zN{Z6 zY}-F3GN42cb|>R$3+jK%-(JTvXs+7RCGJH7D#R5LGCz8<23Ry-GIAgMaNlf08?f-d z(2m;6a9m7MmtaYDIk#D4Sn6YH$vkEv5!SA^%Wl|9oGMI&+#DiZotE4M3~+BNUBzJq zu)3D;M0l=#PF4lPAVODwJAC6pJdX*v^ygGj1o{-Dr$iibOQ(N6KYhE>6z zuYuC*h^zPmF9Mk{yq{>1>~=(W1tm|;8I|D+3gh0g z%6SpQ+t=nHNBG`-2~=t4hwnBaBN*dsM6U?P&+;u*l-wHxFJTG25*DW5w zol8M1x=BkdG&PpFkFMk|+O*~Y?Wy=0rFvM>_1n~2GgXpVCdB{e$y)#S5?LB?i-*Vc z_4N|mjkTxM4JH$X;TyeWal@tW4!%&-BvOfo|lc>~Zn^-_9| zrz;@YCM8liSOKI`zQ`Dbyt<%^s+iCdXSdPTlgM#=bu3@G;D|@&`k-CW_~PKf=A5kz z6GXEz_v>EM{B*pUl^%TZ@(>3Gsl~TEvFpwDlMUKC*bo`UY@>i z7|W>jnjK<4cL?SaSMz@kIxPaP?4S8FMd$MKek_pAwv{ry44Oh4ouH>_x9YN3EHvb- zJTC5&%}HVoM;Jyg!~f%H8}%6slLOU1+)AQyTCM^N zO!;AQ2EQ0AVd0~2>I9Y^&jFZnYDVhcb`03JHV*4o4~v>G9~FxQ*lDyUPSo6n))=s9>fFg=XZ(x7kcdG^Yx%#CpU-hY$}Pd?xOJ{ z4=3M&fQeCFOS3pKc_Sq0&eG^n z(3iOE@ciT0dzcUeA7sMLIs3<6(3!e_HY(jzMPNrc)5trvnQ^rl9~b`%$Bd<;oo!=4 z4tR000rnbTF(V*|=`$F$Dtpxp+ymNQzGx4QEJ9%HOT`Q*IDo!M*g||5V7su$Q`!+; z&Z_nfr4E&XAaZx*DN5j>0RgkVmvIGA(2u@?2`o<=42%X>+;psc4FlvjY+;Yow}e!l zyj$iL+9*AZyK8Qm6(1S+j(5m6q31@!ky(AYvkuxIB1|&gZ)%pg-f!6*{ZPO0Q8{DIH(t7%~4~!XWT_a3(B0q|Cg+{08fNn--(+8034K(pDi|U3II& zM$nx{Fg}B6rdctW^zKYmk(FDkUz|KRXU1(+;F_TwKSG!c0G?vG@|6&tIP;YVZ^8(N zyTu;(iG`c_j?@q_uRFW!-h$wJ>pusbp88b<|5F6MtKFTmoDdfj70pT|W0om$DUtRF zTO?GEHVWDr?d)_#J=^;b$9o5B$OMvWrost(@T6*X07NYQ0U~ClV`4&@?DzDLfD~dn zw@%?(LL8^|avr4V;=kblj5_q&OSY?^>?CHl$gDQ6b`SnEn$5J)7Q&d3{E z?=0;;x!GGyUOR5Fi_BqgbUfFGg&U2=(}FDAoBh`k;=|*?n?Z5||J*IiU|d=XC6TDP z3cifrGyJ_rP+Pj4ObO+f_&DUt^-%1T--P0n%UVylRS?py8uN4EZ#YcyEaEfN>-g2J z6Xs;=Co4;vqz)L$ul$Shi&_;ZpO{Zq-plMYmVn>uFDsq2(XYKZ=o=Kh#y~=6qqo2U zOk&=D>A1Koc^Dw1Dn^l3lUnTut4T zXEXQ6{STJ+iyh=dQNk$nJ4d3b8Kb8~M3q8F&w&TJ5<+rINPQf6k80#Jkx^5H94rRI3JNwVk-RbJ>|t?8Y|e7!wp+w=bM$bf6&mM-Xa=Z4}_>FdMH@7FCfS z4a=Cwo9z18MTod3o3Zw<`}4QHIuqf3wU!af40-^0q*i?;CA01&VI@jIbN&7oooDK4 z(rgUfg5lX4jTwD9_cLX$v0=XJ(YtBq$w%@I&@=?USTsO!zVG@=aaKQ+3}vk}9KGL% zj(2dROoF7u8;^s^d4#1R&ChVLoK6Qzh6g=wo$9&-eIE|JUc6*?KTgoixw>)@rCsaS z6u>EN7(c~vm4&XUU(N5|#)aLSp>VH`Mdfy?uHIZSm5d5uKqJHW*2J!E&s#KUG#}%l z*K*(1W^8^|h*0T^W$4>ax!v90m^Qi-^Dc0t30bMkb>%&2(&*-=Q0!P>y&pBB7{>ql456`qCmPt$i9#LC>~zs^3sA$_zNsrh zB5`xc%i9P>7XY5{LrG&Y==c!^CHCp+0;8cg`Z=w)r1yF&D>dOxH@zQF72^pb}E0>|}+hL5Xp zL7d37`rZx+3s9h5r~UqB$?luomq=B4HtqUJSo0ry&fa_*wH!498#?2-!wpu08-cKsyazBu!aQ&?fSxB z0=c?V*R_dRq1!B3LzT$Zr7FaLa9P0FSH-dykG)>~q-HD+RWo|%vb1}(4V zU{e6r;i~DVSkaVh`s-F^R2b7fV^*{54uA3Ox2NwHkigiCr{olPHSW@b`yv*E7g`0mk-Lp zsq+#r5%a%l6mRC(#u*!`mB7*wl+$UDr0JZ}yvI8cu84NC7zyW7-@yPKMZZ2TJmQTE zFw0|-JYR0u^#ef7TH}9_iV^TzYQCBdfBn{D#5P2+_Zp)fo%jzB64)pZZnO_-Zf_a+ zpeMGK#`2U^#`3b0(zTISr`w8*t#qI`^1I9V!dGs)jiJIWJUZ2pUSN?f%tS_01$p8Y zuaU%{o3bTCAHwf~{V^aB1AOZz9}Q=a$6@Xo2t)aaX8)Y}b()%V=&^l-FrL=u_bM$6t@U(Mo>I3=416 zx+3WSFGc~@GGIlRYmTTYM?^|TFxh_)fEhYc^&R1=lxdRP!J@jxmg{2J9BaD(innE z4rW=OX+e;x-~D|rH3IiefO>;nMiuF7G~v#Ct-lMCj%IW9Hpb7YI#{~f_;0#f(>F_m zXr|ocKJI0u@CwNVv2qNFrI8Sf*9v_Xf4SxTHGrA$}-*+O}De$V;9T>w` zINm&x|6-S`j%lL53fhIzQAKVw-?!U+>i`wBq=elDqxw%KhE2^hGmRnP9-<21AP9OO z`J3!4=TR>`Vf+ZV)=I12&u8*HA)>TK1Bt?pbIusM&DYGJ#BszX{-1_g`F;MTjkTaj zrB%Q)gx`6(p2IT<+jF?xcrrDe(^53OitTvyi1sznc1GkkHEc;%+KWGe(QvAQKYUVs z-gX3c$t!g~;s!X(SbrwH0@sh<$BN9@hlcUa`q z=y%qXJN;CcZZIl|N*ogwS#+rw%ay0pIp*1GlpQkz2?2UA6D0D}Z1sFy*24EP#Dd63 zwm0vWp$hd)N?}}AVq8}n%_FITeCk#C@AxmQ!V;~o1h@vBi##?EX-*rR0gxOCdMOq~ z9ctLxCj7>7Ax@z|#+-)8`9mSu6(9XPk`$_(ScJ`f2E{oh1Tk-az6=K^bv`oXf@6Wl zQ$hp|9`O@4g-4){B@X0WiEP2>^JTxS4dvaYyv5aVJZ2X*>szz2MpGn}L+zkLA6g%B zX%(!IRr8>qa#s#6L8>t$HS@O4JPB9=aNa|YFd?6-x3%ajByLja#C)P-OAW+-=!=Vz ze_FeRE^BcK8Kl@Yq;=4_O=Ss~HQ`S+IHp7)T5mjhqeC8k1jXyIKSgQrq^VxK)GO#= zn7UCe-#4b^+RBQ%>#o}x#a>r&AYOr^eZ5)8;)yN!QKB(F%B9tDu_zij2SVoAwAJ0P zN5nviZbt5U2dum>l-ppT@iLuXbxcUxI)DGGyTOp(IyDu~CSo2ameQRl)@CB-7FXI@ z!$n8?QmH7yARMOek(qoY zFOUU*+%N;bz#R%1)W4a)Tn@-TUkBjS;J0c>((D9*BnayNpsdFpfE&N_mAsGFnWE>B zODYquy8H*izqy=k#MV!bu6Lj%AAO|fJ#BDX4jDUlIJIgplv_3{Xh{A>A>1gZ-G6dk zP#~K3g634NW>Qi0>vKWc`5*>xqNU-N1B_1#Xbyc{MoEXFBrnj70VeL)G-Fie>(ewo@!mn; z_IPDwfjs}aw0c_imyRw{AUSqz!8MdQ?G;2dh@3VzX{2SV`baT{kCrFtLK#BDn#)8f z@;HVuiDy$fAeLr-lII}?VX~6{LUAmgt3$YUKUa1CD)ex2eTLlg{Tbe}-wwadjfcNB-@g_!f=;(JaXnTV_L5|3v_wqXj8GU(1(1(%xy&HHX=!NQ2F=P5VRQ zY&S=rdZ~m#%US*Vk#u3(x+Ndm5uFkXKF^)k?xWSNb>2>`lJj$h1^XvkhZ=|FiA)2N z<5@~RW5Gw}M|1wGhaZl7gE@tDRIy8*W?59I-Yg>jI%PONa$$aeMk(0)Lng>LMl2%O zxLYMdy#r(XN8f=85SKO^c4p;gDj+RM?khbO@rd$*7GKk%=rIWwfLAU97sGnhMGa(hq+_U>7yYRcZEQXN&i@W^dkL1WR4ty%Pt^?zKa zA+vLy;RFwAXPSBM+Cs`^dohUYble(dgX`yig|=IOp+Gwflu2~5-w!Bsn9~T*#{}J5 zbQ?P_Ew8&0RpS))b4}Z#B_dobC4+v=G_yPCE&DyV)w$7~*O%26m*5*SKRe+@RnRe3 z%w%XGXH0)J&n}lQ;5O zEFKkIwZD>oQ z__~@8ZcQI^MN7H@9Vz6XiIJAi^dk>UTcW>1R~(B1$&ct{ZsfQI0YvJP<_M2%ay{=A z8r_eIXEW6BNr9XsxF1!V;3*+OoguaiG{-|pxKVvL&Of5t3ww54maFglB z1hI%W6}mo!ctB@QVWKDy1MZ!Sv9GUBc>aUiW;xzSTb+_c3O^srypfR&HyI<~J0`r^ zdv^bRorDuaWK@NYmgqcjt@^4(dxjIV^8Hgjt z;N>a_9bSz2R3G|{2L2JMf-`BaGo4flPQAeWIeW!~a~G<*+gP!JF@l}h*eqcX?QWv^ z5&m9m6&RYwlt7`dGmlmX&lG^zW_>#_Vve=y%GE50U28~I=C1bE5-h}AkF{~Kd)^o= z=xsf4l&ttDJ$m-iLapAePOA3WgX?-{bah11Be|BY)PC9T#|EAIQtu0?yq43vZh#4M zNY{PwBv+^H11vU08zV%bYn~L`*7JI3Hg&uW_q_emk@Hpir66s&wwYEt5M8k(LJ1}i zUBi#)3&EOTL>h+Q!#1PY{WkmVo|rMY>Er{KU;+myppc z)fhZBUgX+yM5Qfgd7N~(Aq%K%bIX-u~!mL)~@UvGXP6(>}N7;x(Fe$U7^I{*49C+ zxw^XC_FEJW9-c_nb1N@jH)>6;d-3~AulrcoK8=g1Bh}p+&(gWrvi)9VE?qe7abx`E zOY>!ZMSWIseX!D&FJhh&Rx+g=A3Szu{(5%<-OJP#p6QdMpFmXSxTp#mZN6FcpY}Fh z;}`%=&;|yH6=k`8s_VWCA9NB?T00&}o9x5zw5miFbzo9RvAe=!Pjaj8XJ-J`DzC=Z ztl(aHwfqMwJwZ`9TNsp*)fT~|6D5$(%&osgnQR|ZrMA#bpOD_0D;kzTLEuJbrtK^? z*hSsHJK^OGfZH8^vu#IF;GN1~)VjkA6zx)2;KlDC^5+Y!57}gaL)hfOJ``GgxD-&a z-h(5{`*J<`TCwgT&Ua)1G)1r&P83d;6-ee4NEy2RN`?mga$26Ak|bjNL=j5N7G1SH z(YL_onx$NT|K`&a=BAa}^(SE^r$x}xOLwBdw)pkJcr;siS#k1orpl~t??b>HLQGw~ zlK$?^RfFF0YjcgkXo~S#Sr+-4sfypNc$uFz3|VFXmL!DgNiqbI`|O)wXY%g&*4aYh zHZk~~L2eI!meGl93KeN6KmTBnKmp81gl4eCNP@fiR+J2AnOf4Wna-v{RbW4LurfFF zq2zRhgeQKZh;f?3au18s35|bgab%E$iY}{0!|LmGTWm1u_=1}A&0|FNPNWmAG>$2l z>}E@)8WjxmUF)<((jRVL0>Io8_hQum2fSvtH@iLB0IKUQ`rIRT4&|yFvJkhP>?!~7 z3AgM+Z6}MF1f$zcL+9)P-rL(N9CO%; zOfK0S7Vz`JI^)QOiiWRxlr}NviT(C?zoIy9l%@CT>a0O$SW&rKP6hpJY}G}OcKBd@ zO42yN#oF3n7*pII4s6oFMBMIUW!FK0Fqnv!L@;Nf* zVB}fW>(lh-mv~~0_3!k5dPpa3p@tE$nTcIHm}3|G;6`x98(NIX2MU|9Z|Q8V=il~i zsmCkQMDZR58kts9NT=YqIK~V-fqsnbdQ?mG_WE?cv5Z;i7Y;CxR!B6mPBbT}0FXR- zgqBp0SJ{aKKZtnZm__R@n!k50G^i2Nq_-SkK+fh2*-SS1Nt|Q^8;G-0?UHi4y?Auc zF5COvD4DvYeup4H$FcNo94~DwCM`JFUp!@jZ}g0OCZar++2f4c{wdFy#d@r3!Rmyj zMWjxXOW=zgS?6`{9Imqx&@ZzxtH7~0Q$=T@*ssABmE6E*GEo^c%UDfJeQ-X$*kO9o zHS<7HyD6o5gMxr$LK|YZ`SnLI&9UM2a{u9ormj_OB^Ts+mve;){jwK?5r#EUmFbZg z@vJ7eM-y`HQAj{L46e^e6*fB188lc~>hi}J5D>N}%mb@@E}qp6RDvh~`FQaODb*Ic zYdAkIRjhryQ%{`kWkTcaRl=K#tHmDimzQydCB6>YakNQpSN-&%1OS;_Fb2OvI)(YN zuNyLxA_fu>N5^kwTzq^HX(k!;3D z#cW%_n(69QG+p<-7fZWwE~i^Rg|%H~C)^fi8=RK4=XEi$>#J2{dL$g zLc!}7kJ2pstA;4)#qUx<0od z)48(c0+VY-&s?hnQ|xqNWb061q{1*>ZH0k{!b(>a5A7zGEM{ON9DMLy6G;2$cA>k> zSx;Ni{r2qN;eUSU%Ll#N=~_M79T;))MP~oR@POEQyo(cs?*8wf7+?Xb;j=PaT-t#F z|{g#`^7F2-rT`a-vCDW-EFqk+Aog8PB$T> zaVJ8AHMAYAW+8UIOX zTcrgY>K8H=L6C!>5zg>mWu4jAs>(@CrC8bCt8m4kffBRfRW zr2#x=;jRl&7#so?0fh%jv9EZl;P} zQQy%@cM)UWN9Ev~Dm((ND$$_BXA_@x7DQh(>ILRn+gu{>kgz93WE8kr@+A07QB91YjTem>vqvuJ`uWyHitd)}5i`c4>7 zQnkqdWm8_8&(`z}r3sJZDI(V$hiQ(69o=46tmfqy_4*Mc(?N*sbY?tCI510{7^E)$ zE|~)JBY31cS0*VOlG|-W&v(lKOV?gR_m4y2_Y^X?V3c1&b;BDz%+%5kNup>DYt`kM{5cDUDeW_2gy@K=lpkW9`u5} zb%9Dde0=qeF~vnVg0OHAv?~{^K)_5C* z2vp_d)LsF8XxkHsGo1}~OziEamIFpcNRN6lJflmRHJ)Fe?M4x?JzCAp2#Y56*NSrJ z%mKzdIw$2as?z3mz+sWErrWYWRjXV>$9YxxaybO4Z2yx+i|x9M%_?4;*kp0iDRnH0 z6LqQ0xasu%m)~ps3F5vFPkDLJktjnF2g z^|Z{_XaLtu0I12uR}duyxHIejQJb!G4&mj6Hcvs8f&yH&@(BJwJw0Bu`#*^#Cvmk~ zW}Lw4Nq_?pMMh>SJ%WpmkalwdVjjKRPJXNx3@GMnd}*vhKtOzjCvdbu^*&+gAkbSM z3q{EbF;C#Kh^rPB$CqdKJuu0I{p-=^#e83Eu$lAkL&<^5F;un=0$CuPt%#8dtx$He7By$#~g5 zCT`M#8Prr--yKdKikE-{)>(HBHEV!9RWHPrI6U(uC-_;}In>;?3L!ee!!v=$(c*EP z?TI?3=NQH5_He8z#bNVE*?3M%64ObfOvdgurl`I8OrH{ z?iU_bB#nuSD8*)6<-b9&Ot>J3l~0DMfVCynp77Qu`Zp8_VzNTTguu@1l{(#Sf~@CpK%BR*E)!vlg*)OjQe+>01I>Jklb0T~eJ zFRAZrX4NM<)7mm*L$WF*>U5UPm&Ks*K%naj+1?ySO;$Hss#5}zN{Pu`MSHW629s;}$LI?*m@F=%Q|bD3PYiHx4j@Kt7U&y(+MiANl|o&>gPw zc^EAM)>55}NCTwB>u4Oy+ED_F zQ^LASiK19-;SzoKg9H5BEu;EM;9eaH0=(BlC-1jH#vBTdBIiSlAZ3MUH24*32#D1L zHCE@*z4V!4Xf$C9c#YqPPvLQ(G1cU@jO<8!MzNFy%!AIBfPY==ayqC|ICu#wAurT0u$^xZVB5~n zI`UunZr^Xe&m`D%nBvObELpUZAvWT|bV0jU2E5YB``Z6207bwPD+u3sT3Gswc_24X zpgB#Q8<`L_>|jmpe_25=O5C5Vk$r@Qwrj%4aH_1gzu>NrA(FP^_)yE6$ch*9K+em0 z?HQx6s%~oiB6)$XFKrC7Tr!C?Kay<8M2j5VnVARh8SoTou`G0`k(j1 zdH+BJc%Bs~j)o7_e|{4V0sn9M#_;!2#?SvgS{jJdQMBXa-~aphU?m7R%v2sjaR2WM zK@X;F0jUBk25RNxxlA*Rmh*}|7hQN)r(TLPv;F6+udZ zAWD%YMY>X@3y4Td45)yD^dg2L2BioHD!oZZ5RMoD>C!ufUP5>)aL*<8zBAsrZ;bc; zym!c8j6Fj3UfGMazHfeW&b9V7?v}rejKne55&ZDp>kjkqGOB}05z^m&EXr&$cw0_k zzA8|#m7pEf^j z{uJE%yT4*dGT09W=|On7jdx%4(OyrK+)X>>upg@6^%PzCL)=3F)t^5aDEQuuQ$2dB zY{8z2+?CvKeIf1<9jnHzTQ50$Y}|?y8j9QoD=wcve7s?pcP3sd`C>C;fUg>Ouu5T7 za5D96R7R^|b60L32FSXYd-Eo~o}C_GE2~1kjuec`mKuP^82bp062@M+Y>bI0$uFMQ5mwC;fp`--+gVi&& zSzZu;OCSR4@OhZW)cR@1?rZRY(>oW(3Jk!=ibys`L&3ZCykkH3J)va1m(hT%4O9+$IWBg zk7JCC_eYUK@vN5k94DtIsspD3&^9bw)cX2xfy)7I?l(==%N=j4|x7S%Ey%#16ts1-w%-6L` zTWP{x2vwb_QoP^%3nOu2=;zy&yG$0qncz7h*u@yBpmk|Ls)GR891egiSQDvv>x@C=)x$0|_u^b6*o_9mv_bP;$P!}pYXAs%`=B(`53W=5YYK?GOje(EPbwnAK(JD@fvBOmZuD~JP?3*d4DBTSBT{A4DAZp zqa}1M-kXc;%C>%{sFx(|5?z#hl)8lG2*mw`bN@TxDml|YefJi}(w#LwjA}T$@@VC^RRZ+p{`TXAII`F1Xo&6jbk5do z0cjSswyzAt{wMwN`slc?e4;csqx=PMNwG)o;T#4= zGA}5%ms;ocLZvcrxwAzXem%lWf!(QeEA#o_6d0@Ss=NI?01Zh(q+;J1OIgeVZb9pk zMwI7j;cH3TrR1p6vj!EYw_^sw@urKBz@q{BL|?6iz0_+3_z`w#Ja)W{##eie+oje3 zLx9ax_$##OulYE(2<>p>y`7!#;<1bb!WD%3SIQ;GuDc;qTdF1AZF6Wh;7VTAR=4CY zEJ1C%c4L3Uh(F_+;h+Q~=JZPXa~E!e_NGQwm0m4)YIBYffyj+dCsyL4^3a z{7bv9%-nV%7O6>C@F+992AAKQLpO!+1gGXq?wxJ{qp)m~rhes;?VkExW>_LKYzhae zS%BT(Ding+&qe9brb%E?)#0d;?8_=;0!&|;SI6DQKU?VJ^fiCzB-s#TL>v|0n$j!KZUh}3rnGSx>069nc5yF zEE@_>!vGuWh>ng1U?p1N{zNh;d%TNc(SZNE*-m7=r$=MX0$l{&HXL_mTm)rCqt_-U zuC*i(2~{;{y0yS#*&F)`oZAKz;?#Yt%fN^NzAYn=m7-3$mZ(m2Se~}Ewe5U;&1v%| zeMEenoLAUYsDmm4d1;k=cD+2ynbaAxYvb&b*&-^X*lu1q6RUB%Z`RYG(eKWfS}Ee%@<3)=oJY_yoUTkHpRTT`I$9K<3r8K-q+&! zo=pk7QXM>r?0Xox-rAX;(0W))&we=;)4o`3DPY}nE!H|>9(v)ZhBkFLD^oNJs0}-H z^_j^c0FIUq%(%gWxXG{qg7k30xh7qZ&L%WL8QmZo`T$8@e}rJ%cOSFY6+JQ{6%L+x zCk;5`N~zrDTtwqGKA|_1B9wvaWKE7@Mvu&uer=Sw6DLu4yW*{BxdkYVtc!d43SMbq z6+G!^>BfLn%VXFP8s^sM&C=+dCl&!|PvHYLS~S|PU3+}wDOE;5nc_Z2ac^!$Z${*L z9)07R)zRhnug0zglVK4*AOXA}2un*pBo!DfCQmrzntY7BRrmO6a9yuGmsOb9Y?IN< zIxHX~6Ksc}r?oI7wjkw!l`$@M4f5=(bkHlmmwR}0dU)f<2Z#6l^8R6Wr#saeBcK=0 zb!E#{9g7>AWv9_&8Ow9sT_S93#_4jT*7%1&(2Ok`%QkpM7zh4?wZD&K)yQtVOjSL- zd-w`Vqvc=|1Q#UB%mHdHcf$A%USlh=uG?scw&6%9r^@a|FD!JWU^Q~{jlk6mEKG6n zPM*}_FAe)a9$RE@h4N>@yKSA_<0!`!H)02awJ{u?^cOCA(sPCR_$8VG#Ues-M;X;S1ez*SzT zU(NjN>5UwRXAt60B#c59TRl6fPY3-iStv+U{z9%gAX^G6R=fN>>LQ&mmaxW~>AvfmT?y?EwUcMN?XP!{SbA4)& zwAYMubt&Wlg!=U`?h+^22-RbhB^ZeaHBXL_;KSArHc*~qWZM)WY;&`oFjI6qcbvza z2~IuueG^-hnMAfoVrj6*@>1gwa1NCYs08n_M(qb_cT@z7fK`k3RrOt8;3+;na2(gw zDKAFh5#WPgvD}Rhh9LCX<(JQGez&c@niE(y67ji8OoUx9z;}I*asB|$qdB%yh8-Ua z*tVJLDNi9LO^dWtf^mG$uL(W>I&pJ z@9GTaRhcd~i&?WQ_gK${MvKZaIqP}5O4$z@z%4V1B;)GkdObp|e+|G%N7stk$V0d~ z9=fgy7SI)437zx+O>(f))YAu_xuINhkId?CeOKU!-c%?tLE&Ytt1m!sHd0m#JTH!I zOM>{up(g>7_<7#_BtAmE<;WV2Hq`~B{$NOP{Dcorq*I`EOLwcyNIew>xywAZHJhiN zQ+xP2BucF6#&Ct*+YfHt(k`%548M^ZBoFH>b{mP5k1T17t$fq|ez{PETMfR$bh~z7 zT>~Vl&;b*-2cAxqu}uLnkXV4&TgSE+?zj&0NIK+Kxoyx0;dKbhkB0<_L3qy^R4e0~ z=@|<`XnKD+;Y0YwCy4#Ck$$Vit~13`?PMzW$ipE65+F&9p#ZX*`y@r-e1hBVUFrIA z_u5_r8tdp?i}q;9EcFlJ^q?uQ1o{1T=wS^a{)7G<|B1c>Yc<&9ZAt*K-c-uoxWnjn z_RI1m?(T%9)!7TpAyK0k)8cl?wW25o1D*A3%SE$-@u*;-H|F62^We$3CTSO$IlyM| z7kqXW-9FnU5X`_X6L)hs9sKQ3L~GKRFnvq3fQEVg{WpV~W02H{fkNDR9a88Dr46_pG+Gqf;;2bpg{A;<32(O?re1iI!DgWx|9&PAZjx0NnWq z`~o8HK`sckMQi?uzU2LXu5X{2N#4ox2}cW*MS0eU%;1VUv7~lV-OscX6l;q^L@PO) zY2od!-5wp4-%1)RR5_&$RnmAZxthXL`G=6z`{J8RFTaKJKdWyk#VCWGSR<0yD~O^Slw7(KnBDAB3Ptc{O4Qbo9sx9d8y9h7>nSJ>lwT!P>UK6=>F zmCcoJ9ag}OTKt18f!7b~~8+-P~L6y|M$NErtZb945XI%|TnwN?w4HjRof_Vucn&+H}w|(XJ z+fKQ^B%5Vfonq$O(vbmhJMKhWTwGK1t15uwH-GmsZYUZuWM;xISIHCKUaDYnWGraSR7K?p+2SqvwVo=uF42Q_ z-0%E`G$X8JoeY%MfL9MmKO_O({Qg$jfFDeIs`p!G-hOOhc_q2d(3Nuj)gUONcJ>Aa z(PIrDSTiM-YB6>j^2A&=!?yKA0d)1QS2Pva^ic$C#2N)EAGK#P=?P&R64+HoHXfn0 zEiP*OH#WJM8M2v|qR;WuZ+4#zyvCJ`J`0D;C*>eHwVR=0VhL*PEdEtH6MkTG;8dw& zezHc6#@wD0Z7g1KEawT1U2oZtVo>VSd!=~!B0=ie%e?+qOmfJTSCIM`bo)KmgzkdT zTT7dh%hoh{?Uc(_*4bCq^Eyk^tnJaFQQC@|n!vGPgKa*Qp}m-bTe_hH-&wk1E~_4% zdPzt)+p>C8Wv4t#)H|m!8j(3x2KLg?gt2%*D}@Lf=nu|c&D!rU!PaHP5VN`7wyj+* zOP1B;hKM_JUvb@!eZWRqpk@+};GN-81+z$eEosvWfZXbcbRFeAK>Py$;^slv-)p8= zkY&=bFYG>!HzW{5Op~kgvzp4hPT90XX8>RaonFA_8YuwDPMDZDi}DCf70T$qICWZm zK~>v@m(e8^@&HgWylF160;3j6l(>?PwE7Db9+sYK5_&kQY(77dTx{QB+HT+veAoFM zo>@ScM0>Wgc@U6j7E{*73!~+MqubN=1b(mw{z-_&b3;3M#cPh;c`QQ)WdXtROFfYB zoc#D^MSIPzOx@EU^kDkM$C3`Wh=#!$EkAF79q+Wq3z||xBVKAk>w+} z=M)`gsUYo9td?7DKZ?Bkoc)-Ecox-x_oZp)S0bjnAw}QYKO~Dql+lsspLr-XSAhRkhh@{n#5V8~o9Df@36k^zCo3s#R@XKYs-Z!sX&Y9kHWBGBnl{I;>W^$&+FD~rtn3$liWyIE zmEFz5;zoB~8=XvzTH!n zXrCF>+M7-eVKWs#G6>U-d}7k{jf+AL#C?EWX39YYvN+bo8M*PPOqBlZCm`GZ2GpI= zHeH6R$GPpUVs9#(CL4L2Ha34zv>?cQhx4veCU#BCB@_M`xy&UFWrY!5^P z264djf0|l&>G4mWBL)gxqP_JD34i}irv}Us%Lk_apLq7a#D@Q)iVK4hs^KYXD}ZSu zXXSSB%cHZ7`zZk9`jdu48&U-elurIQt516%Ba!h@5@n=g|0*Lf5|zQgWc3)5`9*!T zCYI(H;wIy7hlbyg%rX5-L@~_hIZ=9KI=D278c6>vpR_X6NIpx!fAceuc_2G_=-AVL zC}a=d0z@_IZXD5(PdZOS(tyClIH#ZVkmR#)^4G+Ao#n5u0Xi1!0@KNrE zNv87gAcntXB3@zYfBp)4DsU}D@V~7B-;d`8Pg~Oy2AC17VIr>CSjCa5 z!Vezskb{*>@wTn;Y6E3n89?npbv@bO(Lo#o9>3ow2V4t4HMB~_LAQdVW3-9KVN+VT zGfIx&%tE^_><&8BsFj6prjQ6y)tCAt@4Km-%X($6^m{)z@Bwa!_Jd(PJ!;AG4oRnb zfcZbhKSKF?lf=gF=+s~~l)RtV)9B3{+AD@5x=$T;!h#KDg@# zF61Jz+1KA_0fNefFG&l3Q{{pV4ar3$!FRT?xyAR7egfy(Fo>T;=oqY=Q&K;b*=I|9 zyu6C=(OS~jbBI#^W+z51rp7I2QWX$=f_3*uE{X@f%3nihJ`(Ak!H_Iwl**C`Q`_r~ zJ#`Sp{_Ln0cw6*Kwj+8;#NmSp^M^<969CT?4!@)Ovuhv3-oIM;tH=HtWB;r3rA7%Q zpb?KT21}n+YyUmX}!X4?+Eh5AUt5i)s$Z3Dr zz=7)so^}Zs%M3z7SxIv8pR@=+2~2IY*7>^j|Nbpy7Q&T$d^QOp)h{lPgvdW>=@9wf zh(^adlRD@B8%?DEfcJfQbbvG`{nriMd{?te#dd1ELf6-D;1KvxyR4;>cj;!}e*kUB Bk!t_| literal 0 HcmV?d00001 diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/openai-agents/agent_terminal_workflow.py b/examples/openai-agents/agent_terminal_workflow.py new file mode 100644 index 000000000..21a037257 --- /dev/null +++ b/examples/openai-agents/agent_terminal_workflow.py @@ -0,0 +1,202 @@ +import asyncio +import logging +import os + +from agents import Agent as OpenAIAgent +from agents import ModelSettings, OpenAIProvider, RunConfig, SQLiteSession +from agents import Runner as OpenAIRunner +from terminal.env import TerminalEnv +from terminal.judge_agent import JudgeAgent, judge_from_env +from terminal.prompt import SYSTEM_PROMPT +from transformers import PreTrainedTokenizerFast + +from areal.api.cli_args import GenerationHyperparameters +from areal.api.workflow_api import RolloutWorkflow +from areal.experimental.openai import ArealOpenAI +from areal.utils import stats_tracker + +logger = logging.getLogger(__name__) + + +class TerminalAgent: + def __init__( + self, + tokenizer: PreTrainedTokenizerFast, + max_tokens_per_turn: int = 1024, + max_turns: int = 8, + max_total_tokens: int = 32768, + dump_dir: str | None = None, + rollout_stat_scope: str = "rollout", + ): + self.tokenizer = tokenizer + self.max_tokens_per_turn = max_tokens_per_turn + self.max_turns = max_turns + self.max_total_tokens = max_total_tokens + self.dump_dir = dump_dir + self.rollout_stat_scope = rollout_stat_scope + + async def run_agent(self, data, client: ArealOpenAI, judge_agent: JudgeAgent): + """Run the agent workflow for terminal task execution.""" + run_config = RunConfig( + model_provider=OpenAIProvider( + openai_client=client, + use_responses=True, + ), + tracing_disabled=True, + model_settings=ModelSettings( + temperature=1.0, + extra_args={"max_completion_tokens": self.max_tokens_per_turn}, + tool_choice="auto", + store=True, + ), + ) + + async with TerminalEnv( + task_name=data["task_name"], + dump_dir=self.dump_dir, + rollout_stat_scope=self.rollout_stat_scope, + ) as env: + # Create agent workflow with terminal tools + agent = OpenAIAgent( + name="Terminal Task Agent", + instructions=SYSTEM_PROMPT, + tools=env.get_tools(), + ) + session = SQLiteSession("terminal") + content = data["instruction"] + + max_attempts = self.max_turns + reward = 0 + judge_reward = 0 + tracker = stats_tracker.get(self.rollout_stat_scope) + + with tracker.record_timing("run_agent_total"): + error_count = 0.0 + attempts_used = 0.0 + for attempt in range(max_attempts): + attempts_used = float(attempt + 1) + try: + with tracker.record_timing("openai_runner_run"): + result = await OpenAIRunner.run( + agent, + input=content, + session=session, + run_config=run_config, + max_turns=30, + ) + except Exception as e: + logger.error(f"Error running agent: {e}") + error_count += 1.0 + break + + with tracker.record_timing("env_validate_reward"): + reward = env.reward() + if judge_agent: + with tracker.record_timing("judge_agent_reward"): + judge_reward = await judge_agent.get_reward_from_judge( + session=session, + dockerfile_contents=data["dockerfile_contents"], + ) + if judge_reward >= 0 and reward < 0.99: + reward = reward * 0.65 + judge_reward * 0.35 + + tracker.scalar( + reward=reward, + judge_reward=judge_reward, + attempt_index=float(attempt), + input_chars=float(len(content) if content else 0.0), + output_chars=float( + len(getattr(result, "final_output", "") or "") + ), + ) + + if isinstance(reward, float) and reward >= 0.99: + tracker.scalar(success=1.0) + break + + if attempt < max_attempts - 1: + content = f"""The previous attempt didn't complete the task successfully. + Please try a different approach. + Original task: {data["instruction"]} + + Previous attempt result: {result.final_output} + + Please analyze what went wrong and try again with a corrected approach.""" + else: + content = f"""This is your final attempt. Please be extremely careful. + Original task: {data["instruction"]} + + Previous attempts: {result.final_output} + + Please provide a final, carefully executed solution.""" + tracker.scalar(success=0.0) + + tracker.scalar( + final_reward=reward, attempts_used=attempts_used, errors=error_count + ) + + client.set_final_reward(reward) + + return reward + + +class TerminalAgentWorkflow(RolloutWorkflow): + def __init__( + self, + gconfig: GenerationHyperparameters, + tokenizer: PreTrainedTokenizerFast, + dump_dir: str | None = None, + rollout_stat_scope: str = "rollout", + n_trajs: int = 1, + max_tokens: int = 32768, + max_turns: int = 8, + ): + self.gconfig = gconfig + self.gconfig.n_samples = 1 + self.tokenizer = tokenizer + self.dump_dir = dump_dir + self.max_tokens = max_tokens + self.rollout_stat_scope = rollout_stat_scope + if self.dump_dir is not None and not os.path.exists(self.dump_dir): + os.makedirs(self.dump_dir, exist_ok=True) + + # Search hyper-parameters + self.n_trajs = n_trajs + self.agent = TerminalAgent( + tokenizer=self.tokenizer, + max_tokens_per_turn=self.gconfig.max_new_tokens, + max_turns=max_turns, + max_total_tokens=max_tokens, + dump_dir=self.dump_dir, + rollout_stat_scope=self.rollout_stat_scope, + ) + self.judge_agent = judge_from_env() + + async def arun_episode(self, engine, data): + clients = [ + ArealOpenAI( + engine=engine, tokenizer=self.tokenizer, tool_call_parser="qwen25" + ) + for _ in range(self.n_trajs) + ] + + # Collect trajectories + rewards = await asyncio.gather( + *[ + self.agent.run_agent( + data=data, + client=clients[i], + judge_agent=self.judge_agent, + ) + for i in range(self.n_trajs) + ] + ) + for reward in rewards: + stats_tracker.get(self.rollout_stat_scope).scalar(reward=reward) + + interactions_with_reward = {} + for client in clients: + client.apply_reward_discount(turn_discount=0.9) + interactions = client.export_interactions(style="individual") + interactions_with_reward.update(interactions) + return interactions_with_reward diff --git a/examples/openai-agents/config.yaml b/examples/openai-agents/config.yaml index 7be7a094f..09b2eddd0 100644 --- a/examples/openai-agents/config.yaml +++ b/examples/openai-agents/config.yaml @@ -19,7 +19,7 @@ cluster: type: nfs nfs_record_root: /tmp/areal/name_resolve -allocation_mode: sglang.d4p1t1+d4p1t1 +allocation_mode: sglang.d4p1t1+d1p1t1c4 rollout: experiment_name: ${experiment_name} diff --git a/examples/openai-agents/terminal/README.md b/examples/openai-agents/terminal/README.md new file mode 100644 index 000000000..2b71f96d9 --- /dev/null +++ b/examples/openai-agents/terminal/README.md @@ -0,0 +1,124 @@ +# Guidance for starting training a terminal agent + +## Overview + +The terminal agent training system consists of two separate instances: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Training Instance (Has GPU) │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ AReaL Training Pipeline │ │ +│ │ - Actor Model (Inference) │ │ +│ │ - Rollout Workers │ │ +│ │ - Dataset Loaders │ │ +│ │ - PPO/GRPO Training │ │ +│ └────────────────┬─────────────────────────────────────────┘ │ +│ │ │ +│ │ HTTP Requests │ +│ │ (Tool Calls) │ +│ ▼ │ +└───────────────────┼─────────────────────────────────────────────┘ + │ + │ +┌───────────────────┼─────────────────────────────────────────────┐ +│ │ MCP Server Instance (No GPU) │ +│ ┌────────────────▼─────────────────────────────────────────┐ │ +│ │ MCP Server (Flask) │ │ +│ │ - HTTP/SSE Endpoints │ │ +│ │ - Task Container Management │ │ +│ └──────────────────┬───────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Docker-in-Docker (DinD) │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ +│ │ │ Container │ │ Container │ │ Container │ ... │ │ +│ │ │ (Task 1) │ │ (Task 2) │ │ (Task 3) │ │ │ +│ │ │ + tmux │ │ + tmux │ │ + tmux │ │ │ +│ │ └────────────┘ └────────────┘ └────────────┘ │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +**Key Points:** + +- **Training Instance**: Runs the RL training loop, manages rollouts, has GPU +- **MCP Server Instance**: Hosts terminal environments in Docker containers +- **Communication**: Training instance sends tool calls via HTTP to MCP server +- **Isolation**: Each task runs in its own Docker container with tmux session + +## Step 1 Start mcp server: + +1. Pull image: `docker pull docker.io/cruizba/ubuntu-dind:latest` as mcp server + +1. Enter mcp server and configure server + +```bash +# Prepare task data +# Install https://github.com/laude-institute/terminal-bench +# Download datasets to specified directory, like /data/tb: +tb datasets download -d terminal-bench-core --output-dir /data/tb + +# Reset docker config +apt-get update +apt-get install -y python3-pip +update-alternatives --install /usr/bin/python python /usr/bin/python3 10 +pip install --break-system-packages requests terminal-bench flask +apt-get install -y fuse-overlayfs +mkdir -p /etc/docker +tee /etc/docker/daemon.json > /dev/null <<'EOF' +{ + "default-address-pools": [ + { + "base": "172.16.0.0/12", + "size": 24 + } + ] +} +EOF + +# Restart docker +supervisorctl shutdown +start-docker.sh + +# Start mcp server +python -m examples.openai-agents.terminal.server --tasks-dir /storage/openpsi/codes/puzhen.pz/terminal-bench-core/easy/ --tasks-log-dir /data/tb/logs +``` + +2. Test mcp server + +```bash +python examples/openai-agents/terminal/test_client.py +``` + +## Step 2 Train agent + +1. Prepare datasets + +```bash +# Install https://github.com/laude-institute/terminal-bench +# Download datasets to specified directory, like /data/tb: +tb datasets download -d terminal-bench-core --output-dir /data/tb + +# Convert datasets to parquet format +python examples/openai-agents/terminal/tasks_to_parquet_converter.py --tasks-dir /data/tb/tasks --output-dir /tmp/terminal_bench/easy-data/ +# Make sure the output-dir contains `terminal_bench` in its name +# Example: /tmp/terminal_bench/easy-data/train.parquet +``` + +2. Start training task locally + +```bash +# Download Qwen3-4B-Thinking-2507 from huggingface + +huggingface-cli download Qwen/Qwen3-4B-Thinking-2507 --local-dir /storage/models/Qwen3-4B-Thinking-2507 +## set mcp server address +export MCP_SERVER_URL=http://$MCP_SERVER_ADDRESS +python3 -m areal.launcher.local examples/openai-agents/train_agents.py --config examples/openai-agents/config.yaml actor.path=/storage/models/Qwen3-4B-Thinking-2507 train_dataset.path=/tmp/terminal_bench/easy-data/train.parquet train_dataset.batch_size=4 gconfig.n_samples=4 trial_name=oss-qwen25-7b agent_type=multi_agent_terminal experiment_name=openai-agents-terminal n_trajs=1 max_turns=3 valid_dataset.path=/tmp/terminal_bench/easy-data/val.parquet stats_logger.wandb.mode=online +``` + +## Experiment Record + +![experiment record](../../../assets/qwen3_8b_terminal.png) diff --git a/examples/openai-agents/terminal/__init__.py b/examples/openai-agents/terminal/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/openai-agents/terminal/env.py b/examples/openai-agents/terminal/env.py new file mode 100644 index 000000000..8960fde52 --- /dev/null +++ b/examples/openai-agents/terminal/env.py @@ -0,0 +1,477 @@ +import asyncio +import json +import logging +import os +import re +import uuid +from pathlib import Path +from typing import Any + +import aiofiles +import aiofiles.os +import requests +from agents import FunctionTool, RunContextWrapper +from mcp import ClientSession +from mcp.client.sse import sse_client +from pydantic import BaseModel, Field + +from areal.utils import stats_tracker + +logger = logging.getLogger(__name__) + + +class TerminalEnv: + """Simple context manager for managing terminal container lifecycle.""" + + def __init__( + self, + task_name: str = None, + dump_dir: str | None = None, + max_retries: int = 3, + retry_delay: float = 1.0, + rollout_stat_scope: str = "rollout", + dockerfile_contents: str | None = None, + ): + self.base_url = os.environ.get("MCP_SERVER_URL", "http://localhost:8000") + self.task_name = task_name + self.container_name = None + self.uuid = str(uuid.uuid4()) + self.dump_dir = dump_dir + self._mcp_session = None + self._sse_exit_stack = None + self.max_retries = max_retries + self.retry_delay = retry_delay + self._connection_lock = asyncio.Lock() + self.rollout_stat_scope = rollout_stat_scope + self.dockerfile_contents = dockerfile_contents + + async def _log_tool_call( + self, tool_name: str, arguments: dict, result: str, container_name: str + ): + """Log tool call to file asynchronously. + + Args: + tool_name: Name of the tool being called + arguments: Arguments passed to the tool + result: Result returned by the tool + container_name: Name of the container for the log filename + """ + if self.dump_dir is not None: + dump_path = Path(self.dump_dir) / "terminal" + await aiofiles.os.makedirs(dump_path, exist_ok=True) + log_file = dump_path / f"{container_name}.jsonl" + async with aiofiles.open(log_file, "a", encoding="utf-8") as f: + log_entry = { + "tool_name": tool_name, + "arguments": arguments, + "result": result, + } + await f.write(json.dumps(log_entry, ensure_ascii=False) + "\n") + + def __enter__(self) -> "TerminalEnv": + """Start the task container.""" + payload = {"uuid": self.uuid, "task_name": self.task_name} + try: + response = requests.post( + f"{self.base_url}/tasks/start", json=payload, timeout=360 + ) + response.raise_for_status() + data = response.json() + self.container_name = data["container_name"] + except requests.exceptions.RequestException as e: + raise RuntimeError(f"Failed to start task container: {e}") + return self + + async def _connect_mcp(self) -> None: + """Establish MCP connection with retry logic.""" + from contextlib import AsyncExitStack + + for attempt in range(self.max_retries): + try: + if self._sse_exit_stack is None: + self._sse_exit_stack = AsyncExitStack() + + logger.info( + f"Connecting to MCP server (attempt {attempt + 1}/{self.max_retries})..." + ) + read, write = await self._sse_exit_stack.enter_async_context( + sse_client( + url=f"{self.base_url}/sse", + timeout=60 * 3, + ) + ) + self._mcp_session = await self._sse_exit_stack.enter_async_context( + ClientSession(read, write) + ) + await self._mcp_session.initialize() + logger.info("MCP connection established successfully") + return + except Exception as e: + logger.warning(f"MCP connection attempt {attempt + 1} failed: {e}") + if attempt < self.max_retries - 1: + await asyncio.sleep( + self.retry_delay * (attempt + 1) + ) # Exponential backoff + # Clean up failed connection attempt + if self._sse_exit_stack: + try: + await self._sse_exit_stack.aclose() + except Exception: + pass + self._sse_exit_stack = None + self._mcp_session = None + else: + raise RuntimeError( + f"Failed to connect to MCP server after {self.max_retries} attempts: {e}" + ) + + async def _reconnect_mcp(self) -> None: + """Reconnect to MCP server after connection loss.""" + async with self._connection_lock: + logger.info("Reconnecting to MCP server...") + # Clean up old connection + if self._sse_exit_stack: + try: + await self._sse_exit_stack.aclose() + except Exception as e: + logger.warning(f"Error closing old connection: {e}") + self._sse_exit_stack = None + self._mcp_session = None + + # Establish new connection + await self._connect_mcp() + + async def __aenter__(self) -> "TerminalEnv": + """Async context manager entry - start container and MCP session.""" + self.__enter__() + # Initialize persistent MCP connection with retry + await self._connect_mcp() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Stop the task container on exit.""" + if self.container_name: + try: + payload = {"uuid": self.uuid, "task_name": self.task_name} + response = requests.post( + f"{self.base_url}/tasks/stop", json=payload, timeout=30 + ) + response.raise_for_status() + except Exception as e: + print(f"Warning: Failed to stop task {self.task_name}: {e}") + return False # Don't suppress exceptions + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Async context manager exit - cleanup MCP session and container.""" + if self._sse_exit_stack: + await self._sse_exit_stack.aclose() + self.__exit__(exc_type, exc_val, exc_tb) + return False + + async def _call_mcp_tool_with_retry(self, tool_name: str, arguments: dict) -> str: + """Call MCP tool with retry logic and error handling. + + Args: + tool_name: Name of the MCP tool to call + arguments: Arguments to pass to the tool + + Returns: + Tool result as string + + Raises: + RuntimeError: If tool call fails after all retries + """ + for attempt in range(self.max_retries): + try: + result = await self._mcp_session.call_tool( + name=tool_name, + arguments=arguments, + ) + output = ( + result.content[0].text + if result.content and len(result.content) > 0 + else "" + ) + return output + except Exception as e: + logger.warning(f"Tool call attempt {attempt + 1} failed: {e}") + if attempt < self.max_retries - 1: + try: + await self._reconnect_mcp() + except Exception as reconnect_error: + logger.error(f"Reconnection failed: {reconnect_error}") + if attempt == self.max_retries - 2: + raise + else: + raise RuntimeError( + f"Failed to call {tool_name} after {self.max_retries} attempts: {e}" + ) + + def get_tools(self) -> list[FunctionTool]: + """Create async function tools for terminal interaction. + + Returns: + List of async function tools for keystrokes and capture-pane operations. + """ + if not self.container_name: + raise RuntimeError( + "Container not started. Use TerminalEnv as context manager first." + ) + + def _format_terminal_output(output: str, keystrokes: str = "") -> str: + """Clean terminal output by removing shell prompts and the exact keystrokes command. + + Args: + output: Raw terminal output + keystrokes: The exact command that was sent to remove from output + """ + if not output: + return output + + lines = output.split("\n") + cleaned = [] + keystrokes_clean = keystrokes.strip() + + for line in lines: + # Remove shell prompt + line = re.sub(r"^[a-zA-Z0-9@]+:[^#]*#\s*", "", line) + + # Remove the exact keystrokes command if present at the start + if keystrokes_clean and line.startswith(keystrokes_clean): + line = line[len(keystrokes_clean) :].lstrip() + + # Keep non-empty lines + if line.strip(): + cleaned.append(line.rstrip()) + + return "\n".join(cleaned).strip() + + class ExecuteCommandArgs(BaseModel): + """Arguments for executing a terminal command.""" + + command: str = Field( + description="The terminal command to execute (e.g., 'ls -la', 'python script.py', 'mkdir new_folder')" + ) + wait_time_sec: float = Field( + default=2.0, + description="Time to wait after command execution in seconds. Use longer values for commands that take time to complete.", + ) + + class CurrentStatusArgs(BaseModel): + """Arguments for getting current directory status and file listing.""" + + include_hidden: bool = Field( + default=False, + description="Whether to include hidden files (starting with .) in the file listing. Set to True to see all files including dotfiles.", + ) + + class FileContentsArgs(BaseModel): + """Arguments for viewing file contents with different viewing modes.""" + + absolute_path: str = Field( + description="Absolute path to the file to view (e.g., '/home/user/file.txt', '/var/log/syslog')" + ) + tail_lines: int = Field( + default=0, + description="Number of lines to show from the end of the file. Use tail to see the last N lines (e.g., for logs). Set to 0 to disable tail view.", + ) + head_lines: int = Field( + default=0, + description="Number of lines to show from the beginning of the file. Use head to see the first N lines (e.g., for file headers). Set to 0 to disable head view.", + ) + + async def execute_command(ctx: RunContextWrapper[Any], args: str) -> str: + """Execute a command in the terminal and return the result. + + Args: + args: JSON string containing command and wait_time_sec parameters. + + Returns: + Command output after execution. + """ + try: + parsed_args = ExecuteCommandArgs.model_validate_json(args) + + # Call the simplified MCP wrapper + raw_output = await self._call_mcp_tool_with_retry( + "keystrokes", + { + "container_name": self.container_name, + "keystrokes": parsed_args.command, + "append_enter": True, + "wait_time_sec": parsed_args.wait_time_sec, + }, + ) + + # Clean the output + output = _format_terminal_output(raw_output, parsed_args.command) + + # Log and track metrics + await self._log_tool_call( + tool_name="execute_command", + arguments=parsed_args.model_dump(), + result=output, + container_name=self.container_name, + ) + + tracker = stats_tracker.get(self.rollout_stat_scope) + tracker.scalar( + tool_execute_success=1.0, + tool_execute_input_chars=float(len(parsed_args.command)), + tool_execute_output_chars=float(len(output)), + ) + + return output + except Exception as e: + stats_tracker.get(self.rollout_stat_scope).scalar( + tool_execute_error=1.0 + ) + return f"Error executing command: {e}" + + async def current_working_directory( + ctx: RunContextWrapper[Any], args: str + ) -> str: + """Get current working directory and list files. + + Args: + args: JSON string containing include_hidden parameter. + + Returns: + Current directory information and file listing. + """ + try: + parsed_args = CurrentStatusArgs.model_validate_json(args) + + # Get current working directory + pwd_result = await self._call_mcp_tool_with_retry( + "keystrokes", + { + "container_name": self.container_name, + "keystrokes": "pwd", + "append_enter": True, + "wait_time_sec": 1.0, + }, + ) + + # List files + ls_option = "-alh" if parsed_args.include_hidden else "-lh" + ls_result = await self._call_mcp_tool_with_retry( + "keystrokes", + { + "container_name": self.container_name, + "keystrokes": f"ls {ls_option}", + "append_enter": True, + "wait_time_sec": 1.0, + }, + ) + + # Clean and combine results + clean_pwd = _format_terminal_output(pwd_result, "pwd") + clean_ls = _format_terminal_output(ls_result, f"ls {ls_option}") + output = f"Current directory: {clean_pwd}\n\n Files: {clean_ls}" + + # Log and track metrics + await self._log_tool_call( + tool_name="current_working_directory", + arguments=parsed_args.model_dump(), + result=output, + container_name=self.container_name, + ) + + tracker = stats_tracker.get(self.rollout_stat_scope) + tracker.scalar( + tool_status_success=1.0, + tool_status_output_chars=float(len(output)), + ) + + return output + except Exception as e: + stats_tracker.get(self.rollout_stat_scope).scalar(tool_status_error=1.0) + return f"Error getting current status: {e}" + + async def file_contents(ctx: RunContextWrapper[Any], args: str) -> str: + try: + parsed_args = FileContentsArgs.model_validate_json(args) + + # Determine the command to use based on arguments + if parsed_args.tail_lines > 0: + command = ( + f"tail -n {parsed_args.tail_lines} {parsed_args.absolute_path}" + ) + elif parsed_args.head_lines > 0: + command = ( + f"head -n {parsed_args.head_lines} {parsed_args.absolute_path}" + ) + else: + command = f"cat {parsed_args.absolute_path}" + + # Get file contents + raw_result = await self._call_mcp_tool_with_retry( + "keystrokes", + { + "container_name": self.container_name, + "keystrokes": command, + "append_enter": True, + "wait_time_sec": 1.0, + }, + ) + + # Clean the output + result = _format_terminal_output(raw_result, command) + + # Remove trailing shell prompt if present + result = re.sub(r"\n[a-zA-Z0-9@]+:[^#]*#$", "", result) + + # Log and track metrics + await self._log_tool_call( + tool_name="file_contents", + arguments=parsed_args.model_dump(), + result=result, + container_name=self.container_name, + ) + + tracker = stats_tracker.get(self.rollout_stat_scope) + tracker.scalar( + tool_status_success=1.0, + tool_status_output_chars=float(len(result)), + ) + + return result + except Exception as e: + stats_tracker.get(self.rollout_stat_scope).scalar(tool_status_error=1.0) + return f"Error getting file contents: {e}" + + return [ + FunctionTool( + name="execute_command", + description="Execute a command in the terminal and return the result.", + params_json_schema=ExecuteCommandArgs.model_json_schema(), + on_invoke_tool=execute_command, + ), + FunctionTool( + name="current_working_directory", + description="Get current working directory and list files.", + params_json_schema=CurrentStatusArgs.model_json_schema(), + on_invoke_tool=current_working_directory, + ), + FunctionTool( + name="file_contents", + description="View file contents with different viewing modes (full content, head, or tail).", + params_json_schema=FileContentsArgs.model_json_schema(), + on_invoke_tool=file_contents, + ), + ] + + def reward(self) -> float: + """Reward function for the terminal environment.""" + try: + payload = {"container_name": self.container_name} + response = requests.post( + f"{self.base_url}/tasks/validate", json=payload, timeout=360 + ) + response.raise_for_status() + result = response.json() + return round(float(result["score"]), 2) + except Exception as e: + print(f"Error getting reward from API: {e}") + return 0.0 diff --git a/examples/openai-agents/terminal/judge_agent.py b/examples/openai-agents/terminal/judge_agent.py new file mode 100644 index 000000000..4ec57fbeb --- /dev/null +++ b/examples/openai-agents/terminal/judge_agent.py @@ -0,0 +1,71 @@ +import os + +from agents import Agent, ModelSettings, RunConfig, SQLiteSession +from agents import Runner as OpenAIRunner +from agents.extensions.models.litellm_model import LitellmModel +from pydantic import BaseModel + +from .prompt import JUDGE_PROMPT + + +class JudgeOutput(BaseModel): + score: float + + +class JudgeAgent: + def __init__(self, base_url: str, api_key: str, model_name: str): + self.base_url = base_url + self.api_key = api_key + # Parse model_name as comma-separated list for round-robin + self.model_names = [ + name.strip() for name in model_name.split(",") if name.strip() + ] + self.current_model_index = 0 + + async def get_reward_from_judge( + self, + session: SQLiteSession, + dockerfile_contents: str, + ) -> float: + items = await session.get_items() + + # Round-robin model selection + selected_model = self.model_names[self.current_model_index] + self.current_model_index = (self.current_model_index + 1) % len( + self.model_names + ) + + agent = Agent( + name="JudgeAgent", + instructions=JUDGE_PROMPT, + model=LitellmModel( + model=selected_model, + api_key=self.api_key, + base_url=self.base_url, + ), + output_type=JudgeOutput, + ) + try: + result = await OpenAIRunner.run( + agent, + input=items, + run_config=RunConfig( + tracing_disabled=True, + model_settings=ModelSettings( + temperature=0.0, + ), + ), + ) + judge_output = result.final_output_as(JudgeOutput) + return judge_output.score + except Exception: + return -1 + + +def judge_from_env() -> JudgeAgent | None: + base_url = os.environ.get("LITELLM_API_BASE", None) + api_key = os.environ.get("LITELLM_API_KEY", None) + model_name = os.environ.get("LITELLM_MODEL_NAME", None) + if not base_url or not api_key or not model_name: + return None + return JudgeAgent(base_url, api_key, model_name) diff --git a/examples/openai-agents/terminal/logging_config.py b/examples/openai-agents/terminal/logging_config.py new file mode 100644 index 000000000..f07bde53f --- /dev/null +++ b/examples/openai-agents/terminal/logging_config.py @@ -0,0 +1,30 @@ +import logging +import sys + + +def setup_logging(): + """Setup logging configuration for the MCP server.""" + # Configure root logger + logging.basicConfig( + level=logging.INFO, # Changed from DEBUG to INFO to reduce noise + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[logging.StreamHandler(sys.stdout)], + ) + + # Suppress noisy third-party loggers + logging.getLogger("mcp.server.lowlevel.server").setLevel(logging.WARNING) + logging.getLogger("asyncio").setLevel(logging.WARNING) + logging.getLogger("litellm").setLevel(logging.WARNING) + + # Ensure terminal_bench loggers also output to stdout + terminal_logger = logging.getLogger("terminal_bench_server") + if not terminal_logger.handlers: + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(logging.DEBUG) + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + handler.setFormatter(formatter) + terminal_logger.addHandler(handler) + terminal_logger.setLevel(logging.DEBUG) + terminal_logger.propagate = False # Prevent duplicate logging to root logger diff --git a/examples/openai-agents/terminal/models.py b/examples/openai-agents/terminal/models.py new file mode 100644 index 000000000..5ff2326c2 --- /dev/null +++ b/examples/openai-agents/terminal/models.py @@ -0,0 +1,793 @@ +"""All types and classes for the terminal-bench server.""" + +import asyncio +import contextvars +import logging +import os +import threading +import warnings +from pathlib import Path +from time import time +from typing import Any + +import docker +from pydantic import BaseModel +from starlette.responses import JSONResponse +from terminal_bench.handlers.trial_handler import Task +from terminal_bench.parsers.base_parser import UnitTestStatus +from terminal_bench.parsers.parser_factory import ParserFactory +from terminal_bench.terminal.docker_compose_manager import DockerComposeManager +from terminal_bench.terminal.tmux_session import TmuxSession +from tqdm import tqdm + +from .logging_config import setup_logging + +# Suppress SQLAlchemy 2.0 deprecation warnings from terminal_bench +warnings.filterwarnings("ignore", category=DeprecationWarning, module="terminal_bench") + +setup_logging() + +logger = logging.getLogger(__name__) + +# Context variable to track request start time +request_start_time = contextvars.ContextVar("request_start_time", default=None) + +# --- Pydantic Models for API and Tools --- + + +class TaskRequest(BaseModel): + uuid: str | None = None + task_name: str | None = None + container_name: str | None = None # Optional, auto-generated from uuid + task_name + + +class TaskContainer(DockerComposeManager): + def __init__( + self, + uuid: str, + task_name: str, + logs_dir: Path, + docker_compose_path: Path, + client_image_name: str, + no_rebuild: bool = False, + cleanup: bool = False, + ): + container_name = f"{uuid}__{task_name}" + + # Initialize parent class with required parameters + super().__init__( + client_container_name=container_name, + client_image_name=client_image_name, + docker_compose_path=docker_compose_path, + no_rebuild=no_rebuild, + cleanup=cleanup, + ) + + # Store additional attributes + self.uuid = uuid + self.task_name = task_name + self.container_name = container_name + self.logs_dir = logs_dir.joinpath(uuid) + ## override env + self.env["T_BENCH_TASK_LOGS_PATH"] = str(self.logs_dir.joinpath("client")) + self.env["T_BENCH_TASK_AGENT_LOGS_PATH"] = str(self.logs_dir.joinpath("agent")) + + +class TerminalBenchServer: + """ + Terminal Bench Server class that manages server state and operations. + Provides thread-safe access to shared resources using asyncio locks. + """ + + _is_running = False + preheat_image = False + + def __init__( + self, + tasks_dir: Path | None = None, + tasks_log_dir: Path | None = None, + preheat_image: bool = False, + ): + # In-memory registry for active tasks and their managers + # Format: {container_name: {"uuid": str, "task_name": str, "last_seen": float, "compose_manager": TaskContainer, "container": Container}} + self.active_tasks: dict[str, dict[str, Any]] = {} + # Cache for TmuxSession objects + self.tmux_sessions: dict[str, TmuxSession] = {} + # Docker client instance + self.docker_client = None + # Path to the tasks directory, needs to be configured + self.tasks_dir = tasks_dir or Path( + os.environ.get("T_BENCH_TASKS_DIR", "/app/tasks") + ) + # Path to the tasks logs directory + self.tasks_log_dir = tasks_log_dir or Path( + os.environ.get("T_BENCH_TASKS_LOG_DIR", "/var/logs/terminal-bench/") + ) + self.preheat_image = preheat_image + # Thread locks for thread-safe access to shared resources + # Using threading.Lock instead of asyncio.Lock for cross-thread safety + self.active_tasks_lock = threading.Lock() + self.tmux_sessions_lock = threading.Lock() + self.garbage_collector_task = None + # Background event loop and thread for GC + self._gc_loop = None + self._gc_thread = None + + def init_images_sync(self): + """Pre-build all Docker images from tasks directory.""" + + print("Initializing Docker images...") + if not self.tasks_dir.exists(): + print(f"Warning: Tasks directory {self.tasks_dir} does not exist") + return + + # Get all task directories that contain docker-compose.yaml + task_dirs = [d for d in self.tasks_dir.iterdir() if d.is_dir()] + total_tasks = len(task_dirs) + built_count = 0 + skipped_count = 0 + failed_count = 0 + + print(f"Found {total_tasks} task directories") + + # Use tqdm for progress tracking + with tqdm(total=total_tasks, desc="Building images", unit="task") as pbar: + for task_dir in task_dirs: + # Quit if server is shutting down + if not self._is_running: + pbar.set_description("Interrupted") + print("\nImage initialization interrupted by shutdown") + break + compose_path = task_dir / "docker-compose.yaml" + if not compose_path.exists(): + skipped_count += 1 + pbar.update(1) + pbar.set_postfix( + { + "built": built_count, + "skipped": skipped_count, + "failed": failed_count, + } + ) + continue + + task_name = task_dir.name + image_name = f"tb__{task_name.replace('.', '-')}__client" + + try: + # Check if image already exists + if self._image_exists(image_name): + pbar.set_description(f"Skipping {task_name[:30]}") + skipped_count += 1 + pbar.update(1) + pbar.set_postfix( + { + "built": built_count, + "skipped": skipped_count, + "failed": failed_count, + } + ) + continue + + pbar.set_description(f"Building {task_name[:30]}") + + # Create a temporary TaskContainer to build the image + temp_manager = TaskContainer( + uuid="temp_build", + task_name=task_name, + logs_dir=self.tasks_log_dir, + docker_compose_path=compose_path, + client_image_name=image_name, + no_rebuild=False, + ) + + # Build the image without starting the container + temp_manager.build() + built_count += 1 + pbar.update(1) + pbar.set_postfix( + { + "built": built_count, + "skipped": skipped_count, + "failed": failed_count, + } + ) + + except Exception as e: + failed_count += 1 + pbar.set_description(f"Failed {task_name[:30]}") + pbar.update(1) + pbar.set_postfix( + { + "built": built_count, + "skipped": skipped_count, + "failed": failed_count, + } + ) + print(f"\nError building {task_name}: {e}") + + print("\nImage initialization complete:") + print(f" Built: {built_count}") + print(f" Skipped: {skipped_count}") + print(f" Failed: {failed_count}") + print(f" Total: {total_tasks}") + + def _run_gc_loop(self): + """Run garbage collector in a separate thread with its own event loop.""" + self._gc_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._gc_loop) + try: + self._gc_loop.run_until_complete(self.garbage_collector()) + except Exception as e: + print(f"Error in GC loop: {e}") + finally: + self._gc_loop.close() + + def startup(self): + """Server startup logic (synchronous).""" + print("Server starting up...") + try: + self.docker_client = docker.from_env() + self._is_running = True + + if self.preheat_image: + self.init_images_sync() + + # Recover active tasks synchronously + self._recover_active_tasks_sync() + + # Start garbage collector in background thread + self._gc_thread = threading.Thread(target=self._run_gc_loop, daemon=True) + self._gc_thread.start() + + print("Server startup complete with following configuration:") + print(f"TASK DIR: {self.tasks_dir}") + print(f"RECOVER TASK NUM: {len(self.active_tasks)}") + print(f"TASK LOGS DIR: {self.tasks_log_dir}") + + except Exception as e: + self._is_running = False + print(f"Error during startup: {e}") + + def shutdown(self): + """Server shutdown logic (synchronous).""" + print("Server shutting down...") + self._is_running = False + + # Stop garbage collector loop + if self._gc_loop and self._gc_loop.is_running(): + self._gc_loop.call_soon_threadsafe(self._gc_loop.stop) + + # Wait for GC thread to finish + if self._gc_thread and self._gc_thread.is_alive(): + self._gc_thread.join(timeout=5.0) + + print("Server shutdown complete.") + + def _recover_active_tasks_sync(self): + """Scan for existing containers and repopulate the active_tasks registry (synchronous).""" + print("Recovering active tasks...") + if not self.docker_client: + print("Docker client not initialized, skipping recovery") + return + + containers = self.docker_client.containers.list() + + # No need for async lock in sync context + for container in containers: + parts = container.name.split("__") + if len(parts) == 2: + uuid, task_name = parts + print(f"Found existing container: {container.name}") + compose_path = self.tasks_dir / task_name / "docker-compose.yaml" + if compose_path.exists(): + image_name = f"tb__{task_name.replace('.', '-')}__client" + compose_manager = TaskContainer( + uuid=uuid, + task_name=task_name, + client_image_name=image_name, + docker_compose_path=compose_path, + logs_dir=self.tasks_log_dir, + no_rebuild=True, + ) + self.active_tasks[container.name] = { + "uuid": uuid, + "task_name": task_name, + "last_seen": time(), + "compose_manager": compose_manager, + "container": container, + } + print(f"Recovered task: {container.name}") + else: + print( + f"Warning: Could not find compose file for task {task_name} of container {container.name}" + ) + + async def garbage_collector(self): + """Periodically clean up idle containers.""" + while self._is_running: + try: + await asyncio.sleep(60) # Check every minute + print("Running garbage collector...") + current_time = time() + idle_timeout = 60 * 15 # 10 minutes + + with self.active_tasks_lock: + containers_to_cleanup = [] + for container_name, task_info in self.active_tasks.items(): + if current_time - task_info["last_seen"] > idle_timeout: + containers_to_cleanup.append(container_name) + + for container_name in containers_to_cleanup: + task_info = self.active_tasks[container_name] + print(f"Container {container_name} is idle. Shutting down.") + try: + task_info["compose_manager"].stop() + + # Clean up tmux session + with self.tmux_sessions_lock: + if container_name in self.tmux_sessions: + del self.tmux_sessions[container_name] + + del self.active_tasks[container_name] + print(f"Successfully cleaned up {container_name}.") + except Exception as e: + print( + f"Error during garbage collection for {container_name}: {e}" + ) + except asyncio.CancelledError: + print("Garbage collector cancelled.") + break + except Exception as e: + print(f"Error in garbage collector: {e}") + + async def start_task(self, req: TaskRequest) -> JSONResponse: + """Starts a new task container or returns an existing one.""" + op_start = time() + container_name = f"{req.uuid}__{req.task_name}" + logger.info(f"[PERF] start_task called for {container_name}") + + # Check if container already exists + check_start = time() + with self.active_tasks_lock: + if container_name in self.active_tasks: + self.active_tasks[container_name]["last_seen"] = time() + log_operation_time( + f"start_task (reused) for {container_name}", op_start + ) + return JSONResponse( + {"container_name": container_name, "status": "reused"} + ) + log_operation_time("start_task lock check", check_start) + + compose_path = self.tasks_dir / req.task_name / "docker-compose.yaml" + if not compose_path.exists(): + return JSONResponse( + {"error": f"Task '{req.task_name}' not found."}, + status_code=404, + ) + + logger.info(f"Creating new container for task: {req.task_name}") + + try: + image_name = f"tb__{req.task_name.replace('.', '-')}__client" + + # Check if image exists + check_image_start = time() + no_rebuild = self._image_exists(image_name) + log_operation_time( + f"start_task check image exists for {req.task_name}", check_image_start + ) + + # Create TaskContainer + create_manager_start = time() + compose_manager = TaskContainer( + uuid=req.uuid, + task_name=req.task_name, + client_image_name=image_name, + docker_compose_path=compose_path, + no_rebuild=no_rebuild, + cleanup=False, # set to False for future reuse + logs_dir=self.tasks_log_dir, + ) + log_operation_time( + f"start_task create TaskContainer for {req.task_name}", + create_manager_start, + ) + + # Start container (potentially slow) + start_container_time = time() + container = compose_manager.start() + log_operation_time( + f"start_task container.start() for {req.task_name}", + start_container_time, + ) + + # Register container + register_start = time() + with self.active_tasks_lock: + self.active_tasks[container_name] = { + "uuid": req.uuid, + "task_name": req.task_name, + "last_seen": time(), + "compose_manager": compose_manager, + "container": container, + } + log_operation_time( + f"start_task register container for {container_name}", register_start + ) + log_operation_time(f"start_task (created) for {container_name}", op_start) + + return JSONResponse({"container_name": container_name, "status": "created"}) + except Exception as e: + logger.error(f"Error starting container: {e}, task name: {req.task_name}") + log_operation_time(f"start_task (failed) for {container_name}", op_start) + return JSONResponse( + {"error": f"Failed to start container: {e}"}, status_code=500 + ) + + async def list_tasks(self) -> JSONResponse: + """Lists all active tasks.""" + with self.active_tasks_lock: + tasks = [ + { + "container_name": name, + "uuid": info["uuid"], + "task_name": info["task_name"], + "last_seen": info["last_seen"], + } + for name, info in self.active_tasks.items() + ] + return JSONResponse(tasks) + + async def get_tmux_session(self, container_name: str) -> TmuxSession: + """Get or create a TmuxSession for the given container.""" + with self.tmux_sessions_lock: + if container_name not in self.tmux_sessions: + print(f"Creating new tmux session for {container_name}") + + with self.active_tasks_lock: + if container_name not in self.active_tasks: + raise ValueError( + f"Container {container_name} not found in active tasks" + ) + + task_info = self.active_tasks[container_name] + container = task_info["container"] + user = container.attrs["Config"].get("User", "") + + self.tmux_sessions[container_name] = TmuxSession( + session_name="agent", + container=container, + user=user, + ) + self.tmux_sessions[container_name].start() + + return self.tmux_sessions[container_name] + + def create_validation_session(self, container_name: str): + session = TmuxSession( + session_name="test", + container=self.active_tasks[container_name]["container"], + user=self.active_tasks[container_name]["container"] + .attrs["Config"] + .get("User", ""), + ) + session.start() + return session + + def update_task_last_seen(self, container_name: str): + """Update the last_seen timestamp for a task.""" + with self.active_tasks_lock: + if container_name in self.active_tasks: + self.active_tasks[container_name]["last_seen"] = time() + + def validate_container(self, container_name: str) -> bool: + """Validate that the container exists.""" + with self.active_tasks_lock: + return container_name in self.active_tasks + + async def validate_task(self, container_name: str) -> JSONResponse: + """Run tests in the container and return test results.""" + op_start = time() + + if not container_name: + return JSONResponse( + {"error": "Container name is required"}, + status_code=400, + ) + + logger.info(f"[PERF] validate_container called for {container_name}") + + # Validate container exists + validate_start = time() + if not self.validate_container(container_name): + return JSONResponse( + {"error": f"Container '{container_name}' not found"}, status_code=404 + ) + log_operation_time( + f"validate_container check exists for {container_name}", validate_start + ) + + try: + # Update last seen timestamp + self.update_task_last_seen(container_name) + + # Create a temporary tmux session + session_start = time() + session = self.create_validation_session(container_name) + log_operation_time( + f"validate_container create tmux session for {container_name}", + session_start, + ) + + # Get task info + with self.active_tasks_lock: + task_info = self.active_tasks.get(container_name) + if not task_info: + return JSONResponse( + { + "error": f"Task info not found for container '{container_name}'" + }, + status_code=404, + ) + task_name = task_info["task_name"] + + # Get test timeout and parser from task config + task_dir = self.tasks_dir / task_name + task_config_path = task_dir / "task.yaml" + + max_test_timeout_sec = 60.0 # Default timeout + parser_name = "pytest" # Default parser + + if task_config_path.exists(): + try: + task = Task.from_yaml(task_config_path) + max_test_timeout_sec = task.max_test_timeout_sec + parser_name = task.parser_name + except Exception as e: + logger.warning(f"Failed to load task config for {task_name}: {e}") + + # Copy test files to container + run_tests_path = task_dir / "run-tests.sh" + test_dir = task_dir / "tests" + + if not run_tests_path.exists(): + return JSONResponse( + { + "container_name": container_name, + "status": "error", + "error": f"Test script not found: {run_tests_path}", + }, + status_code=404, + ) + + try: + # Use DockerComposeManager's static method to copy files + copy_files_start = time() + with self.active_tasks_lock: + container = self.active_tasks[container_name]["container"] + + paths_to_copy = [run_tests_path] + if test_dir.exists(): + paths_to_copy.append(test_dir) + + DockerComposeManager.copy_to_container( + container=container, paths=paths_to_copy, container_dir="/tests" + ) + log_operation_time( + f"validate_container copy test files for {container_name}", + copy_files_start, + ) + logger.info(f"Copied test files to container {container_name}") + except Exception as e: + logger.error(f"Failed to copy test files: {e}") + return JSONResponse( + { + "container_name": container_name, + "status": "error", + "error": f"Failed to copy test files: {str(e)}", + }, + status_code=500, + ) + + # Run test script + test_script_path = "/tests/run-tests.sh" + + logger.info( + f"Running tests for container {container_name} with timeout {max_test_timeout_sec}s" + ) + + try: + run_tests_start = time() + session.send_keys( + [f"bash {test_script_path}", "Enter"], + block=True, + max_timeout_sec=max_test_timeout_sec, + ) + log_operation_time( + f"validate_container run tests for {container_name}", + run_tests_start, + ) + except TimeoutError: + logger.warning(f"Test timeout for container {container_name}") + log_operation_time( + f"validate_container (timeout) for {container_name}", op_start + ) + return JSONResponse( + { + "container_name": container_name, + "status": "timeout", + "error": f"Test execution timed out after {max_test_timeout_sec} seconds", + } + ) + + # Capture test output + capture_start = time() + test_output = session.capture_pane(capture_entire=True) + log_operation_time( + f"validate_container capture output for {container_name}", capture_start + ) + + # Parse test results + try: + parser = ParserFactory.get_parser(parser_name) + results = parser.parse(test_output) + + # Calculate weighted score + score = _calculate_weighted_test_score(results, None) + + log_operation_time( + f"validate_container (success) for {container_name} with score {score}", + op_start, + ) + + return JSONResponse( + { + "container_name": container_name, + "status": "completed", + "score": score, + "raw_output": test_output, + }, + status_code=200, + ) + + except Exception as e: + logger.error(f"Error parsing test results for {task_name}: {e}") + log_operation_time( + f"validate_container (parse_error) for {container_name}", op_start + ) + return JSONResponse( + { + "container_name": container_name, + "status": "parse_error", + "error": f"Failed to parse test results: {str(e)}", + "raw_output": test_output, + }, + status_code=500, + ) + + except Exception as e: + logger.error( + f"Error validating container {container_name}: {e}", exc_info=True + ) + log_operation_time( + f"validate_container (failed) for {container_name}", op_start + ) + return JSONResponse( + {"error": f"Failed to validate container: {str(e)}"}, status_code=500 + ) + + async def stop_task(self, req: TaskRequest) -> JSONResponse: + """Stops and removes a task container for resource cleanup.""" + container_name = req.container_name + if not container_name: + container_name = f"{req.uuid}__{req.task_name}" + + with self.active_tasks_lock: + if container_name not in self.active_tasks: + return JSONResponse( + {"error": f"Container '{container_name}' not found."}, + status_code=404, + ) + + task_info = self.active_tasks[container_name] + + print(f"Stopping container: {container_name}") + + try: + # Stop the compose services + task_info["compose_manager"].stop() + + # Clean up tmux session + with self.tmux_sessions_lock: + if container_name in self.tmux_sessions: + del self.tmux_sessions[container_name] + + # Remove from active tasks + with self.active_tasks_lock: + del self.active_tasks[container_name] + + print(f"Successfully stopped and removed container: {container_name}") + return JSONResponse({"container_name": container_name, "status": "stopped"}) + except Exception as e: + print(f"Error stopping container {container_name}: {e}") + return JSONResponse( + {"error": f"Failed to stop container: {e}"}, status_code=500 + ) + + def _image_exists(self, image_name: str) -> bool: + """Check if a Docker image exists locally. + + Args: + image_name: Name of the Docker image to check + + Returns: + True if image exists, False otherwise + """ + try: + self.docker_client.images.get(image_name) + return True + except docker.errors.ImageNotFound: + return False + + +def log_operation_time(operation_name: str, start_time: float): + """Log the time taken for an operation.""" + duration = time() - start_time + logger.info( + f"[PERF] {operation_name} took {duration:.3f}s ({duration * 1000:.1f}ms)" + ) + + +def _calculate_weighted_test_score( + results: dict[str, UnitTestStatus], + test_weights: dict[str, float] | None, +) -> float: + """ + Calculate weighted score from test results. + + Args: + results: Test name to status mapping + test_weights: Test name to weight mapping + + Returns: + Weighted score between 0.0 and 1.0 + """ + if not results: + return 0.0 + + # If no test weights provided or only placeholder, use equal weights + # Filter out placeholder key used when test_weights.json doesn't exist + filtered_weights = { + k: v for k, v in (test_weights or {}).items() if not k.startswith("_") + } + + if not filtered_weights: + equal_weight = 1.0 / len(results) + total_score = sum( + equal_weight if status == UnitTestStatus.PASSED else 0.0 + for status in results.values() + ) + return total_score + + # Calculate weighted score + total_score = 0.0 + total_weight = 0.0 + + for test_name, status in results.items(): + weight = filtered_weights.get(test_name, 0.0) + if weight > 0: + score = 1.0 if status == UnitTestStatus.PASSED else 0.0 + total_score += score * weight + total_weight += weight + + # Normalize if weights don't sum to 1.0 + if total_weight > 0: + return total_score / total_weight + + equal_weight = 1.0 / len(results) + return sum( + equal_weight if status == UnitTestStatus.PASSED else 0.0 + for status in results.values() + ) diff --git a/examples/openai-agents/terminal/prompt.py b/examples/openai-agents/terminal/prompt.py new file mode 100644 index 000000000..6825b0302 --- /dev/null +++ b/examples/openai-agents/terminal/prompt.py @@ -0,0 +1,264 @@ +SYSTEM_PROMPT = """ +You are a Terminal Agent operating inside a Linux shell environment. +You can interact with the system using provided tools with multiple retries. Never simulate, predict, or describe command results — always perform real actions through tool calls. +--- + +## WORKFLOW (STRICT) + +### Phase 1: Initial Exploration (First Actions) +1. **Start with `current_working_directory()`** to understand your current location +2. **Use `file_contents()`** to examine important files (README, configs, requirements) +3. **Create a mental map** of the directory structure and key files + +### Phase 2: Task Execution +1. **Plan your approach** based on what you discovered +2. **Download missing tools** using `execute_command()` if needed +3. **Use `execute_command()`** for implementation actions +4. **Use `file_contents()`** to verify changes and check results +5. **Use `current_working_directory()`** to monitor directory changes + +### Phase 3: Verification +1. **Check your work** with appropriate tools +2. **Test functionality** if applicable +3. **Provide concise summary** of what was accomplished + +--- + +## TOOL USAGE BEST PRACTICES + +### File Operations +- **Prefer `file_contents(head_lines=N)`** over `execute_command("head -n N file")` for better error handling +- **Prefer `file_contents(tail_lines=N)`** over `execute_command("tail -n N file")` for logs +- **Use `current_working_directory()`** before file operations to verify paths + +### Command Execution +- **Use appropriate wait_time_sec**: 1.0 for quick commands, 5.0+ for long operations +- **Check command results** before proceeding to next steps +- **Use absolute paths** when possible to avoid path issues + +### Error Handling +- **Read error messages carefully** from tool outputs +- **Try one corrective action** per failure (fix path, add permissions, install missing deps) +- **Use `current_working_directory()`** to verify file existence before operations + +--- + +## Error Categories and Solutions + +### Command Not Found +- **Symptom**: `bash: command: not found` +- **Solution**: Install missing package (use apt-get install) + +### Permission Denied +- **Symptom**: `bash: ./script: Permission denied` +- **Solution**: Use `chmod +x` (see section 2) + +### File Not Found +- **Symptom**: `No such file or directory` +- **Solution**: Check current directory, verify paths, use absolute paths + +### Syntax Errors +- **Symptom**: Command syntax issues +- **Solution**: Check command syntax, quote strings properly, escape special chars + +### Network Errors +- **Symptom**: Connection timeouts, DNS failures +- **Solution**: Check network connectivity, try different mirrors, use offline alternatives + +--- + +## EFFICIENCY TIPS +- **Use `file_contents()` with head/tail** for large files to avoid long outputs +- **Start with `current_working_directory()`** to understand the environment +- **Combine related operations** in logical sequences +- **Avoid redundant commands** - check results before repeating actions + +--- + +Be methodical, explore first, then execute. Use the right tool for each task and always verify your results. +""" + + +JUDGE_PROMPT = """ +You are a judge for a terminal task agent. You will be given the agent't session lists which contains the agent's actions and the environment's responses directly, so you can evaluate the agent's performance based on the actions and responses. +## Quick Reference: Scoring Overview + +**Score Range**: 0.00 to 1.00 (two decimal places) + +### Immediate Failure Conditions (Hard Caps) +- **No valid tool calls**: Max score 0.09 +- **Only parse errors**: Max score 0.30 +- **No initial todo creation**: Max score 0.40 +- **Skipped exploration phase**: Max score 0.50 + +### Primary Scoring Components +1. **Action Output Success** (35%) +2. **Todo Usage & Planning** (25%) +3. **Phase Adherence** (25%) +4. **Tool Usage Effectiveness** (15%) + +--- + +## Required Execution Phases + +Agents MUST follow these phases in order: + +1. **Planning** → Create initial todos (first action) including exploration tasks +2. **Exploration** → Read-only discovery of file structure, key files, and environment +3. **Plan Refinement** → Update todos based on findings +4. **Execution** → Implement the solution, adjust / maintain / extend plan where necessary +5. **Verification** → Test and validate + +**Phase violations incur significant penalties (-0.20 to -0.30)** + +--- + +## Detailed Scoring Criteria + +### 1. Action Output Success (35% weight) + +**Evaluate**: +- Percentage of turns with valid actions +- Successful parsing and execution rate +- Recovery from failures + +### 2. Todo Usage & Planning (25% weight) + +**Requirements**: +- First action MUST create todos +- Initial todos should typically include exploration tasks (file structure, key files) unless user provides complete details +- Todo list is kept up to date throughout based on discoveries + +**Penalties**: +- No initial todos: Cap at 0.40 +- Never completing todos: -0.10 to -0.20 +- Poor maintenance: -0.05 to -0.15 + +### 3. Phase Adherence (25% weight) + +**Check for**: +- All 5 phases in correct order +- Evidence in both todos AND actions +- Extensive and relevant systematic exploration before implementation +- Proper refinement of plan based on discoveries + +**Violations**: +- Skipping phases: -0.20 to -0.30 +- Out of order execution: -0.15 to -0.25 + +### 4. Tool Usage Effectiveness (15% weight) + +**Good Tool Usage**: +- Purposeful actions progressing toward goal +- Appropriate tool selection +- Using simpler tools when available + +**Scratchpad Usage**: +- ✅ Reward (+0.05 to +0.10): Complex reasoning, hypothesis tracking +- ❌ Penalize (-0.05 to -0.10): Duplicating todos, chat-like usage + +**Tool Misuse Penalties** (-0.05 to -0.15): +- Meaningless action sequences +- Actions contradicting logical workflow +- Fundamental misunderstanding of tool purpose + +--- + +## Quality Modifiers + +### Error Recovery & Learning (+/- 0.10) +**Bonus Conditions**: +- Fixes parse errors and continues +- Adapts after command failures +- Shows clear improvement trajectory +- Error messages lead to corrected actions + +### Discovery Quality (+/- 0.20) +**Look for**: +- Systematic exploration +- Information synthesis across phases +- Building comprehensive understanding +- Effective use of scratchpad for insights + +### Efficiency & Focus (+/- 0.05) +**Assess**: +- Avoiding redundant actions +- Maintaining phase focus +- Clean action sequences +- Working within token constraints + +### Assumption Avoidance (+/- 0.15) +**Penalize** (-0.05 to -0.15): +- Acting on assumed file locations +- Implementing based on guesses +- Making changes without verification that they worked + +**Reward** (+0.05): +- Explicit verification before action +- Checking file existence +- Testing assumptions through exploration + +--- + +## Critical Penalty Areas + +### Overthinking Detection (-0.15 to -0.40) + +**CRITICAL: Thinking without action is heavily penalized. Take concrete actions immediately.** + +**Analysis Paralysis** (-0.15 to -0.30): +- Excessive thinking (10+ lines or multiple paragraphs in tags) with no corresponding actions +- Repeatedly questioning tool availability instead of trying them +- Over-analyzing instead of executing concrete actions +- Explaining basic syntax instead of using and testing it + +**Approach Switching Loops** (-0.10 to -0.25): +- Cycling through same options +- Revisiting rejected approaches + +**Redundant Action Attempts** (-0.15 to -0.30): +- Retrying completed tasks +- Ignoring "already completed" messages +- Creating duplicate todos + +**Writing Full Actions in Thinking** (-0.10 to -0.25): +- Drafting complete tool calls +- Writing out full code snippets instead of executing them +- Pre-planning entire scripts rather than building incrementally +- Long thinking blocks with no actions between them +- Note: Brief planning is good; extended thinking without action is not + +**Severity Scale**: +- Minor (1-2 patterns): -0.15 +- Moderate (3-4 patterns): -0.25 +- Severe (5+ patterns): -0.35 +- Extreme (prevents actions): -0.40 + +### Gaming Detection (-0.10 to -0.30) + +**Watch for**: +- Minimal actions to "check off" phases +- Artificial complexity for simple tasks +- Suspicious early mistakes with dramatic recovery +- Unnecessarily prolonged trajectories + +--- + +## Key Reminders + +✅ **Always Reward**: +- Planning-exploration first approach +- Clear phase progression +- Learning from errors +- Efficient execution +- Strategic scratchpad use + +❌ **Always Penalize**: +- No tool use +- Missing initial exploration +- Phase skipping +- Overthinking/paralysis +- Gaming behaviors + +⚠️ **Your Role**: Evaluate HOW the agent worked, not WHETHER the task was completed. Task completion is verified separately via software run unit tests. +""" diff --git a/examples/openai-agents/terminal/requirements.txt b/examples/openai-agents/terminal/requirements.txt new file mode 100644 index 000000000..6e7068bc1 --- /dev/null +++ b/examples/openai-agents/terminal/requirements.txt @@ -0,0 +1,4 @@ +FastMCP +pydantic +starlette +terminal_bench \ No newline at end of file diff --git a/examples/openai-agents/terminal/server.py b/examples/openai-agents/terminal/server.py new file mode 100644 index 000000000..9e42f3184 --- /dev/null +++ b/examples/openai-agents/terminal/server.py @@ -0,0 +1,222 @@ +"""A MCP server for running terminal-bench tasks independently.""" + +import argparse +import asyncio +import contextvars +import logging +import warnings +from pathlib import Path + +from fastmcp import FastMCP +from starlette.requests import Request + +from .logging_config import setup_logging +from .models import TaskRequest, TerminalBenchServer + +# Suppress SQLAlchemy 2.0 deprecation warnings from terminal_bench +warnings.filterwarnings("ignore", category=DeprecationWarning, module="terminal_bench") + +setup_logging() + +logger = logging.getLogger(__name__) + +# Context variable to track request start time +request_start_time = contextvars.ContextVar("request_start_time", default=None) + +server_instance = None +# --- MCP Server Setup with FastMCP --- + +mcp = FastMCP("t-bench-multi-task") + + +@mcp.tool() +async def keystrokes( + container_name: str, + keystrokes: str, + append_enter: bool = False, + wait_time_sec: float = 0.0, +) -> str: + """Send keystrokes to a tmux session and return the result. + + Args: + container_name: The name of the container to send keystrokes to + keystrokes: Keystrokes to execute in the terminal. Use tmux-style escape sequences for special characters (e.g. C-c for ctrl-c) + append_enter: Whether to append a newline character to the end of the keystrokes (necessary to execute bash commands) + wait_time_sec: The number of expected seconds to wait for the command to complete + + Returns: + Terminal output after executing the keystrokes + """ + + # Validate container exists + if not server_instance.validate_container(container_name): + raise ValueError(f"Invalid or unknown container_name: {container_name}") + + # Update last seen timestamp + server_instance.update_task_last_seen(container_name) + + # Get or create tmux session + session = await server_instance.get_tmux_session(container_name) + + # Clear the terminal to avoid historical results in next calls + session.send_keys( + keys=["clear", "Enter"], + min_timeout_sec=0.1, + max_timeout_sec=0.1, + ) + + keys = [keystrokes, "Enter"] if append_enter else keystrokes + session.send_keys( + keys=keys, + min_timeout_sec=wait_time_sec, + max_timeout_sec=wait_time_sec, + ) + + # Capture the output before clearing + output = session.capture_pane() + + # Clear the terminal to avoid historical results in next calls + session.send_keys( + keys=["clear", "Enter"], + min_timeout_sec=0.1, + max_timeout_sec=0.1, + ) + + return output + + +@mcp.tool() +async def capture_pane( + container_name: str, + wait_before_capture_sec: float = 0.0, +) -> str: + """Capture the pane of a tmux session. + + Args: + container_name: The name of the container to capture the pane from + wait_before_capture_sec: The number of seconds to wait before capturing the pane. This is useful if you just executed a command and want to wait a bit to capture the output + + Returns: + Current terminal pane content + """ + + # Validate container exists + if not server_instance.validate_container(container_name): + raise ValueError(f"Invalid or unknown container_name: {container_name}") + + # Update last seen timestamp + server_instance.update_task_last_seen(container_name) + + # Get or create tmux session + session = await server_instance.get_tmux_session(container_name) + + if wait_before_capture_sec > 0: + await asyncio.sleep(wait_before_capture_sec) + + return session.capture_pane() + + +# --- Custom HTTP Routes for Task Management --- + + +@mcp.custom_route("/tasks/start", methods=["POST"]) +async def start_task_route(request: Request): + """Start a new task container.""" + data = await request.json() + req = TaskRequest.model_validate(data) + return await server_instance.start_task(req) + + +@mcp.custom_route("/tasks/stop", methods=["POST"]) +async def stop_task_route(request: Request): + """Stop a task container.""" + data = await request.json() + req = TaskRequest.model_validate(data) + return await server_instance.stop_task(req) + + +@mcp.custom_route("/tasks", methods=["GET"]) +async def list_tasks_route(request: Request): + """List all active tasks.""" + return await server_instance.list_tasks() + + +@mcp.custom_route("/tasks/validate", methods=["POST"]) +async def validate_container_route(request: Request): + """Validate a container and get test results.""" + data = await request.json() + req = TaskRequest.model_validate(data) + container_name = req.container_name + if not container_name or container_name == "": + container_name = f"{req.uuid}__{req.task_name}" + return await server_instance.validate_task(container_name) + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Terminal Bench MCP Server - Run terminal-bench tasks independently", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + parser.add_argument( + "--tasks-dir", + type=Path, + default=None, + help="Path to the tasks directory (default: env T_BENCH_TASKS_DIR or /app/tasks)", + ) + + parser.add_argument( + "--tasks-log-dir", + type=Path, + default=None, + help="Path to the tasks logs directory (default: env T_BENCH_TASKS_LOG_DIR or /var/logs/terminal-bench/)", + ) + + parser.add_argument( + "--host", type=str, default="0.0.0.0", help="Host to bind the server to" + ) + + parser.add_argument( + "--port", type=int, default=8000, help="Port to bind the server to" + ) + + parser.add_argument( + "--preheat-image", + action="store_true", + default=False, + help="Preheat the docker image before starting the server", + ) + + return parser.parse_args() + + +def main(): + """Main entry point for the server.""" + args = parse_args() + + # Initialize server instance (don't start yet) + global server_instance + server_instance = TerminalBenchServer( + tasks_dir=args.tasks_dir, + tasks_log_dir=args.tasks_log_dir, + preheat_image=args.preheat_image, + ) + + server_instance.startup() + + # Run FastMCP server - startup will happen on first tool call + try: + mcp.run( + transport="sse", + host=args.host, + port=args.port, + ) + except KeyboardInterrupt: + print("\nShutting down server...") + if server_instance: + server_instance.shutdown() + + +if __name__ == "__main__": + main() diff --git a/examples/openai-agents/terminal/tasks_to_parquet_converter.py b/examples/openai-agents/terminal/tasks_to_parquet_converter.py new file mode 100644 index 000000000..831332812 --- /dev/null +++ b/examples/openai-agents/terminal/tasks_to_parquet_converter.py @@ -0,0 +1,232 @@ +"""Convert Terminal Bench tasks to RLLM/VERL format.""" + +import argparse +import json +from pathlib import Path + +import pandas as pd +from terminal_bench_task import TBenchTrainingTask, load_terminal_bench_tasks +from tqdm import tqdm + + +def create_prompt_from_task( + task: TBenchTrainingTask, system_prompt: str | None = None +) -> str: + """Create a prompt from a terminal bench task.""" + if system_prompt is None: + system_prompt = ( + "You are an AI assistant helping to complete terminal-based tasks. " + "Follow the instructions carefully and use appropriate commands to accomplish the goal." + ) + + # Convert to string format (you might want to use a specific chat template) + prompt = ( + f"<|system|>\n{system_prompt}\n<|user|>\n{task.instruction}\n<|assistant|>\n" + ) + + return prompt + + +def convert_tasks_to_parquet( + tasks_dir: Path, + output_dir: Path, + train_split: float | None = None, + system_prompt: str | None = None, + task_names: list[str] | None = None, + test_tasks_dir: Path | None = None, +) -> None: + """Convert terminal bench tasks to parquet format for VERL training. + + Args: + tasks_dir: Directory containing terminal bench tasks (or train tasks if test_tasks_dir is provided) + output_dir: Output directory for parquet files + train_split: Fraction of data for training (ignored if test_tasks_dir is provided) + system_prompt: System prompt to use + task_names: Specific task names to convert + test_tasks_dir: Directory containing test tasks for validation set + """ + + # Load tasks + if test_tasks_dir is not None: + # Load train and test tasks separately + print(f"Loading training tasks from {tasks_dir}") + train_tasks = load_terminal_bench_tasks(tasks_dir, task_names) + print(f"Loaded {len(train_tasks)} training tasks") + + print(f"Loading validation tasks from {test_tasks_dir}") + val_tasks = load_terminal_bench_tasks(test_tasks_dir, task_names) + print(f"Loaded {len(val_tasks)} validation tasks") + + tasks = train_tasks + val_tasks + else: + # Load all tasks from single directory + print(f"Loading tasks from {tasks_dir}") + tasks = load_terminal_bench_tasks(tasks_dir, task_names) + print(f"Loaded {len(tasks)} tasks") + + # Create output directory + output_dir.mkdir(parents=True, exist_ok=True) + + # Prepare data for parquet + data_records = [] + + for task in tqdm(tasks, desc="Converting tasks"): + record = { + "prompt": create_prompt_from_task(task, system_prompt), + "task_name": task.task_name, + "task_path": str(task.task_path), + "instruction": task.instruction, + "data_source": "terminal_bench", # For reward_fn_key + "metadata": { + "test_weights": task.test_weights, + "max_test_timeout_sec": task.max_test_timeout_sec, + }, + } + + # Always include extra_info with task configuration + extra_info_dict = { + "task_name": task.task_name, + "task_path": str(task.task_path), + "instruction": task.instruction, + "test_weights": task.test_weights, + "dockerfile_contents": task.dockerfile_contents, + "py_test_file_contents": task.py_test_file_contents, + "max_test_timeout_sec": task.max_test_timeout_sec, + } + + # Include additional files if present + if task.additional_files: + extra_info_dict["additional_files"] = task.additional_files + + record["extra_info"] = json.dumps(extra_info_dict) + + data_records.append(record) + + # Create DataFrame + df = pd.DataFrame(data_records) + + # Split into train and validation + if test_tasks_dir is not None: + # Use pre-defined split based on directories + n_train = len(train_tasks) + train_df = df[:n_train] + val_df = df[n_train:] + else: + # Use train_split parameter + if train_split is None: + train_split = 0.9 + n_train = int(len(df) * train_split) + train_df = df[:n_train] + val_df = df[n_train:] + + # Save to parquet + train_path = output_dir / "train.parquet" + val_path = output_dir / "val.parquet" + + train_df.to_parquet(train_path, index=False) + val_df.to_parquet(val_path, index=False) + + print(f"Saved {len(train_df)} training examples to {train_path}") + print(f"Saved {len(val_df)} validation examples to {val_path}") + + # Also save task information for the reward function + tasks_info = {} + for task in tasks: + task_info = { + "task_path": str(task.task_path), + "test_weights": task.test_weights, + "dockerfile_contents": task.dockerfile_contents, + "py_test_file_contents": task.py_test_file_contents, + "max_test_timeout_sec": task.max_test_timeout_sec, + } + # Include additional files if present + if task.additional_files: + task_info["additional_files"] = task.additional_files + + tasks_info[task.task_name] = task_info + + tasks_info_path = output_dir / "tasks_info.json" + with open(tasks_info_path, "w") as f: + json.dump(tasks_info, f, indent=2) + + print(f"Saved task information to {tasks_info_path}") + + +def main(): + parser = argparse.ArgumentParser( + description="Convert Terminal Bench tasks to RLLM/VERL format." + ) + parser.add_argument( + "--tasks-dir", + type=str, + default="tasks", + help="Directory containing training tasks (default: tasks)", + ) + parser.add_argument( + "--test-tasks-dir", + type=str, + default="", + help="Directory containing test/validation tasks (default: empty)", + ) + parser.add_argument( + "--output-dir", + type=str, + default="data", + help="Output directory for parquet files (default: data)", + ) + parser.add_argument( + "--train-split", + type=float, + default=None, + help="Fraction of data for training (only used if --test-tasks-dir is not provided)", + ) + parser.add_argument( + "--system-prompt", type=str, default=None, help="Custom system prompt to use" + ) + parser.add_argument( + "--task-names", + type=str, + nargs="+", + default=None, + help="Specific task names to convert (optional)", + ) + + args = parser.parse_args() + + # Convert to Path objects + tasks_dir = Path(args.tasks_dir) + test_tasks_dir = ( + Path(args.test_tasks_dir) + if args.test_tasks_dir and args.test_tasks_dir.strip() + else None + ) + output_dir = Path(args.output_dir) + + # Check if directories exist + if not tasks_dir.exists(): + print(f"Error: {tasks_dir} directory not found") + return + + # Create output directory + output_dir.mkdir(parents=True, exist_ok=True) + + if test_tasks_dir: + print( + f"Converting {tasks_dir}/ -> train.parquet and {test_tasks_dir}/ -> val.parquet" + ) + else: + print(f"Converting {tasks_dir}/ with train_split={args.train_split}") + + # Convert to parquet only (with extra_info) + convert_tasks_to_parquet( + tasks_dir=tasks_dir, + output_dir=output_dir, + train_split=args.train_split, + system_prompt=args.system_prompt, + task_names=args.task_names, + test_tasks_dir=test_tasks_dir, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/openai-agents/terminal/terminal_bench_task.py b/examples/openai-agents/terminal/terminal_bench_task.py new file mode 100644 index 000000000..966a8c415 --- /dev/null +++ b/examples/openai-agents/terminal/terminal_bench_task.py @@ -0,0 +1,131 @@ +import json +from enum import Enum +from pathlib import Path + +import yaml +from pydantic import BaseModel +from tqdm import tqdm + + +class TBenchTaskDifficulty(Enum): + EASY = "easy" + MEDIUM = "medium" + HARD = "hard" + UNRATED = "unrated" + + +class TBenchTrainingTask(BaseModel): + """Data model for a task which follows the same format as terminal-bench.""" + + task_name: str + task_path: Path + instruction: str + difficulty: TBenchTaskDifficulty + test_weights: dict + dockerfile_contents: str + py_test_file_contents: str + max_test_timeout_sec: int = 300 # Default timeout + additional_files: dict | None = None # Maps file paths to contents + + +def load_terminal_bench_tasks( + tasks_dir: Path, + task_names: list[str] | None = None, +) -> list[TBenchTrainingTask]: + if task_names is None: + task_names = [p.name for p in tasks_dir.iterdir() if p.is_dir()] + + tasks = [] + for task_name in tqdm(task_names, desc="Loading tasks"): + task_path = tasks_dir / task_name + task_yaml = task_path / "task.yaml" + + if not task_yaml.exists(): + tqdm.write(f"Task YAML file not found: {task_yaml}") + continue + + with open(task_yaml, encoding="utf-8") as f: + task_data = yaml.safe_load(f) + + instruction = task_data.get("instruction") + if not instruction: + tqdm.write(f"Instruction not found in task YAML: {task_yaml}") + continue + + # Get max test timeout if specified + max_test_timeout_sec = task_data.get("max_test_timeout_sec", 300) + difficulty = task_data.get("difficulty", TBenchTaskDifficulty.UNRATED) + + # Load test weights + test_weights_path = task_path / "test_weights.json" + if test_weights_path.exists(): + with open(test_weights_path, encoding="utf-8") as f: + test_weights = json.load(f) + else: + # Use placeholder to avoid PyArrow empty struct error + test_weights = {"_no_weights": 1.0} + + # Load Dockerfile + dockerfile_path = task_path / "Dockerfile" + if not dockerfile_path.exists(): + tqdm.write(f"Dockerfile not found for task: {task_name}") + continue + with open(dockerfile_path, encoding="utf-8") as f: + dockerfile_contents = f.read() + if not dockerfile_contents: + tqdm.write(f"Dockerfile is empty for task: {task_name}") + continue + + # Load Python test file if it exists + py_test_file_path = task_path / "tests" / "test_outputs.py" + if not py_test_file_path.exists(): + tqdm.write(f"Python test file not found for task: {task_name}") + continue + with open(py_test_file_path, encoding="utf-8") as f: + py_test_file_contents = f.read() + if not py_test_file_contents: + tqdm.write(f"Python test file is empty for task: {task_name}") + continue + + # Load additional files if they exist + additional_files = {} + # List all files in the task directory (excluding standard files) + standard_files = {"Dockerfile", "task.yaml", "test_weights.json"} + standard_dirs = {"tests", "__pycache__"} + + for item in task_path.iterdir(): + if item.is_file() and item.name not in standard_files: + # Read the file and store with relative path + rel_path = item.relative_to(task_path) + try: + with open(item, encoding="utf-8") as f: + additional_files[str(rel_path)] = f.read() + except UnicodeDecodeError: + tqdm.write(f"Binary file found for task: {task_name}") + elif item.is_dir() and item.name not in standard_dirs: + # Recursively read files from subdirectories + for subfile in item.rglob("*"): + if subfile.is_file(): + rel_path = subfile.relative_to(task_path) + try: + with open(subfile, encoding="utf-8") as f: + additional_files[str(rel_path)] = f.read() + except UnicodeDecodeError: + # Skip binary files for now + tqdm.write(f"Binary file found for task: {task_name}") + + tasks.append( + TBenchTrainingTask( + task_name=task_name, + task_path=task_path, + instruction=instruction, + difficulty=difficulty, + test_weights=test_weights, + dockerfile_contents=dockerfile_contents, + py_test_file_contents=py_test_file_contents, + max_test_timeout_sec=max_test_timeout_sec, + additional_files=additional_files if additional_files else None, + ) + ) + + return tasks diff --git a/examples/openai-agents/terminal/test_client.py b/examples/openai-agents/terminal/test_client.py new file mode 100644 index 000000000..1de2bda0f --- /dev/null +++ b/examples/openai-agents/terminal/test_client.py @@ -0,0 +1,129 @@ +"""Simple MCP client to test terminal server functionality.""" + +import asyncio + +from mcp import ClientSession +from mcp.client.sse import sse_client + + +async def test_mcp_server(): + """Test the MCP server by calling tools.""" + server_url = "http://localhost:8000" + + print("Connecting to MCP server...") + async with sse_client(f"{server_url}/sse") as (read, write): + async with ClientSession(read, write) as session: + await session.initialize() + print("✓ Connected to MCP server\n") + + # List available tools + print("=" * 60) + print("Available Tools:") + print("=" * 60) + tools_result = await session.list_tools() + for tool in tools_result.tools: + print(f" • {tool.name}: {tool.description}") + print() + + # Test 1: Start a task + print("=" * 60) + print("Test 1: Starting a task container") + print("=" * 60) + import requests + + response = requests.post( + f"{server_url}/tasks/start", + json={"uuid": "test-123", "task_name": "hello-world"}, + timeout=60, + ) + if response.status_code == 200: + data = response.json() + container_name = data["container_name"] + print(f"✓ Task started: {container_name}\n") + + # Test 2: Send keystrokes + print("=" * 60) + print("Test 2: Sending keystrokes (echo 'Hello MCP')") + print("=" * 60) + result = await session.call_tool( + name="keystrokes", + arguments={ + "container_name": container_name, + "keystrokes": "echo 'Hello MCP'", + "append_enter": True, + "wait_time_sec": 1.0, + }, + ) + output = result.content[0].text if result.content else "" + print(f"Output:\n{output}\n") + + print("=" * 60) + print("Test 3: Capturing terminal pane") + print("=" * 60) + result = await session.call_tool( + name="capture_pane", + arguments={ + "container_name": container_name, + "wait_before_capture_sec": 0.5, + }, + ) + output = result.content[0].text if result.content else "" + print(f"Captured output:\n{output}\n") + + print("=" * 60) + print("Test 4: Listing active tasks") + print("=" * 60) + response = requests.get(f"{server_url}/tasks") + if response.status_code == 200: + tasks = response.json() + print(f"✓ Active tasks: {len(tasks)}") + for task in tasks: + print(f" • {task['container_name']} (UUID: {task['uuid']})") + print() + + # validate task + print("=" * 60) + print("Test 4: Validating task container") + print("=" * 60) + response = requests.post( + f"{server_url}/tasks/validate", + json={"uuid": "test-123", "task_name": "hello-world"}, + timeout=180, + ) + if response.status_code == 200: + print( + f"✓ Task validated successfully, score: {response.json().get('score')}\n" + ) + else: + print(f"✗ Failed to validate task: {response.text}\n") + + print("=" * 60) + print("Test 5: Stopping task container") + print("=" * 60) + response = requests.post( + f"{server_url}/tasks/stop", + json={"uuid": "test-123", "task_name": "hello-world"}, + timeout=30, + ) + if response.status_code == 200: + print("✓ Task stopped successfully\n") + else: + print(f"✗ Failed to stop task: {response.text}\n") + else: + print(f"✗ Failed to start task: {response.text}\n") + + print("=" * 60) + print("All tests completed!") + print("=" * 60) + + +if __name__ == "__main__": + try: + asyncio.run(test_mcp_server()) + except KeyboardInterrupt: + print("\nTest interrupted by user") + except Exception as e: + print(f"\n✗ Error: {e}") + import traceback + + traceback.print_exc() diff --git a/examples/openai-agents/train_agents.py b/examples/openai-agents/train_agents.py index 3fda65287..202ac5ba3 100644 --- a/examples/openai-agents/train_agents.py +++ b/examples/openai-agents/train_agents.py @@ -28,7 +28,7 @@ class AgentRLConfig(GRPOConfig): default="math", metadata={ "help": "Type of agent workflow to use.", - "choices": ["math", "multi_agent_math"], + "choices": ["math", "multi_agent_math", "multi_agent_terminal"], }, ) n_trajs: int = field( @@ -123,6 +123,19 @@ def main(args): StatsLogger.get_log_path(config.stats_logger), "generated" ), ) + elif config.agent_type == "multi_agent_terminal": + from agent_terminal_workflow import TerminalAgentWorkflow + + workflow = TerminalAgentWorkflow( + gconfig=config.gconfig, + tokenizer=tokenizer, + n_trajs=config.n_trajs, + max_tokens=config.max_tokens_per_trajectory, + max_turns=config.max_turns, + dump_dir=os.path.join( + StatsLogger.get_log_path(config.stats_logger), "generated" + ), + ) else: raise ValueError(f"Unknown agent_type: {config.agent_type}.")