From 8cefe5a41fbf3d01bc362a21befc8d851661f93a Mon Sep 17 00:00:00 2001 From: kaarthik108 Date: Sat, 15 Apr 2023 19:35:05 +1200 Subject: [PATCH] Update formating --- chain.py | 4 +- ingest.py | 6 +- main.py | 116 +++++++++++++++++++-------------------- schema.md | 37 +++++++++++++ schema.txt | 33 ----------- sql/ddl_customer.sql | 9 +++ sql/ddl_orders.sql | 8 +++ sql/ddl_payments.sql | 8 +++ sql/ddl_products.sql | 7 +++ sql/ddl_transactions.sql | 9 +++ utils.py | 1 + vectors.pkl | Bin 14384 -> 15244 bytes 12 files changed, 141 insertions(+), 97 deletions(-) create mode 100644 schema.md delete mode 100644 schema.txt create mode 100644 sql/ddl_customer.sql create mode 100644 sql/ddl_orders.sql create mode 100644 sql/ddl_payments.sql create mode 100644 sql/ddl_products.sql create mode 100644 sql/ddl_transactions.sql diff --git a/chain.py b/chain.py index b3f3f5d..e684e88 100644 --- a/chain.py +++ b/chain.py @@ -8,7 +8,7 @@ from langchain.prompts.prompt import PromptTemplate # from langchain.chat_models import ChatOpenAI -TEMPLATE = """ You're name is snowchat, and you are a senior snowflake developer. You are currently working in a snowflake database. You have to write a sql code in snowflake database based on the following question. Also you have to ignore the sql keywords and the context and give a one or two sentences about how did you arrive at that sql code. Be a little bit creative and humorous. +TEMPLATE = """ You're name is snowchat, and you are a senior snowflake developer. You are currently working in a snowflake database. You have to write a sql code in snowflake database based on the following question. Also you have to ignore the sql keywords and the context and give a one or two sentences about how did you arrive at that sql code. display the sql code in the code format and the answer in the markdown format. If you don't know the answer, just say "Hmm, I'm not sure. I am trained only to answer sql related queries. Please try again." Don't try to make up an answer. Use snowflake database documentation https://docs.snowflake.com/sql-reference-commands for writing sql code. @@ -22,7 +22,7 @@ def get_chain(vectorstore): """ Get a chain for chatting with a vector database. """ - llm = ChatOpenAI(model_name='gpt-4', temperature=0.2) + llm = ChatOpenAI(model_name='gpt-4', temperature=0.8) chat_vector_db_chain = ChatVectorDBChain.from_llm(llm=llm, vectorstore=vectorstore, qa_prompt=QA_PROMPT, verbose=True) return chat_vector_db_chain diff --git a/ingest.py b/ingest.py index f35b43f..17313ab 100644 --- a/ingest.py +++ b/ingest.py @@ -2,15 +2,13 @@ import os from langchain.embeddings import OpenAIEmbeddings from langchain.text_splitter import RecursiveCharacterTextSplitter -from langchain.document_loaders import DirectoryLoader +from langchain.document_loaders import DirectoryLoader, UnstructuredMarkdownLoader from langchain.vectorstores import FAISS from dotenv import load_dotenv # import pandas as pd load_dotenv() -PERSIST_DIRECTORY = 'store' - -loader = DirectoryLoader('./', glob="*.txt") +loader = UnstructuredMarkdownLoader('schema.md') data = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0) diff --git a/main.py b/main.py index c03b5ff..a065fa7 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ import pickle -import re +import html import os from langchain import FAISS import openai @@ -12,47 +12,52 @@ from streamlit import components from utils import query_data_warehouse load_dotenv() +from functools import lru_cache openai.api_key = os.getenv('OPENAI_API_KEY') -with open("vectors.pkl", "rb") as f: - print('Loading model...') - vectorstore = pickle.load(f) +# @st.cache_resource +def load_chain(): + with open("vectors.pkl", "rb") as f: + print('Loading model...') + vectorstore = pickle.load(f) -chain = get_chain(vectorstore) + return get_chain(vectorstore) + +chain = load_chain() st.title("snowChat") st.subheader("Chat with Snowflake Database") -ddl_transactions = ''' -CREATE OR REPLACE TABLE TRANSACTIONS ( - TRANSACTION_ID NUMBER(38,0) NOT NULL, - ORDER_ID NUMBER(38,0), - PRODUCT_ID NUMBER(38,0), - QUANTITY NUMBER(38,0), - PRICE NUMBER(10,2), - PRIMARY KEY (TRANSACTION_ID), - FOREIGN KEY (ORDER_ID) REFERENCES STREAM_HACKATHON.STREAMLIT.ORDER_DETAILS(ORDER_ID) -); -''' - -# Add more DDLs for other tables here -# ddl_table2 = '''...''' -# ddl_table3 = '''...''' - -# Create a dictionary to store the table names and their corresponding DDLs -ddl_dict = { - "TRANSACTIONS": ddl_transactions, - # "TABLE2": ddl_table2, - # "TABLE3": ddl_table3, -} +class SnowChat: + def __init__(self): + self.ddl_dict = self.load_ddls() + + @staticmethod + def load_ddls(): + ddl_files = { + "TRANSACTIONS": "sql/ddl_transactions.sql", + "ORDER_DETAILS": "sql/ddl_orders.sql", + "PAYMENTS": "sql/ddl_payments.sql", + "PRODUCTS": "sql/ddl_products.sql", + "CUSTOMER_DETAILS": "sql/ddl_customer.sql" + } + + ddl_dict = {} + for table_name, file_name in ddl_files.items(): + with open(file_name, "r") as f: + ddl_dict[table_name] = f.read() + # print(f"DDL for table loaded. {ddl_dict[table_name]} ") + return ddl_dict + +snow_chat = SnowChat() # Create a sidebar with a dropdown menu -selected_table = st.sidebar.selectbox("Select a table:", options=list(ddl_dict.keys())) +selected_table = st.sidebar.selectbox("Select a table:", options=list(snow_chat.ddl_dict.keys())) # Display the DDL for the selected table st.sidebar.markdown(f"### DDL for {selected_table} table") -st.sidebar.code(ddl_dict[selected_table], language="sql") +st.sidebar.code(snow_chat.ddl_dict[selected_table], language="sql") st.write(""" @@ -106,6 +111,7 @@ if 'messages' not in st.session_state: st.session_state['messages'] = [("Hello! I'm a chatbot designed to help you with Snowflake Database.")] +@st.cache_resource def extract_code(text): # Use OpenAI's GPT-3 to extract the SQL code response = openai.ChatCompletion.create( @@ -123,17 +129,10 @@ def extract_code(text): messages_container = st.container() -# Add a button inside the container to get the value of the text input widget -# Input container -# input_container = st.container() -# form = st.form() - -# query = input_container.text_input("", key="input", placeholder="Type your query here...", label_visibility="hidden") with st.form(key='my_form'): query = st.text_input("", key="input", placeholder="Type your query here...", label_visibility="hidden") submit_button = st.form_submit_button(label='Submit') -# messages = [] if 'messages' not in st.session_state: st.session_state['messages'] = [] @@ -151,35 +150,35 @@ def extract_code(text): st.session_state.generated.append(result['answer']) def message(text, is_user=False, key=None, avatar_style="Adventurer"): + text = html.escape(text) if is_user: avatar_url = f"https://avataaars.io/?avatarStyle=Circle&topType=ShortHairShortFlat&accessoriesType=Blank&hairColor=BrownDark&facialHairType=Blank&clotheType=Hoodie&clotheColor=Blue03&eyeType=Default&eyebrowType=Default&mouthType=Default&skinColor=Light" message_alignment = "flex-end" message_bg_color = "linear-gradient(135deg, #ff5f6d 0%, #ffc371 100%)" avatar_class = "user-avatar" st.write(f""" -
-
- {text} -
- avatar - -
- """, unsafe_allow_html=True) +
+
+ {text} +
+ avatar + +
+ """, unsafe_allow_html=True) else: avatar_url = f"https://avataaars.io/?avatarStyle=Circle&topType=LongHairBun&accessoriesType=Blank&hairColor=BrownDark&facialHairType=Blank&clotheType=BlazerShirt&eyeType=Default&eyebrowType=Default&mouthType=Default&skinColor=Light" message_alignment = "flex-start" message_bg_color = "linear-gradient(135deg, #36d1dc 0%, #5b86e5 100%)" avatar_class = "bot-avatar" st.write(f""" -
- avatar -
- {text} -
-
- """, unsafe_allow_html=True) - - +
+ avatar +
+ {text} +
+
+ """, unsafe_allow_html=True) + with messages_container: if st.session_state['generated']: @@ -188,11 +187,12 @@ def message(text, is_user=False, key=None, avatar_style="Adventurer"): message(st.session_state["generated"][i], key=str(i), avatar_style="Adventurer") op = extract_code(st.session_state["generated"][i]) try: - if len(op) > 6: - print("op is", op) - df = query_data_warehouse(op) - st.spinner("Loading data...") - st.dataframe(df) + if len(op) > 2: + with st.spinner("In progress..."): + print("op is", op) + df = query_data_warehouse(op) + + st.dataframe(df) except: pass diff --git a/schema.md b/schema.md new file mode 100644 index 0000000..eeacb3b --- /dev/null +++ b/schema.md @@ -0,0 +1,37 @@ +**Table 1: STREAM_HACKATHON.STREAMLIT.CUSTOMER_DETAILS** (Stores customer information) + +- CUSTOMER_ID: Number (38,0) [Primary Key, Not Null] - Unique identifier for customers +- FIRST_NAME: Varchar (255) - First name of the customer +- LAST_NAME: Varchar (255) - Last name of the customer +- EMAIL: Varchar (255) - Email address of the customer +- PHONE: Varchar (20) - Phone number of the customer +- ADDRESS: Varchar (255) - Physical address of the customer + +**Table 2: STREAM_HACKATHON.STREAMLIT.ORDER_DETAILS** (Stores order information) + +- ORDER_ID: Number (38,0) [Primary Key, Not Null] - Unique identifier for orders +- CUSTOMER_ID: Number (38,0) [Foreign Key - CUSTOMER_DETAILS(CUSTOMER_ID)] - Customer who made the order +- ORDER_DATE: Date - Date when the order was made +- TOTAL_AMOUNT: Number (10,2) - Total amount of the order + +**Table 3: STREAM_HACKATHON.STREAMLIT.PAYMENTS** (Stores payment information) + +- PAYMENT_ID: Number (38,0) [Primary Key, Not Null] - Unique identifier for payments +- ORDER_ID: Number (38,0) [Foreign Key - ORDER_DETAILS(ORDER_ID)] - Associated order for the payment +- PAYMENT_DATE: Date - Date when the payment was made +- AMOUNT: Number (10,2) - Amount of the payment + +**Table 4: STREAM_HACKATHON.STREAMLIT.PRODUCTS** (Stores product information) + +- PRODUCT_ID: Number (38,0) [Primary Key, Not Null] - Unique identifier for products +- PRODUCT_NAME: Varchar (255) - Name of the product +- CATEGORY: Varchar (255) - Category of the product +- PRICE: Number (10,2) - Price of the product + +**Table 5: STREAM_HACKATHON.STREAMLIT.TRANSACTIONS** (Stores transaction information) + +- TRANSACTION_ID: Number (38,0) [Primary Key, Not Null] - Unique identifier for transactions +- ORDER_ID: Number (38,0) [Foreign Key - ORDER_DETAILS(ORDER_ID)] - Associated order for the transaction +- PRODUCT_ID: Number (38,0) - Product involved in the transaction +- QUANTITY: Number (38,0) - Quantity of the product in the transaction +- PRICE: Number (10,2) - Price of the product in the transaction diff --git a/schema.txt b/schema.txt deleted file mode 100644 index 7aa5a91..0000000 --- a/schema.txt +++ /dev/null @@ -1,33 +0,0 @@ - -tablename: STREAM_HACKATHON.STREAMLIT.CUSTOMER_DETAILS -CUSTOMER_ID: Number (38,0), not null, primary key -FIRST_NAME: Varchar (255) -LAST_NAME: Varchar (255) -EMAIL: Varchar (255) -PHONE: Varchar (20) -ADDRESS: Varchar (255) - -tablename: STREAM_HACKATHON.STREAMLIT.ORDER_DETAILS -ORDER_ID: Number (38,0), not null, primary key -CUSTOMER_ID: Number (38,0), foreign key references CUSTOMER_DETAILS(CUSTOMER_ID) -ORDER_DATE: Date -TOTAL_AMOUNT: Number (10,2) - -tablename: STREAM_HACKATHON.STREAMLIT.PAYMENTS -PAYMENT_ID: Number (38,0), not null, primary key -ORDER_ID: Number (38,0), foreign key references ORDER_DETAILS(ORDER_ID) -PAYMENT_DATE: Date -AMOUNT: Number (10,2) - -tablename: STREAM_HACKATHON.STREAMLIT.PRODUCTS -PRODUCT_ID: Number (38,0), not null, primary key -PRODUCT_NAME: Varchar (255) -CATEGORY: Varchar (255) -PRICE: Number (10,2) - -tablename: STREAM_HACKATHON.STREAMLIT.TRANSACTIONS -TRANSACTION_ID: Number (38,0), not null, primary key -ORDER_ID: Number (38,0), foreign key references ORDER_DETAILS(ORDER_ID) -PRODUCT_ID: Number (38,0) -QUANTITY: Number (38,0) -PRICE: Number (10,2) \ No newline at end of file diff --git a/sql/ddl_customer.sql b/sql/ddl_customer.sql new file mode 100644 index 0000000..2b734ad --- /dev/null +++ b/sql/ddl_customer.sql @@ -0,0 +1,9 @@ +create or replace TABLE CUSTOMER_DETAILS ( + CUSTOMER_ID NUMBER(38,0) NOT NULL, + FIRST_NAME VARCHAR(255), + LAST_NAME VARCHAR(255), + EMAIL VARCHAR(255), + PHONE VARCHAR(20), + ADDRESS VARCHAR(255), + primary key (CUSTOMER_ID) +); \ No newline at end of file diff --git a/sql/ddl_orders.sql b/sql/ddl_orders.sql new file mode 100644 index 0000000..f7cef21 --- /dev/null +++ b/sql/ddl_orders.sql @@ -0,0 +1,8 @@ +create or replace TABLE ORDER_DETAILS ( + ORDER_ID NUMBER(38,0) NOT NULL, + CUSTOMER_ID NUMBER(38,0), + ORDER_DATE DATE, + TOTAL_AMOUNT NUMBER(10,2), + primary key (ORDER_ID), + foreign key (CUSTOMER_ID) references STREAM_HACKATHON.STREAMLIT.CUSTOMER_DETAILS(CUSTOMER_ID) +); \ No newline at end of file diff --git a/sql/ddl_payments.sql b/sql/ddl_payments.sql new file mode 100644 index 0000000..0d04254 --- /dev/null +++ b/sql/ddl_payments.sql @@ -0,0 +1,8 @@ +create or replace TABLE PAYMENTS ( + PAYMENT_ID NUMBER(38,0) NOT NULL, + ORDER_ID NUMBER(38,0), + PAYMENT_DATE DATE, + AMOUNT NUMBER(10,2), + primary key (PAYMENT_ID), + foreign key (ORDER_ID) references STREAM_HACKATHON.STREAMLIT.ORDER_DETAILS(ORDER_ID) +); \ No newline at end of file diff --git a/sql/ddl_products.sql b/sql/ddl_products.sql new file mode 100644 index 0000000..5dc6aa7 --- /dev/null +++ b/sql/ddl_products.sql @@ -0,0 +1,7 @@ +create or replace TABLE PRODUCTS ( + PRODUCT_ID NUMBER(38,0) NOT NULL, + PRODUCT_NAME VARCHAR(255), + CATEGORY VARCHAR(255), + PRICE NUMBER(10,2), + primary key (PRODUCT_ID) +); \ No newline at end of file diff --git a/sql/ddl_transactions.sql b/sql/ddl_transactions.sql new file mode 100644 index 0000000..3d576d9 --- /dev/null +++ b/sql/ddl_transactions.sql @@ -0,0 +1,9 @@ +CREATE OR REPLACE TABLE TRANSACTIONS ( + TRANSACTION_ID NUMBER(38,0) NOT NULL, + ORDER_ID NUMBER(38,0), + PRODUCT_ID NUMBER(38,0), + QUANTITY NUMBER(38,0), + PRICE NUMBER(10,2), + PRIMARY KEY (TRANSACTION_ID), + FOREIGN KEY (ORDER_ID) REFERENCES STREAM_HACKATHON.STREAMLIT.ORDER_DETAILS(ORDER_ID) +); \ No newline at end of file diff --git a/utils.py b/utils.py index 7d37788..ac67c05 100644 --- a/utils.py +++ b/utils.py @@ -37,6 +37,7 @@ def query_data_warehouse(sql: str, parameters=None) -> any: try: cur.execute("USE DATABASE " + os.getenv("DATABASE")) + cur.execute("USE SCHEMA " + os.getenv("SCHEMA")) cur.execute(query, parameters) print("executing query") all_rows = cur.fetchall() diff --git a/vectors.pkl b/vectors.pkl index 3bf017dd0d4a42c72f8cada8fa07ce6f6573b296..114cfd39dfd20104af66c143fbb6889a8d2aae02 100644 GIT binary patch literal 15244 zcmb_@1yt49_CJb%VxS_TsEA^r(jsur{;G(@NC}9TNC{U;(r!cqBn0fn4(#Bb{S^ZP z#2Q;c#qJzM#rW@Y9i8{yy!Y1nulJj^W?(MgIA_NvHk=u?@@6s(^*`w?BLbpAgTexo zQ5K0ILGjVCarAFUoW+;`Wn5ftx>i3Y7jN&}oLsY+xl?mHr8f?V3=9blRz`*Tk4cCM zidRNQ<)+sTOi)I|E2HRoozRf@fcW@Wx+A^CUyuHESDZ!kxR9s-Wo~*y5BkQ*rSH$@ z#r^e?+W!8*%Ak0EfAuT1gCdk6QSrIy#y@Vg2pFgIrzJ)w#0Jqqe?2-kz0R+PiPvjI zMhAyPdF|EPe-klZkX^oB7BA+Zzx z<-;bw9^fAoKfymDBq}sMEZ6OO>+}XcmgO&&6&^A%H{CWa+&s!J-qS52WRRU#B69eKy{6#*LfeG|oVn9SfNSr#Z34!T#)m!pc?>09%*NZ}?j0z5!kejZj z&b~$5cxC9Hbx_wu{G?w*K)kEX&qSryiVsu9<@PqW($H|3(9cFgTSKGfkFuiwHvIFu zA75+K5dT~6$7h`)^Le*zZXge>WZ3dJzArbByIMDZHce9T-1cj@*7$dBndK=Phv%_1 zhHY`JO*2@Ry2UZ=zKi1cjb!NG-I_hwU5!0AZiS6b-*Ir|9&S}>1Gzh&;jZt8A=1we z*QIub2ZvJG8D%;Sv02FS!;ua>x1Xpdax-p9xha0dbS(gEtnHbWR{}Ujhd`RjZ!Rq z@`BACr{ev)?}A!o_rb>41GJki;sZ@%VZk6X+}3Rae)KSA>klvFw06Y;%h?!cGK%F) z`i?z`b}sFf=L0C%Y%}t40T5>@hPg+cW8LlqJ?#mj`1$HsG^bU&Bx73Vtoz2=`2! zs8XE!jERp%Vka+ic$GZ@iq|b=C8=Fh9crBBvj-RRP2a?nEr8Q2^z7DfS{GmEh^9q{IZ7k9E z@a&5@4C)!pg2pzK4ZF3K&YhbEsn0$^;oPQj3++gJ;d7W(e_Q~xPsoq%hT=L=e-r1m z1%vWTc0Xk$G}&M(jUP5fan~MMuZl%5biFYyt6m4#vM=s9x(+scm<7rAdgCr5d+hCL zQZy=U2i3wXz#h-=nD;?UY`PTMtxaL^`Pp#dOII))Ie}Glw}U~|ZKY#T1#JDzbX;iq z2c8O>gP%*TKt}FMv~f#=(dF4NJ4pjMOShqQt6EE%%j6_MR{^NuZ#z{i}YY^1xsbme0<)(_K=%bV!I zH5kPfeqO;E*7Ap#&n>|tKMTJYIbuk~Vsy@Zj@FvjanhNWJU+h>pWA!~telsRQw-Zk zy*|!|T}B5)Z?Wjj3Z$IC{S;%|yCVd*kNkplziec$4mU$lb5iZ~g&6h5WVkEFZ-FM~E1MZ+q!ME;)^xmVaYtv?O#~zPh zx7~I{t0Cw3obm?1#oMt>3l*3=I0KXy-g!$Ad*RayF2)>VH*d9- z42~>ebt`v4vWY(%(QG4*-Lwb8KN+Z6dpU>nIT$Ay3Mt`ak zj_pt1;?IpQ0o{k5EZgZqWml$+iw;$0pM=Tv)qh@pwXG-5ek zD!2_-?Fuj|FCGfl)|04~_?A;{XzrTCJs*|x=+k!;Bga2s2IYFv)1pRfymdn$T)@+F1g{kefRkLW+rq9mV1{9Boww1#@aca#afJ^{fi0 z7BR3}JC)wy1!%S_nsA|LhhA$Sw@Z zc?j>z^PonJ-yB3e?EIJoqL^*b~k|A;sGTEH%t z1VHC4Jyo$@JCL{w1;=dJepU1bz8l;hj7rXf!P97%qou^zN?qc%$?$ojmB>|LVzntO ziN8YF(GCkU`r+9$8nHb(S0yli! z4ci)PNYU~P9u#vJvp+559%TQdaJgR$Cu9=O* z8>%70BVcmnUZm$MYJ1y)mvtu4+F?S}HVpDD#ZluNWvWduDIEgz4!HbmGEPh=r}%~P zemV(UU;sS>BmJ%s?@edBJ@ZBU*vzlHAjj`ITccUdcWS?30++IM#zRm{FD|g0xViA+ zggC72y@!EG1(?NqOVmSz@vp#V-WpuL?L64qF2~g;ia5QKV&(u|u3AVrhtpqeCGm5@ zPuw*A87EAH%r&*7OB*dQcIa)DeQRGna=cQ!3r{76V1v{RaQfzaN9BBp(RGFV;Weh? zstKEvY5Y)RM;LZEmme?6a;)*WITP4F{Oe9kt5|}IS{CD$4I81=5D&<1Av?s>aAQ>C za*fYR)HO2vW-9ZJ>cXgxam@D~vexmvKpe(~*fd4Utn(oF*?+@&oY`!XitrBeZg-SL zO-K`>k@yaa^7S!Gr;1Na4C2ILyz=uZs^z*E(WD-m@#GoLlWPC(s^QHPygt7dAC+cF_>>29KN3!&zH%-;`?8%l z+6V>5>1F%!eofL?tBLQi`&~z&w;1&%qk2ZFJ*fWJ7)+i|#o{aN^jH}+xfDMlD;H4p2%rJ~SG@5aAi zq=QiKXl}-FY$J`vN7nk#U#b*q0$K-C$+NiS)_%-6e4%1<(#%4|xk;qIBC+&jf9Z;T z3cSAAK#E?Q3RKU8gGQY6qRP70dr14<6RAIVgXo=D5OY*qpG##5O&KRg1%ois}c^)6@3ar3|F(mcXm)uJSWX# zM|h+mpIs2hTTM(5d?qw26Ll+M(8y^rzHs~v@8{=2neA!?{S2sAFf80qK9%@@#k6#m zMW1UOQe!bJEqe^49S+trT#3CZ41sj4isD7u&Jj`zDhNkrz~>TQB<=*_TBx(m0Q+0k zkbA`RlnxHs0x4q~!L7IpNZJ;j+Zf6I+XsV1!VNC2ono_yQG9sNoMb%G?hE(GZ-|dw z=Rv}8dq!RW%~F=As2*5V^WFIV)HyYNknR=wKt)=C4?ZD7Tgw^b6Lg`m;xx)$*3h%y zCeS{xFz*A}Mq5i4=hOl1>ea#nFyq+ceDu8|NF0u}%7Sr>_A91!s1`oS8caH1A(H;( zJvVldC!Od45tg$QMjJcB?X~sg^Hr%R^fzHAh@K!mV@3HnEXAXRM0tQ2H`BPutyb!s zP|dR%1`XxkPWv;`l002MnN8m_9ZtDr;?9ywq;nqdFW-Ah^3VXa?z*)zl9A4UY$*sT zTlRxKYif!fX16!XKzN2Lqs-;pjJ_0?Ix^KL+|-#!9C1?Bv`Py|C&)WC7T|zE8`18` z6KDn3k#q}`wZ6dwCX;qW!K2Z#hWu)MTO|IW9{Z~HB5U0;nEi;iigCqOq!%tDX?nH( z3iDjUUf;~%gn_X7@dJSoK-v)BH0y$v>zc|;>yd)^5fnO${ayaR9PCh{1?y;N`rhx3VOSPlm7Uh4j8ncvZH2G226z;fS6khnIK&k}= z=?EYn0aM-TOT}A%QyuiX0Vgy^vS*bC)Hrgb&10}Tc}%f(c$BK$+S*u<^O#dE)HuK8 z)EQ9=NZJKSck-FdIw<)I)@8PB|j#ar8Gy<>_W?{yc~M=NILh0&p3IU5r>22ndeOK{L9pB(5qyS z;{d;_IPmi_Mi_xUTl?epH9C0G_@d%lawC}P*q@U=kV)$y#U7W}yv!n`Ko#{qR+=WF z;PHCxzXAC*{Fa=p)@tO%B;qf%mw2`9cYfp8HWs1L5BF}egQ;P&1t$aPWH$VR29U?% z&F9o2pVnQC%@$FeWqKZar<{-Eqp*JBXh_{?jsok*XQRtbC7*A%P1HV+HYI=3h)cdM z3cc(r{C37kT>5AfBivxEJX=e7d0l9Zo8l;I#dVmTGeC1V>q}s zmQF?6VVky38DWj=^j%gF1}P>)brrJ}+&jMrtPNWWjKgw`5|)%^b?D}U-vzItS*8mP zejEg(zZhXGqge(D4|#3KB_IzgbR+FWTOA9UsUX!X>;BaeeKm(2H1+e9QaqM{+t6D; zbBaQy6$&9o*8=$#%GWmb+h#|k8Ugb1YOO>50)H#EEF5!xfbh>ia~Xc;wi&0|2cegU zhlKxP!pjn$19`SWzpz=P2dhEU#Sq66Ah7vBi$e#s9}N?+mMD)hX?l2@m$0 z>dKZy^<>f!?ZRJ6`~g|SF})3j^%--Z%J3?v%5nBX}(gEjU4QZE^~8G zX#ar^FLR0sC!7&+Wgpo!T;Y8HMIVbAo4$S(s!j)zPqE?8ZI%k(O!fNdAbENAuxTrt z;N2ZNd|nFn%{rjRp=37p`E+d5Y`yR`sJFhQbkw!BL_PzH=dEKY6W;I#cXpzfZ#=Tj zQEPgd@A2u|&hd=gC^ZgW9IFopENe*u>jf^}_1eud*Cfg0vn9$cHp>ZyQ$9s1$~O+p zzQ%S9@1x=Gyo?}%0%57j{K++e6BdM6!VI$8SNP!IXE+O?{4y1w+1jVVFV|A zB{kWQFFd4bzs4!jp;uL;?HK8AFpVo?I>YXvjU~OavE*TNB*H~8gO$khVwZLwVSKAn6!Vg;?+ppR8L3;9v42JV#>Ouc4RbL!yN^q*tmyRSf%L)Abvy}$G)K4 z5#&Jg7Wqi^dcp(~PCiWDa%2T3Kjla>Y?>vMDm*fOLu_Nl z6tUGUN>i_wyob|ka`X56=f_Vd@n!r`ewe$NVb+JC+^F_SaIehed*<)J_3aPhLyL`y`bC*I{kQqJw7N0=?m0@H zR}}}hXZYg8*&7_og6qQBO{2j%tsmH5OJ_xMI^jIi6|B>^Jly12#pN$Kpwp-bpZCiL zowDz&HO8Y9*K2zz2NfmIhfmR8COoly!QgUYkr4a+?xy)8@BPpu061I{!K7mGzT_4JgvBBe+nnpbSuo-`7 z&`7o&JelIq1Vh+vIN9?YY*{uBcEeKsYX2JOceg;X%ykoj$@X|Xo0xQr~JuMdv;-2~g}55sn+3>D+5yU2^n&G77|JN#uxI#Rr$ zz*z_DdNkrsue6hL#$1CfzNLKZ%GZUn>aJn!PAvkzga_z3C`UzW!nQ|?!FF&pYz|6= zL*4XcwmuPFn9qkj^DTLeJKOlq)v=&luoMmZ>q^CrU)ZRbX>fny6Mp(^cl7Qefm1{U zE;*(TJ3K0|i}hxDe?4i`@2es9Jcb7q*_c~811s%v zq22hJQm92NQ2p`o`kT-)B^xZAy&Sy?rh#hobDVjx4W@nVN3kt{<2vUdBl0-v4QMCR zb1>(?3m%(vn16Y=1fG@Lhcn{>piS3tu&Y)J$Z43^ z;{#k_aIX^YzV<;Eyat`-b&*%y%*4YTy(OjFaCZO0Mu!=`_qa4{Z{a(eMAm+6Z?N!O zz?SX5sEBcOf{%^g@uH#Aao|lSm}t{Ts-H6kuC&vVa=vV1_PrfA#YA0$3x?H{>Uv}& zU5}Q|zdOG6b47{;9$A;oCymX5kf+Nr=B@#*30{X`TX&-6TN=F>Bw$2?Xq8UcbU3!? z0`7NrmgMtef%*+x9?xVKlz;FcN>z5{y_?+H{kIrAf$aTk{_EK+Yy77IcVj8%5;dv+;`kmLCo26>rW-p&OWGc4U za*9QGTd?3IbH)9<{n(?B8o!$#*YSjPm)e5AX?KWC>V;nO7C_E{NSL8@7HO?)o@pz& zWR!!PI(#|LTwlznHxATYy#cn5XeISl>;hGK9^c*Yjmo==4E5e{EH{hd2a}KTFoSYF zwzRqEPyR>WI`XV`2f(7PH+LU+Si}fvzlxDd*TC15KHxp&cMKi2j9vd$0BZ+ku+E!u z75ncmf>nR4hsFonO7wo{{An}1pIRHG*D2U{=u*r|w@~@cY7d&<^9XNlVBQs9FnXnf z=jzqrb) zA2z&Eed%CJLm&)Q*qgPLdn>FZ!c}Zu9uLDC&4<+*Q$Qmq3P-=02kmEHUq@b>XM z)cAB1ctvM$3-*Op{@RdTag41RbpfKuS5U1hqGA_eXvGM~j%x<@I{83)P7C~OJp#{Y z@8_>f+@OPoAx<3f)$y?0QS#0U<0Yr=!8a{UpsxytN%qpfzAsqry@l9pdL7uJWyfNT z@)W9c1JNHC<}`=upby2So;1*;259uC38BUPIANsJvr`HZr?Ah7Ex6#R7~8GzWavW( z+TBWCvS=*JXt@&`E-8comtVr&tA}_-ODmpuv@tZa%)pNC=yyxeO?cTp6N&OD6A$8h z2N$?mYZle#TIkez27`lj6bm~(hsJdrVARaIaNOh}6wTR$$EI<-bUha5pI(Z#GuKf~ z8Yo206iZeUci3RSwo>A}H$d3SE@>@?*(WODwO<*Ur1r%w%PxV#qyunkz*PltDDKah z1>-+wqrjeKPxgX!*%+w)ZVC5CUxd#i=Wya%9#!6(dgD6~-ZH_}3*}x|)awaPZdD5X zTyNlttf`7sqXtQ$HwJ_|bHaL@_+k(YyIvoczx3h@431&RAL-(HcCh6l&}&&O?su$l zvMKD^GY1|TWW&I|k@$YlZLC?KA!qDPqTgGR_BKs|#Hkng(VdS(KcMKzdc7~Gc_8u8 zG~$)X>b(ruIcxuoxyLAG8nE^GO1`m6UFcWc2Z*l~)9fd)UfpgfZVed1E=8ZjR<ie#c444d9hvG|ay~x?N=6-Ic@bR{A6nbWq%%kn z7|9>@>G1NR-m3R&<0a`pEe?Ed-%RHt#==TjC$Z&}Fi>-9jx#jSaI&N93@qLyrO zE(t%r&{lmOsUPV(-xINBak-Ig4~r? z2`|s%fXh+1B0L+Z&v9|T!9aY00*_bV28>zZi#ZEsVjTA5cB|*(%aD3F`uH7e-+Vg| zZ?eVZn?)|L>PQ6>@sr2 &QZ!QbrNsow+!OT>|^_quY@16|?y`}&f=;vl=*eA5&Y z$ZN3>h`Xpxv`~N88TKi27g}ELfKRWiQ>BH*Lgv%iyvfzN=rOwpPrNKdyFyd8y>4w> zrlVBX67>)EymyMf$vUkt{d^4Sn=v2^Pzl{fKjS7QMyjhLl8|^6RUZx`VXxG-=WLjt zd=vJ@FM<8;$6&*|x-c$#2K(4!Igs7}sw3XyOBD`U+eyu#Cti{dKb{9~EAN2YzGteA z^~BD`do>msYGt^ zqWu8Uck__66q-$W##?6ZMxl+B6O*vA&NJTq_$MIVQ$3X%2>!(*{$BF6up9i$kb$!I zkw(&jt%ih|i$G}L{h`+tW9QtU`pAM>_69JwRyUqq;jB3Bs;AZ=(uMXwoCvLK zEAai=Kd|IfkVM)VvinDa#hQ&++O?iUJS6)*KhNkhB(4O3HJ*d?QKwN)Y}s+Hs7KUF z*#hx@^xzf=-|*pdWRuzta`<{?HV(^m$0H3SzT?3zAZ^UsEsqj<7{;WZWKFi-WhXaJ z!qK`W(0$uIAnh--D&{XYl}I1KrYWnTR3nI+4jhVDoQv-t?ZK?Jnw&fU2o5YCY{-tR zYsC7*T8X-pFP>Qr5e;4y9$PdBNDtwp9*;2gWjG|4bpzr85SmiU`B`CZsfM)Wa02N! z4ffcrp&~NPTxe6O*QPS@7X2<~1nH$XF0h+)1y9<38V|gVR1nv|W#3&m=0Y;{)k{X0 zO1WqZT5m5CHX1Uahc}G0hq{HJB5aT^&{lZgEsi zv;WL5?$wqA*NA!^+pRC^zg~lbf7`%)t+N2)N^x$KKQ3CgMXhsmHtI{ns2+7gb#R7^P56G{}lb_S8~ggMl$sl?krm-_C^p3#IASzlFD*NpT%in_7c z;|XYS7=;e5Zgzs|OvXU>1orIiBsgZ?0iN$X3tKOy@GBp0qH&E~EPC1tM!FtvMr~&| zTQ$SG$!6$(D-jb?_2squ9D$yPTcSskCiMgtFRF>&2T^BZjwAI+>gUyHz|N z_U#id_|_lA+O}K$A@mT+U-Vh%uogJH%uUK45)Ao6?jz|4oK?3Ci1;;{kpV9Y{xz|Ne{yH$bhmcJBz#?K}0<>Xgre@dhtV}5m2Kzbg9ZcaS9fIp}?4au7! z@jfR$!B3gi^t%V*7xI0C)0}iXR#Y9~4t5h1sk%Dy%KE^kylaP~^%a+;7SeC2U13&2 z8O~jrf({MSg{GEx#TMv4VK%Fn-&C%sa>C0SPpH~AHe~TY1K^wfOnu zPTbbcm^@+=xUY8zC}x~IGoC)XpL*gQ58tIp9xzPc5>$;Ffcu8i9Qt~Z$SV_^_+j2C zS=aP294VQ~zWXkQbD5=zgOS^C`d$Y{S`v1awGh0+gvTbX=FjeaX6hJVL$><3fhuQj_VFrwOuZ{Z?qdVQnM%Bu{KcY!3i5giS}$z!91jOxzu@etoxlq6 zTNmMK!cxB3_%w=ZH=MldK>1?i;c+m{`bj5A#OYKI57fHce|`YaOa>y~Uvo(6oC+zh zN0rueB#;&(?rtZv2@(&Zb)ybod3^)CJa<4vzKzeAl@7m8(E;Lg)^ougAaBENbT5E; zRqs@r&g=uj#jDiXlst_@UYcJ}#*1f*`3;N+Y%ANDdZ;n}6xQcLv(a2h_!XR*=b@hQ z2n|HK3hi^;6y&Q_G<#t*yAd-BzCQ5`#9h zJh=7bL50H8)>_jGLe`Ijja|~X;5X9Es^|SoVaMcTyk&Vr_z~&ep-znITKHVveOnOF zJ|yxNEM~FzDqtB*;O{Id0O2+G{ZrmJISk|yG>_lu<=x^8e#+fg}O57NBL@kk?c~VnW|Td z0dO&8BwhE2Q>=mF%nqEcA&J^v{_=PHcKtffxcVANZAQgsu_K=wGrJ>sWPymHL@mS3Yknwd3a1a|R9D1@Ybf`YkX^i8?OSMWMsvE? zg`{z~h<(>)IdJFTWgtA~_eXn!>*hQpA0Uf)1kHSC_Ua^4Ka$oQO*6d&5Lo?YbAXr~ z(|jm~iP;U!SQ*WJvEH^R=wSB|$y3stxJcv+Xm-o$%#_h_cXvt5Gdnz*!Dtq#o3N{XrUv<|gHAUFJPj%RY~0 z4W0P?4;_)_Kr{;<#Hqev#{>GE;fVcwOxu=l>sA+8)PqLQOa5kaC0;sQ&aW3nvv;Fb ziP?piPr>KJWe{3NTkiPImq#D#DtcZe^hRWxnlvN0fVpvj>9y(bh5D#T?mr$u`T3!R zGRi+PBr-adj#D&riE>ka+vn#Uf1XB3*HRx7Q6Ca%XJZu5xUkzmBBD1%-t~2GHRZdaU{s$j@U9ox&Q3lP>kf1%!t9 z2SrE4i^C^=rJ5QVJ^_IdAtu%e6K@}{zD{ob1Du=(I{6InaJTsJfvby;h4Wx<9}l;_ zUjBXh`Z&3`dYg3eRv)M_2}+>tMux2h=`FU<|c!qlyqvtL>Wv=Q_|^=SQC2WuQ$Z$ z>h^Q-^7ip}cXI2iF!2qDrSm=XR2w@xGrGB-lFs;;h{G->(PK>F!$N-DsH^Mh^dE0? z4fv0D^>w42{{2OLBLkEXCIP|0bXX_u@9*-Y!2Q$mXv^j%o?+2ZAtq5j0`a%?IrZt| z)z{nm@9**qn;54I3ivOt`;k1G|C&4xuRi}KbJ4NEe@oksEC2VLsh>>=`)_5?j}Bxh zL!(3)P=J12`DcN3`t^ZJA2U%L&OZxnd|0$eWI%9;I>+i4>FWNuLnj}Kd>=X-MAxgo zj1LQm`s+HA@d0t_TXl7PJbav7{hi!A2D|(GRiM^ZU2R0Re4^t;%p;={qT+vM_|G@} zh_~&3jkl-MP`AGBK7TdIxPXb`K-RzX$e)+_|L@`c^Z9Yq_K|c@EjS=PfZ!ut`^RB2 zIzjdW6)YlybK?@ijKx{19$}CbW->LG4otPPwso)$wzhRNcd!WzG$*vNGY_=0aWJLoEcB9RJS`2(8%=>dZVJGuL~_)y+{ z=HbUe%}oX+1Q096|7+g7e#!rM zNX^jD$he4<&``}I^&;Zqq7qWnSc~`SUi3NikswapS`@q4m$w zkBCi87!jHrH$F=3_oP~u!N{<&wEL8#xTs`xFTK<(!_d&^xTyHZ#n;aLWnErn`!tDdC}q>#lWL`IEOXVukY z-!^$nT+E+!VBjNu(jz`B#noTpJ+mslAj*ky(zG5bJnrNUji;0CSr);#lU5xoJ+Tg*5&PslkFN8@B z*iYvX^E5A1HR>{+EqH7Uj_<9&;nq?dJ;D!`MBc(Ni#R`$7Yx5DVz71OGWh*Lmk&L5 z2b5+l;d{S@IQ4-8UpTrB1{C#$mQhZ)@#X;>7W)!S3U!pz&yGX!w4btZcra{Pa0hx0 zDS#V07Vzdb0-@FXerU9GF2AdTkbS<1WINm&#=NhO)s;L*>S_!dE$6Zi1Ma}!XL?Zc ztP$+rI9;`UzBNqZR`BTf88Bz{aPdrC?$>r8ICLBXZL77zeWRnX{@fe*GG+^l)3wHU z(^l|yLpNIg1sM1EH>6DQgij$yX#K^KtL)Gl`O!l%;2bvWZ zY2q9lF+g(idZqgGC0?%Bl#k-RWm})~^_`uW!{iFQp*xBxHI1QK@^emch1Rr$1D7l&wLyp)+1A4#iI%C6t$;kXLO4mwmpVMelX& ze6ls9Zy%30-R7w#*{k8sygsZXH$^JQIigBh{F-vQg8TD3VyJ)kJ9CdWME12m?Mt7Zv{(VBJDKp2-427d$xr!V*Nd!Z zT`Sezg;_YNbTK<`a-Mf-ISgpOA|A|qLMT=}e*iH}F7pfdr*PZ&<#2pjUFow;CXAhw zQ??Jcb}A& zvohfJ*nK!ZKLROcV3xm%y|?QOjh;1yRzj53?$kY%k$sG{lDkSJWdx!eF_Kn^cHNPv6Jd*??A}Ph!^YNT3Zn*Calj6 z4u`&Z@%c|WNMoD1uwF@k#Z`xCEnT7Gq&AXC;WXSCavj^)-j`{w>}SuPGA?S%yM4-l z(4ZUGp|m|7y|062f&KATwM#f+ohwfDGysET6>dHqYboYO+DW@lc7WX1?_p(_5eynO zm(zNH*D7FBAMC`MZC~SV>zOQ}QHEwucN_V^w((7Q0W5H=McEp z?j#wgqIta+0ocy846-f9@hvwjS(Bi*ykj$8y!z&gOnH-~`-RvX3UPviEf6lSR}*DX zZ@6(%9E7f1k8is_WjEdG&>lD7tim45xMdBcafYR`{AC3$_sc_FZ##I9mId2ABPG3a zTU9S^ZsWx^E%2#T4v2dBQ0~STtnlH}T1Da0W2Kzdsv=xcN>3O|v9Fi$xuHGT^H>ke zJJ<}){n`(A!xw}7vDv5$NrV?t4e4v`NPN(6m)ygtmB0x`Jta{Nfcg(gOSPzW*WoF1 z9fj(Zx6hhWesX^|%G(wcm=JjNW_ioQb!cr5@O01h*fMH2{&qTtYuZ=v(tew;=B%Tt zyzNAyq(nQwH;3Wc0htu~ep4YWX) zjW+nX%D$YiR8?HnUA}#=Cg0I`Gn^V009C#AGKX8HO5oKYl4?A4cCljB5@A9vA+UWj`S-H%goUdJlfq>KF4|oQpfgr3<{k5!Dvr|JZvr*s_ z^*VIucNX5}>=f(4vlmv$E%Z`Ryry_C!N&(5mV%akE^*l`d^G$sceUTbiDTepxv7dD+KZ&?xf&jzrKLrRh?f)U=v1WuUVHr#~Giz znCjY^dcT3DPO1KQmwWZFhtoPPVo&?VgJ(9N?}}SUbpcmq)ndfWd`+e)^DtLKstzr*BFmm^-l01tM;lsG+rdvb6JW*GZx|d`AaldaQb37n;Uvu zgWVKI=KN^9;32Ho)C{I2*zk|1j;nrtY%USjF}p5VXrdiPbzfb4kEWrs*q~H1(DmNI zTI|e)`f0uJ&6&xpY1=A-wWP|_#)vwP@a%@ zNba+2tH4^h>UkvEeK?5gj-60VYC4@wopA+*TB%Uf8EI7PJ8uHM)+?nPo#QtSt<`X9 z^6}dI;=SwhV@@28i)O8a6!k?$@nJKUl+ZfbD(8GM`JI>TH9S4HVjbMGz6G~U zA49+JF8si#li0Oz5F-vQzvDLpogEIk72SUW_e%%BeD9g)&z>{V<6xqF7x!dcOZG_DdWxZ8R-aqGHpCiU6XDdi!TC~qGQl>9sx`6c*{}{{3&Xl z`cOq0hy}Ur!9H~tBWZ5R`7GG3+lUkAxvtjAhrmO=kaJE8@4d)D;vA^)lpg-*+h@PzsK_vwDV@XcMD6BlZ7-a_xAs>*pACq2btuVrwRU0vmD#%1R0 z;DHN`ck|5?13<)B^yLR#9d4M{Mj~!O;aOIMx5v^EuF^P1Z>Vy%0sT?Uxb2hyETY{k z*c`JLgZz&o=}TTO>l2oI@?-XK+fmehKR;VW9IS|*4Q`c%#N|@zmS~)9v9p}Cpe7GJ zS}epO+j8M&1V1y{qeOlHRHsWIVr_|SrPlxEswB#iXCseu1bX+{op|d zPrNr~BY@#ktlMf0ZYtJOp5F4(==UePH&E{}sr{Nf!F@p2f>wne2)*}C4pPX6$z$HD zfxJig)5)($n|)P{a_^uRxjKUXa!=yFhOFA&j_@Sd7qWa-VcQ5TH_|hVV#^#i-{t9F z5AfbI8$;)iMv7An3oJdc0M1@$;r81pkl(zz3o=qaa^se>k$fZhJ%)=9pGCTk8Tok# zJ7)oU%*s}gK586q^U*#`A0WZ;*GL7z0SMUe6$8_J`MrFPhc2_O)X1s zpV=HF4<@{U$~*Z|Ih1FDO(2{u;Rye7I8x&pZS=le%OvrZ&hme zGi1#>PWk6njhbutGc;(h%0A8(3UYdM?tN_x@lXL9=XDi=UcF+ZZ5!wSMZZxI1pA>6_!>0{^xPsA| zWp|4nF`ww(^OUG#+<4T1 z@Mav6Zk4YU6(HSDYHW2{)E{}trV4SZ&{#|>GzBZXJQXo7sLGwiMeVG=HJCKJ6Oz{v zH4i#lT2c)kgl(tC!JU^|S+h!a73nV~yn*n4iFePUWw&dpnxpp!zQ@{iw+cVTOG~G! z7LK0B$S2FIo*PJ~%%`Dx`3^=pO;Op+mq|CdoP+%S}z09FK^#u%B2RkVc*#bD9QZ*gr!pcnwwnc z^qi!2A`W0^Xrz#rbQ2sUJb;*42+SuwN5Ng{-j8XWFWHD{^>|X(1uC}kt=u^V)d0U0NibVMW z(%ncpTHq!5@amH3?gzNCG(qK_z6!-G#=Nbz(%!u*=y@&Hd>I<%;K7+U!F~S=E^3N; zOrbd7rFO@;_7p9JYL^jii+<#!2f=gdOc>a83fy{ih|L(e2#JGT2hR24d~Y~jn!Qer zy;e)1{$Q{5oPp*J;y$o7dc9`uvggiP6nu7Lhp}?}@<=2cmk6_&x_6AwlT2smHBPe! zMmbgH4K0Fxe%poCggS1%P}!=t#w$HIF&ua8X(AIgy3w2l(k8bTGd?Vg%0!AM63z%; zgx6K=#Qmuc7GeEM>zV8`R?NfTQ`st{IS7(ggx2L_Mc*=^r9xiFG?U1{b1P#d;&h;y zp1?7o&$yVKQEy_SRY5SZM;A?u4OEY4{x}b_llO!6gI|V(nDBx$pTt_ruR{8O6=KdMW^F*;m$d&a(rBYl)TPKvd-r`}E+~J!pE#*)wrZOev2_pO@*eq-gqG=x)4VQ{7m(%z8t2B$e8XgreLlRXx1d?54LZ6JPT zySmu}>2#Igj1#_BkY)s8j;4^$K+@SPKkFoXv|Z0LE}oJDiY~HuO>9);FBxeYCNw$W zpGFr8&D{6p1xDBm=sgsKo_~JSOiDc2m3q|+-^u5B@w7=mS`V8Q+n19sgnE}dBh7gj z)ry!u%Yt`g;UT+a$ozD0L&L7h(5Bts zcK%X+}O5A%KR!-1mJSdmZ#4!?A9tg{uHCl#V&A2Uoj`X0*+Yb#AhpMtSRqOrr040d_X zItWXi1?}>3p>_vT*7dq0@47OJ+4&|y_26r4WQQ!iT=yyRj=SY$Lsbmk7jfOCDk=Z{ zdW_a~!A#dF(Ae%f(}}+V>z>_&76W?Yf;QXbsON{lB}$8x9{z=IzE-flovQG`oZr5R)ZRFt5yj z^$wni$0NR$+cvBwO+BV3Nu`0{rPXgTsY8ZV3 zD^nxonNAIe|MHq`4q3!M?>fgj%B>-(_66Rwxr=0@RZj{((hPQwvtXloJK_4EN5DVT zQ3lTH4^2vJu{(alTeCNTZQL?wH}N^2eC;@Qc54m~-BnP!VLI;k_>B!4vY4CtZls$3 z1UI!mu=5i$VcxU)Kzo#>7ioBkD{|(DreKU zxeXNSXRA?v{(DT@vjZmE+~ch)XL9F&`Is8HRNipVl0_}uj{OanmTdds1kT5eh!QgKl!_J^Zyg9m_qxuv$+&DaY+t+e^r9bicsElJ#KD z3kzvb?YU^-oh4K3koExHzx<@thdV)gTWxG;eh7vZ{FcWipXSsjD#|JP##a-y2(7nX z!+GNa@j$gQ=D<4e^uj?<8Qe|u9V=X9&4+|M0rc76nrW)5TzFUyc0O=Msuv|>U@F#o zxD~wjZ(;+QordV`Eu=v!jzU@3I&S;5KaBSI28~xqNU?-tHg(`fWho0e?!t;rb!T7K z8Y-eT=-Diz?^fYbDUjYwE<2%ErgDrd*YjooB52LbAW1!=?y=j60xq+dMKkc zNYh{0f;70CX%)F*=!~8iek30wyD#BK8?7LYyb2ZvuHg7^19WkWWKs`r5Ou9n@mlp@ zxq))9v6aA4C^~f*f9T$XEeX0n*eGfjO&CXzH=w^|D_C)J7f#i) zfgIcblMY?Q*(Uul^ZOMjb~%OHE3!fSY+CFDqCaTaV}0+`Wa%brNa@+ zVHo8kNX*gs!?ry6uiTmT# zdr;BT8BYdwf@cq}3!Y{l?(WCH)!}GXXF=rr;;k#9r(xnCXNtTffH)VjI;-a9N#0$%?_e;$kMfVJa;j? zs=CScrq+NjS*cABvX9Yoi{tEcXClG)hB)Nh41;&M55So->k`^?y#9n0@Xz|*tR8~z0TQ# z17}^vp7KkcXV40JJqlo3FV~ic#~ERPhy#|Kbj1xR-UqdajBo~B<3qA#a+xCp;{6k)?EYZdLEu+IgA-fOwM74dxrYin{J zGP<~N(wnf{r8>HL_XXla(qGXKb0`NB)M~c7w30Bn2ENax8Sp(T?2|c~G*$}s-B4HB zSAQbD#l})qgStZd$ax0qkob>v=`;h2{VMQw&vaHp3CE$gno13aJQG?N2{+^}huwMQ z_C=%@hhe^bFFarSC^miy6reU#>J zy!e9-$0!aK($Pj{q!Uk)rm7}Q&R?%0EWlmwM?!eNL7X_BEk08VD1R``TMJsgk_j8f zuzP`%fp9`_I4&OMB~g7d$|I2eMZ$LRd0d%yo*g|j1CJ@kRb5xsmTop*h5f#4#1n;j zqzM#!{PQXlK03$>`{^l#{boV2Ul&|^As#!l^O6bwc*5fXrgv=~Vah^*jc_hHPv~jB z@55AdI1vk!YYrQ1oCsnev@fm#7 zx?Zf}R$Z{%QKKx>p`$XbK_qLRzLb;Jpxo!GNK>NV3Z>g~Ry1KdQ2aFYOFTe2ek#~D zoKARjkJU9&-LOo55b3()?_A|&ks zxx0qK4$CF*Bc%njvCqa_nzspkVg7qEl)b%!LaSWMa8f?XwXqic&UNg4WAur+BomHf zW#_YudW*D^wv;$;AU1ODgpKa!qrj-9cXOyeJ%BJ2<710SpKgJkFEc<1Z797k-V3C6 z2$yRpxq6>Oj8!@38Y<>yTd}4`E-RV85QJu;{@`!x?!;w70+-!}_JHy4yvYy!hDZGi;4S8uJSn$Ygp1F)?x6aLUHBcry+FN%gJ0jKetF5L7jeKmWBlT#2gJ>tFBGTAPZ4i>5!Wr&8(A8iE#BT^e^F()5#=_q4o-INVKhnWCuJJE{ zCrBJBFHh;fDHbx}I+C6R!6DVA`CxSCAuu_=wM0G$`t{n2(>wIV+LP_*c}*B;1tc#Z z_5!bST*w<|!Xl?AM)gQMc$jLqnk2A|bSz$>Sy0)^W8zuV6Q=m;Sxsgwm7(y|r28cD ziIm5YxYl!r?6J*5-ajM*37`4ldWjm%Bs^GzU0du&GlYFJI^(IZcXQ z0Jo4Dw^&tdTR486q3~#5XWnP+ZPUq{B%^sDactyLEL-UVLE~&7LuV1`?HUTjS=qGM zP2;1e?ikgPYUJcr3e5&EP2Y>OnyEBnXH7=+NcXU-KoJ?19wu%s5Ie(0;5n!E&R zo)2Pz4~#=UtH!U|$+a8rXF@}d`#Mdd<;hc%?oFaPu!D6qG5}W~bH05NW~grpf5mB! zD#AWgUY|$mBNgf0vL7j{nBJikc=+N`px(!~=k$eMPy|j34LxB{AM|1$uz#2x^+RKP zv3H};I&A4jhrNVD(l7fis@ffva?-~dy-3~{N|RzZ`7+VlY(n=tg1gH1?SFt2bJ07r z)=ylks7&CxY=;ig=;$^QVJ6rFSus(!f8CI&}wIWICMit;5!H`GAcg`^Ed8hZT6jjCC37w^zdY0nU<2cN7L;o zKyw7_v&l@FwXcgpS^;U_xXY-q+{N@N?5;(`@ppkvQ_VqU_8 zuUOqT8DCylqMH6{JNBagt4cF5u}7I^I6(0wuYOF-0VK)`QjA!3P(Jw8b5v1%OZ0OV z`XLKmo2O!&hrZxCwz;4^qrAejnQigt@Q%VONhYob@M^LLj`LlnEGT(SR~67cg2?B# zQ+hw>#>5&0Z-2SHn|QjX1`|k^DUF;<@I|^79}&2p{O&x$m-YC3a|!#|l@F*{P(S~>dSo~-WIsarw%wIlYeFO zJ|``w2oCr){SqUN=UxTQEIa2QbK5xtNDp!H$uO{ESK+6DW@empJEQB7ygL5Qs|3G* z5MC9#krNjp&0LVYKF}PE`umYQBG6qCJtJm>)sw1S zyaVr>KT!#d;&!U0nBU;%v_iappewjIK7i=XH-(nKjZ?hEi~(qU&@rZt6nDOoYPJMx z^S+$sKT7JFy@KC3;U?+vMq*!_=2ft&MGfW3kU&jb1h-JVz@*t7iT~zc_0iRp^;)M% z=L`XwT>#C&$O~lgm`zbgb3gKUEyyW$1;-`_z|$9tWy>A2Wtuq% zjDWd&iy3)a7-@KyWoyl1LvJk?b5hc++RC&mk|buRbRX!{Y8$WPUQ5!cNW^Cg4?$t8 zXGnU8rGB`GKb!btab^^dN2OTwLYkd$@*YAnE5wH4vwjI4fY8f#S;Ck&;X@hC z-)L{YVPSSD=p8bnId7bp1><{WqEJ1F>#$pm1`2r!6f+RvF=+Ow>S}vj_Pn}5B|L&t z9l+c)SNN14h9=*a3xB8(u1LESZN+ZNT-fimR2F)_X0DAyI3s3AGr zT!F#G;Kv!ruvx zq;wl-O*bfpOVaoUUOn5%C&*?yZl%JXBkEhi&HQI{m2xxDdbi8I(Bm4cSB>%$mlUZvTKw9Yc`(A*z$Zvue`e3(Zjz^*#5TC^s9YPLa_zZqX4DHcs}=kuLVmuJ#?B z=*WlBKaQj%N5n>r45Pyl9pG>K#~Z#r!L}ZKgMtJ6x(9}Ob`N&<@eR_~|N9*u zPnEfUDxG*qGPmsLYHe?2ZJv;rVxEv1A8&0=$2CTVC5A6J(=)JpP|&}8_&?iXK%nP8 z@9&Rq|MSiLzv2;1r;p-d5=0ctlcJ)dlA;pml#lt}m;4!2%fH|7@ucnixghso+NdX; zdC}Jo4hVMl4R!Ym=<6T+kDxo)TRYOn|Fd28b|2u^-9MNT_~)z8|9p%8HYERMfBz$* zmVaGMOa1H7p8vGB|G!-Y26*=M(Cq4uSO3MXdjEOF|5}$Gv_h|dzybeKQ@sOyJSZ=J z75;yfgWy1S{~&jdV4nbg%ELc>_aCpCI(#IZNsA0i2_vM)();7wm^w@U4*;-D8JnU` zPK|9Y&P_pV=bTvcNoqPA)!NymV|avvo1;yHtG&IAQ^)8q8@DJ|XPXFTN7rzNj*gM` z5svCu*FVn1X&{3RFfI5ibN|@xpMCyc=l$O$M_<2BUw8jtpI{2~Um?{zgKFh}3A8%4 u^CWd_*I4&|-D4`jaDB~*uh5jl(7&KJG%ivgYA>zWPQA2aJyTP|ZT}DD6gfoz