From 80192589b13cbb81419d9dd8240797bd0b02f09c Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 26 Sep 2023 15:57:55 +0200 Subject: [PATCH] feat: Add `AzureOCRDocumentConverter` (2.0) (#5855) * Add AzureOCRDocumentConverter * Add tests * Add release note * Formatting * update docstrings * Apply suggestions from code review Co-authored-by: ZanSara * PR feedback * PR feedback * PR feedback * Add secrets as environment variables * Adapt test * Add azure dependency to CI * Add azure dependency to CI --------- Co-authored-by: ZanSara Co-authored-by: Daria Fokina --- .github/workflows/tests.yml | 7 +- .../components/file_converters/__init__.py | 3 +- .../components/file_converters/azure.py | 115 ++++++++++++++++++ ...re_ocr_doc_converter-935130b3b243d236.yaml | 4 + .../test_azure_ocr_doc_converter.py | 110 +++++++++++++++++ .../test_files/images/haystack-logo.png | Bin 0 -> 30437 bytes 6 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 haystack/preview/components/file_converters/azure.py create mode 100644 releasenotes/notes/add-azure_ocr_doc_converter-935130b3b243d236.yaml create mode 100644 test/preview/components/file_converters/test_azure_ocr_doc_converter.py create mode 100644 test/preview/test_files/images/haystack-logo.png diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7539bf9c5..98845da2d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -201,7 +201,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Install Haystack - run: pip install .[preview,dev] langdetect transformers[torch,sentencepiece]==4.32.1 sentence-transformers>=2.2.0 pypdf openai-whisper tika + run: pip install .[preview,dev] langdetect transformers[torch,sentencepiece]==4.32.1 sentence-transformers>=2.2.0 pypdf openai-whisper tika 'azure-ai-formrecognizer>=3.2.0b2' - name: Run run: pytest --cov-report xml:coverage.xml --cov="haystack" -m "unit" test/preview @@ -901,6 +901,9 @@ jobs: image: apache/tika:2.9.0.0 ports: - 9998:9998 + env: + CORE_AZURE_CS_ENDPOINT: ${{ secrets.CORE_AZURE_CS_ENDPOINT }} + CORE_AZURE_CS_API_KEY: ${{ secrets.CORE_AZURE_CS_API_KEY }} steps: - uses: actions/checkout@v4 @@ -915,7 +918,7 @@ jobs: sudo apt install ffmpeg # for local Whisper tests - name: Install Haystack - run: pip install .[dev,preview] langdetect transformers[torch,sentencepiece]==4.32.1 sentence-transformers>=2.2.0 pypdf openai-whisper tika + run: pip install .[dev,preview] langdetect transformers[torch,sentencepiece]==4.32.1 'sentence-transformers>=2.2.0' pypdf openai-whisper tika 'azure-ai-formrecognizer>=3.2.0b2' - name: Run tests run: | diff --git a/haystack/preview/components/file_converters/__init__.py b/haystack/preview/components/file_converters/__init__.py index c5ca1f7c2..08f7a4613 100644 --- a/haystack/preview/components/file_converters/__init__.py +++ b/haystack/preview/components/file_converters/__init__.py @@ -1,4 +1,5 @@ from haystack.preview.components.file_converters.txt import TextFileToDocument from haystack.preview.components.file_converters.tika import TikaDocumentConverter +from haystack.preview.components.file_converters.azure import AzureOCRDocumentConverter -__all__ = ["TextFileToDocument", "TikaDocumentConverter"] +__all__ = ["TextFileToDocument", "TikaDocumentConverter", "AzureOCRDocumentConverter"] diff --git a/haystack/preview/components/file_converters/azure.py b/haystack/preview/components/file_converters/azure.py new file mode 100644 index 000000000..c19ebccb6 --- /dev/null +++ b/haystack/preview/components/file_converters/azure.py @@ -0,0 +1,115 @@ +from pathlib import Path +from typing import List, Union, Optional, Dict, Any + +from haystack.preview.lazy_imports import LazyImport +from haystack.preview import component, Document, default_to_dict, default_from_dict + + +with LazyImport(message="Run 'pip install azure-ai-formrecognizer>=3.2.0b2'") as azure_import: + from azure.ai.formrecognizer import DocumentAnalysisClient, AnalyzeResult + from azure.core.credentials import AzureKeyCredential + + +@component +class AzureOCRDocumentConverter: + """ + A component for converting files to Documents using Azure's Document Intelligence service. + Supported file formats are: PDF, JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML. + + In order to be able to use this component, you need an active Azure account + and a Document Intelligence or Cognitive Services resource. Please follow the steps described in the + [Azure documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/quickstarts/get-started-sdks-rest-api) + to set up your resource. + """ + + def __init__( + self, endpoint: str, api_key: str, model_id: str = "prebuilt-read", id_hash_keys: Optional[List[str]] = None + ): + """ + Create an AzureOCRDocumentConverter component. + + :param endpoint: The endpoint of your Azure resource. + :param api_key: The key of your Azure resource. + :param model_id: The model ID of the model you want to use. Please refer to [Azure documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/choose-model-feature) + for a list of available models. Default: `"prebuilt-read"`. + :param id_hash_keys: Generate the Document ID from a custom list of strings that refer to the Document's + attributes. If you want to ensure you don't have duplicate Documents in your Document Store but texts are not + unique, you can pass the name of the metadata to use when building the document ID (like + `["text", "category"]`) to this field. In this case, the ID will be generated by using the text and the content of the + `category` field. Default: `None`. + """ + azure_import.check() + + self.document_analysis_client = DocumentAnalysisClient( + endpoint=endpoint, credential=AzureKeyCredential(api_key) + ) + self.endpoint = endpoint + self.api_key = api_key + self.model_id = model_id + self.id_hash_keys = id_hash_keys or [] + + @component.output_types(documents=List[Document], azure=List[Dict]) + def run(self, paths: List[Union[str, Path]]): + """ + Convert files to Documents using Azure's Document Intelligence service. + + This component creates two outputs: `documents` and `raw_azure_response`. The `documents` output contains + a list of Documents that were created from the files. The `raw_azure_response` output contains a list of + the raw responses from Azure's Document Intelligence service. + + :param paths: Paths to the files to convert. + """ + documents = [] + azure_output = [] + for path in paths: + path = Path(path) + with open(path, "rb") as file: + poller = self.document_analysis_client.begin_analyze_document(model_id=self.model_id, document=file) + result = poller.result() + azure_output.append(result.to_dict()) + + file_suffix = path.suffix + document = AzureOCRDocumentConverter._convert_azure_result_to_document( + result, self.id_hash_keys, file_suffix + ) + documents.append(document) + + return {"documents": documents, "raw_azure_response": azure_output} + + def to_dict(self) -> Dict[str, Any]: + """ + Serialize this component to a dictionary. + """ + return default_to_dict( + self, endpoint=self.endpoint, api_key=self.api_key, model_id=self.model_id, id_hash_keys=self.id_hash_keys + ) + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "AzureOCRDocumentConverter": + """ + Deserialize this component from a dictionary. + """ + return default_from_dict(cls, data) + + @staticmethod + def _convert_azure_result_to_document(result: AnalyzeResult, id_hash_keys: List[str], file_suffix: str) -> Document: + """ + Convert the result of Azure OCR to a Haystack text Document. + """ + if file_suffix == ".pdf": + text = "" + for page in result.pages: + lines = page.lines if page.lines else [] + for line in lines: + text += f"{line.content}\n" + + text += "\f" + else: + text = result.content + + if id_hash_keys: + document = Document(text=text, id_hash_keys=id_hash_keys) + else: + document = Document(text=text) + + return document diff --git a/releasenotes/notes/add-azure_ocr_doc_converter-935130b3b243d236.yaml b/releasenotes/notes/add-azure_ocr_doc_converter-935130b3b243d236.yaml new file mode 100644 index 000000000..a25926157 --- /dev/null +++ b/releasenotes/notes/add-azure_ocr_doc_converter-935130b3b243d236.yaml @@ -0,0 +1,4 @@ +--- +preview: + - | + Add AzureOCRDocumentConverter to convert files of different types using Azure's Document Intelligence Service. diff --git a/test/preview/components/file_converters/test_azure_ocr_doc_converter.py b/test/preview/components/file_converters/test_azure_ocr_doc_converter.py new file mode 100644 index 000000000..9e06173b4 --- /dev/null +++ b/test/preview/components/file_converters/test_azure_ocr_doc_converter.py @@ -0,0 +1,110 @@ +import os +from unittest.mock import patch, Mock + +import pytest + +from haystack.preview.components.file_converters.azure import AzureOCRDocumentConverter + + +class TestAzureOCRDocumentConverter: + @pytest.mark.unit + def test_to_dict(self): + component = AzureOCRDocumentConverter(endpoint="test_endpoint", api_key="test_credential_key") + data = component.to_dict() + assert data == { + "type": "AzureOCRDocumentConverter", + "init_parameters": { + "api_key": "test_credential_key", + "endpoint": "test_endpoint", + "id_hash_keys": [], + "model_id": "prebuilt-read", + }, + } + + @pytest.mark.unit + def test_from_dict(self): + data = { + "type": "AzureOCRDocumentConverter", + "init_parameters": { + "api_key": "test_credential_key", + "endpoint": "test_endpoint", + "id_hash_keys": [], + "model_id": "prebuilt-read", + }, + } + component = AzureOCRDocumentConverter.from_dict(data) + assert component.endpoint == "test_endpoint" + assert component.api_key == "test_credential_key" + assert component.id_hash_keys == [] + assert component.model_id == "prebuilt-read" + + @pytest.mark.unit + def test_run(self, preview_samples_path): + with patch("haystack.preview.components.file_converters.azure.DocumentAnalysisClient") as mock_azure_client: + mock_result = Mock(pages=[Mock(lines=[Mock(content="mocked line 1"), Mock(content="mocked line 2")])]) + mock_result.to_dict.return_value = { + "api_version": "2023-02-28-preview", + "model_id": "prebuilt-read", + "content": "mocked line 1\nmocked line 2\n\f", + "pages": [{"lines": [{"content": "mocked line 1"}, {"content": "mocked line 2"}]}], + } + mock_azure_client.return_value.begin_analyze_document.return_value.result.return_value = mock_result + + component = AzureOCRDocumentConverter(endpoint="test_endpoint", api_key="test_credential_key") + output = component.run(paths=[preview_samples_path / "pdf" / "sample_pdf_1.pdf"]) + document = output["documents"][0] + assert document.text == "mocked line 1\nmocked line 2\n\f" + assert "raw_azure_response" in output + assert output["raw_azure_response"][0] == { + "api_version": "2023-02-28-preview", + "model_id": "prebuilt-read", + "content": "mocked line 1\nmocked line 2\n\f", + "pages": [{"lines": [{"content": "mocked line 1"}, {"content": "mocked line 2"}]}], + } + + @pytest.mark.integration + @pytest.mark.skipif( + "CORE_AZURE_CS_ENDPOINT" not in os.environ and "CORE_AZURE_CS_API_KEY" not in os.environ, + reason="Azure credentials not available", + ) + def test_run_with_pdf_file(self, preview_samples_path): + component = AzureOCRDocumentConverter( + endpoint=os.environ["CORE_AZURE_CS_ENDPOINT"], api_key=os.environ["CORE_AZURE_CS_API_KEY"] + ) + output = component.run(paths=[preview_samples_path / "pdf" / "sample_pdf_1.pdf"]) + documents = output["documents"] + assert len(documents) == 1 + assert "A sample PDF file" in documents[0].text + assert "Page 2 of Sample PDF" in documents[0].text + assert "Page 4 of Sample PDF" in documents[0].text + + @pytest.mark.integration + @pytest.mark.skipif( + "CORE_AZURE_CS_ENDPOINT" not in os.environ and "CORE_AZURE_CS_API_KEY" not in os.environ, + reason="Azure credentials not available", + ) + def test_with_image_file(self, preview_samples_path): + component = AzureOCRDocumentConverter( + endpoint=os.environ["CORE_AZURE_CS_ENDPOINT"], api_key=os.environ["CORE_AZURE_CS_API_KEY"] + ) + output = component.run(paths=[preview_samples_path / "images" / "haystack-logo.png"]) + documents = output["documents"] + assert len(documents) == 1 + assert "haystack" in documents[0].text + assert "by deepset" in documents[0].text + + @pytest.mark.integration + @pytest.mark.skipif( + "CORE_AZURE_CS_ENDPOINT" not in os.environ and "CORE_AZURE_CS_API_KEY" not in os.environ, + reason="Azure credentials not available", + ) + def test_run_with_docx_file(self, preview_samples_path): + component = AzureOCRDocumentConverter( + endpoint=os.environ["CORE_AZURE_CS_ENDPOINT"], api_key=os.environ["CORE_AZURE_CS_API_KEY"] + ) + output = component.run(paths=[preview_samples_path / "docx" / "sample_docx.docx"]) + documents = output["documents"] + assert len(documents) == 1 + assert "Sample Docx File" in documents[0].text + assert "Now we are in Page 2" in documents[0].text + assert "Page 3 was empty this is page 4" in documents[0].text diff --git a/test/preview/test_files/images/haystack-logo.png b/test/preview/test_files/images/haystack-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bf001d00a58cd2025f778366c1877be1e5b92abd GIT binary patch literal 30437 zcmeFZcTkgU7d{ve6-5Q4BT_`^MWiEDX-X3zbb^5NW{7kMsDKhm^i_&4hn=Q zEp((ykWPTm0)(*{6p%ODWw>LYFS zry$S;RS<~sg!%$dvis(D8Sozk?5WlRP)RSxGVq1UNli};1S&()9NC@+zF&HwZ4Lv0 zuD>MzQA|5>`GY{dfsfSHjQt__Eg6?B5<(-np_>C{FeKJx`cG)NNbp zhtyA~g8hk5*tB${(&V6Y8ga2=1Y$LMZ7gVJ^raKm%(fy@G(F=9&@|xR^S{J_P>QpU z2T=Kc2LbfoK_G98|E`4p4#Iy8<9~O-|KE2(1$vudK<2I{XVBhf+`?7b zd?i+{i@oD@Nln8eF0{btgjrou<%ZF}v}*JOLx&GZ(LovGW>!iBn?L8ZddHf6yQScx zv-wVfaFgd>)06Lyp;qNKq7StCcDRoF$ER@6DeQCVT|X@DlM-<`oCmlARG9*zy2tPZ zBQNYFVxqLGet4y?^!$Xo>Cy4(HHPrBJCcAqpc_U8jQJ-!t~ypfsL|KW7rBC;myOCy zg$%wsiO3`@F;U%-GSR%Ew7YTO^k3qp1c_t7gvM+TX`1)#NH= ziey;qANF1(Ka1xEy<)zIRV?l==*!vJeP_o9$g*p{LNEd~;R^bjM<0IzN4>`vkPYKzueCeO8=EE*{E& zSd&{<_$^^6qwtSsRUv_@{**ao2#T54DBvmdt`i+6 zv(K(X16RJU(K=Tbj+E+Ho)txqZ$|AB^F=eG{=KV*7Z{IPVnM76jdNCtjACjv`9|l< zKrP>gH4`6Z*J+WfP1XWYk}rUs10Svue^+30M9;nl-T}ReY%upC-vkP4zNC6q8n#CL z|K@``;V8WFs#G6%b(9GMftin=OJ1qFlb*HH^lK}^^EO=9YG=* zafhEHP5B%MG;9{I*n~zWTA3GF1$Z5HJBtRkxkVGxf`iMB_a^eNph{qT*!4k_z`VQU zc?GXtL_1-dxbg9el`27g(DAo3X?q}0rWHNBaaGAFw`r?8?SB6Ldc@*owXl(LDrV68 z;?mIVi!Oy$^p(J}0yh(ibp}o9Mv5(GJ{w-`Gohpdaa$5-ESY4K3C81;ps=m@vc^?o zpt%Ab6y&F`W)YhWnSUvjLjrz zr~@!0NJd$`;C3Ii3ToBlN5HaCjicfQHI-2@tJ2=u+#hNkW$B_-X*ODFKH8i^oNm{* zA&47w2FtTra^Ah`X&X#4lFfccR|pmDSVol_ zDL*wrE;a+)ol&E6c;)W3|McnZUY1LM$erEI34O6<*zdApT9pSPiA<&Q=_-l^+v7NC zO3;(Eqyt0Wefsr!H*nbwm-u7Ap13~-QN|Q6r9n@AXfdi7@Td(Y*0Hz0W?XM~iy1}P zP=i#ZJFWIyVQ!R&Hl%wJC1kB276TmU-`XUKP1|o z86y_=WsIAALnUA*e`@-TVAz6MWI`g<&=N8B0e_0p2As%OGp(t(RhjJ$_$rh&h2wG^ z+R25^!W7hfu4TG|TWv_w9C1UAi4D#@M;$w|P3aS&q|20Q^5Spm)S#0<*CiOS z@-{R1!QxvW3XA3TIvNE@6_EgNn%oGqd+s-27C@?lmsG<> zsSm(25dxJhGvzw542$a2WP_;60MygL0GCRkBX9LEK49I%GV4GtsU(Z{en{sA%0N<4 zz=K6bdy`sNBy&_cH?)^rnt2Hr3Enh0U|BJB303K*R%9=9>zy4nPcndb{(beU3-zwY z^|;?|Vmd){v)|3{Gm-aAQq}OtvSj5i^15fXfw@4pBee#cI@(}5`g)d9zfLZueX8#C zTh(L9EAF=>8^?}V1K(=XqS~hInG(F;?>w{G8AuvFVB1%80Xvaf(eq-YDCDbI!`_b> z&(eUe6;oBq&>1+%<22}o|Bsj0;&0CVZUZCjdpEYMG-2r78YZrz!Inos^A<0Ex*DhUtx-LpOVf2Hks|x)Nn1m6l{Oh@H zrjsUzb=T+-?FW#`rK(t!#sAS*K~PlWVsiT8eCWyAIF5!mnXtFT2OOAr7r6~NNF6m| zq6r2^-Zt?9|DeUc&B>soHDkpZ{&qfofGki~n{Q``TW2)BKwmmrs$5n1Y4y&7^3&tx zK~lX~7U*D+>)1;0%T(Spae6KqnQ`!{a#YMAy+RKia*W1imo)T`YP>DF+wht!cqcrIf>Tz$q51 z1G*lfVz(^31^da=)se;Z4wUmbnLSKh0$-6qvT$gr)OWOc{hqa;88Fwb>h!t72;=4GB=UmPEuX&|qPHuA z=v@psXw{?wrS*?R(~9boxHd}MO@?pKo~qiXb2$iaE3g|ArAdeC@@}`MmP(rHzOXyb6%t#Vj+T8W9Pa!k9^M$8JqxQ1TsJ(ZCmRmqoQO#U4 zN^3nX$jSD)SlE@uyIYcoj*UR9c~vAAPjje9k9P;A7teUpo1s(?ZhMkFXRvWJFqwmM zVDwd%AXNBVW*H?a>y%`i`q4pSI!`#?ATo13MqOf_mAF;+e{J8Tq%S2dkBYWtFh>Te1>I z)C7)q#H{s-d=E@rF>a>EXXz(xo*i>Skll#XimeXZLzQEw z(*=WI_qw&CM=JH9oTpw#Y5NTXX>a<)kpaie)*_^l@=IR_-i0709o@#&9Lf9WaSn9} zDaXg{Zmt$T++kCc7QmAArLVctf4BN^KP*cR1>tl3lx^H#>tU6lcMXc;JZ+oP-w=sz zbuO~rem0IWARfBe0mr0&940f;e`Z;O34L7UwyN^B$=Un6^vC^x1~u&)Gc6Oh9Jxkz zHx`lzS$;SEJ_Jz?t&m+J$vsbECW!@p8 zoHua1y&pQa(LNJfN^c|F>==x%zIY27H)qvWi?I1)_sQ9aCQ1TD;N`ksvmG6r;-giF zp&pQl{T*ks&dagt#0X`087Sdiqc<*Wv*YCwE$|L(-WNUZuL=GdwR(3wJ1?TjC@Sc< zViD4J@Oi?QmS9KU(L(21HFvm*Ag0w`=()V4XL&)W`!*cT<1UYzc_~5qcCCLkzZcH` zeF-?qEPyuCyA+1pO7dTVSdPj~NO19fGNEcNsGW$|7&Gxu9=#hw zt%75zm0|ol$8sR>rxGeV z;>)&inQ5)5AZ?!VG+YS(gMI3`3C3q$LM1;0R%mc}%o_L_4nK?43qrbD)b|6S9N7J( zI+s!l(t1PY{sc^4CrDKcI6XbaYj?oi?eEOlCGPXSj*1=y%O>A%HVww^PZK07Jxt!^ zeq+^=Xe}%0cYA}N)bG}G)}g+h?J*%`5u#wI9<}O#qRWXwXE|T6%&m@Lj?-)gFH0@l zR2y880Y-6|`Ie~8pqSxW3^A=5dx08ywc{VN!HBUr6#g8O<3N|{1xIG=ddtz@fT~6o zjeY13(_Z}`RehjYqI=_x4LDv=i$m0n;I*{Yw}!ntOQyjadZ7t2=Xs#LbCYdy?`*>C z!|iWW-Jy!gGLNZ_&5-3EjNsN|=n_M3>442gUORld^(nd;1ZG2vqVV3_m*d8Y zH-@)#n%-?s?L;1fW!<=Zne{zqwM;EvPePeTAt{NC$x(EFBf^9+uP#f{w_f9Won7CG z1iS%vJuHts;IVo4Um*d3^~b&B)dV0QcxzQwM2^-czW%X?5h~DFRu#9SZatR86Tq#> zRr+br{dj+Eu{mLHToiuE!_I!kil#axdi3{>UFg?(w!^JRu8*am74@8qZ89pzMQ|_i zsbX7}T3N?XkEA}9P>X#)Y?-@740Gd~qVp|YG%&~tHL~vd6S6Rv&q70-5XMu;1AbXN zX_dJf^zJj$MVqps+Pd?v9+(~#5hO9#>6fRQ^{4zEY7n~|8|zi(#5-^{j{)its9>^B zh#fA_^iCP?Iq*%H`kZ z2Ioo$qcbf9^^*cV?8&?NY@i7#jv>19sNGeC(%fG7H)`yumnT?eDH7+_sMG{Xj_W(p` zd`t7NB@`Pn0D--1Lr$XI-0i5ZkG8KBR|5kvsPU6QBTh(zcUYzy{^vYc=Hcn|9n+lR zLycE8ogc7m-PLT1RR@Jud9gEy2aB~kDQsGEjq3BS@U4NH6mM>P7`W1gACRfgjEC``r$5V zAsu*>9o^Fj{jfnuRb&7yXp0*}@z@VHsWfOJ33%~cn0rS{fnOvJ=UqzKlE=BZlB^gN zvz9Cq_Qw;NT}y>hzGy+KR+^^PGSkJTSeFu&2Xh2`0ac1vQz&H3E1yRBV?E zM+(c?0;e=agi^|AB^&Zq;DUxrBxF~wFgY5_zv_X*X36cML***=e<52ef{(h55OuIc z;NaJ%(##f+(HxxgS4!!HEYD|m{&`-`CUXoAw#9M8xPIC>9UU+=C;P#Y4fzIh?C@~1 zb0%65IG2P8qWk_Ij7sCn;aMa?G8+=o}RV zwUpJwx%b<&%31!Q50kC9ywT;jg-B^ospZaeEr4kaK$^(dGLCFd@Krz<`8k<0OkGVf z!8A&Gv}R@`>$F)QF=Y?fG;K1WTSJIK3q3Oq1BQ5{S1_s; zhD$9rCq98rAuKING0#6mR0v@my4n3_=$a%hfE{cXLpGG)I3GASepkFTJonW2_v^@L zgiEQiP2*xUP8{y*F>Y_9cc~ytg4CO$p1w`)BK;(|=N@3v|KZvN!z*!w-aYl?d zu-J?xIq=)neX`f8^$cauitiBb5B0|Nr`Zi~qlW}?(5nb#dcCW@GSZ!`=JDCzyOKXh zeU#-qdDXd&S&Em?Jw=+8=OLyQ`my#ytHl_lox}{q(#0&dvGac*vG$;W_A#!h#eJOp zwVO+v2;f{M?Ked&?pymwhgN%k9t~x{dd2J@k^Vi$=`;H^;K_W4w26Cd$iBazoD#P? zCuuiNIqBe_l!0?p0(#wI&JXtKBX;?A8=RA=BXO+5q9yU?Zj^JdeBy!gM7KVRiHS3u zJZ2u8J4LpPnj3}U68l)^&1zOlTSPr#JIZxR9$W54XQ5kyNw74VEPKZv+_PHm>~tC5 zWZz{wzqVfwpm~1aQ5VJ%)H)OjMC(E{Dp|Eky8HBYGUiO0YzSWsTgi1ag#9H3R()v} zQ;YakBZv#Qft^~11@{FBoIs9__Z0}ucL7(ZCyh8yGicIOf0~E*{I#sxuyN*!H+-(C zDacdnnDeHMn^h3JokVbQTRO*hy0Aa6h`kO6i35(}W7rUYu9-A7zrcE+?XL=K%7Qsk z`s7?CUBD)PF#SDh!saouop}Io6}M zrPC`2jW=cFCaoI9UuMh^;1c?F>Y-CQHXaH|ISl2<%>c-g-G-E9jI1|~^aO>4#&n;~A~0@&>p z0UB!`Bzqi;DPmX%4ggdv5tFqt$#*~I>++laPMp)~0um+j(bJ4wrDh-YI+;eb*(!v_ zg*4p^d^gl301<2G=E z7Urqlo74$%lbZ-wmBTuVRwQ*tIM;V$z}<0)bN_yYu0kb{0kiaa*e}27P--^$m0^@k za<<%4-`RWcUnMY^A72x09-cV>D`?$hIa5Qk;-^+O2qCNKzl`z(ZiA&CD`@gz%8kKa zS4iP#09^1e(Xd1}F?3OAs!5fUnlW#Z{f=WnA9IK;bsZ4Yc69$lwYB=syj%2N=h1E) z9UVT+x#1o!k@Q@l>=Q)M=`euy#^NS#Dsb|-)#=}QCq<%70umPj8at=XuS;b!zs@l0 zeHP57rLNoGH$NsGuo@qg-|TuY#FL=FYQku;VqwCIaAXP2ZN9^RBXtn5@>B)h2YDz* z2V?o`3Wy_*r(ik8h(3XLngscT^NiPr3p>;0QdX5QUFo|L@&0jIUlbP?{jDp4a5FOC zD}0|HE1Kd+#CsFIlWoYB!=V9-gzwRFkX;aSW(Y8Zg4bMC=}AD65lBh~-oi_Klru3c zZBfAwWA2&#<-VK%j#H1e={qIn8yEx)*TbknR99Ax`$dz7t>t1jGGNpL=J`!o{ryl; z^mPsgL|-Y2z67clBa~W*lGvAZgYdl#G-guv6kGpb##|tXC0IGV9+GIt5As}P&-_+E zEn^pzTM!epPk(H?vR`A>9mcshcWnoN+dpMp%H9`;m|~^yGH$K+%xJ2|#5~mQqn*)% zCzysoh)-7SSQj;#4^{5>&@?rlFPzJo01IB;c5<*(Bp=RWl60sw)9sUw6S4rv2GWv< zAv`sGrIH#7HN^{)!469RwfkWx|AwFPia)b zAV4Jlbgimq&!teA^8JbQY|>3Bx5diy_?L%o4N!QNJ%!haOrx|l!H}4|Qu~8K{oj3= zt&f+aAU%~xHNb=Fx+Z{HF=aUa-B65F?rbRCWcGluEu4FfclzRrzDY0w~etFh8F&2xXOlEg($d_Fj7}B|SLu8gvRR zANMD_VDuT2UEMEosMdXr(n1Z$qfA1snzb&G-6ZB?*VeKWa=M#le7+}|7O*1)<+D2i zMYYZ2t>!W8F_FLYm?ZU*Cx@Q+WsmiGrkcEB*P~F2Z&HsKQ825g zyd^!T*JG_U>_*ijez6r_Ou4k-C;?; z`h3A}Ej|2^c>*WvlQat0JrsJ&yOs7C9l0?p=@jK;>@7uB@t?gFh94b%v_UY!3EmgaUzkJ(7UWvKO4n1_BIFlr9cDy8KtT?<*-Cm)b$mW}hZg9tiV68hZeUaOTWs|qv48BiD zdp38LyXzG>nc2Tj@LRq1pac>cI*!7;s_=bm*~j@-Y+S`-cw?Bx=0kO-tgc$^O6%CK zJ52FibhbbKNf31!i54QI#01u%P?weIgX5(?3SY+f5HBHK2qknJ0wAe)@BZ&P$B0U+ zVJtyrR~v967#t(7&BevH=vvNK?0~mLQ~bGN7X5~C%D^ew2E|5U zn$rZoIk)yvRF~cjPJK(&(gmzX@4F$sW0Apva;Rr_pUcG7GoQE-ah>jeP_B^!0*Qr) zvBzD)LPN;=h5D{r4{-u<0b}iU$_X_~eydb90@x_=29EzQWX8wvB$r zP^pQ|hIP^Z7yx?s5net&S1IfVwQAUh`}GTBG-XsK!*;*&lIoxx)7hlALBYjl%!P6D znVf~Qtez{}N~+FZG@@5-f}0A+Z5!Kf{|H2fdd^Q~N?D6-=Di)kf^Gr%J?OO@)?$Ur z*@p^E0zu(HjBKmv=gaHlOeO639V66B-wPpS+X~9H+A}I&m{`R(AvKFT$CSWL;&*FN z7bx6?POk2>^eW!LQ(PfuB^UG96TTxhf$)sWD9WMi7FEGGSX%s5?nUaDxADS-!X=eH zjGj033cE}k)Ls~$PuZ}x(o<_j(XaPwR~KZNYRBA*Gv^7FCd{A`&9YQ($e6G^7gx>; zQS<$ZBKCg;%ky@v(aNxFyk!ZVUwi7LPB{m_w0CpJKD%6IdpZ++Q9BWoT>Pk|v1UFI zEkxtwAzISN)0XwbDg(?!#S4mZu!^MD_};K?5x>_79u`=lHrFRrIF{CMRXoVr=xmsV znGgV^f35p6+n&|EK=9O!GWDmH$r)ar{Wr!XgkdnSs2B5MwTDiMVY4~IsR`gqW&=)E zISd@3q^h6Vkm-M#PReQ8YA3$i0LOz3JajEpZ&c;Je*&H@a_KtWnd?hn?sa_Xz&mG~ zXmq$x1AkxL33lAx{-rMdzFMS=8H-3_+IovgD>40D&@QGjYFMN>ioU=XifvUF{nR zNBY)fdh8tDGn8-S3sly=bxY!~UQ`CpbiK>|(t{Q7{MPgB#cJ4|ma`kE+_&cqK#QOu zN|WCF3fY^a5qU!$6Aq$!rrMtqY!cBVM9$)Ol`f!osEscZ@)T4MzNPm_uHqyC>~6J$F79*ITR&WdxOV?Ou;=^euf!h zVZU>Au4F9PT&LxhiE$aW1qrkP*+4x@ch9%@?UebSOYPi|E%%A9D=*$gJ8sce555sPB_GE82w z{NE|LX$kJ9+eo+=o?-`J^4tv@W0Fz17?ujIIx-S*8eZMfCY*wq!iM^fg84RrHYOBw z>_ioEnqu=+0QUkddtvRE(s;Bs1b_u|MUx;q=gbf1qX#%uHvl%oIl0UZWe#%2(}O~l z1RlPkq>0?CzhVNexR~|*_t5l(vfCz7TBj>2r#=o5eTD#7z#@>~&^jPHJXo8?XK`r2 za;fKE0#W7yXyQC=u^pZ|YGf%>KBX16vL@Ep;$i$)QWO4Y2 zC*44GZke6D2yf&Bo)Bx0TkKnJ5GHwNqkl)3edlzz9dU#>x@Gvu_L@HFD91w-1<|ZsD;_o9q??BF8bvbhq}+cRhtKqH^w= zR+Hpe&rei;mTgJiZg5PS?^&|hSsqxtv;=^_ps=E1cj&B$Bd_`f0{Udw`-#oz!p`}R zT%sAu+-&hcYvZfO1s@c)C@ogvRK)I=N($$oKyFW6_snz^aj`%R%uan*B z@TZfp!pzR(r=PfD4WZ(JUHiA(Z{y2nryPE8WO0A*zA=moBWe0E=mPzZYB;y5I569w zAv=?ZuG7k@rGR5rCbvTO$dgzv>*Q+q=vllw@A`0iTdmU8M4PdTH4Sz6qq0uB8F%O# zxd~Mwg8n-5p8(nCi(_Vr%d>@Es}dd_l(peisGSUDuG<4cQ2>B(!xU_$FXblGIVns+S$ICA{E1YQBuWCaP0s z7qFkJ3${WI6}wm+a#tWv2pf&mY9KzcP9c*S%xv-?MKeU}Ar_Tj@0yHC{-X7#-y@YB zy(vWbJ~cUmmNx;ja)HN3T6YxoObP{$Gab%%VnhX&d@AT%1cz(9{-xMnaV(jktb_;i zx8iecm~$w^0h0;5TH;n?r{Nz$G}Ka)Hgql}vD$a+lP%iE5G=W~UX+PK%+|@Ma2Zqa z^xtiYs!__ZfX*4R=mIuEC3!^xxhJ#KXkS;7j2X!PF>ysTa(dSY1(b%Rfr zF3~>S)uqc0*UDaTn7Y;I8mK)P`99Ayni(7f&qlt%`HohD)IW{c~CA?}!Z1d^XK!WWnB12PjNZ*`fy)Ku4H-2jk$Ha$v+*hjWn~l}ciR zooX8fd#E~IsDEVHc|cKrw$mI>8m(Q;Ie8Gmdctr`3GsZxwl);7k(rXGj}#H=5?Rgb zLAM-#ipZDMn{)e>NS%AmnDyO!w~1Y2%WUAAi{$xRR>=2C`~;B_3o30rjvrjuP8Efd zonHAkq=D{H%!b@@cUgMbb>c~)a_C5k4{ zW=Vef?`N7fD~i!>wc^=tAAfxC9>^P;*VW}&zXc;U=L&7)8zhq)_$1m&S181%G8q8^ z8c4Nxg_Vi5UL(3*tBFP>&jeVXXO%Is$Zmvg0oG>xl7JEW-&aEsWrZqTX*l}kTujw{r2<=Wn>Lo!A6EBI)OLx*aZ2QC30edIJU z!f?B}bkp7yFcRWMMkyH>=Y@j9o|hZZd9uk1HS3lnfE}QfrZa-xW&dJrtutk%4CR_X z;XrO%0#$}{`$Uw}I|I~<3|qcA=GWxo=@2qrHc0!o+h_pdbL+R4>xOJ5``HRIUA(Sl zSb6v-<3un2i%UF?geEenI zE5R8;q#AcINGKz!@CrVw?cxJaw2Q1ZKayXhNi3{5WIjWj1ydk2|o~D|?6Y56)mUR~DQ0+agpcM;9uz z(C=$*-go{MA4GkNBKug2R( zKKw9$$;LWNF})$1af627lPxzLZY~Q(I1#=@Y#1)3&-inq4_Lu{bUI%(N|EH1E|_?6 z4P3&b-97nk)6NVk8Ug?=xlJqW?wmUJTm6C5FsP4O&(;5C%WbW;K{Cj!uXY>Z{D$#f zmKldmPJ8!10!h0SpDwfvk2o&rxxqO{=k7`(Wkf|1>BEnv>R7Hw;R2sbZ1nYh)-Z}v zKJO!2szOd}0-*1rhoaxy!42%JTHE{mAc8X8#zg?9?Zp!6onE;MrE8@TmwAX|GtoN@xy6lkb%pqop zr+d+-FC51UZ5U^9ta08$fiZh32Y+jP5(ICUFeYi42d`<2jRrBA^4>)~Rm>LK?;NX= znMT@qe{|B)-Q_@T0?{lBWlp@`l;xx>fx~N1GQV(bRl)ItbjT%)9HxSA{Z#7>yX7hpDAC%T;pc3cLO_;f&ZjphEG}nOgOb z7v#c6p)IX>Fup3lx@D<%P1WwDSIne)wD?imXWmN}H1B9?(>4@!8D}N!ETG@|KFENV zW|*}uG>Y4p>HIh%XX=dh#dLs3({LUfy-jFQ2?_KddUq$i`^++w4S)fI4@>fZTyH`! z`|dX1ThQ&+17{hV`L!0hu97%!O`1OUgzuD~Oo=RK1`XriR)Yy2ll2)hQyAguKcATF z(`V7K_hj-m#yGsr5@5-0=dDCMfej)B;xHK++=JFnzs5aq6`3<~1d;$zi3;t6wLR^3 z_BS!X+uMD|?_i9lSb-qH_Ntk12=^y`N|`{G3cYD*sES44GTM&0gGI&W*!@=$RBox< zUzLLHLQl>AKbG}f?X&n)tG^82Svd|1#WACZqz@5(hhuiaYD z7ia`4)mJa0EV4u%q*rs0s3T71TVYz)uV~cT$=E2N0^<_p3q?$!$dFxOC!`2RkL?9* z)0;Ylv0I-nN~N%ItNM}ysBs|-WWqhM8YJ-W=$3_+y>7DCuS;4$s{KBpeWBarpq(Gf z0N2ezeRlNep=49H;%Bi@Ob(N{yjj+M6v;YBxpbd6+e@AZ8CR)~ zwMO7CsXpkQWQ{-dSrEMZm#xrkIjcJQ&WhbHdB_^zCpB$3X^I#yR~Upf0_f@5nQkQ? zAfzgn6WyW#YVOT3t>C8pdcrP+fGML%kwowF>Cml-0TW4Smm!is^q73qd!cj*u>Zq1 ziJycmIpzeM|0$);?fJbn(}C&fOwql<`E~9yy^dEvOE|d#k(+nj@^Pn)>)&ty>-omN zF9#Vvw2k;BRmT=Mn4D2+z5Qg|&IpLY2ml=Ow%N8ZPdO!m#Dwr4@u)Uj_ z^jJ|66|Cu(2B7w$dKcJ$;1JYj=2LV@uLAvbE05Xu^l!L5&`J?Vkw9G?bpgtmZi;Di zwV5QQ9W*DH(8^AM6D2!6w5mD3qgYxQ3({2o2T5`xk4&3}ZQXw)3(F#MEij(cRj{DGuB znSe@`UI4rofC>!?(~NHIG;Q(TGh@Muh;KC{|Gv`ns0S;{M3I&Cm*&c)Xq_Buz z?Sv%`mRT*ETZ$!fV+TMc(LjanXAHTiCaf5oIb*TChYzIaQT9s8$Wbut!TT)9e`8fS zQoIK^Jr-7QG@|liW!hQuqHts6w4%9qnQgnXSMtD>CYK&;{($J?I3xQV>R-xm$KBPR z1`8qlTr;)P(mrf&>7H-X1x=s0tP(gH4op7~ImHV+?%0hFdbmIrG0G>Z#E3c3$xso-e}E<7 zdzrPgiP1_D9cI>}I}YlAVNz9GAnn`+UjHmPO(HIS| zdUc+>Up-i->ncHU8o&HGr6i7)5N^I4j(>K99G{>o#NToXN<5hQdHM?-u- z_WOtUH1p4wl$gKNIbF`OBLg5xnXzZH3K5B=(EPyM2qwE`8+HMr7fW6(HL z<+C@rfU9F8mXDEAw|YhzXOQc#(izZ0M9==dZPe$7^PKQc{zHzuer>^*xf2_F$ z;be4y&6ytHwO3sI;--)DHw~F8vaD&|Z-9ww2dPc~fmN8hpiq42N>x%&nF~)%!Kmty z6=C~$vphHLHCJ0fHe28Xkj%NIS)xTxJUcLdyR}J5!F%@u<`zBYevvHchj z3z%aEA?n&hetu4+osglIU3}?AWO{nUs)&nQykifYN3aGonsO?pNRe6Gwhp)A7 zdnc`KR4l@a&3p;*yiLgDqm2)0(zBl#aK{pqqjDR1jHhDzK$`l~UgNvflO5WOq2Hri zJ-=E2p`1=A-^~ z{Os8YrJbPLngU4JOoD(eB=pz=T_x-(Otzzk?($i8xhgX3cAzQe>eY%Gx2N@;N!jzG z@q4KVoTnAmBjuQh7-aQU7-tT#d)0WOK6Y+*^tR#OIO3X6y1d~Md>i*LT2^T+{KGU{=eV@^5wW?7cF)>9=GGYJwx1~TdV^`%OluS3;ml@ok=9pP!|s>^ zca5u$?JqH+`&iU@6fW-yn@mEA%!z&NAv@2UT4r9qWV}gCl7-UhO{#43#d$FPJ^!|4 zI7EL+9_nsdlDg0Oht=ya)uM7qx)`$M34h}Y&jZqFR2UOZP=|1zt7x7-*1 z#$^MYm2|&X?H{)nN8LNkH}!Dme}m>U0Qn|A$o3xPd$P`Hi2pL=KYAX7=CGbC<5dty z7ys`sh*$`+XPWS^U!_q2#7atJOQ-6FzOLHOQFB>GpAimxO(7#MVI%eXIN?E)1AX|$ z!~ipJ4qqVK-pndMD#?{o?5(j4i-iHO6CnHo9uXD=@H;@EVUXM8o#uQX42ZwZprXFV zVA0Ur19{+({0JOuAm=n^crz|?Vh~a%rE(Dz=1UIJK*beg`JCna;1LI)i6g^spvq1_ z-^ARNXp^|t`S0_9_7-?P9-wf1A;0e~FWF-D2?4k&?SRRD-Zp_dxa4xfITKLE3daG2 zTVPL;wN?MpaXD|Khvd~h+nqw&Go0RxAG7qZPAy+lMi)h$j(Ge(Z0Rs)Iqq-*L3^@1 zlhfqgw9$lNA%j(9T`}2g0~;VGw8q^($4HUOVZWaVpkOqgbHsmAHln4_-UfyeMKH~< zu&@gL74K4NMb4%lh5{oNZB9w^vljz!s{{eoP$}t01_Y3-h6!l^x$sun9xO1l#{eI~ z*KS1jv*+}tHXuSpRTgD!@;bw>0di+DwhFRP+XjBICX?%IVhCa_b{c1hf@#SeM{*U+ zXHzo=9wzao9!&$tK5%c(Sy^_mIq6B+5rPS55kS6ICBNn;kmLb+ROtmTOrGYNJ?2k2 z2C6u7!9cOqYdF`07R16LhO+|jAV46Gvj(uc0TDd&jSPPwe7qY0*ogR=8JYj(@+uDD z7#dD&BJNk72Xu>M^amt0(0PAD)MWBS5MaloRBQm*8hI#xzB;Qu2XH6{D#6O+0pM0S z>ljqVGq~IgPykh$ik%f3{96q4H)3HC1p5Bu-+rc^QKSye)|Da|4hhpVhXSUCtQGsu zatr=XvF|T1;Cm-$oCB#g1A}aGmS46r17`6ykmRUZH#_L61pix^7f>1B?!Ut5uOoo% z(6gLB1U%I~@>;!DeWtbVQ#CENG35yAad0WUnZN}PhLRHSF zj4ZGe?)^=w0(1`&ADsXhNnVpL5W|Ch{vxkliUqFU0*z67HJ4!9cBLf0OI$RRr7MAt$m-CP) zqMjiaWflYdl*w`-HW?^=*kd)}=mN*_lP`z@WZ!b=kClc&=Ip0vZ##0?lM58o@2{c{ z`$=S-{h1)#nFT0{a8C9(DD026XU8ymZ|)3YnOhEr+mu4we{UKeTHduNc?b;p%71{2 zCsJ9;d&yk`kozJkNBg*+@(uY-uI&=2au?#6rg8VT_t*h23tBy&Ev?}DP?u76`uhL# zAhlu86-V?YLIUjp|0pyUtLxPz8rtIDWeG7|t6weJVPj2SC0ip4M?locg5DBpLqZua zBO-L;>z%@08+A(@=|StV=&I2ZMf?xv$>42Nb7>pb$)*!|sfEFTKy8GS5-*!4+KFQw z^u;QZtj1saMv=9iXZdv#(&0)&&&gL+jyW<}{Y<^t7fe>H$|#a_31^&W!)h`odNGMC zE>*n&B;`i}F;$5yNWq_`d~rS;zd0dD+rS-)13eFKnS!HyRYI=g#~COLf4Y3U}euz@Q5Y_u8Dk=0NVwva4zUnP0zpj z+kRq8=tII!-sAH{_wkk11WahK_V}Aw>mWQfI$n4*r)fIPyVKBxCR622*)Eg&a#tU)W9OW= zx2(^;T@!hA{;JcZB1gnDlgeq^#{tN$$g5$+gzv7^_Y%Gb9xgjY+Z^Fu;bz}v2yh0i z{lWn&;Bzk~53*NYe4ner$;)!BaoZPtba0=k!qMa7$)`Nh0d`{}b><@1vqIV&j#xmOch2 zB{Erh?2?LW^X7bt%(tBhOW6jD2FeGkk2d=0h=(SY9`ev@HKITqQ09No<)`=zQUC#aOp`6_XvLJ4rDn?4hVcCyG8enU z-@HB@v|-Y?ARUy0-4RDbn%abDjj#5oUBrVeY-KOM0^GVm!NxryM3SD!H}pXhY261n zB9KjA=_aBD?rv0ICvn52>8DvZ??KAFF{A|sPq`kZ9_HJ2wS`sg?`QpR zcdx)fc>G14Y~@hvw#j^>s7+o+jO-=RN&D+Pn6Dru+ZBN>Yi`a)=P!az2YfvvR1AmP5`$ zPC0#$<1$LbFgiKLWQB-@A%~$N%K13Qsmb{;!iG6~Uvq!HkMHqMxcAe3*kkY0>-9Wb z*Y&*Kl{qInDk|!>?<$?%7jD*h04PX!KGdpv?yHRbvgVXLs@zSv4~5{u`F_CzH(uH< zl(cYU>^G_Ns&w)nI-OxIc0WOMnzb4yE5LRe>r*{|onG&9Z)o|Sf zI#@1TMXSd3v0d%OQDJoyWPH`9%8<29LLWBr=+wKyO>H>ixqgvQ(8W^Q22$;6H@`b4 zoKmLC1uL2;iG8=c=3VJznww%fIfp@t9cgl65lWvU=b9c=HVU&>AB{58!cw?zz1(FqVR{(^IsHI4xm%f!2COeB8=U33_y-zNwfUn`lEBBuM zrR-Zg`d?P1HaU8U4{kt%!q&fH;~yw*nq&tFo^4j@&`7>)T=*$!ybR;}O-n92MG%iP zQ96I&6#d4Ck`qskDv!=Af@cV6+P9L&FCHh!Qu*h{u+-uAlmYF#3TVh1bt%zY=4n_n zPvdyY-NKOkExXxAvpQ$<^TA+MtsTCx&-}{$DzP$Si?Z1_1eF@zF3oHEE~V*{B#u;@ z@&NkFvY%~zrolblJ%2VUOUgFzi5yOP+UyjI!8{j%g zm~MjVLfJQiD7ed*_7Z|59X+G{S%Z$)935oaKHcc&eB8=wgVD&LUL(D*Ce{R=Q#cRv zKtGv|EzHs*M>G6a<84!-bfaRkqB-I>e}Lz%-A>6aJHG|>Eq3!cB6E^oHlx^ zZ1sF6UE#FZ!@Bia&>VdH-(_(^4r9^YTlQv3wabuHM^a0>k(F}ee@m?{=REmkEJ|Fz zIglfHcHW*$Srz>fT~WJMPat8BK zYz;0H$h4bEzgOw;53JvJ6iuo_;R&Sl^`Vxe!fVKg)cMTA%PRrY)D$iyS8-_5y=BUF z4>`6GkZ)}T-DIjI*d4XlP-Y2HJXciOO^!Vi^z(6M(E(8VF%1k3YYjQk%&hi|bky$l z-<(mJ_;;rJA+8p1)K;BZW3&7`d8_<50k!>&(xr#x%^xUxpL)dZY|^!u+Oyjs9Z0hB zEiS1vnBHy#N_%DH#mx}Mp#rnGEJ0JpaW+iAh|loNH-5$l?XcCZ^ZKby-9`|_)-pl- zLcI4#ZtfKn)OtLHhCK5Gb!6r%GE{2s$T6{Yh2N;&-UrgAOm3LFLmcp`p86z~B;Xvs*^n#2(-nC>;oylTp`M zwm*`fa7DA0`~difG>-*X?Z&r-?b5^WD=W0QGXNCC+>X7nS<7gb!f4X`GX%wa$f}xC%Usx4;8em9wvxVUr9m4o4i&<^7kF^XTXe{g z*3^M2zqGJ+9^K*naJ*KN$9z1cVPhnCYC-)o9EMlf8MDO`#d(`Fd+}`Ij!`|JLh0YI zKypnjgHmHmg1ae{lwiBqQ>2)wr?6t5KZUSPW-O{#3^=D3o-caV^t4KLyb6O?=}{n# zV^l)xzE#vrgcDTC)M^v^cVZPDOrOkN)By=b*fD_#(W18T0ne?$Pq}s7}O=-PFE-U zOl!fAOvj0@&~(PBmZCK&XAX9T(zkAX#FIrmYox*^>Jq}0s_GG&^=H75PL=qOc5|e( z*ux68Sna)3E51F=s?oFNnsS34tlWChj#%}Y#P{ny%)wco>E)A|57+buZ&Lzd^-z(mA7>|Uj$Yo&OeAZi_TmB$2km%9yns7t?C?MT`-x5ri3 zK6qR+_Wj}Idk%crRUfSAu0yj%&{W(ljSs3sx4L$c#K}#4AW>qu#0Vg}U9A9y4#2A%{?GQd~(-_?@KTPSGCnwuS6+G7_ zyYyN(2Z03^P|fAlxA6-~aMU_joM#_gns+^o-9N!U!M0HZPyUhy&Mo_qU!92XwEs*h za&ZO*nW+%Je1rHz_XXdC^yB0EEC==NHCH>w-3nBlSd1A9Bs&QG_lvrlR&w0>rEBV! zU`1t!$OAThlOEXekWx3tMMUho-;^PNS88Sv+^a&Upv@~=f=KoNV$;QhZ*(5p^%pn& z{-ipmv5yxwZMqkYtuhntcTgBfLiaV3cYW-q>IyPXdqBss!Z@eLgq0+nPed-kogb~< zUPnKk6pp0XZiX~?py5gD?+Z492R<#&fD#hXE;4<3t$Dd^ZFo9#DuW?4PtP7py`2EN zyw;&{Em0n{O$3!o)l~8r>EUGRRCoXMtKyd1%_B^Mf1I4}~F8 zTG~M;gp^zUD<^-|ip2e;&;6>qMqT%Uo)6wG_qDIdP@1Lm=hBB8g>LvjYH&w-&KXTu zbBBOv|L%GMCkUYYziX)F!}F9vZ%z4yc(b_sLies0rM4rV81E&>1;sx1Sk!T;J4M%# z3tT$8sUc15n;K~|f@gXY!fHyW6AK~@@nX9@F>{JZgxNr`4ik|)i;s7c9e#ZYz6wkB zt8vLuY+mC&Hl)`##{$Xn0_acgG{wIF%|uKbfSY9L$p<wz*f(dV(XEiwegdWC zpFLovyUsP)LG>P`qqO%0Byx=^ozkM#R@0qlZ2W;LvKOQ7m0qrkADeg?qB3fLE)V(h z{j)Q*xA4PXXb}Tx(Ji)!*4Hym-w+xwy~=wgPo8q2b^gUwdL?S6;`zAd0Z9FC(7GSv zDh(AG32G{XNiyvYiYH1|!lp#8ocMLf#3k|gn>!H~OcM|-MBaNdMKhbo24B_fcI&*- zjbM^+DQK6awTa=*wTZ2;{M}l2;OJSghB&%G5+|J1eB+Gino7HW@ zT(OE@OG})q{2QO&b$TLvHq$EECcxN*PYysSOk(`*2`g`+6Oeca$9E!CTn|tHxRFyh z)~EkswXJV(tLDA!pXa_w@}~)d>NrxvPr_jl77LK=+gm>>Hk?2)Tg}EEBUn1q!cmS% z-WyPu`P6Dc9ND`g3>pW^wOd1*h1lJ#o!WF>y}HLGdao+F@L@JLeac&%s(+X`vzaHC zMD$#U{cD*FDz6*q$4+$g>dO%o=8m6ZmbO`7%nA~I9i(3{%W^Sb;0>KZ5^n2s{GQiF z>Jhp}(=$%PCJ#0_9p{8?X%C6CW@gx3qX97CdAV*ut5*4sdSU0>Mvp}1x<2vb!PP|T zi69W5Um8O8L*!!=8G7tPgo-hXQxUEEE7$t%-WBp3!7(9DH@vE$>)y>Q0d*HaCB*2m z+xvXOGrrAIJ^is|J`(R!HVAtKo$D*U=F5BG=F+lstnhD5YOt{ns1$Wio9VLoE|kHH zkin^onfoAjefO?J8=`1xykuVpkdz@w=B1DdkrJ?5j1zPjsc(^c%z@siDi=fl7RRw+$3Tf3M-k3e;xa zs$vKho1jI^2}MmYuT;MB|5(sz8EZ0$Or4XVyA0I_N20gU&%PsG^Ow2y4|{Va^4jd| zVO?{heH@n)(hv$mZ=&Gcn; z;Kaj|6{=^y91ZKH$5p_wdc^3gDU5nQG;oaQm6sZXe>maQ$fYN3fRsxb)>L{0FRB1N zIM35Fw0y!LNT(O@S5*T(rx5@kVwF+T{Vb&`me4I6IeJ_a=lJA79jnZ_y5)EVlo^q* zGlr=;UGNbUBeW5HiVkqRSBNeCbJ4T2e?j(;hoGO2#@fjoiusSwz}26keP}GTZuQ2iCm>ziMx#1KO#Q*py#fg zH+|G4w{V9YlqcxNAP5>FiS9R6R*E)O0&iGtZvc2sXvX)d0>cs14|l2;`opL(@FcZ@ z8_-mSjk83Ox0HKoo>;Uv`nn?B+x8Xh540l@kx`oRN_qN(F><~OiN~x5Kb^^i(tV_NRic}O zup#e1GrM(&tpRf%#f#_b?n4e_-(iu6SjDj+8iaE2-@wXd^z}|o`AsdRly9Dd_X+F2 z@|3OBt=qO`UKo693!bBgC_9e7~<9;4yRTrOE4*+VfL&ixrPVJ6-x2 z4y-;(*>8i9jQu_zu&`@g7j|xLjg}mq_9zIX&_Hs*yk&DW$tyZ+$yUqej15qg=c+ty$b>7QrHHlw0_HBA_Gg#|xvI zVE}ArntMVzBX~EEgHEdc=%+;4sS`RSN9DG@*DJxQSS7kWy%L>1hMa0keVY2_c8+*9 zX9>I6omQ3@u6d%YCUnowxy+iP-M%7Cupr`c8JPf6GOUE(8wbM0C8>oM`i(6DKD$e(Z z1kekn>L3C->t!B)h~OA<&l|jgs3HD}vfH}(OtWarzIIZcv;dw->;X>e&XwneFJk7- zhrk|$MSY?tz;@OSgtdWIsmCTc;qs<-E_@5L5Vlx7uFE=FN*Qc|wrlyLE*dbdmkn{r zGfAasH{?jR7@*v%|fnGx=KSlQykc>_XG_*bv#h9niHo@!-LZFGcY5DLm@cprD z{{r~2nSx6@ZAM8hWl>-?n>6L(1>9V}BHhvvh6-EW-Kh%%wBn2^gzthpcpMJ6fyzEJs{m)-CoY-6 zMM3k$=hcUa`B@?-1L!Ejq7!n#;jh84N_uFo@oX*|V z9d0a6c$Xi-rF0<;fIDM093Gbd}LU*u<=nRkS>*VsGvqwJZ32w|1 zlIz$WioCu5yh(@}8r3){;(-KYCMoau+rm(%&0^Osa_GSfT-tSoxS;l9-8hj7q*y~G zf1i>N-Fq`>_X^pswnZP{$-gh~x$Xw0*~lJY|UY!A89pS*d!y0KjhtZX$iMuQt6wKYAvy@t$Cdc)VvM_Eii>g2Vk$zzWjvKcNCjlFDbsLT zW;B}OQ?Pwb_ndA(EoH7@L$10qh(0B_=J&n`>HKmkOA2H7 z(B>=~MBWRvJI|D@+QrV}`%%SUPNW=i{FC<``$g;GrFdq#<0ZSyKe@B|eqfJJSlQts4+s4cWfAF0Nfmwb>yCEclUAhkEWFxm;3 zW^FN!!KxR%_{wQ#9*H8ZsN^l3H65aB0hP+qJGMSQOW%iCp)>e?$)h2D1COer- z1y*$pSB#M)0H6sn&M@ zNHg8J5B@qre6{w^E1VP!#Vn)0#Dd~19JVulwVUj1>O;g>ZnXxEotbi3sxL;`iU@qz zw0PN~d9ve;mG^d^7X4aHSiEnrEF@eJ1owkvpM?bUp6)=arv|vnp}~ARutJf|W~0U0 zY24D-S~}V2n4mexcd8~>K@M$I{JOt(V6WVY39>W<>JhdNr+hP;Y)=27{3;Fl;v&!Vw!^;7>uK)1fo=@KOk#qv$ zL-m;V_^|m?*y%AKPT2d0za02JSXg;x(89%{k+-RwL#~N~LVi>2f0NaM;BSKk0$o4=dn~&U)zua(pT{PK^ zsoc`y2anhZ*l0x)4)%U0UbEWkiDxsIN+t*ac#&I9?zzs!U3zNS=<(qb&l^wF&I&^X zsO2DcJ-~%V?(FzKJ$1fy6nGDsh5)~T>20|Cs83TGp--JDkJ__cC-iO{60#5gvj~Jk zCTABb*@WN3XK!qXCTKjPh2%2za}ZDPhKHmyraw0rRFGqCq)ql)IVt|9=+-01PTh); zl1rXzi%xRGmP~aR#8Mx!U#9;Ue3j*qZ=)t{^eb6F6px`};%`oebnRv~o!o%LXB@~b~fHv_)H&v6bw3u6t3}b5C zwvUI`r&I=i6>W6v*txXuc^%wLee?mB1u**zKF!S0xV