From 00e9207563f098dafed5f45bcdd48b4ff1b90a5f Mon Sep 17 00:00:00 2001 From: Rachel House Date: Wed, 21 Aug 2024 12:47:51 -0500 Subject: [PATCH] [DOCS] DOC-818: Update GX Core Overview and Try GX (#10237) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../docs/cloud/connect/connect_python.md | 2 +- .../docs/components/examples_under_test.py | 18 ++ .../manage_data_contexts.md | 2 +- .../docs/core/introduction/about_gx.md | 75 ------- .../docs/core/introduction/gx_overview.md | 103 +++++----- .../docs/core/introduction/introduction.md | 34 +--- .../overview_images/gx_overview.drawio | 79 ++++++++ .../gx_workflow_steps_and_components.png | Bin 0 -> 55370 bytes .../docs/core/introduction/try_gx.md | 164 ++++++++++----- .../docs/core/introduction/try_gx.py | 164 --------------- .../core/introduction/try_gx_end_to_end.py | 189 ++++++++++++++++++ .../core/introduction/try_gx_exploratory.py | 100 +++++++++ docs/docusaurus/docs/gx_welcome.md | 2 +- docs/docusaurus/docs/resources/get_support.md | 2 +- docs/docusaurus/sidebars.js | 23 +-- 15 files changed, 570 insertions(+), 387 deletions(-) delete mode 100644 docs/docusaurus/docs/core/introduction/about_gx.md create mode 100644 docs/docusaurus/docs/core/introduction/overview_images/gx_overview.drawio create mode 100644 docs/docusaurus/docs/core/introduction/overview_images/gx_workflow_steps_and_components.png delete mode 100644 docs/docusaurus/docs/core/introduction/try_gx.py create mode 100644 docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py create mode 100644 docs/docusaurus/docs/core/introduction/try_gx_exploratory.py diff --git a/docs/docusaurus/docs/cloud/connect/connect_python.md b/docs/docusaurus/docs/cloud/connect/connect_python.md index e0ec5f1aef3a..18b3176d9dcd 100644 --- a/docs/docusaurus/docs/cloud/connect/connect_python.md +++ b/docs/docusaurus/docs/cloud/connect/connect_python.md @@ -61,7 +61,7 @@ Environment variables securely store your GX Cloud access credentials. ``` :::note - After you save your **GX_CLOUD_ACCESS_TOKEN** and **GX_CLOUD_ORGANIZTION_ID**, you can use Python scripts to access GX Cloud and complete other tasks. See the [GX OSS guides](/core/introduction/about_gx.md). + After you save your **GX_CLOUD_ACCESS_TOKEN** and **GX_CLOUD_ORGANIZTION_ID**, you can use Python scripts to access GX Cloud and complete other tasks. See the [GX Core guides](/core/introduction/introduction.md). ::: 2. Optional. If you created a temporary file to record your user access token and Organization ID, delete it. diff --git a/docs/docusaurus/docs/components/examples_under_test.py b/docs/docusaurus/docs/components/examples_under_test.py index 1a64944aa592..849a656d6b9e 100644 --- a/docs/docusaurus/docs/components/examples_under_test.py +++ b/docs/docusaurus/docs/components/examples_under_test.py @@ -8,6 +8,23 @@ docs_tests = [] +try_gx = [ + IntegrationTestFixture( + # To test, run: + # pytest --docs-tests -k "try_gx_exploratory" tests/integration/test_script_runner.py + name="try_gx_exploratory", + user_flow_script="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py", + backend_dependencies=[], + ), + # To test, run: + # pytest --docs-tests --postgresql -k "try_gx_end_to_end" tests/integration/test_script_runner.py + IntegrationTestFixture( + name="try_gx_end_to_end", + user_flow_script="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py", + backend_dependencies=[BackendDependencies.POSTGRESQL], + ), +] + create_a_data_context = [ IntegrationTestFixture( # To test, run: @@ -524,6 +541,7 @@ # Extend the docs_tests list with the above sublists (only the docs_tests list is imported # into `test_script_runner.py` and actually used in CI checks). +docs_tests.extend(try_gx) docs_tests.extend(create_a_data_context) diff --git a/docs/docusaurus/docs/core/installation_and_setup/manage_data_contexts.md b/docs/docusaurus/docs/core/installation_and_setup/manage_data_contexts.md index 3682543b8dbe..7ab8dd46fe29 100644 --- a/docs/docusaurus/docs/core/installation_and_setup/manage_data_contexts.md +++ b/docs/docusaurus/docs/core/installation_and_setup/manage_data_contexts.md @@ -288,7 +288,7 @@ Environment variables securely store your GX Cloud access credentials. export GX_CLOUD_ORGANIZATION_ID= ``` - After you save your **GX_CLOUD_ACCESS_TOKEN** and **GX_CLOUD_ORGANIZTION_ID**, you can use Python scripts to access GX Cloud and complete other tasks. See the [GX OSS guides](/core/introduction/about_gx.md). + After you save your **GX_CLOUD_ACCESS_TOKEN** and **GX_CLOUD_ORGANIZTION_ID**, you can use Python scripts to access GX Cloud and complete other tasks. See the [Introduction to GX Core](/core/introduction/introduction.md). 2. Optional. If you created a temporary file to record your user access token and Organization ID, delete it. diff --git a/docs/docusaurus/docs/core/introduction/about_gx.md b/docs/docusaurus/docs/core/introduction/about_gx.md deleted file mode 100644 index 8197f10c1179..000000000000 --- a/docs/docusaurus/docs/core/introduction/about_gx.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: About Great Expectations ---- -import GxData from '../_core_components/_data.jsx' -import PythonVersion from '../_core_components/_python_version.md' -import GxCloudAdvert from '/static/docs/_static_components/_gx_cloud_advert.md' - -Great Expectations (GX) is the leading tool for validating and documenting your data. {GxData.product_name} is the open source Python library that supports this tool. With -{GxData.product_name} you can further customize, automate, and expand on GX's processes to suite your specialized use cases. - -Software developers have long known that automated testing is essential for managing complex codebases. GX brings the same discipline, confidence, and acceleration to data science and data engineering teams. - -## Why use GX? - -With GX, you can assert what you expect from the data you load and transform, and catch data issues quickly – Expectations are basically unit tests for your data. Not only that, but GX also creates data documentation and data quality reports from those Expectations. Data science and data engineering teams use GX to: - -- Test data they ingest from other teams or vendors and ensure its validity. -- Validate data they transform as a step in their data pipeline in order to ensure the correctness of transformations. -- Prevent data quality issues from slipping into data products. -- Streamline knowledge capture from subject-matter experts and make implicit knowledge explicit. -- Develop rich, shared documentation of their data. - -To learn more about how data teams are using GX, see [GX case studies](https://greatexpectations.io/case-studies/). - -## Key Features - -GX provides a unique framework for describing your data and validating it to ensure that it meets the standards you've defined. In the process, GX will generate human-readable reports about the state of your data. Additionally, GX's support for multiple backends ensures you can run your validations on data stored in different formats with minimal effort. - -### Expectations - -Expectations are assertions about your data. In GX, those assertions are expressed in a declarative language in the form of simple, human-readable Python methods. For example, in order to assert that you want the column “passenger_count” to be between 1 and 6, you can say: - -```python title="Python code" -expect_column_values_to_be_between( - column="passenger_count", - min_value=1, - max_value=6 -) -``` - -GX then uses this statement to validate whether the column passenger_count in a given table is indeed between 1 and 6, and returns a success or failure result. The library currently provides several dozen highly expressive built-in Expectations, and allows you to write custom Expectations. - -### Data validation - -Once you’ve created your Expectations, GX can load any batch or several batches of data to validate with your suite of Expectations. GX tells you whether each Expectation in an Expectation Suite passes or fails, and returns any unexpected values that failed a test, which can significantly speed up debugging data issues! - -### Data Docs - -GX renders Expectations in a clean, human-readable format called Data Docs. These HTML docs contain both your Expectation Suites and your data Validation Results each time validation is run – think of it as a continuously updated data quality report. The following image shows a sample Data Doc: - -![Screenshot of Data Docs](/docs/oss/guides/images/datadocs.png) - -### Support for various Data Sources and Store backends - -GX currently supports native execution of Expectations against various Data Sources, such as Pandas dataframes, Spark dataframes, and SQL databases via SQLAlchemy. This means you’re not tied to having your data in a database in order to validate it: You can also run GX against CSV files or any piece of data you can load into a dataframe. - -GX is highly configurable. It allows you to store all relevant metadata, such as the Expectations and Validation Results in file systems, database backends, as well as cloud storage such as S3 and Google Cloud Storage, by configuring metadata Stores. - -## What does GX NOT do? - -GX is NOT a pipeline execution framework. - -GX integrates seamlessly with DAG execution tools such as [Airflow](https://airflow.apache.org/), [dbt](https://www.getdbt.com/), [Prefect](https://www.prefect.io/), [Dagster](https://github.com/dagster-io/dagster), and [Kedro](https://github.com/quantumblacklabs/kedro). GX does not execute your pipelines for you, but instead, validation can simply be run as a step in your pipeline. - -GX is NOT a data versioning tool. - -GX does not store data itself. Instead, it deals in metadata about data: Expectations, Validation Results, etc. If you want to bring your data itself under version control, check out tools like: [DVC](https://dvc.org/), [Quilt](https://github.com/quiltdata/quilt), and [lakeFS](https://github.com/treeverse/lakeFS/). - -GX is NOT an independent executable. - -{GxData.product_name} is a Python library. To use GX, you will need an installation of . Ideally, you will also configure a Python virtual environment to in which you can install and run GX. Guidance on setting up your Python environment and installing the GX Python library is provided under [Set up a GX environment](/core/installation_and_setup/installation_and_setup.md) in the GX docs. - -## GX Cloud - - diff --git a/docs/docusaurus/docs/core/introduction/gx_overview.md b/docs/docusaurus/docs/core/introduction/gx_overview.md index b92126f9458d..6b0ec4bcc205 100644 --- a/docs/docusaurus/docs/core/introduction/gx_overview.md +++ b/docs/docusaurus/docs/core/introduction/gx_overview.md @@ -1,101 +1,88 @@ --- -title: Great Expectations overview +title: GX Core overview --- -This overview is for new users of Great Expectations (GX) and those looking for an understanding of its components and its primary workflows. It does not require an in-depth understanding of GX code, and is an ideal place to start before moving to more advanced topics, or if you want a better understanding of GX functionality. +This overview is for new users of GX Core and those looking for an improved understanding of GX Core components and primary workflows. It is an ideal place to start before exploring more advanced topics found in the GX Core documentation. -## What is GX +## GX Core components and workflows -GX is a framework for describing data using expressive tests and then validating that the data meets those criteria. +**Great Expectations (GX)** is a framework for describing data using expressive tests and then validating that the data meets test criteria. **GX Core** is a Python library that provides a programmatic interface to building and running data validation workflows using GX. -## GX core components +GX Core is versatile and supports a variety of workflows. It can be used for interactive, exploratory data validation as well as data validation within production deployments. -GX is built around the following five core components: +**GX components** are Python classes that represent your data and data validation entities. -- **[Data Sources:](#data-sources)** Connect to your data, and organize data for testing. -- **[Expectations:](#expectations)** Identify the standards to which your data should conform. -- **[Validation Definitions:](#validation-definitions)** Link a set of Expectations to a specific set of data. -- **[Checkpoints:](#checkpoints)** Facilitate the integration of GX into data pipelines by allowing you to run automated actions based on the results of validations. -- **[Data Context:](#data-context)** Manages the settings and metadata for a GX project, and provides an entry point to the GX Python API. +**GX workflows** are programmatically defined data validation processes. GX workflows are built using GX components. +## The pattern of a GX workflow -## Data Sources +All GX workflows share a common pattern: -Data Sources connect GX to data such as CSV files in a folder, a PostgreSQL database hosted on AWS, or any combination of data formats and environments. Regardless of the format of your Data Asset or where it resides, Data Sources provide GX with a unified API for working with it. +1. Set up a GX environment +2. Connect to data +3. Define Expectations +4. Run Validations -### Data Assets +At each workflow step, different GX components are defined and used. This section introduces the key GX Core components required to create a data validation workflow. -Data Assets are collections of records within a Data Source, like tables in a database or files in a cloud storage bucket. A Data Source tells GX how to connect to your data and Data Assets tell GX how to organize that data. +![GX workflow pattern with related GX components](./overview_images/gx_workflow_steps_and_components.png) -Data Assets should be defined in a way that makes sense for your data and your use case. For instance, you could define a Data Asset based on a SQL view that joins multiple tables or selects a subset of a table, such as all of the records with a given status in a specific field. +### Set up a GX environment -## Batches +A **Data Context** manages the settings and metadata for a GX workflow. In GX Core, the Data Context is a Python object that serves as the entrypoint for the [GX Python API](/reference/index.md). You use the Data Context to define and run a GX workflow; the Data Context provides access to the configurations, metadata, and actions of your GX workflow components and the results of data validations. -All validation in GX is performed on Batches of data. You can validate the entire data asset as a single batch, or you can partition the data asset into multiple batches and validate each one separately. +All GX workflows start with the creation of a Data Context. -### Batch Definitions +For more information on the types of Data Context, see [Create a Data Context](/core/set_up_a_gx_environment/create_a_data_context.md). -A Batch Definition tells GX how to organize the records in a Data Asset into Batches for retrieval. For example, if a table is updated with new records each day, you could define each day's worth of data as a different Batch. Batch Definitions allow you to retrieve a specific Batch based on parameters provided at runtime. +### Connect to data -Multiple Batch Definitions can be added to a Data Asset. That feature allows you to apply different Expectations to different subsets of the same data. For instance, you could define one Batch Definition that returns all the records within a Data Asset. You might then configure a second Batch Definition to only return the most recent day's records. And you could also create a Batch Definition that returns all the records for a given year and month which you only specify at runtime in a script. +A **Data Source** is the GX representation of a data store. The Data Source tells GX how to connect to your data, and supports connection to different types of data stores, including databases, schemas, and data files in cloud object storage. -## Expectations +A **Data Asset** is a collection of records within a Data Source. A useful analogy is: if a Data Source is a relational database, then a Data Asset is a table within that database, or the results of a select query on a table within that database. -An Expectation is a verifiable assertion about data. Similar to assertions in traditional Python unit tests, Expectations provide a flexible, declarative language for describing expected behaviors. Unlike traditional unit tests which describe the expected behavior of code given a specific input, Expectations apply to the input data itself. For example, you can define an Expectation that a column contains no null values. When GX runs that Expectation on your data it generates a report which indicates if a null value was found. +A **Batch Definition** tells GX how to organize the records within a Data Asset. The Batch Definition Python object enables you to retrieve a **Batch**, or collection of records from a Data Asset, for validation at runtime. A Data Asset can be validated as a single Batch, or partitioned into multiple Batches for separate validations. -Expectations can be built directly from the domain knowledge of subject matter experts, interactively while introspecting a set of data, or through automated tools provided by GX. +For more information on connecting to data, see [Connect to data](/core/connect_to_data/connect_to_data.md). -For a list of available Expectations, see [the Expectation Gallery](https://greatexpectations.io/expectations/). +### Define Expectations -### Expectation Suites +An **Expectation** is a verifiable assertion about data. Similar to assertions in traditional Python unit tests, Expectations provide a flexible, declarative language for describing expected data qualities. An Expectation can be used to validate a Batch of data. -Expectation Suites are collections of Expectations describing your data. When GX validates data, an Expectation Suite helps streamline the process by running all the contained Expectations against that data. +For a full list of available Expectations, see [the Expectation Gallery](https://greatexpectations.io/expectations/). -You can define multiple Expectation Suites for the same data to cover different use cases, and you can apply the same Expectation Suite to different Data Assets. +An **Expectation Suite** is a collection of Expectations. Expectation Suites can be used to validate a Batch of data using multiple Expectations, streamlining the validation process. You can define multiple Expectation Suites for the same data to cover different use cases, and you can apply the same Expectation Suite to different Batches. -## Validation Definitions +For more information defining Expectations and creating Expectation Suites, see [Define Expectations](/core/define_expectations/define_expectations.md). -Validation Definitions tell GX what Expectations to apply to specific data for validation. It connects a Data Asset's Batch Definition to a specific Expectation Suite. +### Run Validations -Because an Expectation Suite is decoupled from a specific source of data, you can apply the same Expectation Suite against different data by reusing it in different Validation Definitions. +A **Validation Definition** explicitly associates a Batch Definition to an Expectation Suite, defining what data should be validated against which Expectations. -The same holds true for Batch Definitions: because they are decoupled from a specific Expectation Suite, you can run multiple Expectation Suites against the same Batch of data by reusing the Batch Definition in different Validation Definitions. As an example, you could have one Validation Definition that links a permissive Expectation Suite to a Batch Definition. Then you could have a second Validation Definition that links a more strict Expectation Suite to that same Batch Batch Definition to verify different quality parameters. +A **Validation Result** is returned by GX after data validation. The Validation Results tell you how your data corresponds to what you expected of it. -In Python, Validation Definition objects also provide the API for running their defined validation and returning Validation Results. +A **Checkpoint** is the primary means for validating data in a production deployment of GX. Checkpoints enable you to run a list of Validation Definitions with shared parameters. Checkpoints can be configured to run Actions, and can pass Validation Results to a list of predefined Actions for processing. -### Validation Results +**Actions** provide a mechanism to integrate Checkpoints into your data pipeline infrastructure by automatically processing Validation Results. Typical use cases include sending email alerts, Slack messages, or custom notifications based on the result of data validation. -The Validation Results returned by GX tell you how your data corresponds to what you expected of it. You can view this information in the Data Docs that are configured in your Data Context. Evaluating your Validation Results helps you identify issues with your data. If the Validation Results show that your data meets your Expectations, you can confidently use it. +**Data Docs** are human-readable documentation generated by GX that host your Expectation Suite definitions and Validation Results. Using Checkpoints and Actions, you can configure your GX workflow to automatically write Validation Results to a chosen Data Docs site. -## Checkpoints +For more information on defining and running Validations, see [Run Validations](/core/run_validations/run_validations.md). -A Checkpoint is the primary means for validating data in a production deployment of GX. Checkpoints allow you to run a list of Validation Definitions with shared parameters and then pass the Validation Results to a list of automated Actions. +## Customize GX Core workflows -### Actions +While all GX Core workflows follow a shared pattern, the outcome and operation of a workflow can be customized based on how you create Batches, define Expectations, and run Validations. GX Core components are building blocks that can be applied in a variety of ways to satisfy your data validation use case. -One of the most powerful features of Checkpoints is that you can configure them to run Actions. The Validation Results generated when a Checkpoint runs determine what Actions are performed. Typical use cases include sending email, Slack messages, or custom notifications. Another common use case is updating Data Docs sites. Actions can be used to do anything you are capable of programming in Python. Actions are a versatile tool for integrating Checkpoints in your pipeline's workflow. +For instance, a GX Core workflow might: -## Data Context +* Create a Batch using data from a Spark DataFrame and allow you to interactively validate the Batch with Expectations and immediately review the Validation Results. This workflow could serve to inform your exploration of which Expectations you want to use in a production deployment of GX. -A Data Context manages the settings and metadata for a GX project. In Python, the Data Context object serves as the entry point for the GX API and manages various classes to limit the objects you need to directly manage yourself. A Data Context contains all the metadata used by GX, the configurations for GX objects, and the output from validating data. +* Connect to data in a SQL table, define multiple Expectation Suites that each test for a desired data quality characteristic, and use a Checkpoint to run all Expectation Suites. This workflow, when integrated with and triggered by an orchestrator, could enable automated, scheduled data quality testing on an essential data table. -The following are the available Data Context types: -- **Ephemeral Data Context:** Exists in memory, and does not persist beyond the current Python session. -- **File Data Context:** Exists as a folder and configuration files. Its contents persist between Python sessions. -- **Cloud Data Context:** Supports persistence between Python sessions, but additionally serves as the entry point for GX Cloud. +* Connect to a group of SQL tables and define a collection of Data Assets, each batched on a time-based column, and validate the data within each Data Asset using the same Expectation Suite. This workflow could provide a way to implement consistent data quality testing across a sharded data infrastructure. -### The GX API +Equipped with an understanding of the GX Core components, you can design data validation workflows that logically and effectively validate your data across a variety of data store types, environments, and business use cases. -A Data Context object in Python provides methods for configuring and interacting with GX. These methods and the objects and additional methods accessed through them compose the GX public API. +## Next steps -For more information, see [The GX API reference](/reference/api_reference.md). - -### Stores - -Stores contain the metadata GX uses. This includes configurations for GX objects, information that is recorded when GX validates data, and credentials used for accessing data sources or remote environments. GX utilizes one Store for each type of metadata, and the Data Context contains the settings that tell GX where that Store should reside and how to access it. - -### Data Docs - -Data Docs are human-readable documentation generated by GX. Data Docs describe the standards that you expect your data to conform to, and the results of validating your data against those standards. The Data Context manages the storage and retrieval of this information. - -You can configure where your Data Docs are hosted. Unlike Stores, you can define configurations for multiple Data Docs sites. You can also specify what information each Data Doc site provides, allowing you to format and provide different Data Docs for different use cases. \ No newline at end of file +Visit [Try GX Core](/core/introduction/try_gx.md) to see example workflows implemented using GX Core. \ No newline at end of file diff --git a/docs/docusaurus/docs/core/introduction/introduction.md b/docs/docusaurus/docs/core/introduction/introduction.md index 17adc5140ac6..60daf9eb4481 100644 --- a/docs/docusaurus/docs/core/introduction/introduction.md +++ b/docs/docusaurus/docs/core/introduction/introduction.md @@ -1,6 +1,6 @@ --- -title: Introduction to Great Expectations -description: Learn about the key features of GX, how to connect with the GX community, and try GX in Python. +title: Introduction to GX Core +description: Learn about GX Core components and workflows and try out the GX Core Python library. hide_feedback_survey: true hide_title: true --- @@ -11,41 +11,25 @@ import LinkCard from '@site/src/components/LinkCard'; import OverviewCard from '@site/src/components/OverviewCard'; - Learn about the key features of Great Expectations (GX). Connect with the GX community, and try GX in Python using provided sample data. + Learn about key Great Expectations (GX) Core components and workflows. Use the GX Core Python library and provided sample data to create a data validation workflow. - - - - \ No newline at end of file diff --git a/docs/docusaurus/docs/core/introduction/overview_images/gx_overview.drawio b/docs/docusaurus/docs/core/introduction/overview_images/gx_overview.drawio new file mode 100644 index 000000000000..14c21976a8a4 --- /dev/null +++ b/docs/docusaurus/docs/core/introduction/overview_images/gx_overview.drawio @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docusaurus/docs/core/introduction/overview_images/gx_workflow_steps_and_components.png b/docs/docusaurus/docs/core/introduction/overview_images/gx_workflow_steps_and_components.png new file mode 100644 index 0000000000000000000000000000000000000000..b3c75b9f39559e89ee9dc678f3148e901dd2facd GIT binary patch literal 55370 zcmeFZ1zc6#wmwWsN_U5JcZW2Rk{gij+M7;kkZzF{6;M)2IyVSNOCz8(NJ)2n3&nHa zbKiT<{eSWMPTcboS$mH)=U8)&G3FT0oa@m@byfNMs3fQ`FfjKO6=XDEVBkw&U|?O4 z?*Wp%1YK_60_&VB6)s2ovhK`%t z)X9n6!q(K<#njQ29c<|ah=BX1)|TMA1}YFATL%YII&L{Wc23|Hql&4yE!Yj>YD32( z1$R?`H2fq=hUoW@*y#sW;h$A?Z%4wkx>W=ghzb~!#C0d^jKK+LG5 zprxWr$1M$fcCfX#1TONH=Jt@=F48tG5Jx~F$Hgtc&UyO>5NeuQnY!5iLB;JzJuF>Z zZ6V-ali}uN7ho6oH3e65QwPgmVy<5jGl+|YrOPi7ppTo5N0yHBA)w>-he!I(3cwez zDe&x{)-i`TIzqr~o)8y%D+h=to2#3plj|>Swidsd*sHi(=&RWq^V+y7@VfJ>amqtf z|FiPnk8t#ocQJLcQGr+^eNKj*>C zY3*)nVd;9O?UyPy2*kn778vc%iQM%zhk(JB=69aGlbO1>Ks^7tnH9w0ZgzKVoPdD% zD@C^g%|BWAR;p`j;Rb}pZESIIao&yZ_J)F`t+mbFqXqfyZaA9$ZhR+jwK27Tc>e1D zQ}jnC{j%e4Yq~%nfVw~U@Q=5DH*)lnwsg3S&);^udkzpr|M&a;#TqVuSswpy)M>aW zdYgJWN!fW>*m23qNSmo>>$7q5{PLfNse}8S&3_48-MoJXv&2O7~0s#idY}_0jfNxyDM_}Q|I0dyTj*cMS$8w}*G+@G|1!Sfx`SMQ%U6~b0ARSQU=SBK8;CUoZ0aEU zA5y8mE^7bLMi~MDD%#IdW@qW<=6#2LOx@iee_iN)6|~>@1E^3^mSBsAx9A4wZsq_n zxBn%Q0}A4uLRU9a7q>fwyua@L;hQVO-NoEe-O|Mts9=^ZwNZtSEOm>X-Jv(eAGB+?w@QtDhbJc2)Z?i?WNQgQ=UX#~-x-`2QcK z!u~mDYu;{BINhCqU%;+}Q~q|hVF~uIb%B5#Z(-Tbg8T=t1i%&mCjJo-nL5~7-|iU9 zf#$ax0;yYYV+)|!hj%v|Z7nQrvDSYHt^PVrtsvmv0mQ`*^!WMxF3$f8^d%=JB*iZP zwEJ%(IZ@>TbX(0dqj8gwO>IvVk z)%*v)%lw26oSMI~=id)Q+-`0E(=o7|w5*&o&;L&w5gx9;_A%G*Eyf?VB7c$nV}l0Z z&DAXw{IioEy1M>;d-iw1iT`Lm=Kj5C|M`6U=i@9XQ#W%P!0kVaxb1BU|2tOhe>hM7 z&fx!ip8oT>!kwr8ZriKckX};eSi+=$h|M?}rU-_6*(;Z+y{M*-mIqCl=#$H^1K9Bv6ee=H& z*smivf&cWL|DQR3|M{fsul%iH>FVzA_k5}M_XcGD+1LNfYx~b9W70MN<SORGydUx{QEWOKf)U3 z0XX(@{|#OG=e@rorvH&d^z-g-X8f-kcO7p3O~Ee`Fw)))ISdRHjH1j#Eia=jLzG!n z*^3r@>Yn%N$Jq*Kwz&_O6g?~zE9HkmWkCGkLNg#AChMu$JNbKHYaU4>xo4^n@P$L%jllHFi)mdlDK! z^seLa!5W=m=0xe#yAU53?;qli64nZ9Bj>zSuiv(mePm%c%k;76nHw|j&N`-&ynag| zUv;UHdafHfh$5fHo{e7K_~eB=LYCxwJ0h_RGy}(be>U>lC(kZ4*w($Q%UE4CvwBsa zpKy@yoFc;3!#36cCeSuCGv5mjwn}E-=AeT=8rP^*Z+2$;h;*pO7;<9WS{-+Jl-c>k zh=bbXMJry3QKj&VnFyR!q{T(Fp%E2B6T1LbF(EFT|B}!dspiRi2fKXWrF(r4NQ?#x z7Pjg#-^5aY296)A2SVl2z9|sx+t_SH{#I9=Dxld+u?8MFp>+#MhRPY6v(FfH;$t=MSTAkWu^P!X4t0tDLH1h&4lUoTy(5o6zVfTN^A|#cO(^fbT?Ul>-OK42V)obimH!3y< z75go6fx+XGtJX`S6tKemAG)(358yngC9kK$9`sSe*~Y`1Wge#;#LBc$ATR>KmY9zP zn~eO~Gy@?j9vhaBM)JDhGh+!3HfJQvnLQ4pxE#Wha2UkmP33k}Z&FxcWynlw=>b37 za#`(m-vl7ppPV_i+-!;g+N75D)zo7FZ3s!iv}xezv8e)7;5xe_0?~j^hBkOQk;rLM zz~JzdpGF{0g~5=&HB_i1qe(cvwMD$poC3m@6krQ9#TU{DBP6%BkjvUR5Q1|{6W@Vx z@Ry>Myr%zf2J+4UKAbnfm!F{kPql|5jn`I_LHN#zJ8W0NgRKCluvDXhi;M;)T32H} za1{p(ZO#{FCZ`LS=3B1$r{vh>*uc2uIQ8(b70eMn&GFvLDx-y~p*G1h`Rcu#=OE!G zHE4{*lq6ET+~-l~n4e<~dBC+; z*>&UT8@D7O>BaLwFH`(7mmr}nr^gL$C}P$q@t9|%Wi3n9F4<`(X5t~wnL0X?u4x9X zgD;+2LK}Q0O}(G)OP%?DeQ#kor~CR<)3-wcri?Y0(W}$%>#Zx3Y!~APwRo*G)a3XZ zH&daAm1f;gi*5hqRgL{8&7>c z<8wDY73p}iMG_Br-6`S1HFq?5o zyn#z@wNY_lJP4WGdj4KB04GL5*ie_V7Tted4m!0I_F-k-`OJQfYo!Uy=5IMge^s#^ zNG9s7!-n&sFQ?=9ZAYZl_T_u#lci(6{NekuD$N}ZS1S0c8R3@$b80WZNnMa371R5t z)thK5iQ*?ZNzAhQT^6Mw41=HGA%lrI*VDQCrQYu&V=j-?h^tJh7xzoptIe4?_%P>$ zmk-)5q*uD9_6=I^6*39`@Yd+NQNMh8-8Yv%XWjbfTS>a*&h886GT-UewzI&stQHH3Gz3r&w*~&wu;G?m*wdIVUoKJ7Bx}_Gi`2#S{j?&NH>TVD~zA*?ipB6IP zk1AmnW>3pJ(hRs#%80#UcSsq2y>Q6hm-euf!dS#h-=zIMgW-Aa7Ps@TkL}ryr`mFJ zzAQaxm^vP0a8)1Oo8CAVNjB6#ijx>>>NPXwwAvR9zH_p{F;cSXbCg~$DHK$G13Nc< zXl#xdpA>v~GcMGZKl#CUjAruq^+3nO+P6gGo~@J{*AE`LWQ|6to{Bgnn$zP6Zv_Yh zj!>(P1hmuaDfR^dB+lJ)dC4yvW$Z7fE`lP^SfT0RepOOF{*{coLm)gqwpc{_@l^3- zWB%>?GwcFr6Jz@lcoIyYY;qg+m`zUg<#rfrd+f9ANeKZ%|2Yq;Md>~y-sh;gD^b2Y zhf`7}=-KiFm{Y52^kK$>BwcZf7u zr*(%>o9eBwVcqV{@T+BsZUwzjlFxhz6X;i z7a`c*j!xjVaDgQRE0M-vPhY6TS-W1Taq)_v1Z1(J*{Bg^=JF(CxH^&4>XZ^SReh=N znV-Dn*O#_(K8Yl2knV&qo)YkfR%JQB_Z=p37 zy@=B)g!z#EB0p83I_b<(^6O^4J{wm%H-N z^e-1I1W7fublZ6*;iiWt#baNh)kMZMRJC^659=D=Y#`@cRp@0iI2xD6yt;Hksn~?{ z8!+W?%)BA9?ksjK#0n2D#!FrFt_tgaww7}mFIdLctk*D6N54z9q5pPHe&gw2Y}mo4 zB4^(;#Vb|ABrHp)V;i?u6 z4V}wu%Km^8_w_B1J=H@{+Pz5)jkPkgnb-qzL^v>GFH{GO>t4}cH4HY!Wj)8Bqz_~9 zEHP4jKbR;h`6!QT0FvyG&hx0sS<$kig~xyHLC$rB>^0exLS$(E4dsN`Zgl?s=IYfW z(5wzkj$4V5PKLJXbUK%&z{eeU4#$p~+SJ4^0bosP(sCq+jFqIr{n?T^%k_yXW;_?r zQjx}@#Klz5(DjnFme<(njYF__p!np)OMABRLG18IHs6B!pkd}8$N1j_*2Z#OOLkqb^G8a33SNFFFHuPeW4p| zkNDf4@2bj}65)0kBO~sjvosa-r4iH?9&ikM5xwUqne|qP9WBtUzZA+p$XhiNCUAPqL)dcc3wG`-oj|c#g z^LCpqn$Bs_5Jee2BaZm@Cib0s=}(20 z(}5(K@l{@{wJRHMzO$3iFfT6WLy5k|(UH3C%xheYf|EQ+J+&(VfsQ9+t}Zc5;d0_M z!>568!H6uEubjU{FPY3=6wk4yuD3ekkeMRfTN^Y^z;@MrFk>p_vH>9$-4xKPGp$86 zya#da->aDEWpt*rGx~DyBWi!%z*WmYcijV6|R9KIfVVX9a@_@qUaf+z){oPaKk z(~x+vJwHd_M~Z>bHLI*xMNz{mUBmQR58X;~98tl7ANNOYuJiE8%$bk2wA{O@W(+4k zMbHZr=WxPwI8$X)&KP-6iZLPj_z@8hek?G@A!0sCuK*dnY-a6LOY{(<^j~XiLp><0 zoz4>s@LkOb7(W}E??az2DD!9b7UOchD%xzucGO>s{Q~O@d5Seip6uMj;rh8vMD#&?LYs8|zKW9(?7`L|Ot zI;_KKVy9#Fn4A|2*W z3~=i=r6*4!AU?dg!PTj2Ff0#Vnin2PoHS6X=}$$NmvifmCrzS(b;^RVZgs9fuvV6$ zYu>l3kysQ(KgD6c8aa9B?$0p|LNYxYC?eO`tme?f3-iD7ZM|1C=+pg3g12?;N~2k8 zKgBF`gG4CMdMFNDoMpgE@yrg1;Ct`=Q$YvPYLi%gxth#6DvK+%n}aeFc}A^vDGGwa z*=zvwDFoxANg)qQw`Ze+L}U3e2scIOIumIQ;aSB^E2FRD{2>e{Qb!zYAnFV`ew7SO zBF!F_%3|4L3QCTo1y|jI98SINJZ+Am4Oi=_J?NlX0m6^XFvI2!;!8663uE!e2HA#_ zYfHg7P_vmKzYV4vqNp2qt7{Tx4BWUJw43SVMfBoyuCWq7Dh(El3<8|Y4=A)pz{(*q z=s?kN&`R;LD)Q;XuRdjJaXAz?Wldk8xW=-$3qS6lC7WjA(0O5$UX>b>y+{>@Lp+p>8_Dw+PV5T zA36$;ZQ12H!RnjRzwVI}Wx^XmNr36R%&jFT5O>yA+b9O$7orkVBEN?TU{>^Se4ee6 z4M)eXJ5gz+fbuPH(n#huKzS3%9PDRBm6#R}mPDw@K zd@Ala(a$}9U<@CANX=NGZ}k=|2aWO%*|)xI(c5OGeQ43D)%vJ1AjA*jrJy~UxNE~4 zOJjUiQt(ab{C&t1Z2;YBgcatC3;p19AD2eetwo!d2_Mv2II?0FobuVN!}Is4wBtB; zxe=Z${)Fyy|MD4b=Q4KUJ*)FvXN_lJ(Xa0pf6CGp8H=JzGD?W#pD<0lkD+v6p)o_k zjIOprX8sz3T6>;%uq#kWDW{ZUKHGT>aVJ#TUierf3`Hk5T%biyw&D5qP3wNgp1LuN z^;Tb3L_E9ai`2|$V`_MDV-ljJg)tICW7Z9#NEH8q^Nt1c+I#JI(a*5T&%eQy)NJDK zj~%dl6T14j8bla;N zQA;X5TpUS$ho^7`=D~5<*2*|IVE;3s^0*kLmFL6B16I8GLR9DNs z7Rt~h5xgnHE?Lso(R6M6HcE)Z&T-|dJktg?b0UpNd?{}XVe^g8d69I3V!gFblf*+p z`I#~oMQ1DT#SMf@cSzoOA>F8LBpAP?=`etlJeQ(Dd*kwkE+GQc{)BY>@H>Cy%~U$x zK`)seyCOA+GUtQ(w{w4O6Mxvp*Q=K)LG|KYC{t~WppJ!Fhl_{#lL_Th&mu7U4V}Va zM)n@Z7hSl|X0f8^h?%xiz3b2jCPjm~ssv>P!GA{fl~rKI!8kS7MhxDVB8|@w?;t5Q z=|n}-(NBn3bh<)Qfv3)E_%?-TShM}=@@mI>C9B*~+;;C-8<<{)_fV&~qSQyWyRO$< zRL1e?aeLiyj|rT=n#u*;E@VNb!oRIfA3;7UWEDB~vq*m$viOsH8510^sKAWbr6`~2 z7>iN3TK4^|7r{@ED+Z^y*57iwU3P<6$RC%JK&*M*mlJpJIg?Hs-fTB4F47=-uwl`R z4={q9TL?|jx}ftN-9NOpdEZaBiyusGqnEH7bRr3UTt{c|5IuTt-P9eSkSh29_dS@f zWa_E|wKQExJtmrKItb1NeI%siRK+x|Tn<^3=W3V{RM8g~Nu2a;Mdl;Y$G0t8)U%*) zw2AlU9Ovc^eKYXgBv_9kcBwG%H zy@5Gn>f(C3tjE`4#z3^7rI3~&{k={Ek-n*A8S(&XN0u4R-=z3;?iFoHdu#^?PcE(0 zSpfeS^ID*>F5FwL08gcy9~LPh(t6i-oe| zNgK#PalCEVZ!~l*bOnYt1K{1#ZF%$L_?-Owt@`!|U zxvnRD;c`0&GKYndfE_+ceWl$QNJWt-(>)4D?iSJ`Lt)J12*-Iar5y+PiKO_a^ObxS z2zH{h%Qs^2mOJ<0Se z28r&!%EhZTkrm{8x|=u}(AT*>(qnuP&usi&;20}1g4~cNm7V${37T1TuOXYUr&3e{ z@3-kP2=7ENACCW&$(R%cs6Aen%_D49Pq@`$z%3G|7~Bf!sx%oVM{bO?KOrLseXeX+ z`8aHs>S3FyEOsr5{3GNg!^+NCHfRN^&QfWZ+}@#IS%U`K$9g6~%O~?VWOgCv;kYH| zj~vWx*gWhj%wu>s1k3Vub7F$=8(Wa0x#=T%EU!qP89p8nd9|<`3i{}+C59sKZHw3r zW_ZOEk$8>gX!)=$LT95%W!KTRi(JRthEFHLortEr=_s9-7Rvd$n>{MMff%t^d^DPz zj(kI`akF_tG~W3Q!iCAJ4vY%J(~pYfG^~>D&x&q}48!w(6qHGSW6d!M9#6fD zeE@Rie=M(}0ydBqzc5!Cb0)f`*MZuF`KDQiW;{<9>aZiT{~UgV$a78-o{AfeAxMgyLrjS>d>6&4U5}1^;Gu!7dg;cgp->~T}^XF_NZZzl7%gJ zXq4*(s0rhxfoUo++Un~!;xwvc=GRQUE6nZ3t8AIx#f>bxrPwcXRfU&9l#fTvm3S|uY>|+CXK&u@z*pwR ze=J5<3dRr5z<%lDy^79V{nc>e>08z5Q<#pr+&~n=T~8cmDiU~|lHhS0vL&JDd7cve zsaN2s&d;@xG;b*uKc~es;(}mhKs@3W?`59|LeH1Gqv1plp_Av*bvi_ciu1Vqa?qme z`x}9~)Z`P<_!~ym%+EeFwrFEZsBVIIh<}_4lzb!@OyT215%3q`8iO$tjgB#-2?bI0 z6VYHZC7JLUVTA2QY`)BVzfaUki&zn-c1lIfODdADPbE`iV28MA-N#=X&AQdjpc>4_ z=4n$TJ2jwEK(6zIeRokefN|ztwAdE+cXHIN7IQ+iIVzh=|MI=XxA^d+g^XxF)17C_ zi0H10QLag6r-IK3FOcmHsxvUV`d+tDMo5SDFSTckq3TqLew4N83f+|>H+(%rwQw?5 z^COk?r5Y+-uu1c4^_OjW`3mM11?QUzc!HkFhsD$cUoltj@4h%Ki6e=<(U(KU|Cx1U32`E~XHa zM3~4tpv95l@!;0|A&gh|e7wZO(ND>?zQAyV0UtZ%HOIq{m!F1@#Q0u(S72lV4a|q5 zfm=Z>va*&$t>%=DkEvRkcy${rp+%6c&RWdZpBK=vUPcX7o19=-cl$rx2!h!?@AXA* z!b-0rF0px8vcdXhuZrI%j9!bUJSsW}!^{ySx+t~x-E_E;SxhRY4b<)u%pzRV zuyUN9H#AMP&`z5`B}kn8X_jHLpqwPMvt`$EMCN*ZdXjnA&I%`P4cqGZNzZbs08vs?K3NpUP?r^YnxI$*GPTf1N zXI-fNP|=G5Kb68K58_Lvy+I_cVggtmA(^mHF~4DhlMMVZhWxlJVV3)HL)vpsKAMr- zvt><7Uyrv!U>QuXW55NMbXfDaO_jH7wM|VP>8Wmq9wkOP)vcqj5H!(?=}3o7XDm>& zN1y^rpTwM~OZYP2B%Tlv%n@!fkVWlh!oW5{MEoScob-t6zPVSvL5m1^eZYBUD}cVE zVW~eHQ;6gf=pWsOfk#V){u6@c4%EcLFzsDepjg6BvLw=kj6t$BtOqNyc9}b4 zx?*nCnW08X#yC8Rif}cdjv}OY$wx%R;W)|8i3fpDjp%hs;#+D&}NC=7L-C zsc%aC2pft{iNzD1`D6j)L})Ia|c4_CJ&n$vQIR%c3wSIlWeztHf$%g{h9vHK%lPl-uq6_kkR zA_N@2r=!#83F6{vXi*0n>e)kqNX_AtWQ9>Hvppw|K|>SPG8W-7 zXUw438aS5c0d|pwJm%SZ+=wyZK8dJDW7O_T{!enVL~sP-oVmxkr(y-3)5;*HrZ)*& z%T`t};$cdgh98^{%qi-VW28`lc9>nY=Z}BT0Ql{-`3f9^HLD$!;eiY~N4BxxFc>j866TT^UNB?vZ zTpbb`8m(L&Li$w6X*WwD0PZu94!V)~upYx~S&%}E%asHMYdfrg102=DNNV*EpEjIu z+6|>=oznhMTEWZclpLN^h>EjPIdBrNHC4bc@1xwYrg}&hRZdCMw3zwx7@Y4wkCeRK zPBfUX7ioqL^th(w6fZ#T5bMoZLjT}8uy5JLOl3zj<}T(M4<2WPtALNw^lBVAIes?SfpHiq& z>hygqMZ%+tm<3_sLCi&dj@*Kdst3?hSlaS1jY+m@B7=QslYU>!O7bcOZh*4$3k(r9ECnLZ{$qJ_#^k-mQQ-$fEWMz9PM!%< zf&8UB%4END*-|g@$0APYxM1FAxZ|cGuf^>{Jj^r#os?q{2RGjszV0%h#gvXUB?`cD zDV5IIMF;PUrE0D7lvs6rQa_Ra-T+P8j+h#p{-CGFBkJRdgLt1U1acsYd=fqnF~>F1 z-JK+QYt0K!pylL^(JqYrwn&p#B5du03E#^z^lJf;dcvZMH0+Y8h0i(LL6mJ`_v=7K z3OPMdzG=$s5;P=ejeS_J>ptI~!;o*^5fl!qDpc5oL>RS!-r~MY-~CR$0lxR(ls{`m zAWF~V%6rWhZcR*jFW`HYWse}49R_Yx&eXch)niPV9IA9;>49k^G}rT)MmZ7Qy7vY1 zMDip?4bNZXMp(CmV0=D2Q(UUx5<5|v^Dk*1_j*}1`9U@)(`Jv9Cfn`f8-kMa;{bC7 zmi8{v*!1GwQR=xt$ys@Ahf#C1%QUb% zJ-=FQJrAzZ$Ay+Hkrtn$(aj62G6L`0maH8Q>2hs22A9kp_XhJh(vFYOLBMM3>@ijJAb0o*^N5hiQ{{)(Td74*yqP^H$^tv5Fuhit zzStN{80`Mx+}c=oaYy8%=g~8DaarP|`-WY8-t(aHkqGixIHAgd@mQPnLy0wu?H{zv z%5}sH4u*WE_kD2I8)i(jMniBCCEv5e zJdIFno_4U8CTE|%nxn}&98uy1IYiUq8jXH#yc(f1m!^7OLng>>fcY|krONV!nRg{3 zyACRtvkUJ~K1M+BB0m>N89q4*X&}ZO%O2UfJxxm#QiEr9-_PqNa@6Je5)nV>wFDl; z6Y{6#_bpAI@O^umrcbfR%C-xqv(UxCa|;|hz^9D9&@2GrG2zQqWQASE;!BBin^x`c zcZgM4cAjS$;Y}E$w=JR%j)pG^^geS6xWQwI@|OQ>+jN z;)^5BTHK3q4CbRqsnW12HyIb0&}T~ajtq1M_!_0=pKu}qH33$HDs>Ly&V7QCNDWB9 zSn}mjc=ne^95j;94cX#K6?VBmjM2_6)A0yNJb+!}k@yK&y}Y45JfRg_gRv=xKtnx$ z3$H@Iu+$dTEH$CDJElbh9spDiVF>(0AaWi7K%uGrbVf%h&Jsqd$+gxt8`7f!JQQ4c zZ&M1v>|t9~FdqTdDJ!;u2r#Ho4EvTygB)rZFJ}xIQ%xdx9eZiGhI)y!vPz5hwyQ{Ax=$5rv%gFz}Ku1Sf|Y z4lDz3YGQRvvqb@gZ^L8z-g;92^p*p9D|oROn6pjOrAk{0d7#bGJ*$J+XF!`7*7kEO z*aKihs}+mMGHs5x=BPs6lt$sKsL$s#svYqeMb)sL_C8uTRVt^2 zV063Dh7^0mx43EZ*~|5spAMS$B-8;qkL@ziTn^Wll zrZiTiZPxDjz~VP>*)2=Zr%~df)P1>Zj&<lx#%eK?_oTXtS^ z8rCkr@S96(K%(>z0<7G3|KkRB5b{s`ERERMySD5rlPt9QUF*~XeU@{0Mr46g9({{e6kmnrA?!zb~;rN#ft{cpvuNsS?y~0NlULjgUo? z)Mpam>;9c{34_ZRAkXg4>Yq>rlrD77PGH?$<*)TS;_FMP=kWm6Zt0iUdc$XdnldiL z=+0#ro#_B^=@MkmD0tCw9%j_Q-;r#;7{>9_HXwKtbY@ zd(yMT(&5CO5od6y6Lm*G%>sX}G{Hswtd>um}rEC zbr|M)P|A|OAADV|uNIfQ8_&<{W%V;kR|DxbCLk&3SCP6lqQkz6Smq{LG$I%7uaoX4 zbPDGK`vqRcl73*FLV!CQrI%d9DCSE@69NAm+C*LYb3~D#+X=lXRc%SeHDVZEYwU5g z2m1U{{quTf!$3`dSX^x-u!+!Q0=8ROpMz0(N;rlz4 zz>nlQ_EcP+vsg}10Dsw;hY8IC?sjZ42H8&;7@Aby40$H6O#prV@3^Op{7*&`DVdNo z`%(AHJxf*^1X~EVV~ea2K2ufo%9hF5p&u#*41MSM+v{=}-(}hi37qO)YVdf**R&Z+ zWI@>nsZgwwjnAfCoA@ zxQ_1o*;%{7k>YbIFAab`cdBo%pN#EKxoY`-xs#~T@k@<-9Hj*DDWBi7LyZsW{yk}2 zA$&^jXinC7tSvixTP?;j23*$j)AOFDztL!M7}k2ee8mb(<>C`Pfj#1H^j-ty-|BF?3rwcT|hXng10emFk@nosw zA}3*p@01JMlf-_2yc>_XRsnjcG>a~0D{FRdEKIoe4>17@pNHQQ4x zH`l&KXOPC^>;nXwn6JJV3Z3>*eO)Rd`C5^|ED)$1MJ}=Fp9cd5vy_O~(cD|)Lz~a#>^?bA9*>2i}n|mk( zM$>z`10JxH%#qI(#sR?RjYPn8liRwG)HnYf9_=UJM1?Hot~*>@Q|y;RDz5UKpe#!$ z52aE7&+?>Bo)!i=(p_a7fut$?SUr>F$l4Om%*9 zUel6%S$*#v?q#Vz#Jbw1(r>`pWv}zb;8U1-^BWL(p?-eTQ$9z~)H9XyQ&QXUYat=e zQ+eVWD68$9d&IeGL8!IJ2@!pG?NB7@*iPR>kHA6dr_rwl1=a>1$XVs%vlG%O@5g2c zOdxG#FdPyK>KhRY=}~~6){1_s37UOU)m(D3l$#I@+a*lm98y3jcCBeUS{b+8%I&G8 zZ0=~Ex0ZdIVTg()%fVB1(*D=Sjz=FW%o$eV@i$i@jcCW_bT7GwpM(0H-PwqzYGd8D zrvz88EB)5QnKW90W?d&$xQ}DM6=|B)x*rR)B5zDC4Eu1~f#TEp?hnEQQ%X2L63^VP zT&XPiFuC6+`;m#}%i|Ln<_?r_VzsC95e@EktV2ik?~JNE=S5d7***#N@e~?K7nOpTt^i${Q=DP z`8l=dZUXLQHm7&Z2k%SSv+Lr=F81S5uy_j$_5$CjDPy)7F!>rpSR3S^4L5Dr_$ZGH z&3Y1sx@kaj>APWW-}!l|{4451I_DN;%PPN)cnsk7emhN=_4U;DJ_=)Z*&id;tMxB_ z&Z5a{hwu)&mT$ARmrpyRFX?QjAp5&b+BC{D&FjhI#2FNtFSf{r1PBYOrHw0-JKYQ{ z*YAvV6so7=`wTjKA2m*3ei{bcGM<=hF_D$Cvh}5PWpJh}m7$SWlZEbx@&_$XtA2rp z93^B@-N)Te;d75%Y&yO1;F%)o@(_#XtVj0Y=2u}0dB4jZQ@dKrwT2?q8od zavvXkEAo$iQSw|IzWdX@gn+_=)LEXvJNl8zFJiqy5(0;Q)in2T)UHoS6$ADTcD2`J0gOoq1b9(rVOhB4m`T9{Dg%~y8PQk5rS%h4luwT6- zWs%^}E2JOST^(Q&@!Uz37Z&9>+L&dZ^K0dm^<6T}RIn75&HDg;H@b6xI>Z;}Bs0IJ z1TR7#a-6?_tDV!0K(wG*vF|tN8NJes+4`K)U!Q(bn>F8SA5?rHodMbH+v%OB-sxFQ z8C}_c^M)T%zHn%{tZ{IB1)hHby!rT)1tC8nyWpg;MxcRzQuR@WQ1gZvu2ef(Djt2&;MGoRL(QNC$Ilz!E@!5L{!~SxW-#O zmYhNLrOvjz2)9*}42hW6JJInY)9wytAlcFePM(sof~3!sMGuA~*76?rS>r>#kzU21 zp2^SMXy; zeZ^?-dkJ;51>MhMM0vhDp4oJHCW%<{7aG?#xNVPt0E>%-<-}UD+D`AXSJUz>=D%ll z;~XL4+}zAr?ucJiSVFS@^ce`7p_lM30ow+aQ_+UX@=>5o!(rFt)?u!w!%*BySp~>T zniFP;{pkd7&_=bdSwxeF^9!;sd$A_TbhDQejG)%*V?uBH*_bwV#&+`@irp6`wTE4s zy#;3n1r?+4z&p*$@2Rf-IxT24_%uEn7a=PlJyk1*>pa}A)JV1Ew;sijh^W*!d@a44 ziH4s>qM_L2^VKJrY}Fhv+%tw2mpxy5>Buq;F2%#~uQ~`-@IM$<<>*Dyv|=&b{nT-y zI4bAalk8`dx(7MdHXSdWmf-O7`H+y9EAhgTh5yt2lt+EvCw&xyo_fnHJ&P^6H*nEQ z**A$MRT7Tz^sGWv5E|88u^*X|ClG$3Zi+@_RtCmofj!yZ zjf_Ia?LN{~g*SWe|J81!-cTBUTKBab8XZxin@t{lNb^f_I&&jm=H&;+T^o)M)P8I| z_SA7&EtML7VaGlD0cyCvG}M<8lC|q0f$tKS@|9_H_0boNuoQLjqhnsZn@JD7>jQ-9 zSj(_=rA5GueH6W7zWFg4>od-^SH&)~tE>DTK`_5|^uE!&k zieJfiQj2{kL-ys5VX5OSu?K2LzM2${kLB5nnvVw2@e$3?tJ^Wy=wZ{!H+m4i-N$I< z|4{rSz}~)d=SRFiigAE(E9Z_OEt}Ndpy<%IT z!AI}v%^!Ha-|CTsvz}Kw`bg|ws~AF}vC;5WqtfePy1=HpxAPPd_ipE&M&;>vuY8$d zt+unrsgl|!Tym10CdAgBuSX3%oLFgpbpRU~rRD9@iU{*S4AOp|FZm-k>++V@n5`&X1pT_xwePJ< zm!_U)0_8eS^HTOk&Um`@jHTKpvT9tHpe%Q_4idjb?(9#>{(vZ2)Dq&QKVG}BS( zwQi?ZzKi+rMpl9Iqm7_Ml{_D8LH#S(h7t9yE1P}2N;g9RdD1g27{rmLRxdxFLPcoLRb+zPwvMB4I?1QUlC!>W zV1^>xTn$M?ttp7**wC*HUa^!lA64ixzTE%uN^)u^h^=bnXqd8i57E*7Ox;nh&PJB1 ze>#f3O%?KC`F6viNeyE8dLA(i`!MF|k3yH=~ zxT=|hLk&U7QqrB*v>as2WcGQX+|J^1m^5AlEb^KBJ6g%+$T}qrU_CzRQdZhy@{s=J zJ@|a*E0PUQa-0&~;RcCsGD3!VbJrji?H>*-uHxj>d_{9q9r7yL$rgeVTQKIjHKxrX zV^um*G`Wc%`*#3jtNgX;Fh&!D$B5{es|*q*x%QfC8=?~pL5q19ZXy z4pdMkBnSf-Eguo!kgr;@mUUgJ74!(uFIQ7+vFE7zD{gELV zCD}JxDwCrfA=YUw2(hl<3=R7#d@}onz;*IUTssP%=@m^J6)2V6OE+wRme294vAo<+65J|U(<#GqPP=H>_Q*x#>p?yW+szqzeMPLF;c%Z%X z#Xv^JOtr=R2)#O&IIF)Om-#sh!8k-JP-40=PS`U#%)#%OHrfQ?y$`xlcqrEdN9{yr zq8%h;I1ZNsMf4F9BEN8ApUkwWtaV3J|K~Ld-Q>^2JgvHG1{D_t9CoO5pgm?}sgir8 zhPAR2;3@jK!5i%#?;i{0nS>=5da0mPClfP0l%!QwNX8kW`B=E~ip$07=A^fNTf$4G zI(o!OSFu{rXNg?e@nz_WM!ph(#!zl7R-u!}lp%BuQab+Fi5f4oXs;QsWVql* z>|C}>+MX0g7Om8G!`GEZ<7Z%Hu=$F}AT)q~6X{F2bK`XLrR%tyx3eTtbrKmnwOc@E zZ|wf)B0jhC23aT1R>NXDzLU@F1<%U-#Frq_n1#=f;_=5S>PJEs3L+(5a+L0pL=!VR z{uC~<#Ac12H??_67tfM}T;;S4OFU?0yuYa7k~J63GKCYDU8hd0_{6r7NU(ugvyO{U zd-&cysjoz0XYT;3F1RSZ(YSbcsfVUR8&J*|y(A>>Ihf*rXjc^WW||Lzig$ILS2mVc zCp+r99Q3w1nT?)!j*=C08z2Dtsv)r}aNNF@5$bc?7A#4`L%&cLH1@rCv|)&N4mggG zo@2V+Kzo5zAAUyV!BIh6>3HYb<{_91s3QfN*c41!UYgB0Iu?+|#Go4|+z=*e%7+~9Vib&;Re?iy- z>y+z{BMkezyWRH3J}zH;VjpQwT5O2BRb=LlQml+so~HF<#uF5V6^2vN9hak(Z2SOO z%%ORSm4#gwrMe?3#qdZdlP*~!l)+>X^|6o3kSLRj3Os&GI6iGQ*7VrTnKLXjDrMdl z*EoV1bOh_8JQ%z7Jpr4hW;HuyJY@kWCwl%ym)22ewv3?M$6rZXMOHYM!i)sv^RliwhvK2kOqCf)^by0tGr!kgU2DwWNWILgY(;m?fpvB*7glYr{Ls|#73+dh{jqW zuIGEF4uN_X!YiBu6GPiW-hjs;Y@m*8)A6D0FP;j$Pv(@jU$pQP#YdTa4#u~hk9pb_ zik-YcZZDBs{Ag0s|N8AKgB#~neClpNIJ~F#3|o0XL&FZS*!d;rkLID=^8uUSaiRrSvAwPNDFhae7o|4A zHqD=zZj_)TAtqt!9cP>+bu{;`b6jtV3>spnCC`&ys0VA~qFKwkXVoQ&LbZok8j;1i zgaotaEu4SI&5=c6i9Zcd3};#p3Po5#-5+mN)e$MMLS)eicaqaKW)7=#njpYkeTJ!} zOE~o1)g_|%W_i?`nOA=@KA4YWym_xN8H0xc`58`$ywk!IyrJdIbq6z2!4L|vy&>UVDSVHx@#*zfyqr?hiFxl}aRJgE zNSDFS72D9Lc?}$|1e?fuZAGLu7BR$;OVEu<uygERjZndc59Rv#fusFVv@N zf9;0+h-|tmip9URY+_)4|5e<6Us!7}xE?qPb+$BJ8hu{$03GVLh5`u-`?(Xgh1PB&>xVr@p?ry;?xSjnY-#0Zg=X+=B z)~z~q=H9pXgQ{I@x_kB7-D^G1)2m4|rkdu9GTPBo zS@8>;C{jNQARdZq%#`)HV^(i%d;2jSq3+?Z?@IOh$Wdfj?Tf-|rV|^D`wSKY&GCE%fp9s_k)-qOw)~R!ae^QYFU9{VUF+eC&9%TBIYB@krLnYl2Y_ zXF)MOJRsd`5ekxFy}j~QjWR-OhAV8Wn!k7tO<2#jQl53NbmMkET2kq+-xbiMSd?qEfGDqRRhdqKMU1zXaU|;?}YL1Dy|8l z!9@rO_unp>Ri^!~9FX8HqGXOh+8YrIA9;rjJd>eY(brDh^Po5{#nV^T-{^ij27T9U zk^0_}pTy(te@AGqk4buXJ5K5Kfijag>%LzXYYybP%+*jUEOlY=K&U>{xASz}E#9_One#+=LQ>kHg4gI4^KAAWio3@3XWUeZg- z!GZX$N<`W3koY@k{U@dmJ)m;(eHEwSjRH~?g@2K)6+bez1eaJ}J9T#74X^P}nK7+F zl?Wt+vpU;bqLpZh^-|O-MOc_#G%^%(ArTz6PH7M|x24ZJXy4vx&w&n1`m^XVcoITf z-MCyAg)KjnKW@rbkJ#;i+S1~@>K^sh46+H~8} zk6@7+k$Io|WGT{a$kcA{jHVG~TrN*&{UH@&1Q!!16!ck~+uZ`Ljl9|I_G`*ykeoaf z5Q^S{L(!KB#@jOxP#fZkOS=`D&jn)EdRQ8$deHP0nR$SK{6mas_Z8EGD z%0@BQ^w5B8*DL!2`n5N!v++I~;LcTXXhFYke*(cM>Fj9l+ho4E{349fR2ARjxZW24 zIk6}ixF~g0vmf6>b+Nah6xVc6Ym= zT@sbsmP`$=44lX!!mn{vw>kM`8X1eQY}5mJ>Qq;Q;vo?*=O)b72Hz1rYG$-dya%W! zB$>&$PKrtaA-Otrq6}~sVsT30t2(g&9o27C)-&Pf7zmBA#8ba$c?}LET^9-nz z{BrLXq0QC{XQp50ouM1g5|GHK#N0Ci!mK43E&Fn0@hC2g!drTaM0ZczdF1q~g@u^W zrt?ZJ0-Gjw-99I+vtk4vNLYd0!6}pjMsuI^bXX4Hd1i+1<%aLEEmu1W z$`&pq`!O^MSCPGAHYL{bm^F}l)Q0;^`s9Nd7Sh>;cjW2X!Zv*87~ANBR>aFnE&U}^ z!Q|7l>u=TM-Pd4FqymQbw=pNLYzWDk^#mF~bm7fz+F9O50=!e_&|xopxkjn*qkB($ z*c?jXJpd3JU*b&jAc5bsgZa|+JELc5O8;yzo&U$m&+9ebu4~yPRD(7%WW(*~P=LlM z3;1t2FMl|=*q!M{*#AeIk5C6*9YA{ZB5PxRfP}cU!Xy!M4g7C;FGWyq8ER~sLqIZg zhLErKJe1_C{}Dt{340Su-5@}kNiqhI9!-7eisbF~gmC%K3?l`w%)bn4*Z`$~0~Q*1}$KT~q-wp3;v@+1IVnrT^*aJ(5fBlY{n7NBK; zcm5?Uizy7!4oGfz2yQ=f3bC<13qpMRO>STPr48Lra_F8eBX=k~4)Q28l2H{@HlUKz zLFxn%AYz%^kOV3@@orbcLQr4tyLY@tt)>rx`wA}3^Rs^LkXgloaGMPBlH|Dx(&cab z^rwcCN&w^eH4Io&flTtmp7=kh5EAft|4*&q^mo_)KV9@|o`0=O zlQv|9#MFa<4CJ|F6hV2<)T9++wSY*-7<%tC`@*v&wyo$GYSV77!bIK!&E6jn7+lUq zgUhM4$(atPvk;+F8C!*`S6$ydG;CR;I(Axr!HjLgZZ}aM1Y}XuBw(T&EbsWYB?_#Y zW3TqxW#(T#)m!E(Bq$g(V$tJ2n_*M+WJUqMBSiVH-bn(~c6xlI89RwxvXUTS)pSh# za?Q6a3-2BQF~)xuOk%@Ne5=b0Z<;o-5NOEiYI)0}~^%1U*-f7Z_wg$1;v-+Z8`wgt$G)A>Gvd>%St9;iChdBbE zsmo(QdddhKqe< zWXqL0E`M%{ZX9PJ4K0#~N%*36jlXKcm&~Fa5&p@GqM6NMhl#DzT4dABpSDGe)X9;~ zlf21SJ!ho=`k&0xc%xM(=UOQLq@FIw@rK^CX?irV+3h128sRc{Y z%7MtvXif?vzr>G)W3PhB7dc*csDs05-{VkwFH<_9%OkZXuTjmV?!Jp`F0K)8wRhyb zb-ynhFEAtQ_c1&A=2pfB2&vvFblcodmATpex_kR8|MYBU9W`IJU$`Gnt9)+aEtpiD z{oY^%y$$g6`Q0^9TL6=l*antI?9MdR@Xa+?A+(c3M`(PY@S*oK5QaB330`frIk=jp zk9+F^=AnO#5r@#%jwUde3gvJ(=y@XcG81_(f{DrbB^|BoLbT6@7j=e9Acv6q`@;o< zKp?6gAf}rh#B34&)>eLPmTNlTDmD3tQpgLk!{42+aG10Z;kUmX(&{(JwOuXb_ZgN> z(9Pwco5sDU_*6(JDY?54~JwKd?@@Kwg*?o&@<*76@2gK8KL zYyXc7R*R?PDQgayUF9I-W$5tB?=cph)L^wuLN99K?UjZ~6Q_^OJZas^gw*#kMd8+T z4tj0$O0Y1Z0T4z=X;pcb=&Iy5(5F2`>$=z!{nMtSE&f=Kl|#tGrYBW4`F1hMe&S^Q zWD0Q}TNUb8gQ+vw2n*MS6*b0N2JxJB@(pjh*X`;3+3W4_?^=N4FW9BW@|l!P979O= zp$%Pk32pHfFf9yK0&ZeQ4f|A02KaB75`WdbeS%X{D!jICf(0$0_YM~Wyn3n-u$Hq) zI-fTi7ZE5|RI~EFS!;xF)?~SUx_!7djk*6lh)|?{=ip}Ts$ey93&HJv@U@xeWBgG1 zc~vI=UEQMF-Nl({in(&Ny?4=*J#pI2lyC#B50P85p2+Uh1t~iV0lRs+C-)9DO{`$~ zqQipIdn|6QCx+cFc=4`$mEBKZKxx;fF$)c;Uphdc>yq+%EIBr9<9A$$J9=1x*? z_qV3kg~!(wM%{W6yXXv3Oe!BriJ0uzbQjg+o!gqEOm0S0Ej!FlQdJVb(rh)|&eiq< za8G|HK*1p*&PXy;28atN7q+xETgFCzh~el_IJWB0ix^~3yu7Q9JLJ6@7;U?!F_Xr@ zh8s$tR*=b1zmgecMD6dvG5Gp(vEHo^EE!6s*JGdzH(($n@=^rk4g9{pb&iK(mFyG} z$R!lsK+G1|c=bFTDa0ho={Wb7snHEgUct(2%i_wNw^(&GO`>dvrZh?yVL+Sh96k6G zrgE<^s@qsCZxXp}7pWHyl`ULJmRyd?kjHr^gB-7YYFV}=VrT*|q#YJ0e{1vre_q+lGU^^Y6@ z375Szgw1kU<59C!|4<#7a8c_0vKBY3dC_6PrrrBEnSmXSM?4)znPHTV*FRXa9`-le z1r`1wv`Ty0dQ+s>>ZbVE%8Rp39q*7m)>Qyk3?V*pIhQ#k4qjtQi?F0#d$B2;zZ4VXd?tHA$!^q6uyLx-1a6`RVi<2 z-&?*2pfslCMB|*%{1h$#QYOaeC5GTFG1#FTlV*7!VRCKiFA=HJbugjkeCtf3irz0L zSRyCP)^!re-g4YDW=%1b*jgHBwq{M)lAe zEc9`Im0NX27*phEZ4VNBY}V~<5meds7EsQzK)hN2lkz;CCI3#&ur)5j+8-{42@Tn) zdAwlPFwLlGL(@kSVkNVsrx?0$T>?!f4B@yV?X7=!R;o3L%fY`3t&z_5?MuDyx&66$ z3nLTCoe)ebm%cQ1oA&AZ!^Se&K<|HO0gAG^-3(Pw^zD@#(3 zOQQI~+ZLLb+Hd402FDCMp0Ahkb})xG7aM%GYU%`%#Zi}ORF>s%86N-*#Uv!Qvjk~t z$Jn|UBE>@3BfvKnecC6l{M6C(eg+GkGy_afh=u<8`QxRIc?pNei>sq)U2OqX9R^*o z&LL?|Dxb2?JGLFg4b=yQ@P1WeaIpHd#w{Oi9n-@V5q6l!gP=s3z0Y>}td_qfYX_zS zDm6%S(hjZ-qLFfrQWuHv)+lIvLWCIBRjlILM0Y3Klmg@dn*#|qLlk5tAgdHfgk-J57~S2 zWp~Mpd;)~TLe;POGJepFx~SiPq;oFx&rddr-pl$JPiwX<$O*&o`eY~B$+Nn&FVAg)A#Is9ot14-v?+wKhFB51nF z{hx6r(u=LkpIb1Zr0WIFgXc-Lp86z6n~TBKdc=Q~Rc#p)gH zXHD6LN`D)j%^&9LK+OxQI7TKh$V3;?7Veg*HqFS{~SHGpH zQ;uO05}gdwKS7`?JcuF-t-&`Cj!sm7g!B_}v)Du2@39=)EIeSJaO%3X6%|OuD!nBD zE&T~lwDCDp7r@yO+aGbMd#knum+?P@Q@6N*JL-+bwc4D(%TE9u03C8$EoS zFNx>BuQf1hKbZD zIfG`sEU3v}kC=YB`BC~x=uOL*Y=?!W$y$>V$QTy7NddNQM4fb45>BkSOkJYGluIJI zR2qaw-J91dpW;0lC5NtWSxU@kpk47X@4nhKNH!BVQ}@^^4%Fhwr9Pm@m@`7A)I^^{ zN2yx1@6&9mQNdi++~1wvS?XOe%*3m86}NwUg~-73)_5x(HUCqXF#3ZTSBG}Jp@5$t zM|Sz+W8ccw_eQ;!h(dAq?e z1hc6UCZD0{y16lOKRX|Df3A;#&TmWhp~q!v5PiG-y#Tw+dggw`ZTgDZD?gBVAZl}l zM$^W)U`e#HNSi(w?kfTq?gGsXpNsNz6=&^*93v?<6{pUw(Z7bGrwYHjBvdCA`vSFJ zu|Yi*O@kqvG>}A7JW{#dnYc=*&eixai8vWhc^9AC@)(Ur|1>^8A|)+#9iVpH8sru% zIp>gsgBSm%RKGHk#Y zKF!ramAED&6sqI4V{@+GnT+(Z@xog!tOcOB6KsH}BB1e!w<_EdEn1J4UHwaWee*Y| z2Rxbs96&q)p2KLbCr2%S>#woS19ScPw=G#}sfM*5QfNBfFH-h$`8#@ifg&4OT+j48 zfo6nb?d`<0eb0Y2+VILBYv&Q0{+@j9NC^;Ls6;s%u~fOMa6^~W7PPX zWM}J~qh;N)YhkpR`X_iNDa6h|@&_1}Ql{wfP8`f7fXh#*%#Tn6Y2)+gW-ZtX+BkK9 z>j{XW@6;48YRZQR9?X9(-zQYxpk@Uj`YVi+P`t)zC8?gSZwhm?)Tup8=3qEVq1RZZ zf$I^Wc!}XGupr1HD)40a%{`};Qd7Zm_-yHo=FX5#X=n14&8>14s@S;ha_c245Sok1 z`eSXtFlxdJRQjH`Bcs0krxu_{j%b&fOT=|dYa zj16_6p&Y|Ushh>#mDTChP?^^Cs}k0gS*=MW-mz;;d62$`9K6%A;~$!SsX#vj8voiD zZn{TCgDyJwyd(~a?w&Fl=GRZ%#Hd(9PeEK9iJ)63`S(!!jmQg=kiV)-ZMu-VNMepSkHHegHEbL}o55A{I|IG?%VkM*UB zT>q$8h|BptQTEBRwbA~afvfK^k#W#vT6Wo2kDY?E^L4?kHIA24C_lg07?dEYpWJ!X z*e0jIIwcx->rtYz9Wvx75R!2&Y0B#jK%Iv5Z#HY$QQQe+v<|^z>TbaH)u{MV!qY(G&}lu4=X__?1Sk*;FF&N@Tk0OBt4&3vUq>P`igW}ShpN_J9To2p zG+>w3L**-sCeyx$04fuClXqTTK#OtV&mWD!PI7A=usK_*C}aV(%_(NFI+3%JXcJ>c z_(MvT!~zi&9!l_w_3GTe#}h8!(O@1P}=7PYCL|^Ncb zK{z?lV^Rk4{%sqP2vm7qu+eb~VR)qireY$WS8<_ZIU~pkGpBy&TAx69SQcZQOs(PAW^Be+qeSWrU!}wsQ(oF7OOE zPofvxA|U1!4~c_JO1eq(a08q!nq0Xko4qAOYf@RpFC7UsAzP|f6MPrSs9d8%*Dv#u zM{)~)pjhhkqOL|gLeYoeNt8T&!5bnvA^J^&sud^Ay`)NvfyX;$HM7+$A^cB zoxn6|pmK^S;f#iSVOfZ?Riz~eMYMlzis(lo7QQ)iQP%E`)TtCQwjrp7__CgIHq?j@ zJl(+2Iu2Xrx<26iIkJr*0ElHB1;Y(5G#$s@mcmd?*vl8nXC*B*#5+^5%=IwR5RhP~ zdpf|t`qwZtM>cvdBpa< z(mhsHV?;>J(4FlN>}aAhc8wgQryf)K{q2ugyAwq2}N!BPTFteGm|HR@)Dh%5w*mV~%7 zb&^-+hd>zEMmj8>C}|QmRin7MESA%_x39s+aXWA z-zf#G&1U4k;?ix%BX};SjDKbKdxGc0Wx4~ax=J32UPL6 z0q3}6XiUI(=8Q_+)mO1x=4+5j@*1}GjIjYk%A-hZ&Hu#(I z=ty@4lSg60Rk(gz`zQFjw+C=&-BHw5URuzzj5>Q5IN|2>BjD%(Ifya*)1d9N(fp-w2x4VM z8wzJg`4zabM-7V56L7R$aND`_Nf~roQCKltfPRue&W0GdgsIOp6rN9`J-mK=wUb=V zPcP3UTfS<`>C@tmxDb~LcRj4IFl=zU(tU6xKQ#~5YVRfX+$;wI#Yv)nmMQ^NV#s=S z)`3&|fD+cmAy>BPW?vNlTAu7P&_GUY20({zD$@hoAMlZyRAA&>5JGx82AU(whFAeL zUvN;NfW80JG6K_Ve%OF|zTE`_*uXq)f$Nj_xo-1cfu&z12JWvH(rCe3YM1#{U?!Bnuzljct772f-hQmz-3cn;iEk- zApowMZ+=}g&G~#LjN>Fr6gXag`5WA({cjl>uu1Y;E-K(m#l+W zJ8+(3Mm*nW?NzB29ESmx3)KB~;o&x*G+Au=R;a(4_>^yWp~`7&av%<#0($RHspfI( zjzF~-;Ki6)E~SE{d#7AvPipj4loEa~Aju;lcu)?1HVG4daTo$*J`j71Wch zCU!+y=Z%Bx5vJLoGRnhwA-&HhoX>%E@Na=OKd?N*(%A? z23HdBixN7SZzgOXdB$K`6X5Jarm_WqQa-5B-ZVYWCP^XcbuIu$BuAiv;tggMU3B{a zquv@ND@`tFYdrs9jUGaXwi~M^@Dh8)xL1Vp>?QnDQM0d`p4~Hon6N$NT)8u-OL z2KhuQPM<8xm%z<+fJ#e&7$}YeE76v?{%jH%R1;nmAab%^KLY6kCxO+!FDI5l;4(@v zi6LyKMB=i3Efy3V_yiD@H;!B4GzQ85k5OrDKqKIm}V*YM9dHheTo02H0k>73Vo zK9Ahgh&W}j)^v%nT@ukU!AQH{W4(FFRqjjJ{RwnUoBdh*Jymhu5!&{d#Nh5VXj!TA zL0hN48^+0p_+!PHGJmJRTK-CaR;;u*en+EvBHhq1z#e~5DWet>KDE+2j*LpcM+=KW z7&lId&=WsAS!H`!Qh-N7_UwsO*z1n1qR-!19A<}6HjkG$HrB!i5 z*K#UZoq8FGn(Z5$)0-OnTIrjCVY6L-mn8eh=Qbx2yeM^jT{lXLmSG^VYST9!f9@UB-$)3G(O z!-Y?q$xhwGPwRFHzBB=bg2=6S|O)v64$_ZvhO^9?StxZmgr8M>+a0AIS|tb!L9O9A`#C=1R7?!Lpf*z80nWq?Na zHI6)({duh9G!KYIEMm5G4vmtiysp@cQIOB-WtcIF>Q3v$M)~l&)rZ4w6!n*6M zsXkkNNjX_avVMI!W}(??PWRoQ&5u*x2RCbeT727r^|SO+_V}XtP%H}iaLE_zJDy5= z=Yvyp4(}?=n{#uCsc_1l#ZqRX!~&#Dqt5p4`Z zoaXM0-dvfF@d_}hRPbLO35H|d38ZYF{}`M2h06`G;4>RiI%*+2*qgLfZ;W!-6y;w( z{Owb}|Fxwh?2ukPN{qlQ8jtv7ce~L<9N!Ipz1JPbHoa!VtNw&Mvxx!ArRs2ka_5c5 zZ~D8)ZV}a?yE`Urt2Mc9%yzdPXQS&?v{>PTwJH{jef9kj_Ob`RwuhrT9(1}7cr=Qa zUa}WA`ym@Vkvv^}D){|+wh%YGEm?s*KYmhPqeFI_LT_>Q{X-*{s{HT^$bu=Wl8b$+ zF4{foqRnovL&N$Wcs23Y(++#v6(Rm9nl9bQ>OXdppU+Yo%3#BG^+=c*}aHAQ4jgz}$ zpYABqCwiyy`yk`h5>UwLWNc3$MUhAP?#*tV%Z7-fSK3hVP<{E?7HWuaF>)uVb6~t}cFwbtU-&B%gi>O~w<| zJ;op5u+mZ#%U~ea;4xny#(oeZ5KJ?XVl*--xjC3X*d;#NTRlg|{Mg0#P4&@<&6b~Q zJ9+hXc)4p)Z2v^~(js4pkkbksg-9UmTN&$#@yHNM-9zhxxe^i{4|3;_!s{uln&PD1 z32wT+MMiq#56iaSnnQnN?Yj-7Rw;W=c&+v|xC%Im&&AYJYDF^1-olbw4A?VeX-1j$ z(=)NoXMa(e3RzgtDl@3-8@##6@U?Q92d1(gICXMINIQ9eeI9hS^p?|J=45H_FzD&g zeWp<>Pnzb&JEFNfSSCRGW@#O*;M2smUS&bq!+Vb*h7QE!4?f$wm@w)nZc7jS=~^1{=V)- zJp8-bOk79xFH+rx2lIS{4f;c53n}h(gq7y~p(yC=`9@eP2#6-sC=S*MvtRh@t-a%s z_ESnY@;+u|&(<2fRWF^Rb&>Yru9p?)B1btLKFA-`I2qnMg?lxLkHguoQ#hb;>>gsI z*XkbOiM=x>K@{kkCVIOwC{slj?tHLgwZ4o&a94#iM=SJd$ZcXdVIu-Z-}HwzMAUvt z-;WL(GeQ5g0WtEe9WwJ%-Kb?&^Y0Y&iBa!P#~WedSwN{E{>7(IgstUJZQ z734f?15?)HplG6bh^f6R@tp6-TPE+;;a*SA9v6R``bG>c{5rO4s|W1tsQ}!A?TQ1#smm^1;r#<93=g-uUjQse!l&x4E`a zoDTcb2AY)z)(7)mip%azrgKzt94GV^2f1T;Pp-1JpRAgBL;N5c9eTK4c~Hh2i13Aw zBkXJ?SjU@czNGqnKT7xW^NrAlxmx_PboJ<6lZow|O&i(Vm2RZ{poli(i+EI%5xKa= zRQiE+4s)z7TH6GK!F%~3ZolXOwSD?0l~mO+g_w~GlgL|zCe$xNr@bz(J*F0wtt7gN zg?Eo^N~i5CtJdZy(D43PvnKBmrYToO!!x4z!rf@}at^*VpR>Z9uC-q7n{7A_Q8-h2 z4==?!(R%W#vz|oHz0(1c{>xx?doL0^=%Y{`w^c;b0VSF-ZQKPAl`5x|rj=&E6#6J@ ztgls&qElFlBJ(kDrABClXak4qua3J4&o%o7@&@+@4mV!wR=zp=$9prk2nEmJ8m$G= zv)V7$&axXG)@u@&*gqCR;@@lbFo;=mdNQ?DFen#u1I&FyF_!dV1)nje8Xr?esu5Px zmk9F~;%Xx--EM~dT-4JQKM_6)I&T4LLrvHVYm^E{1{6GXHOgHpiyH3>(GDf4RpNhJ zi1F&xG+y_&-(fnXhMqg!@xFn#no;SVlt_PZh{vjPi}t)M6f;)%P`gL^J*Rp=3)YfKW*;SnXb* zp`QJ!!Q!pqL~+cP4D8F0dJ`!=b8`LERGy12`00i2y#m+7oz9@a*%q7R1N}u9`p54U zg)m$mk7O_pD<)i+1>VuMdIF-WTcLi|zI1J+5jQPPr`HkJT3n}MwbEr@jatqP=+qw0 zk_{7{k}M9wFch(ra|v%s{eV@h9fYuP<(x+L5xUzhIXVsWqR_j+H~&01*YGj(EG=PK zuXC(Ij^V?@qu?2G=Q_0TdeFNVzWdMj_gR(YhCh7O&v1X@AWkK%++Xf5Yu4h7F39H0 z!a|?zzzu+$_Z2lpL|$952%ch#o(8Ym7|6X#pNx@oq}3ND&5p2Ht|x0(X}z!N_aj_*_H&FN;G)B#F=gyd1Dc~bIKf_@xk9RC9AS-wPPYM9?{%?{KGQhk}%_1Rcn|g(dh5FuA&AQo8wGhFG$?%zL<=_}zhiMMR-7dDXk^ z;@^lb*E8vbA1AN2D?Ef|jd}Cq4d%3?3tF9jtPmeBC63k(^(+tW^Sm6Kd&+fx5V(E} zX<=~;ZJ{G{2~oSB)_pXNI+;kub=?gbDz?gB1mY*v?9YSv+GPeQA%nX&ng7^;he6?= zu$UlT+5N`3cw-;BB1}A6m$o=3I|K@M#3;__3=Ln>cHk}N>elh`aDta;NjH1CsNz_U z*qvE<&(Jn2pGFHU9-S6pk>iH3&Pd~+TB3@P6Mam%ldnwolAW`n5%+4oi{b6Gnva#{ zQanUv2;H9uxWf+md}ie_#BqnIKbKNAhq}Uh%%P|Je2NjIgYI&bJwE#P*`_Ik%2uVE4-3 z>xVjrP05||9<)6=vv+V}^bs*#vX+MU2vAy^ExKEol|_0S#wE@kzAq_x90$$U`2J^A)(;ctxu^r^*PXGc&V8xaZ!D}Mc&c>k~$>Sq!!$Qa7?*i&M*vE~kLyVW_VQfWg2`eU%J78QWg4E}Nvp=2kxFZP2< zHwo0>72AME(NK#L&)qrF;T`!Q2BjLbnM=d_7EAwx$3HEAOSGLWOQj8k5u? zDkHSmB!es#{y3IMn1;aQO4u>x#dW;B2g&x2|iuGlHQoWGrdXMPsyoUqy=S-%#*x|%qBk9$Go8PlJTVj(k z^JpRVz>ng5@BVbh%eoJ@A=I}xR+r68qM#E|UxL{YE#%L9ed%6jjz;_J4IYwYJ2qtT zEr6y#3-Vqd0{yw6)Su#V?I@8BKWr9yHmCj%=@F%*7Z>11lI>p4-Pvoc*@)B?6;hLA zOs^zYS%U`qN~p-6`BF4?cr5{^*ajXFg(2`y0e_qK&&j$C0{U~0QU0Ewc~yA1|p@HX}(6F?16$_eG0VQ2s?XjRPg2x`02iRKb z|6gt`5P$LvnJ#Gt${^qGw=!MniE73=8*tBvS}L>(;7)Vzz}pKsHlY^~<`A;j%VK=~ zs|^WHkfVgjRf{=5TERqNT#fkw5*XiY5aPxO)t~L!?BT+Ec|W}d+)}Z15BGQKKP?6Q zMN0s>&(19b99EW4aUTPRJ97QZy|}L+@Bp|SCW(u$Umd)!m%ZbjK6T0stKVpp3{~k|+&&@Du4fRsQ0BZ8TB%BZIdDC}D2-gtT z3jzqWaHm6?z$(0N1sc`fTMvq?GoJaj_+sbLrA}L#G z1vt_SV@#F6(=0QH2^_Y%P5d`IB$qE^f#sd-j(5EZv4VEa`H?X?7B0u!k$<3f@mc5b~OOyyRzG1Pv{uD{Ts!>Y=;JVv#9;kf^*Y*3!sE{gx1mZP{#Y0pTG%2xjE9#A>6aMRKez&uTt@v{H`y41VX5qs~P3G zf4s}#s`NmQr*4zL?%e&5Xvif7$>9!f&;V;yJwEc%h(~8dyz461I9S{IHR*`7O|Kh)D4{_HXxl0)u5&e0 z)r^eG^;?3kg5^avZnu<5-tY46m0mj@==gW(Aaz?HP8%PD%AY-;9i!$j-rZXbB7K!k zQm)0~ZA=Go>^TBAyWMD!G+^_ z{as2!%}fFmECAtBQ3ZDHYwfUgc2C7XdS{garml(&ixclB?8;pLQh1}83twBB5!wl@ zFM6PpWIm)7M1>wuVSWDTMy`xYx@7@HtIAX~Bs6^-6z<*Va@|+&yvwh??Cohk<2hw^ zA`5|xm!Lyb>43gS~FGfX8lA8zqsXvgW>p~A!iHB z=R`5rr670f)lHK8-J#^ycf02>|>};*!_>9un!= zu=t*1#xCT$|1M56fP^vWz7F987MMcdyPC0se*{1Z*BV)wYD-nA)=!Gf-#~gZN|n9g z+D*p8>Z+wv57A5>CKk6#05<2AP`|}_nbOT^x7_()M9EU`Bl8J>p75rLviAEtmF_2w zMBXSiSMcrJ(Bp3DDS-j5_es+0W#eKe@^cz|PnSl|!_IgTaGVALRRG^NHtSGjEp^B- z!m>zuGXsOl?<$zgL=eq_i8HW}qED9&TaG;v1OiR|TD1Pq{0yMZR=a65^A?=8P+sT3 zBcgGq2CGpP%1ty(-KrT4aZniIKuTyc$02-27=ZiUB&6cKzv@O-M062E*`3sTIeCTcXIGTV{*Q*Qcbied5ZMN5`lo% zCtUnXyc~;{F)?4(DSWe1D+Bf@5&a4@X*7V`nvCJ#pI!_&xU^8#3$+rQH3OLAK$emN z)NPia*JsaNBjMnSgN6t$@3TzL@<1-gI;t3gMeIYJDL9)kwrshsVswEJDhRykuUo!T zIL-Qusi$#>uhmbLL+S=!#mPkz2?nr$k?a6SXhpvAbdKS?RQ`NH`7D7L^TXgDJDNR5 zCG}pC2gL}OO}FPt*BdeWXWWFR+nwG^_$K>TPw)2S$xYue#tOnBTaPPWM^87eB0~@< zP(lm?`RPyW_Q4Czll9Kox(&HeF^}fnM_HdI0$jwg#}rO`SkTb02J{4Do@i#dgxx?rg3`I)E_G z{#S#I(hx4|ks-)M&GL2a2yk@-U0+@;2Fb9rol^~!Ue{j7g%Bq$g<4q3XA6YvWcQQ! z;PrKjXmtEWvav;)ON8x zEFeUIOl)A&^|2w6MKCFk=Q2!X$A0_3CyrBtW6{G-dTK&lgI&>1qmdA1Ymq+{Xi~Z&6PeL0ZvKYO9!)z ze1`i8>WvxRL)6C}`aC`{oA3!sw(SM*345<~ay3i+(Jg5Cw4_}%nwNoF+S7K3-iLo~ zNzCXkS^}gza)1YFca0_LKSvxYZzCOG#C(I3cuU_=q9{}*`F{7evQ|WKmhajl`G8`^ z`zK)nl5&1AFZSouY_EjPm|6XqsyQ?HyPf~5?N*g%SR6gBCW#s&YU;evLSspqB& zgxaut3tW&&V<`*~T3~=YnbbnUn*u;A1$(mC!c$je*x;M-X8*c@rK1b5YZHrac%sQGWv@@e+7gh zUtMcS0E&o@1On%oY-NP_3%q^|6f7QYF#&oDE7_j^3^oq7{{=M8R{AAw9q7Uk1cp7$ zGimkqzw7}AGM%F@l&zY^>9v%%4rVbF0K&1TX>dU{o`Y*Bl#5$z?3QP-! zi@lmusY1y#$h4gPXGl%EU5e{&bf44y*GWI`h-Aj4lBwjAD~9{2#>BUmDd<>UbCsV} zFEWNI(Vxo6v4u;*Dh0Cup5m)xG*55Ah?C+@s`DS(K`*V_+XGBj$MICl zf8c5ocO|CRs@*l<*LZVXv8>PF+BUM@Y%m+P7<$>_Qy6XKpXo-F0!73wM%avouPVtW}%1i z)e>`}o=o8do<3rNrJc$>h@Yhb&fX}|3&J4hM8}zI4{(^t;Pf_I9UDD$Y`o@lKnLV# z{)KM?znE6s|5|8KYOceBMcS{t=~6rz&UG2elkK_u*bFE{CBk3zJKPRIe>!5vwx0Hh zksx+MyLjIkw7k^m@Z@zhaNat1hUqRY-`)CBVoUcQ3d@capQk9PiDcvD+zT$(YpC#7 zp6GeO)qSjK;~}*mjx4#Z_n4Z_GLT;Y1PQIUa{VueD4*Br=wf@aW?P5##6GjQ|B(KL zTB>6OA-4L?ozEDqJm!Tia}#$iIv)!X%{(>x>FaeVl^IKIGHMgk#8XQwBu|~TS^Ipp z&T&ZPYQ#?uZo6X=g2$bfnrIagEsW!8v|t(Me|-IMvph(G$QzBHXQpq?G3963ai67b z8y{87ZZ!X$xA?L~SN^-Qds~Mgt@{7f+_i_Zouy&78I+*Wx`Yl|B^^qmiB=VLtG0@c zN@~y$n%UH4-7h6|Dfx|-Vpz8pshyopwY2FNDj2t*lr~*NZ*Gkei;_H~O;ln}X7`yU z&ole~<~e`l$(Nk(d(ZjKd4A_T@5#{tH=p!m8FARdukw&WZv(*b$PX6F8~Nb#ACEdh z(YQj*C_>bcn-I&4wp^yfp|V?oXn?0l8%N zU2ffQFB);uZHEnEPQc~1V`y9refUh>oyL2z%G8sNuiGbQmEvDe`ipWe(BPB zZ!@f9?tavdxYe*1^Z!DLt_4CAeev9k&3b-SCU&X$ZOoX$(U`5vT5AJU_YnOB3pvyz zqu#N~q1ezZPKuybnU=;RTqG&;qfCu4M^V@;|)Cx!y{RIgXEc~zLerfe?|K?P6j?p zBAV?NvU%7RPEFdos*X1u%6(6oi`Ph#7nOkWQiW$3IT5xhrvAVwjk$=6z*_8&cCe~3 zFbI{CQw}=m=InR=myqFxUR*;XRwU@B1$>bsDw0!`E}Iz&xM`EO7{=G9Bgfk@2R@aE z?_itI{r?euN<_@u@V7xKYM%S4d#^KTczP;~*?z{oryf^liXX20)^X^D4e^?(!T9`p zDD*wF3Uu#qbcDUq^y*8CV5OahZ8LN!@VVCm)%b_nfpl{0CAE06gx(9AV)Ol*;|``f zeo({jtIB(%^jMxKWWMGa?Svc4OP3|pib(&hjiqL?CoLu2@90+L9R24ahm8I2o>pVN z`)I@Uy)95*GC$biHl)GOYD$FA_t;w2KBw*yCUq~X)APm(FCE9WPwb21`sGfJqw9=* z5T&CDS?Fixn&hlhFXb5^NJBw5J&x~cvn$gZ#+}aVd~*0yDbkp!-PW0S(=GJBbpDby zviB=G1F`#9jkkj7vCk#lETot)DP}kd`-5V^$pCvD>&e{Gh)xC|-v`Pque$yjaZ_$> zXcZi1n&d>edR`<%=9VL|^{ic<99U`e>`rSE{I>5hDZPoj+*zX!Yfq>P{Js@g;bfje z(6Mi4SB?!czw2s#aQj5|*HfD}+enHD;b%QNVg=;~xBA*Jt!#=RbC5b(DSmYd9knLE zkr0?GLZz-Us3LdjilQi9rcr7l{3P0oS4Y`f9idDgr5 zI-tB{yuLfjt#Hcg(0!U3T#yEIunQw;mi=CMOD85!I+~%k3>+sa%Ixig~^Jilrxq zOe+vm>p*;vGay1PN#(l)SIjwJJgrd;Do7mRyp)9+2W(cs##Em|h{aJG=6=Qr02}WB z*4pQ@^jg6D!Agyp<}$c7wW4>d~mj%QM3Dw9)PFb`=Wu#C3m^6{TgA|5LCP23l=?v5S9%6BoLIl#SDh86W#Rq(}0|J2q zJ1;yLhu46^2W6vz5a7^9UO{>SGF_d)2|ik6nn`U -## Test features and functionality +## Sample data +The examples provided on this page use a sample of [NYC taxi trip record data](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page). The sample data is provided using multiple mediums (CSV file, Postgres table) to support each workflow. + +When using the taxi data, you can make certain assumptions. For example: +* The passenger count should be greater than zero because at least one passenger needs to be present for a ride. And, taxis can accommodate a maximum of six passengers. +* Trip fares should be greater than zero. + +## Validate data in a DataFrame +This example workflow walks you through connecting to data in a Pandas DataFrame and validating the data using a single Expectation. + +:::tip Pandas install +This example requires that [Pandas](https://pandas.pydata.org/) is installed in the same Python environment where you are running GX Core. +::: - + -1. Import the `great_expectations` library and `expectations` module. +Run the following steps in a Python interpreter, IDE, notebook, or script. - The `great_expectations` module is the root of the GX library and contains shortcuts and convenience methods for starting a GX project in a Python session. +1. Import the `great_expectations` library. - The `expectations` module contains all the Expectation classes that are provided by the GX library. + The `great_expectations` module is the root of the GX Core library and contains shortcuts and convenience methods for starting a GX project in a Python session. - Run the following code in a Python interpreter, IDE, or script: + The `pandas` library is used to ingest sample data for this example. - ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx.py imports" + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py import gx library" ``` -3. Create a temporary Data Context and connect to sample data. +2. Download and read the sample data into a Pandas DataFrame. - In Python, a Data Context provides the API for interacting with many common GX objects. + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py import sample data" + ``` + +3. Create a Data Context. - Run the following code to initialize a Data Context and then use it to read the contents of a `.csv` file into a Batch of sample data: + A Data Context object serves as the entrypoint for interacting with GX components. - ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx.py set up" + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py create data context" ``` - - You'll use this sample data to test your Expectations. -3. Create an Expectation. +4. Connect to data and create a Batch. - Expectations are a fundamental component of GX. They allow you to explicitly define the state to which your data should conform. + Define a Data Source, Data Asset, Batch Definition, and Batch. The Pandas DataFrame is provided to the Batch Definition at runtime to create the Batch. + + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py connect to data and get batch" + ``` + +5. Create an Expectation. - The sample data you're using is taxi trip record data. With this data, you can make certain assumptions. For example, the passenger count shouldn't be zero because at least one passenger needs to be present. Additionally, a taxi can accomodate a maximum of six passengers. + Expectations are a fundamental component of GX. They allow you to explicitly define the state to which your data should conform. - Run the following code to define an Expectation that the contents of the column `passenger_count` consist of values ranging from `1` to `6`: + Run the following code to define an Expectation that the contents of the column `passenger_count` consist of values ranging from `1` to `6`: - ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx.py create an expectation" + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py create expectation" ``` -4. Run the following code to validate the sample data against your Expectation and view the results: +6. Run the following code to validate the sample data against your Expectation and view the results: - ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx.py validate and view results" + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py validate batch" ``` The sample data conforms to the defined Expectation and the following Validation Results are returned: - ```python title="Python output" name="docs/docusaurus/docs/core/introduction/try_gx.py output1" + ```python title="Python output" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py passing output" + ``` + + + + +```python title="Full example code" name="docs/docusaurus/docs/core/introduction/try_gx_exploratory.py full exploratory script" +``` + + + + + +## Validate data in a SQL table +This example workflow walks you through connecting to data in a Postgres table, creating an Expectation Suite, and setting up a Checkpoint to validate the data. + + + + + +Run the following steps in a Python interpreter, IDE, notebook, or script. + +1. Import the `great_expectations` library. + + The `great_expectations` module is the root of the GX Core library and contains shortcuts and convenience methods for starting a GX project in a Python session. + + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py import gx library" ``` -5. Optional. Create an Expectation that will fail when validated against the provided data. +2. Create a Data Context. - A failed Expectation lets you know there is something wrong with the data, such as missing or incorrect values, or there is a misunderstanding about the data. + A Data Context object serves as the entrypoint for interacting with GX components. - Run the following code to create an Expectation that fails because it assumes that a taxi can seat a maximum of three passengers: + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py create data context" + ``` + +3. Connect to data and create a Batch. + + Define a Data Source, Data Asset, Batch Definition, and Batch. The connection string is used by the Data Source to connect to the cloud Postgres database hosting the sample data. - ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx.py validate and view failed results" + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py connect to data and get batch" ``` - When an Expectation fails, the Validation Results of the failed Expectation include metrics to help you assess the severity of the issue: +4. Create an Expectation Suite. - ```python title="Python output" name="docs/docusaurus/docs/core/introduction/try_gx.py failed output" + Expectations are a fundamental component of GX. They allow you to explicitly define the state to which your data should conform. Expectation Suites are collections of Expectations. + + Run the following code to define an Expectation Suite containing two Expectations. The first Expectation expects that the column `passenger_count` consists of values ranging from `1` to `6`, and the second expects that the column `fare_amount` contains non-negative values. + + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py create expectation suite" ``` - To reduce the size of the report and make it easier to review, only a portion of the failed values and record indexes are included in the Validation Results. The failed counts and percentages correspond to the failed records in the validated data. +5. Create an Validation Definition. -6. Optional. Go to the [Expectations Gallery](https://greatexpectations.io/expectations) and experiment with other Expectations. + The Validation Definition explicitly ties together the Batch of data to be validated to the Expectation Suite used to validate the data. - + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py create validation definition" + ``` - +6. Create and run a Checkpoint to validate the data based on the supplied Validation Definition. `.describe()` is a convenience method to view a summary of the Checkpoint results. -```python title="Full example script" name="docs/docusaurus/docs/core/introduction/try_gx.py full example script" -``` + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py create and run checkpoint" + ``` + +The returned results reflect the passing of one Expectation and the failure of one Expectation. + +When an Expectation fails, the Validation Results of the failed Expectation include metrics to help you assess the severity of the issue: + + ```python title="Python input" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py checkpoint result" + ``` + +To reduce the size of the results and make it easier to review, only a portion of the failed values and record indexes are included in the Checkpoint results. The failed counts and percentages correspond to the failed records in the validated data. + +```python title="Full example code" name="docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py full end2end script" +``` + -## Next steps -- +## Next steps -- To learn more about {GxData.product_name}, see [Community resources](/core/introduction/community_resources.md). +- Go to the [Expectations Gallery](https://greatexpectations.io/expectations) and experiment with other Expectations. -- If you're ready to start using {GxData.product_name} with your own data, the [Set up a GX environment](/core/installation_and_setup/install_gx.md) documentation provides a more comprehensive guide to setting up GX to work with specific data formats and environments. +- If you're ready to start using GX Core with your own data, the [Set up a GX environment](/core/installation_and_setup/install_gx.md) documentation provides a more comprehensive guide to setting up GX to work with specific data formats and environments. +- \ No newline at end of file diff --git a/docs/docusaurus/docs/core/introduction/try_gx.py b/docs/docusaurus/docs/core/introduction/try_gx.py deleted file mode 100644 index ce0eb5511294..000000000000 --- a/docs/docusaurus/docs/core/introduction/try_gx.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -This example script allows the user to try out GX by validating Expectations - against sample data. - -The snippet tags are used to insert the corresponding code into the - Great Expectations documentation. They can be disregarded by anyone - reviewing this script. -""" - -# -# Import required modules from the GX library -# -import great_expectations as gx -import great_expectations.expectations as gxe - -# - -# Create a temporary Data Context and connect to provided sample data. -# -context = gx.get_context() -batch = context.data_sources.pandas_default.read_csv( - "https://raw.githubusercontent.com/great-expectations/gx_tutorials/main/data/yellow_tripdata_sample_2019-01.csv" -) -# - -# Create an Expectation -# highlight-start -# -expectation = gxe.ExpectColumnValuesToBeBetween( - column="passenger_count", min_value=1, max_value=6 -) -# -# highlight-end - -# Validate the sample data against your Expectation and view the results -# highlight-start -# -validation_result = batch.validate(expectation) -print(validation_result.describe()) -# -# highlight-end -# - -output1 = """ -# -{ - "type": "expect_column_values_to_be_between", - "success": true, - "kwargs": { - "batch_id": "default_pandas_datasource-#ephemeral_pandas_asset", - "column": "passenger_count", - "min_value": 1.0, - "max_value": 6.0 - }, - "result": { - "element_count": 10000, - "unexpected_count": 0, - "unexpected_percent": 0.0, - "partial_unexpected_list": [], - "missing_count": 0, - "missing_percent": 0.0, - "unexpected_percent_total": 0.0, - "unexpected_percent_nonmissing": 0.0, - "partial_unexpected_counts": [], - "partial_unexpected_index_list": [] - } -} -# -""" - -output1 = output1.split(">", maxsplit=1)[1].split("#", maxsplit=1)[0].strip() -assert validation_result.describe() == output1 - - -# -# highlight-start -failed_expectation = gxe.ExpectColumnValuesToBeBetween( - column="passenger_count", min_value=1, max_value=3 -) -# highlight-end -failed_validation_result = batch.validate(failed_expectation) -print(failed_validation_result.describe()) -# - -failed_output = """ -# -{ - "type": "expect_column_values_to_be_between", - "success": false, - "kwargs": { - "batch_id": "default_pandas_datasource-#ephemeral_pandas_asset", - "column": "passenger_count", - "min_value": 1.0, - "max_value": 3.0 - }, - "result": { - "element_count": 10000, - "unexpected_count": 853, - "unexpected_percent": 8.53, - "partial_unexpected_list": [ - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4, - 4 - ], - "missing_count": 0, - "missing_percent": 0.0, - "unexpected_percent_total": 8.53, - "unexpected_percent_nonmissing": 8.53, - "partial_unexpected_counts": [ - { - "value": 4, - "count": 20 - } - ], - "partial_unexpected_index_list": [ - 9147, - 9148, - 9149, - 9150, - 9151, - 9152, - 9153, - 9154, - 9155, - 9156, - 9157, - 9158, - 9159, - 9160, - 9161, - 9162, - 9163, - 9164, - 9165, - 9166 - ] - } -} -# -""" - -# This section removes the snippet tags from the failed_output string and then verifies -# that the script ran as expected. It can be disregarded. -failed_output = ( - failed_output.split(">", maxsplit=1)[1].split("#", maxsplit=1)[0].strip() -) -assert failed_validation_result.describe() == failed_output diff --git a/docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py b/docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py new file mode 100644 index 000000000000..dd37266f8ec5 --- /dev/null +++ b/docs/docusaurus/docs/core/introduction/try_gx_end_to_end.py @@ -0,0 +1,189 @@ +""" +This example script allows the user to try out GX by validating Expectations + against sample data. + +The snippet tags are used to insert the corresponding code into the + Great Expectations documentation. They can be disregarded by anyone + reviewing this script. +""" + +# + +# Import required modules from GX library. +# +import great_expectations as gx + +# +# Create Data Context. +# +context = gx.get_context() +# + +# Connect to data. +# Create Data Source, Data Asset, Batch Definition, and Batch. +# +connection_string = "postgresql+psycopg2://try_gx:try_gx@postgres.workshops.greatexpectations.io/gx_example_db" + +data_source = context.data_sources.add_postgres( + "postgres db", connection_string=connection_string +) +data_asset = data_source.add_table_asset(name="taxi data", table_name="nyc_taxi_data") + +batch_definition = data_asset.add_batch_definition_whole_table("batch definition") +batch = batch_definition.get_batch() +# + +# Create Expectation Suite containing two Expectations. +# +suite = context.suites.add( + gx.core.expectation_suite.ExpectationSuite(name="expectations") +) +suite.add_expectation( + gx.expectations.ExpectColumnValuesToBeBetween( + column="passenger_count", min_value=1, max_value=6 + ) +) +suite.add_expectation( + gx.expectations.ExpectColumnValuesToBeBetween(column="fare_amount", min_value=0) +) +# + +# Create Validation Definition. +# +validation_definition = context.validation_definitions.add( + gx.core.validation_definition.ValidationDefinition( + name="validation definition", + data=batch_definition, + suite=suite, + ) +) +# + +# Create Checkpoint, run Checkpoint, and capture result. +# +checkpoint = context.checkpoints.add( + gx.checkpoint.checkpoint.Checkpoint( + name="checkpoint", validation_definitions=[validation_definition] + ) +) + +checkpoint_result = checkpoint.run() +print(checkpoint_result.describe()) +# + +# +# Above snippet ends the full end-to-end script. + +end_to_end_output = """ +# +{ + "success": false, + "statistics": { + "evaluated_validations": 1, + "success_percent": 0.0, + "successful_validations": 0, + "unsuccessful_validations": 1 + }, + "validation_results": [ + { + "success": false, + "statistics": { + "evaluated_expectations": 2, + "successful_expectations": 1, + "unsuccessful_expectations": 1, + "success_percent": 50.0 + }, + "expectations": [ + { + "expectation_type": "expect_column_values_to_be_between", + "success": true, + "kwargs": { + "batch_id": "postgres db-taxi data", + "column": "passenger_count", + "min_value": 1.0, + "max_value": 6.0 + }, + "result": { + "element_count": 20000, + "unexpected_count": 0, + "unexpected_percent": 0.0, + "partial_unexpected_list": [], + "missing_count": 0, + "missing_percent": 0.0, + "unexpected_percent_total": 0.0, + "unexpected_percent_nonmissing": 0.0, + "partial_unexpected_counts": [] + } + }, + { + "expectation_type": "expect_column_values_to_be_between", + "success": false, + "kwargs": { + "batch_id": "postgres db-taxi data", + "column": "fare_amount", + "min_value": 0.0 + }, + "result": { + "element_count": 20000, + "unexpected_count": 14, + "unexpected_percent": 0.06999999999999999, + "partial_unexpected_list": [ + -0.01, + -52.0, + -0.1, + -5.5, + -3.0, + -52.0, + -4.0, + -0.01, + -52.0, + -0.1, + -5.5, + -3.0, + -52.0, + -4.0 + ], + "missing_count": 0, + "missing_percent": 0.0, + "unexpected_percent_total": 0.06999999999999999, + "unexpected_percent_nonmissing": 0.06999999999999999, + "partial_unexpected_counts": [ + { + "value": -52.0, + "count": 4 + }, + { + "value": -5.5, + "count": 2 + }, + { + "value": -4.0, + "count": 2 + }, + { + "value": -3.0, + "count": 2 + }, + { + "value": -0.1, + "count": 2 + }, + { + "value": -0.01, + "count": 2 + } + ] + } + } + ], + "result_url": null + } + ] +} +# +""" + +checkpoint_summary = checkpoint_result.describe_dict() + +assert checkpoint_summary["success"] is False +assert len(checkpoint_summary["validation_results"][0]["expectations"]) == 2 diff --git a/docs/docusaurus/docs/core/introduction/try_gx_exploratory.py b/docs/docusaurus/docs/core/introduction/try_gx_exploratory.py new file mode 100644 index 000000000000..74c33ea2ec7a --- /dev/null +++ b/docs/docusaurus/docs/core/introduction/try_gx_exploratory.py @@ -0,0 +1,100 @@ +""" +This example script allows the user to try out GX by validating Expectations + against sample data. + +The snippet tags are used to insert the corresponding code into the + Great Expectations documentation. They can be disregarded by anyone + reviewing this script. +""" +# ruff: noqa: I001 +# Adding noqa rule so that GX and Pandas imports don't get reordered by linter. + +# + +# Import required modules from GX library. +# +import great_expectations as gx + +import pandas as pd +# + +# Create Data Context. +# +context = gx.get_context() +# + +# Import sample data into Pandas DataFrame. +# +df = pd.read_csv( + "https://raw.githubusercontent.com/great-expectations/gx_tutorials/main/data/yellow_tripdata_sample_2019-01.csv" +) +# + +# Connect to data. +# Create Data Source, Data Asset, Batch Definition, and Batch. +# +data_source = context.data_sources.add_pandas("pandas") +data_asset = data_source.add_dataframe_asset(name="pd dataframe asset") + +batch_definition = data_asset.add_batch_definition_whole_dataframe("batch definition") +batch = batch_definition.get_batch(batch_parameters={"dataframe": df}) +# + +# Create Expectation. +# +expectation = gx.expectations.ExpectColumnValuesToBeBetween( + column="passenger_count", min_value=1, max_value=6 +) +# + +# Validate Batch using Expectation. +# +validation_result = batch.validate(expectation) +# + +# +# Above snippet ends the full exploratory script. + +exploratory_output = """ +# +{ + "success": true, + "expectation_config": { + "type": "expect_column_values_to_be_between", + "kwargs": { + "batch_id": "pandas-pd dataframe asset", + "column": "passenger_count", + "min_value": 1.0, + "max_value": 6.0 + }, + "meta": {} + }, + "result": { + "element_count": 10000, + "unexpected_count": 0, + "unexpected_percent": 0.0, + "partial_unexpected_list": [], + "missing_count": 0, + "missing_percent": 0.0, + "unexpected_percent_total": 0.0, + "unexpected_percent_nonmissing": 0.0, + "partial_unexpected_counts": [], + "partial_unexpected_index_list": [] + }, + "meta": {}, + "exception_info": { + "raised_exception": false, + "exception_traceback": null, + "exception_message": null + } +} +# +""" + +# Test workflow output with passing Expectation. +assert validation_result["success"] is True +assert ( + validation_result["expectation_config"]["type"] + == "expect_column_values_to_be_between" +) +assert validation_result["result"]["element_count"] == 10_000 diff --git a/docs/docusaurus/docs/gx_welcome.md b/docs/docusaurus/docs/gx_welcome.md index 02d1c091ba54..d327c88618b8 100644 --- a/docs/docusaurus/docs/gx_welcome.md +++ b/docs/docusaurus/docs/gx_welcome.md @@ -24,7 +24,7 @@ import GXCard from '@site/src/components/GXCard'; - + diff --git a/docs/docusaurus/docs/resources/get_support.md b/docs/docusaurus/docs/resources/get_support.md index 7f7f7bbbc7e4..8a910e2d3732 100644 --- a/docs/docusaurus/docs/resources/get_support.md +++ b/docs/docusaurus/docs/resources/get_support.md @@ -32,7 +32,7 @@ Search the docs you're using currently for an answer to your issue or question. If you're new to GX Cloud, review [About GX Cloud](/cloud/about_gx.md). -If you're new to GX OSS, see [About Great Expectations](/core/introduction/about_gx.md). +If you're new to GX Core, see the [Introduction to GX Core](/core/introduction/introduction.md). ### Include all the relevant information diff --git a/docs/docusaurus/sidebars.js b/docs/docusaurus/sidebars.js index bb82b4a76051..e7b12c2684f7 100644 --- a/docs/docusaurus/sidebars.js +++ b/docs/docusaurus/sidebars.js @@ -2,29 +2,19 @@ module.exports = { gx_core: [ { type: 'category', - label: 'Introduction to Great Expectations', + label: 'Introduction to GX Core', link: {type: 'doc', id: 'core/introduction/introduction'}, items: [ - { - type: 'doc', - id: 'core/introduction/about_gx', - label: 'About GX' - }, { type: 'doc', id: 'core/introduction/gx_overview', - label: 'GX overview' + label: 'GX Core overview' }, { type: 'doc', id: 'core/introduction/try_gx', - label: 'Try GX' - }, - { - type: 'doc', - id: 'core/introduction/community_resources', - label: 'Community resources' - }, + label: 'Try GX Core' + } ], }, { @@ -149,6 +139,11 @@ module.exports = { id: 'oss/changelog', label: 'Changelog' }, + { + type: 'doc', + id: 'core/introduction/community_resources', + label: 'Community resources' + } ], gx_cloud: [ {type: 'doc', id: 'cloud/why_gx_cloud'},