From a927f0e86a176f902acde3dc735c68d2ccae39a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Vl=C4=8Dek?= Date: Wed, 29 May 2024 15:32:57 +0200 Subject: [PATCH 01/27] Add missing cluster_manager_throttling nodes stats (#7241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add missing cluster_manager_throttling nodes stats Closes #7240 Signed-off-by: Lukáš Vlček * Update _api-reference/nodes-apis/nodes-stats.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Lukáš Vlček * Update _api-reference/nodes-apis/nodes-stats.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Lukáš Vlček * Update _api-reference/nodes-apis/nodes-stats.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Lukáš Vlček * Update _api-reference/nodes-apis/nodes-stats.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Lukáš Vlček * Update _api-reference/nodes-apis/nodes-stats.md Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Signed-off-by: Lukáš Vlček * Update nodes-stats.md Co-authored-by: Nathan Bower Signed-off-by: Lukáš Vlček --------- Signed-off-by: Lukáš Vlček Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _api-reference/nodes-apis/nodes-stats.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/_api-reference/nodes-apis/nodes-stats.md b/_api-reference/nodes-apis/nodes-stats.md index f28d30c0af..1d504aae2e 100644 --- a/_api-reference/nodes-apis/nodes-stats.md +++ b/_api-reference/nodes-apis/nodes-stats.md @@ -53,6 +53,7 @@ script_cache | Statistics about script cache. indexing_pressure | Statistics about the node's indexing pressure. shard_indexing_pressure | Statistics about shard indexing pressure. search_backpressure | Statistics related to search backpressure. +cluster_manager_throttling | Statistics related to throttled tasks on the cluster manager node. resource_usage_stats | Node-level resource usage statistics, such as CPU and JVM memory. admission_control | Statistics about admission control. caches | Statistics about caches. @@ -832,6 +833,7 @@ http.total_opened | Integer | The total number of HTTP connections the node has [indexing_pressure](#indexing_pressure) | Object | Statistics related to the node's indexing pressure. [shard_indexing_pressure](#shard_indexing_pressure) | Object | Statistics related to indexing pressure at the shard level. [search_backpressure]({{site.url}}{{site.baseurl}}/opensearch/search-backpressure#search-backpressure-stats-api) | Object | Statistics related to search backpressure. +[cluster_manager_throttling](#cluster_manager_throttling) | Object | Statistics related to throttled tasks on the cluster manager node. [resource_usage_stats](#resource_usage_stats) | Object | Statistics related to resource usage for the node. [admission_control](#admission_control) | Object | Statistics related to admission control for the node. [caches](#caches) | Object | Statistics related to caches on the node. @@ -1282,6 +1284,16 @@ total_rejections_breakup_shadow_mode.throughput_degradation_limits | Integer | T enabled | Boolean | Specifies whether the shard indexing pressure feature is turned on for the node. enforced | Boolean | If true, the shard indexing pressure runs in enforced mode (there are rejections). If false, the shard indexing pressure runs in shadow mode (there are no rejections, but statistics are recorded and can be retrieved in the `total_rejections_breakup_shadow_mode` object). Only applicable if shard indexing pressure is enabled. +### `cluster_manager_throttling` + +The `cluster_manager_throttling` object contains statistics about throttled tasks on the cluster manager node. It is populated only for the node that is currently elected as the cluster manager. + +Field | Field type | Description +:--- | :--- | :--- +stats | Object | Statistics about throttled tasks on the cluster manager node. +stats.total_throttled_tasks | Long | The total number of throttled tasks. +stats.throttled_tasks_per_task_type | Object | A breakdown of statistics by individual task type, specified as key-value pairs. The keys are individual task types, and their values represent the number of requests that were throttled. + ### `resource_usage_stats` The `resource_usage_stats` object contains the resource usage statistics. Each entry is specified by the node ID and has the following properties. From 04e6902d08185a46395ccf99ed36674520531cfa Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Wed, 29 May 2024 11:40:52 -0600 Subject: [PATCH 02/27] Add doc review changes to contributor's blocked PR #6808 (#7265) * Add doc review changes to contributors blocked PR Signed-off-by: Melissa Vagi * Update _aggregations/metric/median-absolute-deviation.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _aggregations/metric/median-absolute-deviation.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _aggregations/metric/median-absolute-deviation.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _aggregations/metric/median-absolute-deviation.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi Co-authored-by: Nathan Bower --- .../metric/median-absolute-deviation.md | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 _aggregations/metric/median-absolute-deviation.md diff --git a/_aggregations/metric/median-absolute-deviation.md b/_aggregations/metric/median-absolute-deviation.md new file mode 100644 index 0000000000..7332d7eb2f --- /dev/null +++ b/_aggregations/metric/median-absolute-deviation.md @@ -0,0 +1,158 @@ +--- +layout: default +title: Median absolute deviation +parent: Metric aggregations +grand_parent: Aggregations +nav_order: 65 +redirect_from: + - /query-dsl/aggregations/metric/median-absolute-deviation/ +--- + +# Median absolute deviation aggregations + +The `median_absolute_deviation` metric is a single-value metric aggregation that returns a median absolute deviation field. Median absolute deviation is a statistical measure of data variability. Because the median absolute deviation measures dispersion from the median, it provides a more robust measure of variability that is less affected by outliers in a dataset. + +Median absolute deviation is calculated as follows:
+median_absolute_deviation = median(|Xi - Median(Xi)|) + +The following example calculates the median absolute deviation of the `DistanceMiles` field in the sample dataset `opensearch_dashboards_sample_data_flights`: + + +```json +GET opensearch_dashboards_sample_data_flights/_search +{ + "size": 0, + "aggs": { + "median_absolute_deviation_DistanceMiles": { + "median_absolute_deviation": { + "field": "DistanceMiles" + } + } + } +} +``` +{% include copy-curl.html %} + +#### Example response + +```json +{ + "took": 35, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 10000, + "relation": "gte" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "median_absolute_deviation_distanceMiles": { + "value": 1829.8993624441966 + } + } +} +``` + +### Missing + +By default, if a field is missing or has a null value in a document, it is ignored during computation. However, you can specify a value to be used for those missing or null fields by using the `missing` parameter, as shown in the following request: + +```json +GET opensearch_dashboards_sample_data_flights/_search +{ + "size": 0, + "aggs": { + "median_absolute_deviation_distanceMiles": { + "median_absolute_deviation": { + "field": "DistanceMiles", + "missing": 1000 + } + } + } +} +``` +{% include copy-curl.html %} + +#### Example response + +```json +{ + "took": 7, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 10000, + "relation": "gte" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "median_absolute_deviation_distanceMiles": { + "value": 1829.6443646143355 + } + } +} +``` + +### Compression + +The median absolute deviation is calculated using the [t-digest](https://github.com/tdunning/t-digest/tree/main) data structure, which balances between performance and estimation accuracy through the `compression` parameter (default value: `1000`). Adjusting the `compression` value affects the trade-off between computational efficiency and precision. Lower `compression` values improve performance but may reduce estimation accuracy, while higher values enhance accuracy at the cost of increased computational overhead, as shown in the following request: + +```json +GET opensearch_dashboards_sample_data_flights/_search +{ + "size": 0, + "aggs": { + "median_absolute_deviation_DistanceMiles": { + "median_absolute_deviation": { + "field": "DistanceMiles", + "compression": 10 + } + } + } +} +``` +{% include copy-curl.html %} + +#### Example response + +```json +{ + "took": 1, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 10000, + "relation": "gte" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "median_absolute_deviation_DistanceMiles": { + "value": 1836.265614211182 + } + } +} +``` From 35a2e8cb3ddb0ec3b2dd1bb08fed14b9b71600bc Mon Sep 17 00:00:00 2001 From: Heather Halter Date: Wed, 29 May 2024 10:50:34 -0700 Subject: [PATCH 03/27] Update knn-vector-quantization.md (#7262) Removed comma after "name": "sq" Signed-off-by: Heather Halter --- _search-plugins/knn/knn-vector-quantization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_search-plugins/knn/knn-vector-quantization.md b/_search-plugins/knn/knn-vector-quantization.md index 96db75b3eb..549437f346 100644 --- a/_search-plugins/knn/knn-vector-quantization.md +++ b/_search-plugins/knn/knn-vector-quantization.md @@ -51,7 +51,7 @@ PUT /test-index "space_type": "l2", "parameters": { "encoder": { - "name": "sq", + "name": "sq" }, "ef_construction": 256, "m": 8 From 69f9cf01f84552cc55b587848c99a4b662e96619 Mon Sep 17 00:00:00 2001 From: zane-neo Date: Thu, 30 May 2024 09:03:59 +0800 Subject: [PATCH 04/27] Update documentation to add skills plugin to bundled plugins (#7269) Signed-off-by: zane-neo --- _install-and-configure/plugins.md | 1 + 1 file changed, 1 insertion(+) diff --git a/_install-and-configure/plugins.md b/_install-and-configure/plugins.md index 6b0b28769e..bbfbce9796 100644 --- a/_install-and-configure/plugins.md +++ b/_install-and-configure/plugins.md @@ -285,6 +285,7 @@ The following plugins are bundled with all OpenSearch distributions except for m | Job Scheduler | [opensearch-job-scheduler](https://github.com/opensearch-project/job-scheduler) | 1.0.0 | | k-NN | [opensearch-knn](https://github.com/opensearch-project/k-NN) | 1.0.0 | | ML Commons | [opensearch-ml](https://github.com/opensearch-project/ml-commons) | 1.3.0 | +| Skills | [opensearch-skills](https://github.com/opensearch-project/skills) | 2.12.0 | | Neural Search | [neural-search](https://github.com/opensearch-project/neural-search) | 2.4.0 | | Observability | [opensearch-observability](https://github.com/opensearch-project/observability) | 1.2.0 | | Performance Analyzer2 | [opensearch-performance-analyzer](https://github.com/opensearch-project/performance-analyzer) | 1.0.0 | From 078a3677704a41db0f7d2fe1462c807ba9540e17 Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Thu, 30 May 2024 08:56:45 -0600 Subject: [PATCH 05/27] [DOC] Add join processor documentation (#5985) * Add join processor documentation Signed-off-by: Melissa Vagi * Add examples and explanatory text Signed-off-by: Melissa Vagi * Address tech review comments Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/join.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi Co-authored-by: Nathan Bower --- _ingest-pipelines/processors/join.md | 135 +++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 _ingest-pipelines/processors/join.md diff --git a/_ingest-pipelines/processors/join.md b/_ingest-pipelines/processors/join.md new file mode 100644 index 0000000000..c2cdcfe4de --- /dev/null +++ b/_ingest-pipelines/processors/join.md @@ -0,0 +1,135 @@ +--- +layout: default +title: Join +parent: Ingest processors +nav_order: 160 +--- + +# Join processor + +The `join` processor concatenates the elements of an array into a single string value, using a specified separator between each element. It throws an exception if the provided input is not an array. + +The following is the syntax for the `join` processor: + +```json +{ + "join": { + "field": "field_name", + "separator": "separator_string" + } +} +``` +{% include copy-curl.html %} + +## Configuration parameters + +The following table lists the required and optional parameters for the `join` processor. + +Parameter | Required/Optional | Description | +|-----------|-----------|-----------| +`field` | Required | The name of the field to which the join operator is applied. Must be an array. +`separator` | Required | A string separator to use when joining field values. If not specified, then the values are concatenated without a separator. +`target_field` | Optional | The field to assign the cleaned value to. If not specified, then the field is updated in place. +`description` | Optional | A description of the processor's purpose or configuration. +`if` | Optional | Specifies to conditionally execute the processor. +`ignore_failure` | Optional | Specifies to ignore failures for the processor. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`on_failure` | Optional | Specifies to handle failures for the processor. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`tag` | Optional | An identifier for the processor. Useful for debugging and metrics. + +## Using the processor + +Follow these steps to use the processor in a pipeline. + +### Step 1: Create a pipeline + +The following query creates a pipeline named `example-join-pipeline` that uses the `join` processor to concatenate all the values of the `uri` field, separating them with the specified separator `/`: + +```json +PUT _ingest/pipeline/example-join-pipeline +{ + "description": "Example pipeline using the join processor", + "processors": [ + { + "join": { + "field": "uri", + "separator": "/" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2 (Optional): Test the pipeline + +It is recommended that you test your pipeline before you ingest documents. +{: .tip} + +To test the pipeline, run the following query: + +```json +POST _ingest/pipeline/example-join-pipeline/_simulate +{ + "docs": [ + { + "_source": { + "uri": [ + "app", + "home", + "overview" + ] + } + } + ] +} +``` +{% include copy-curl.html %} + +#### Response + +The following example response confirms that the pipeline is working as expected: + +```json +{ + "docs": [ + { + "doc": { + "_index": "_index", + "_id": "_id", + "_source": { + "uri": "app/home/overview" + }, + "_ingest": { + "timestamp": "2024-05-24T02:16:01.00659117Z" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 3: Ingest a document + +The following query ingests a document into an index named `testindex1`: + +```json +POST testindex1/_doc/1?pipeline=example-join-pipeline +{ + "uri": [ + "app", + "home", + "overview" + ] +} +``` +{% include copy-curl.html %} + +### Step 4 (Optional): Retrieve the document + +To retrieve the document, run the following query: + +```json +GET testindex1/_doc/1 +``` +{% include copy-curl.html %} From 8e049cd1b77d7f57b8d293176f521693b3f24c8c Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Thu, 30 May 2024 17:23:56 +0100 Subject: [PATCH 06/27] Security best practices - 10 points to consider - #5782 (#7113) * adding top ten security best practices Signed-off-by: leanne.laceybyrne@eliatra.com * changing nav order Signed-off-by: leanne.laceybyrne@eliatra.com * adding to best practices Signed-off-by: AntonEliatra * adding to best practices Signed-off-by: AntonEliatra * adding to best practices Signed-off-by: AntonEliatra * adding bonus tip Signed-off-by: leanne.laceybyrne@eliatra.com * updates to best practices Signed-off-by: leanne.laceybyrne@eliatra.com * integrating Darshits suggestions for improvement and reviewdog fixes Signed-off-by: leanne.laceybyrne@eliatra.com * review suggestions to grammer Signed-off-by: leanne.laceybyrne@eliatra.com * review suggestions to grammer Signed-off-by: leanne.laceybyrne@eliatra.com * review suggestions to grammer Signed-off-by: leanne.laceybyrne@eliatra.com * review suggestions to grammer Signed-off-by: leanne.laceybyrne@eliatra.com * review suggestions to grammer Signed-off-by: leanne.laceybyrne@eliatra.com * reviewdog update Signed-off-by: leanne.laceybyrne@eliatra.com * Apply suggestions from code review Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> * reviewdog updates Signed-off-by: leanne.laceybyrne@eliatra.com * Update _security/configuration/best-practices.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> * Update best-practices.md Signed-off-by: AntonEliatra * Update best-practices.md Signed-off-by: AntonEliatra * Add editorial comment Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Update best-practices.md Signed-off-by: AntonEliatra * Update _security/configuration/best-practices.md Co-authored-by: Nathan Bower Signed-off-by: AntonEliatra * Update best-practices.md Signed-off-by: AntonEliatra * Update best-practices.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: leanne.laceybyrne@eliatra.com Signed-off-by: AntonEliatra Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: AntonEliatra Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Nathan Bower --- _security/configuration/best-practices.md | 133 ++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 _security/configuration/best-practices.md diff --git a/_security/configuration/best-practices.md b/_security/configuration/best-practices.md new file mode 100644 index 0000000000..97457cdb4b --- /dev/null +++ b/_security/configuration/best-practices.md @@ -0,0 +1,133 @@ +--- +layout: default +title: Best practices +parent: Configuration +nav_order: 11 +--- + +# Best practices for OpenSearch security + +Setting up security in OpenSearch is crucial for protecting your data. Here are 10 best practices that offer clear steps for keeping your system safe. + +## 1. Use your own PKI to set up SSL/TLS + +Although using your own public key infrastructure (PKI), such as [AWS Certificate Manager](https://docs.aws.amazon.com/crypto/latest/userguide/awspki-service-acm.html), requires more initial effort, a custom PKI provides you with the flexibility needed to set up SSL/TLS in the most secure and performant way. + +### Enable SSL/TLS for node- and REST-layer traffic + +SSL/TLS is enabled by default on the transport layer, which is used for node-to-node communication. SSL/TLS is disabled by default on the REST layer. + +The following setting is required in order to enable encryption on the REST layer: + +``` +plugins.security.ssl.http.enabled: true +``` +{% include copy.html %} + + +For additional configuration options, such as specifying certificate paths, keys, and certificate authority files, refer to [Configuring TLS certificates]({{site.url}}{{site.baseurl}}/security/configuration/tls/). + +### Replace all demo certificates with your own PKI + +The certificates generated when initializing an OpenSearch cluster with `install_demo_configuration.sh` are not suitable for production. These should be replaced with your own certificates. + +You can generate custom certificates in a few different ways. One approach is to use OpenSSL, described in detail at [Generating self-signed certificates]({{site.url}}{{site.baseurl}}/security/configuration/generate-certificates/). Alternatively, there are online tools available that can simplify the certificate creation process, such as the following: + +- [SearchGuard TLS Tool](https://docs.search-guard.com/latest/offline-tls-tool) +- [TLSTool by dylandreimerink](https://github.com/dylandreimerink/tlstool) + +## 2. Prefer client certificate authentication for API authentication + +Client certificate authentication offers a secure alternative to password authentication and is more suitable for machine-to-machine interactions. It also ensures low performance overhead because the authentication occurs on the TLS level. Nearly all client software, such as curl and client libraries, support this authentication method. + +For detailed configuration instructions and additional information about client certificate authentication, see [Enabling client certificate authentication]({{site.url}}{{site.baseurl}}/security/authentication-backends/client-auth/#enabling-client-certificate-authentication). + + +## 3. Prefer SSO using SAML or OpenID for OpenSearch Dashboards authentication + +Implementing single sign-on (SSO) with protocols like SAML or OpenID for OpenSearch Dashboards authentication enhances security by delegating credential management to a dedicated system. + +This approach minimizes direct interaction with passwords in OpenSearch, streamlines authentication processes, and prevents clutter in the internal user database. For more information, go to the [SAML section of the OpenSearch documentation]({{site.url}}{{site.baseurl}}/security/authentication-backends/saml/). + +## 4. Limit the number of roles assigned to a user + +Prioritizing fewer, more intricate user roles over numerous simplistic roles enhances security and simplifies administration. + +Additional best practices for role management include: + +1. Role granularity: Define roles based on specific job functions or access requirements to minimize unnecessary privileges. +2. Regular role review: Regularly review and audit assigned roles to ensure alignment with organizational policies and access needs. + +For more information about roles, go to the documentation on [defining users and roles in OpenSearch]({{site.url}}{{site.baseurl}}/security/access-control/users-roles/). + +## 5. Verify DLS, FLS, and field masking + +If you have configured Document Level Security (DLS), Field Level Security (FLS), or field masking, make sure you double-check your role definitions, especially if a user is mapped to multiple roles. It is highly recommended that you test this by making a GET request to `_plugins/_security/authinfo`. + +The following resources provide detailed examples and additional configurations: + + - [Document-level security]({{site.url}}{{site.baseurl}}/security/access-control/document-level-security/). + - [Field-level security]({{site.url}}{{site.baseurl}}/security/access-control/field-level-security/). + - [Field masking]({{site.url}}{{site.baseurl}}/security/access-control/field-masking/). + +## 6. Use only the essentials for the audit logging configuration + +Extensive audit logging can degrade system performance due to the following: + +- Each logged event adds to the processing load. +- Audit logs can quickly grow in size, consuming significant disk space. + +To ensure optimal performance, disable unnecessary logging and be selective about which logs are used. If not strictly required by compliance regulations, consider turning off audit logging. If audit logging is essential for your cluster, configure it according to your compliance requirements. + +Whenever possible, adhere to these recommendations: + +- Set `audit.log_request_body` to `false`. +- Set `audit.resolve_bulk_requests` to `false`. +- Enable `compliance.write_log_diffs`. +- Minimize entries for `compliance.read_watched_fields`. +- Minimize entries for `compliance.write_watched_indices`. + +## 7. Consider disabling the private tenant + +In many cases, the use of private tenants is unnecessary, although this feature is enabled by default. As a result, every OpenSearch Dashboards user is provided with their own private tenant and a corresponding new index in which to save objects. This can lead to a large number of unnecessary indexes. Evaluate whether private tenants are needed in your cluster. If private tenants are not needed, disable the feature by adding the following configuration to the `config.yml` file: + +```yaml +config: + dynamic: + kibana: + multitenancy_enabled: true + private_tenant_enabled: false +``` +{% include copy.html %} + +## 8. Manage the configuration using `securityadmin.sh` + +Use `securityadmin.sh` to manage the configuration of your clusters. `securityadmin.sh` is a command-line tool provided by OpenSearch for managing security configurations. It allows administrators to efficiently manage security settings, including roles, role mappings, and other security-related configurations within an OpenSearch cluster. + +Using `securityadmin.sh` provides the following benefits: + +1. Consistency: By using `securityadmin.sh`, administrators can ensure consistency across security configurations within a cluster. This helps to maintain a standardized and secure environment. +2. Automation: `securityadmin.sh` enables automation of security configuration tasks, making it easier to deploy and manage security settings across multiple nodes or clusters. +3. Version control: Security configurations managed through `securityadmin.sh` can be version controlled using standard version control systems like Git. This facilitates tracking changes, auditing, and reverting to previous configurations. + +You can prevent configuration overrides by first creating a backup of the current configuration created using the OpenSearch Dashboards UI or the OpenSearch API by running the `securityadmin.sh` tool with the `-backup` option. This ensures that all configurations are captured before uploading the modified configuration with `securityadmin.sh`. + +For more detailed information about using `securityadmin.sh` and managing OpenSearch security configurations, refer to the following resources: +- [Applying changes to configuration files]({{site.url}}{{site.baseurl}}/security/configuration/security-admin/) +- [Modifying YAML files]({{site.url}}{{site.baseurl}}/security/configuration/yaml/) + +## 9. Replace all default passwords + +When initializing OpenSearch with the demo configuration, many default passwords are provided for internal users in `internal_users.yml`, such as `admin`, `kibanaserver`, and `logstash`. + +You should change the passwords for these users to strong, complex passwords either at startup or as soon as possible once the cluster is running. Creating password configurations is a straightforward procedure, especially when using the scripts bundled with OpenSearch, like `hash.sh` or `hash.bat`, located in the `plugin/OpenSearch security/tools` directory. + +The `kibanaserver` user is a crucial component that allows OpenSearch Dashboards to communicate with the OpenSearch cluster. By default, this user is preconfigured with a default password in the demo configuration. This should be replaced with a strong, unique password in the OpenSearch configuration, and the `opensearch_dashboards.yml` file should be updated to reflect this change. + + +## 10. Getting help + +If you need additional help, you can do the following: + +- Create an issue on GitHub at [OpenSearch-project/security](https://github.com/opensearch-project/security/security) or [OpenSearch-project/OpenSearch](https://github.com/opensearch-project/OpenSearch/security). +- Ask a question on the [OpenSearch forum](https://forum.opensearch.org/tag/cve). From 7f51034305ba7c773504d4093bc07689e8e04d91 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Thu, 30 May 2024 13:55:29 -0400 Subject: [PATCH 07/27] Explain k in approximate k-NN (#7194) * Explain k in approximate k-NN Signed-off-by: Fanit Kolchina * Additional info Signed-off-by: Fanit Kolchina * Delete engine row in table Signed-off-by: Fanit Kolchina * Add a clarification to the table Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Update _search-plugins/knn/approximate-knn.md Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --------- Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Nathan Bower --- _search-plugins/knn/approximate-knn.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/_search-plugins/knn/approximate-knn.md b/_search-plugins/knn/approximate-knn.md index 7d3e119349..39e9da7525 100644 --- a/_search-plugins/knn/approximate-knn.md +++ b/_search-plugins/knn/approximate-knn.md @@ -127,10 +127,25 @@ GET my-knn-index-1/_search } ``` -`k` is the number of neighbors the search of each graph will return. You must also include the `size` option, which -indicates how many results the query actually returns. The plugin returns `k` amount of results for each shard -(and each segment) and `size` amount of results for the entire query. The plugin supports a maximum `k` value of 10,000. -Starting in OpenSearch 2.14, in addition to using the `k` variable, both the `min_score` and `max_distance` variables can be used for [radial search]({{site.url}}{{site.baseurl}}/search-plugins/knn/radial-search-knn/). +### The number of returned results + +In the preceding query, `k` represents the number of neighbors returned by the search of each graph. You must also include the `size` option, indicating the final number of results that you want the query to return. + +For the NMSLIB and Faiss engines, `k` represents the maximum number of documents returned for all segments of a shard. For the Lucene engine, `k` represents the number of documents returned for a shard. The maximum value of `k` is 10,000. + +For any engine, each shard returns `size` results to the coordinator node. Thus, the total number of results that the coordinator node receives is `size * number of shards`. After the coordinator node consolidates the results received from all nodes, the query returns the top `size` results. + +The following table provides examples of the number of results returned by various engines in several scenarios. For these examples, assume that the number of documents contained in the segments and shards is sufficient to return the number of results specified in the table. + +`size` | `k` | Number of primary shards | Number of segments per shard | Number of returned results, Faiss/NMSLIB | Number of returned results, Lucene +10 | 1 | 1 | 4 | 4 | 1 +10 | 10 | 1 | 4 | 10 | 10 +10 | 1 | 2 | 4 | 8 | 2 + +The number of results returned by Faiss/NMSLIB differs from the number of results returned by Lucene only when `k` is smaller than `size`. If `k` and `size` are equal, all engines return the same number of results. + +Starting in OpenSearch 2.14, you can use `k`, `min_score`, or `max_distance` for [radial search]({{site.url}}{{site.baseurl}}/search-plugins/knn/radial-search-knn/). + ### Building a k-NN index from a model For some of the algorithms that we support, the native library index needs to be trained before it can be used. It would be expensive to training every newly created segment, so, instead, we introduce the concept of a *model* that is used to initialize the native library index during segment creation. A *model* is created by calling the [Train API]({{site.url}}{{site.baseurl}}/search-plugins/knn/api#train-a-model), passing in the source of training data as well as the method definition of the model. Once training is complete, the model will be serialized to a k-NN model system index. Then, during indexing, the model is pulled from this index to initialize the segments. From 3009d16134d02835751d1e29a3bf080e2cd8a868 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Thu, 30 May 2024 13:56:07 -0400 Subject: [PATCH 08/27] Add version to PR template (#7276) Signed-off-by: Fanit Kolchina --- .github/PULL_REQUEST_TEMPLATE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7eccae7052..bbf3b8d035 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,8 @@ _Describe what this change achieves._ ### Issues Resolved _List any issues this PR will resolve, e.g. Closes [...]._ +### Version +_List the OpenSearch version to which this PR applies, e.g. 2.14, 2.12--2.14, or all._ ### Checklist - [ ] By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and subject to the [Developers Certificate of Origin](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). From 6732d2f9e15b787a3577b4a1644783b66552ed57 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Thu, 30 May 2024 14:01:12 -0400 Subject: [PATCH 09/27] Remove formatting from front matter in foreach processor (#7277) Signed-off-by: Fanit Kolchina --- _ingest-pipelines/processors/foreach.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/_ingest-pipelines/processors/foreach.md b/_ingest-pipelines/processors/foreach.md index 72a0ed1420..d0f962e618 100644 --- a/_ingest-pipelines/processors/foreach.md +++ b/_ingest-pipelines/processors/foreach.md @@ -1,11 +1,13 @@ --- layout: default -title: `foreach` +title: Foreach parent: Ingest processors nav_order: 110 --- -# `foreach` processor + +# Foreach processor + The `foreach` processor is used to iterate over a list of values in an input document and apply a transformation to each value. This can be useful for tasks like processing all the elements in an array consistently, such as converting all elements in a string to lowercase or uppercase. From 9af765f6513279f456ab5c182a1d940c737a3bb1 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Thu, 30 May 2024 15:07:26 -0400 Subject: [PATCH 10/27] Add table header to approximate k-NN (#7280) Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> --- _search-plugins/knn/approximate-knn.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_search-plugins/knn/approximate-knn.md b/_search-plugins/knn/approximate-knn.md index 39e9da7525..c0a9557728 100644 --- a/_search-plugins/knn/approximate-knn.md +++ b/_search-plugins/knn/approximate-knn.md @@ -138,6 +138,7 @@ For any engine, each shard returns `size` results to the coordinator node. Thus, The following table provides examples of the number of results returned by various engines in several scenarios. For these examples, assume that the number of documents contained in the segments and shards is sufficient to return the number of results specified in the table. `size` | `k` | Number of primary shards | Number of segments per shard | Number of returned results, Faiss/NMSLIB | Number of returned results, Lucene +:--- | :--- | :--- | :--- | :--- | :--- 10 | 1 | 1 | 4 | 4 | 1 10 | 10 | 1 | 4 | 10 | 10 10 | 1 | 2 | 4 | 8 | 2 @@ -326,4 +327,4 @@ included in the distance function. With cosine similarity, it is not valid to pass a zero vector (`[0, 0, ...]`) as input. This is because the magnitude of such a vector is 0, which raises a `divide by 0` exception in the corresponding formula. Requests containing the zero vector will be rejected and a corresponding exception will be thrown. -{: .note } \ No newline at end of file +{: .note } From 7c4fe91ec9a16bb75e33726c2c86441edd56e08a Mon Sep 17 00:00:00 2001 From: Nathan Bower Date: Fri, 31 May 2024 09:03:47 -0400 Subject: [PATCH 11/27] Add quotation mark guidance (#7283) Signed-off-by: natebower <102320899+natebower@users.noreply.github.com> --- STYLE_GUIDE.md | 80 ++++++++++++++++++++++++++------------------------ TERMS.md | 76 +++++++++++++++++++++++------------------------ 2 files changed, 79 insertions(+), 77 deletions(-) diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index d0602c92e1..e33f49697d 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -25,16 +25,16 @@ The following naming conventions should be observed in OpenSearch Project conten Capitalize product names. The OpenSearch Project has three products: OpenSearch, OpenSearch Dashboards, and Data Prepper. For example: -* “To install *OpenSearch*, download the Docker image.” -* “To access *OpenSearch Dashboards*, open your browser and navigate to http://localhost:5601/app/home.” -* “*Data Prepper* contains the following components:” +* "To install *OpenSearch*, download the Docker image." +* "To access *OpenSearch Dashboards*, open your browser and navigate to http://localhost:5601/app/home." +* "*Data Prepper* contains the following components:" Capitalize the names of clients and tools. For example: -* “The OpenSearch *Python* client provides a more natural syntax for interacting with your cluster.” -* “The *Go* client retries requests for a maximum of three times by default.” -* “The *OpenSearch Kubernetes Operator* is an open-source Kubernetes operator that helps automate the deployment and provisioning of OpenSearch and OpenSearch Dashboards in a containerized environment.” -* “You can send events to *Logstash* from many different sources.” +* "The OpenSearch *Python* client provides a more natural syntax for interacting with your cluster." +* "The *Go* client retries requests for a maximum of three times by default." +* "The *OpenSearch Kubernetes Operator* is an open-source Kubernetes operator that helps automate the deployment and provisioning of OpenSearch and OpenSearch Dashboards in a containerized environment." +* "You can send events to *Logstash* from many different sources." #### Features @@ -42,17 +42,17 @@ Features are the individual building blocks of user experiences, reflect the fun Use lowercase when referring to features, unless you are referring to a formally named feature that is specific to OpenSearch. For example: -* “The Notifications plugin provides a central location for all of your *notifications* from OpenSearch plugins.” -* “*Remote-backed storage* is an experimental feature. Therefore, we do not recommend the use of *remote-backed storage* in a production environment.” -* “You can take and restore *snapshots* using the snapshot API.” -* “You can use the *VisBuilder* visualization type in OpenSearch Dashboards to create data visualizations by using a drag-and-drop gesture” (You can refer to VisBuilder alone or qualify the term with “visualization type”). +* "The Notifications plugin provides a central location for all of your *notifications* from OpenSearch plugins." +* "*Remote-backed storage* is an experimental feature. Therefore, we do not recommend the use of *remote-backed storage* in a production environment." +* "You can take and restore *snapshots* using the snapshot API." +* "You can use the *VisBuilder* visualization type in OpenSearch Dashboards to create data visualizations by using a drag-and-drop gesture" (You can refer to VisBuilder alone or qualify the term with "visualization type"). #### Plugin names A plugin is a feature or distinct component that extends the functionality of OpenSearch. For now, capitalize plugin names, but use *plugin* sparingly. The concept of plugins will become obsolete once we re-architect the product. For example: -* “Interaction with the *ML Commons* plugin occurs through either the REST API or [ad](https://opensearch.org/docs/latest/search-plugins/sql/ppl/functions#ad) and [kmeans](https://opensearch.org/docs/latest/search-plugins/sql/ppl/functions#kmeans) Piped Processing Language (PPL) commands.” -* “Use the *Neural Search* plugin to integrate ML language models into your search workloads.” +* "Interaction with the *ML Commons* plugin occurs through either the REST API or [ad](https://opensearch.org/docs/latest/search-plugins/sql/ppl/functions#ad) and [kmeans](https://opensearch.org/docs/latest/search-plugins/sql/ppl/functions#kmeans) Piped Processing Language (PPL) commands." +* "Use the *Neural Search* plugin to integrate ML language models into your search workloads." ### Voice and tone @@ -66,9 +66,9 @@ Whenever possible, use the active voice instead of the passive voice. The passiv Refer to the reader as _you_ (second person), and refer to the OpenSearch Project as _we_ (first person). If there are multiple authors for a blog post, you can use _we_ to refer to the authors as individuals. Do not refer to the OpenSearch Project or to the AWS personnel working on the project as a *team*, as this implies differentiation within the community. -In most cases, try to describe the actions that the user takes rather than contextualizing from the feature perspective. For example, use phrases such as “With this feature, you can...” or “Use this feature to...” instead of saying a feature *allows*, *enables*, or *lets* the user do something. +In most cases, try to describe the actions that the user takes rather than contextualizing from the feature perspective. For example, use phrases such as "With this feature, you can..." or "Use this feature to..." instead of saying a feature *allows*, *enables*, or *lets* the user do something. -For procedures or instructions, ensure that action is taken by the user (“Then you can stop the container...”) rather than the writer (“We also have to stop the container...”). Reserve the first-person plural for speaking as the OpenSearch Project, with recommendations, warnings, or explanations. +For procedures or instructions, ensure that action is taken by the user ("Then you can stop the container...") rather than the writer ("We also have to stop the container..."). Reserve the first-person plural for speaking as the OpenSearch Project, with recommendations, warnings, or explanations. In general, use the present tense. Use the future tense only when an event happens later than, not immediately after, the action under discussion. @@ -84,11 +84,11 @@ Avoid excessive words, such as please. Be courteous but not wordy. Extra detail | Personality trait | Description | Guidance | | :--------- | :------- | :------ | -| **Clear and precise** | The OpenSearch Project understands that our community works, develops, and builds in roles and organizations that require precise thinking and thorough documentation. We strive to use precise language—to clearly say what we mean without leaving ideas open to interpretation, to support our assertions with facts and figures, and to provide credible and current (third-party) references where called for.

We communicate in plain, direct language that is easily understood. Complex concepts are introduced in a concise, unambiguous way. High-level content is supported by links to more in-depth or technical content that users can engage with at their convenience. | - Write with clarity and choose words carefully. Think about the audience and how they might interpret your assertions.
- Be specific. Avoid estimates or general claims when exact data can be provided.
- Support claims with data. If something is “faster” or “more accurate,” say how much.
- When citing third-party references, include direct links. | -| **Transparent and open** | As an open-source project, we exchange information with the community in an accessible and transparent manner. We publish our product plans in the open on GitHub, share relevant and timely information related to the project through our forum and/or our blog, and engage in open dialogues related to product and feature development in the public sphere. Anyone can view our roadmap, raise a question or an issue, or participate in our community meetings. | - Tell a complete story. If you’re walking the reader through a solution or sharing news, don’t skip important information.
- Be forthcoming. Communicate time-sensitive news and information in a thorough and timely manner.
- If there’s something the reader needs to know, say it up front. Don’t “bury the lede.” | -| **Collaborative and supportive** | We’re part of a community that is here to help. We aim to be resourceful on behalf of the community and encourage others to do the same. To facilitate an open exchange of ideas, we provide forums through which the community can ask and answer one another’s questions. | - Use conversational language that welcomes and engages the audience. Have a dialogue.
- Invite discussion and feedback. We have several mechanisms for open discussion, including requests for comment (RFCs), a [community forum](https://forum.opensearch.org/), and [community meetings](https://www.meetup.com/OpenSearch/). -| **Trustworthy and personable** | We stay grounded in the facts and the data. We do not overstate what our products are capable of. We demonstrate our knowledge in a humble but authoritative way and reliably deliver what we promise. We provide mechanisms and support that allow the audience to explore our products for themselves, demonstrating that our actions consistently match our words.

We speak to the community in a friendly, welcoming, judgment-free way so that our audience perceives us as being approachable. Our content is people oriented and focused on empowering the user directly. | - Claims and assertions should be grounded in facts and data and supported accordingly.
- Do not exaggerate or overstate. Let the facts and results speak for themselves.
- Encourage the audience to explore our products for themselves. Offer guidance to help them do so.
- Write directly and conversationally. Have a dialogue with your audience. Imagine writing as if you’re speaking directly to the person for whom you’re creating content.
- Write from the community, for the community. Anyone creating or consuming content about OpenSearch is a member of the same group, with shared interest in learning about and building better search and analytics solutions. | -| **Inclusive and accessible** | As an open-source project, the OpenSearch Project is for everyone, and we are inclusive. We value the diversity of backgrounds and perspectives in the OpenSearch community and welcome feedback from any contributor, regardless of their experience level.

We design and create content so that people with disabilities can perceive, navigate, and interact with it. This ensures that our documentation is available and useful for everyone and helps improve the general usability of content.

We understand our community is international and our writing takes that into account. We use plain language that avoids idioms and metaphors that may not be clear to the broader community. | - Use inclusive language to connect with the diverse and global OpenSearch Project audience.
- Be careful with our word choices.
- Avoid [sensitive terms](https://github.com/opensearch-project/documentation-website/blob/main/STYLE_GUIDE.md#sensitive-terms).
- Don't use [offensive terms](https://github.com/opensearch-project/documentation-website/blob/main/STYLE_GUIDE.md#offensive-terms).
- Don't use ableist or sexist language or language that perpetuates racist structures or stereotypes.
- Links: Use link text that adequately describes the target page. For example, use the title of the target page instead of “here” or “this link.” In most cases, a formal cross-reference (the title of the page you’re linking to) is the preferred style because it provides context and helps readers understand where they’re going when they choose the link.
- Images:
  - Add introductory text that provides sufficient context for each image.
  - Add ALT text that describes the image for screen readers.
- Procedures: Not everyone uses a mouse, so use device-independent verbs; for example, use “choose” instead of “click.”
- Location: When you’re describing the location of something else in your content, such as an image or another section, use words such as “preceding,” “previous,” or “following” instead of “above” and “below.” +| **Clear and precise** | The OpenSearch Project understands that our community works, develops, and builds in roles and organizations that require precise thinking and thorough documentation. We strive to use precise language—to clearly say what we mean without leaving ideas open to interpretation, to support our assertions with facts and figures, and to provide credible and current (third-party) references where called for.

We communicate in plain, direct language that is easily understood. Complex concepts are introduced in a concise, unambiguous way. High-level content is supported by links to more in-depth or technical content that users can engage with at their convenience. | - Write with clarity and choose words carefully. Think about the audience and how they might interpret your assertions.
- Be specific. Avoid estimates or general claims when exact data can be provided.
- Support claims with data. If something is "faster" or "more accurate," say how much.
- When citing third-party references, include direct links. | +| **Transparent and open** | As an open-source project, we exchange information with the community in an accessible and transparent manner. We publish our product plans in the open on GitHub, share relevant and timely information related to the project through our forum and/or our blog, and engage in open dialogues related to product and feature development in the public sphere. Anyone can view our roadmap, raise a question or an issue, or participate in our community meetings. | - Tell a complete story. If you're walking the reader through a solution or sharing news, don't skip important information.
- Be forthcoming. Communicate time-sensitive news and information in a thorough and timely manner.
- If there's something the reader needs to know, say it up front. Don't "bury the lede." | +| **Collaborative and supportive** | We're part of a community that is here to help. We aim to be resourceful on behalf of the community and encourage others to do the same. To facilitate an open exchange of ideas, we provide forums through which the community can ask and answer one another's questions. | - Use conversational language that welcomes and engages the audience. Have a dialogue.
- Invite discussion and feedback. We have several mechanisms for open discussion, including requests for comment (RFCs), a [community forum](https://forum.opensearch.org/), and [community meetings](https://www.meetup.com/OpenSearch/). +| **Trustworthy and personable** | We stay grounded in the facts and the data. We do not overstate what our products are capable of. We demonstrate our knowledge in a humble but authoritative way and reliably deliver what we promise. We provide mechanisms and support that allow the audience to explore our products for themselves, demonstrating that our actions consistently match our words.

We speak to the community in a friendly, welcoming, judgment-free way so that our audience perceives us as being approachable. Our content is people oriented and focused on empowering the user directly. | - Claims and assertions should be grounded in facts and data and supported accordingly.
- Do not exaggerate or overstate. Let the facts and results speak for themselves.
- Encourage the audience to explore our products for themselves. Offer guidance to help them do so.
- Write directly and conversationally. Have a dialogue with your audience. Imagine writing as if you're speaking directly to the person for whom you're creating content.
- Write from the community, for the community. Anyone creating or consuming content about OpenSearch is a member of the same group, with shared interest in learning about and building better search and analytics solutions. | +| **Inclusive and accessible** | As an open-source project, the OpenSearch Project is for everyone, and we are inclusive. We value the diversity of backgrounds and perspectives in the OpenSearch community and welcome feedback from any contributor, regardless of their experience level.

We design and create content so that people with disabilities can perceive, navigate, and interact with it. This ensures that our documentation is available and useful for everyone and helps improve the general usability of content.

We understand our community is international and our writing takes that into account. We use plain language that avoids idioms and metaphors that may not be clear to the broader community. | - Use inclusive language to connect with the diverse and global OpenSearch Project audience.
- Be careful with our word choices.
- Avoid [sensitive terms](https://github.com/opensearch-project/documentation-website/blob/main/STYLE_GUIDE.md#sensitive-terms).
- Don't use [offensive terms](https://github.com/opensearch-project/documentation-website/blob/main/STYLE_GUIDE.md#offensive-terms).
- Don't use ableist or sexist language or language that perpetuates racist structures or stereotypes.
- Links: Use link text that adequately describes the target page. For example, use the title of the target page instead of "here" or "this link." In most cases, a formal cross-reference (the title of the page you're linking to) is the preferred style because it provides context and helps readers understand where they're going when they choose the link.
- Images:
  - Add introductory text that provides sufficient context for each image.
  - Add ALT text that describes the image for screen readers.
- Procedures: Not everyone uses a mouse, so use device-independent verbs; for example, use "choose" instead of "click."
- Location: When you're describing the location of something else in your content, such as an image or another section, use words such as "preceding," "previous," or "following" instead of "above" and "below." ## Style guidelines @@ -98,7 +98,7 @@ The following guidelines should be observed in OpenSearch Project content. Spell out acronyms the first time that you use them on a page and follow them with the acronym in parentheses. Use the format `spelled-out term (acronym)`. On subsequent use, use the acronym alone. -Do not capitalize the spelled-out form of an acronym unless the spelled-out form is a proper noun or the community generally capitalizes it. In all cases, our usage should reflect the community’s usage. +Do not capitalize the spelled-out form of an acronym unless the spelled-out form is a proper noun or the community generally capitalizes it. In all cases, our usage should reflect the community's usage. In general, spell out acronyms once on a page. However, you can spell them out more often for clarity. @@ -167,7 +167,7 @@ The following table lists acronyms that you don't need to spell out. Calling out code within a sentence or code block makes it clear to readers which items are code specific. The following is general guidance about using code examples and when to use `code font`: * In Markdown, use single backticks (`` ` ``) for inline code formatting and triple backticks (```` ``` ````) for code blocks. For example, writing `` `discovery.type` `` in Markdown will render as `discovery.type`. A line containing three backticks should be included both before and after an example code block. -* In sentences, use code font for things relating to code, for example, “The `from` and `size` parameters are stateless, so the results are based on the latest available data.” +* In sentences, use code font for things relating to code, for example, "The `from` and `size` parameters are stateless, so the results are based on the latest available data." * Use lead-in sentences to clarify the example. Exception: API examples, for which a caption-style lead-in (heading 4) is sufficient. * Use the phrase *such as* for brief examples within a sentence. * Use language-specific indentation in code examples. @@ -220,7 +220,7 @@ When using lead-in sentences, summarize, clarify, or refer to the example that f #### Referring to a variable or placeholder -When introducing a code or command line example that refers to a variable or placeholder in the example, be direct by including the variable or placeholder name in the text. Surround the variable or placeholder name with angle brackets (`<` and `>`), for example, ``. Don't refer to the variable or placeholder by its color or format because these can change. If variable or placeholder texts have a lot in common and there are several for the user to complete, be direct by including a “template” for the input in the replaceable text. +When introducing a code or command line example that refers to a variable or placeholder in the example, be direct by including the variable or placeholder name in the text. Surround the variable or placeholder name with angle brackets (`<` and `>`), for example, ``. Don't refer to the variable or placeholder by its color or format because these can change. If variable or placeholder texts have a lot in common and there are several for the user to complete, be direct by including a "template" for the input in the replaceable text. In the following example, replace `` with your own information: @@ -247,7 +247,7 @@ When introducing a code or command line example that refers to a variable or pla - Add ALT text that describes the image for screen readers. -- When you’re describing the location of an image, use words such as *preceding*, *previous*, or *following* instead of *above* and *below*. +- When you're describing the location of an image, use words such as *preceding*, *previous*, or *following* instead of *above* and *below*. - Text that introduces an image should be a complete sentence and end with a period, not a colon. @@ -265,17 +265,17 @@ When introducing a code or command line example that refers to a variable or pla ### Lists The following guidelines apply to all list types: -- Make lists parallel in content and structure. Don’t mix single words with phrases, don’t start some phrases with a noun and others with a verb, and don’t mix verb forms. +- Make lists parallel in content and structure. Don't mix single words with phrases, don't start some phrases with a noun and others with a verb, and don't mix verb forms. - Present the items in alphabetical order if the order of items is arbitrary. - Capitalize the first letter of the first word of each list item. -- If the list is simple, you don’t need end punctuation for the list items. +- If the list is simple, you don't need end punctuation for the list items. - If the list has a mixture of phrases and sentences, punctuate each list item. - Punctuate each list item with a period if a list item has more than one sentence. - Punctuate list items consistently. If at least one item in a list requires a period, use a period for all items in that list. - Introductory sentences are required for lists. - Introductory sentences should be complete sentences. - Introductory sentences should end with a colon. -- Don’t use semicolons, commas, or conjunctions (like and or or) at the end of list items. +- Don't use semicolons, commas, or conjunctions (like and or or) at the end of list items. ### Numbers and measurement @@ -330,15 +330,17 @@ We follow a slightly modified version of the _Microsoft Writing Style Guide_ gui ### Punctuation and capitalization +- Use straight quotation marks (") and apostrophes (') instead of curly ones (”, ’). + - Use only one space after a period. -- Use contractions carefully for a more casual tone. Use common contractions. Avoid future tense (I’ll), archaic (‘twas), colloquial (ain’t), or compound (couldn’t’ve) contractions. +- Use contractions carefully for a more casual tone. Use common contractions. Avoid future tense (I'll), archaic ('twas), colloquial (ain't), or compound (couldn't've) contractions. - Use sentence case for titles, headings, and table headers. Titles of standalone documents may use title case. - Use lowercase for nouns and noun phrases that are not proper nouns; for example, *big data*. This style follows the standard rules of American English grammar. -- For plural forms of nouns that end in “s”, form the possessive case by adding only an apostrophe. +- For plural forms of nouns that end in "s", form the possessive case by adding only an apostrophe. - When a colon introduces a list of words, a phrase, or other sentence fragment, the first word following the colon is lowercased unless it is a proper name. When a colon introduces one or more complete sentences, the first word following it is capitalized. When text introduces a table or image, it should be a complete sentence and end with a period, not a colon. @@ -351,9 +353,9 @@ We follow a slightly modified version of the _Microsoft Writing Style Guide_ gui - An en dash (–) is the width of an uppercase N. In ranges, do not include spacing on either side. Use an en dash to indicate ranges in values and dates, separate a bullet heading from the following text in a list, or separate an open compound adjective (two compounds, only one of which is hyphenated) from the word that it modifies. -- Words with prefixes are normally closed (no hyphen), whether they are nouns, verbs, adjectives, or adverbs. Note that some industry terms don’t follow this hyphenation guidance. For example, *Command Line Interface* and *high performance computing* aren’t hyphenated, and *machine learning* isn’t hyphenated when used as an adjective. Other terms are hyphenated to improve readability. Examples include *non-production*, *post-migration*, and *pre-migration*. +- Words with prefixes are normally closed (no hyphen), whether they are nouns, verbs, adjectives, or adverbs. Note that some industry terms don't follow this hyphenation guidance. For example, *Command Line Interface* and *high performance computing* aren't hyphenated, and *machine learning* isn't hyphenated when used as an adjective. Other terms are hyphenated to improve readability. Examples include *non-production*, *post-migration*, and *pre-migration*. -- In general, comparative or superlative modifiers with “more,” “most,” “less,” or “least” don’t require hyphens. Use one only if it’s needed to avoid ambiguity. +- In general, comparative or superlative modifiers with "more," "most," "less," or "least" don't require hyphens. Use one only if it's needed to avoid ambiguity. - The ampersand (&) should never be used in a sentence as a replacement for the word and. An exception to this is in acronyms where the ampersand is commonly used, such as in Operations & Maintenance (O&M). @@ -418,7 +420,7 @@ Follow these basic guidelines when writing UI text. ##### Style -* Keep it short. Users don’t want to read dense text. Remember that UI text can expand by 30% when it’s translated into other languages. +* Keep it short. Users don't want to read dense text. Remember that UI text can expand by 30% when it's translated into other languages. * Keep it simple. Try to use simple sentences (one subject, one verb, one main clause and idea) rather than compound or complex sentences. * Prefer active voice over passive voice. For example, "You can attach up to 10 policies" is active voice, and "Up to 10 policies can be attached" is passive voice. * Use device-agnostic language rather than mouse-specific language. For example, use _choose_ instead of _click_ (exception: use _select_ for checkboxes). @@ -447,9 +449,9 @@ Follow these basic guidelines when writing UI text. * Failure recovery * Migrating from one cluster to another -* Use the serial (Oxford) comma. For example, “issues, bug fixes, and features”, not “issues, bug fixes and features”. -* Don’t use the ampersand (&). -* Avoid Latinisms, such as _e.g._, _i.e._, or _etc._ Instead of _e.g._, use _for example_ or _such as_. Instead of _i.e._, use _that is_ or _specifically_. Generally speaking, _etc._ and its equivalents (such as _and more_ or _and so on_) aren’t necessary. +* Use the serial (Oxford) comma. For example, "issues, bug fixes, and features", not "issues, bug fixes and features". +* Don't use the ampersand (&). +* Avoid Latinisms, such as _e.g._, _i.e._, or _etc._ Instead of _e.g._, use _for example_ or _such as_. Instead of _i.e._, use _that is_ or _specifically_. Generally speaking, _etc._ and its equivalents (such as _and more_ or _and so on_) aren't necessary. ## Special considerations for blog posts @@ -463,7 +465,7 @@ Posts tend to be more personable, unlike technical documentation. Ask questions, **Keep it short.** -Deep topics don’t necessarily require long posts. Shorter, more focused posts are easier for readers to digest. Consider breaking a long post into a series, which can also encourage repeat visitors to the blog channel. +Deep topics don't necessarily require long posts. Shorter, more focused posts are easier for readers to digest. Consider breaking a long post into a series, which can also encourage repeat visitors to the blog channel. **Avoid redundancy.** @@ -483,7 +485,7 @@ Our philosophy is that we positively impact users and our industry as we proacti The following terms may be associated with unconscious racial bias, violence, or politically sensitive topics and should not appear in OpenSearch Project content, if possible. Note that many of these terms are still present but on a path to not being supported. For example, `slave` was removed from the Python programming language in 2018, and the open-source community continues to work toward replacing these terms. -| Don’t use | Guidance/Use instead | +| Don't use | Guidance/Use instead | |----------------|-----------------------------| | abort | Don't use because it has unpleasant associations and is unnecessarily harsh sounding. Use *stop*, *end*, or *cancel* instead. | | black day | blocked day | @@ -497,7 +499,7 @@ The following terms may be associated with unconscious racial bias, violence, or ### Sensitive terms -The following terms may be problematic *in some contexts*. This doesn’t mean that you can’t use these terms—just be mindful of their potential associations when using them, and avoid using them to refer to people. +The following terms may be problematic *in some contexts*. This doesn't mean that you can't use these terms—just be mindful of their potential associations when using them, and avoid using them to refer to people. | Avoid using | Guidance/Use instead | |--------------------------|-------------------------------------| @@ -506,5 +508,5 @@ The following terms may be problematic *in some contexts*. This doesn’t mean t ## Trademark policy -The “OpenSearch” word mark should be used in its exact form and not abbreviated or combined with any other word or words (e.g., “OpenSearch” software rather than “OPNSRCH” or “OpenSearch-ified”). See the [OpenSearch Trademark Policy](https://opensearch.org/trademark-usage.html) for more information. Also refer to the policy and to the [OpenSearch Brand Guidelines](https://opensearch.org/brand.html) for guidance regarding the use of the OpenSearch logo. When using another party’s logo, refer to that party’s trademark guidelines. +The "OpenSearch" word mark should be used in its exact form and not abbreviated or combined with any other word or words (e.g., "OpenSearch" software rather than "OPNSRCH" or "OpenSearch-ified"). See the [OpenSearch Trademark Policy](https://opensearch.org/trademark-usage.html) for more information. Also refer to the policy and to the [OpenSearch Brand Guidelines](https://opensearch.org/brand.html) for guidance regarding the use of the OpenSearch logo. When using another party's logo, refer to that party's trademark guidelines. diff --git a/TERMS.md b/TERMS.md index e12cc171ed..7de56f9275 100644 --- a/TERMS.md +++ b/TERMS.md @@ -1,6 +1,6 @@ # OpenSearch terms -This is how we use our terms, but we’re always open to hearing your suggestions. +This is how we use our terms, but we're always open to hearing your suggestions. ## A @@ -38,7 +38,7 @@ A plugin that notifies you when data from one or more OpenSearch indexes meets c Use allow when the user must have security permissions in order to complete the task. -Avoid using allow to refer to making something possible for the user. Instead, rewrite to focus on what’s important from the user’s point of view. +Avoid using allow to refer to making something possible for the user. Instead, rewrite to focus on what's important from the user's point of view. **allow list** @@ -101,7 +101,7 @@ Use on first appearance. On subsequent appearances, *Signature Version 4* may be **below** -Use only for physical space or screen descriptions, such as “the outlet below the vent,” or “the button below the bar pane.” +Use only for physical space or screen descriptions, such as "the outlet below the vent," or "the button below the bar pane." For orientation within a document, use *following* or *later*. @@ -134,11 +134,11 @@ OpenSearch style: **bottom** -Use only as a general screen reference, such as “scroll to the bottom of the page.” Don’t use for window, page, or pane references to features or controls. Rather, use *lower* instead. For example, you can use the following wording: “Choose the button on the lower left.” +Use only as a general screen reference, such as "scroll to the bottom of the page." Don't use for window, page, or pane references to features or controls. Rather, use *lower* instead. For example, you can use the following wording: "Choose the button on the lower left." **browse** -Use when referring to scanning information or browsing the web. Don’t use when describing how to navigate to a particular item on our site or a computer. Instead, use *see* or *navigate to*. +Use when referring to scanning information or browsing the web. Don't use when describing how to navigate to a particular item on our site or a computer. Instead, use *see* or *navigate to*. **build (n., v.)** @@ -152,7 +152,7 @@ certificate authority **certs, certificates** -Use _certificates_ on first mention. It’s OK to use _certs_ thereafter. +Use _certificates_ on first mention. It's OK to use _certs_ thereafter. **checkbox, checkboxes** @@ -186,13 +186,13 @@ A plugin that replicates indexes, mappings, and metadata from one OpenSearch clu **cyber** -Except when dictated by open standards, use as a prefix in a closed compound: don’t use spaces or hyphens between _cyber_ and the rest of the word. +Except when dictated by open standards, use as a prefix in a closed compound: don't use spaces or hyphens between _cyber_ and the rest of the word. ## D **data** -Use data is, not data are. Don’t use datas. Use pieces of data or equivalent to describe individual items within a set of data. +Use data is, not data are. Don't use datas. Use pieces of data or equivalent to describe individual items within a set of data. **data center** @@ -210,8 +210,8 @@ Two words when used generically, but one word when referring to the VMware produ Use one of the following date formats: -- When a human-readable date format is preferred, spell out the date using the Month D, YYYY format (for example, _October 1, 2022_). Do not use an ordinal number for the day (use _1_, not _1st_). If the context is clear, you can omit the year on subsequent mention. If the specific day isn’t known, use the Month YYYY format (for example, _October 2022_). -- When a numeric, lexicographically sortable date is required, use the YYYY-MM-DD format (for example, _2022-10-01_). Make sure to add a zero (0) in front of a single-digit month and day. This is the ISO 8601 standard date format. Make sure also that you use a hyphen (-) and avoid omitting the year. Doing so avoids the ambiguity that’s caused by the common, locally used formats of MM/DD and DD/MM. +- When a human-readable date format is preferred, spell out the date using the Month D, YYYY format (for example, _October 1, 2022_). Do not use an ordinal number for the day (use _1_, not _1st_). If the context is clear, you can omit the year on subsequent mention. If the specific day isn't known, use the Month YYYY format (for example, _October 2022_). +- When a numeric, lexicographically sortable date is required, use the YYYY-MM-DD format (for example, _2022-10-01_). Make sure to add a zero (0) in front of a single-digit month and day. This is the ISO 8601 standard date format. Make sure also that you use a hyphen (-) and avoid omitting the year. Doing so avoids the ambiguity that's caused by the common, locally used formats of MM/DD and DD/MM. **demilitarized zone (DMZ)** @@ -219,7 +219,7 @@ Avoid using. Use *perimeter network* or *perimeter zone* instead. **deny list** -Use to describe a list of items that aren’t allowed (blocked). Do not use _blacklist_. +Use to describe a list of items that aren't allowed (blocked). Do not use _blacklist_. **disable** @@ -234,7 +234,7 @@ Do not use *disable* to refer to users. **double-click** -Always hyphenated. Don’t use _double click_. +Always hyphenated. Don't use _double click_. **downvote** @@ -242,7 +242,7 @@ Always hyphenated. Don’t use _double click_. **due to** -Don’t use. Use _because of_ instead. +Don't use. Use _because of_ instead. ## E @@ -250,11 +250,11 @@ Don’t use. Use _because of_ instead. Avoid the use of *easy*, *easier*, or *easily* if possible when describing or comparing an OpenSearch Project product, feature, or procedure in technical content. Use of these terms is audience dependent. These terms are potentially misleading or inaccurate and might be perceived as condescending by some technical users. Instead, describe what the user can do. -On documentation landing pages, it’s acceptable to use *easy*, *easier*, or *easily* within the service description only. +On documentation landing pages, it's acceptable to use *easy*, *easier*, or *easily* within the service description only. **effect** -_Effect_ as a noun refers to something that’s caused by something else. _Effect_ as a verb means to bring about. Do not confuse with _affect_. +_Effect_ as a noun refers to something that's caused by something else. _Effect_ as a verb means to bring about. Do not confuse with _affect_. **e.g.** @@ -264,7 +264,7 @@ Avoid. Use _for example_ or _such as_ instead. **email** -Use as a singular noun or adjective to refer to the collective concept, and use _message_ or _mail_ for individual items. Use _send email_ as the verb form. Don’t use the plural form because it’s a collective noun. +Use as a singular noun or adjective to refer to the collective concept, and use _message_ or _mail_ for individual items. Use _send email_ as the verb form. Don't use the plural form because it's a collective noun. **enable** @@ -275,7 +275,7 @@ Use *enable* to describe making a feature or command available. For example: Note that alternatives to *enable*—such as *activate*, *turn on*, or *start*—are acceptable usage where appropriate and may be found in existing documentation. In all cases, use language that corresponds to the language used in the UI, if applicable. -Avoid using *enable* to refer to making something possible for the user. Instead, rewrite to focus on what's important from the user's point of view. For example, “With ABC, you can do XYZ” is a stronger statement than “ABC enables you to XYZ.” Additionally, using a task-based statement is usually more clear than the vague “…enables you to….” +Avoid using *enable* to refer to making something possible for the user. Instead, rewrite to focus on what's important from the user's point of view. For example, "With ABC, you can do XYZ" is a stronger statement than "ABC enables you to XYZ." Additionally, using a task-based statement is usually more clear than the vague "…enables you to…." **enter** @@ -285,7 +285,7 @@ In general, use in preference to _type_ when a user adds text or other input (su Do not use. -Generally speaking, etc. and its equivalents (such as and more or and so on) aren’t necessary. +Generally speaking, etc. and its equivalents (such as and more or and so on) aren't necessary. **execute** @@ -397,7 +397,7 @@ Avoid using. Use *not valid* instead. **IP address** -Don’t abbreviate as _IP only_. +Don't abbreviate as _IP only_. ## J @@ -435,7 +435,7 @@ You _start_ an application but _launch_ an instance, environment, or cluster. **let** -Avoid using _let_ to refer to making something in a service or feature possible for the user. Instead, rewrite to focus on what’s important from the user’s point of view. +Avoid using _let_ to refer to making something in a service or feature possible for the user. Instead, rewrite to focus on what's important from the user's point of view. **leverage** @@ -449,7 +449,7 @@ One word in reference to software. OK to use to call out something for comparison. -As a general rule, if you can replace like with similar to, it’s OK to use like. But, if you can replace _like_ with _such as_, use _such as_. +As a general rule, if you can replace like with similar to, it's OK to use like. But, if you can replace _like_ with _such as_, use _such as_. **LLM** @@ -505,9 +505,9 @@ Avoid. Use _can_ or _might_ instead. **must, shall, should** -_Must_ and _shall_ refer to requirements. If the reader doesn’t follow the instruction, something won’t work right. +_Must_ and _shall_ refer to requirements. If the reader doesn't follow the instruction, something won't work right. -_Should_ is used with recommendations. If the reader doesn’t follow the instruction, it might be harder or slower, but it’ll work. +_Should_ is used with recommendations. If the reader doesn't follow the instruction, it might be harder or slower, but it'll work. ## N @@ -517,7 +517,7 @@ Not navigate _in_. **near real time (n.), near real-time (adj.) (NRT)** -Use _near real time_ as a noun; use near real-time as an adjective. Don’t add a hyphen between _near_ and _real time_ or _real-time_. +Use _near real time_ as a noun; use near real-time as an adjective. Don't add a hyphen between _near_ and _real time_ or _real-time_. Spell out _near real time_ on first mention; _NRT_ can be used on subsequent mentions. @@ -543,7 +543,7 @@ The default visualization tool for data in OpenSearch. On first appearance, use open source (n.), open-source (adj.) -Use _open source_ as a noun (for example, “The code used throughout this tutorial is open source and can be freely modified”). Use _open-source_ as an adjective _(open-source software)_. +Use _open source_ as a noun (for example, "The code used throughout this tutorial is open source and can be freely modified"). Use _open-source_ as an adjective _(open-source software)_. **OpenSearch Playground** @@ -554,9 +554,9 @@ Do not precede with _the_. OpenSearch Playground provides a central location for When referencing operating systems in documentation, follow these guidelines: - In general, if your docs or procedures apply to both Linux and macOS, you can also include Unix. -- Unix and UNIX aren’t the same. UNIX is a trademarked name that’s owned by The Open Group. In most cases, you should use Unix. -- When referring to the Mac operating system, use macOS. Don’t say Mac, Mac OS, or OS X. -- When referring to Windows, it’s not necessary to prefix with Microsoft. +- Unix and UNIX aren't the same. UNIX is a trademarked name that's owned by The Open Group. In most cases, you should use Unix. +- When referring to the Mac operating system, use macOS. Don't say Mac, Mac OS, or OS X. +- When referring to Windows, it's not necessary to prefix with Microsoft. - If you need to reference multiple Unix-like operating systems, you should separate by commas and use the following order: Linux, macOS, or Unix. **or earlier, or later** @@ -567,7 +567,7 @@ OK to use with software versions. **Painless** -The default scripting language for OpenSearch, either used inline or stored for repeat use. Similar to Java’s language specification. +The default scripting language for OpenSearch, either used inline or stored for repeat use. Similar to Java's language specification. **per** @@ -598,7 +598,7 @@ Avoid using except in quoted text. **plugin** -Tools inside of OpenSearch that can be customized to enhance OpenSearch’s functionality. For a list of core plugins, see the [OpenSearch plugin installation]({{site.url}}{{site.baseurl}}/opensearch/install/plugins/) page. Capitalize if it appears as part of the product name in the UI. +Tools inside of OpenSearch that can be customized to enhance OpenSearch's functionality. For a list of core plugins, see the [OpenSearch plugin installation]({{site.url}}{{site.baseurl}}/opensearch/install/plugins/) page. Capitalize if it appears as part of the product name in the UI. **pop-up** @@ -630,7 +630,7 @@ A call used to request information about your data. **real time (n.) real-time (adj.)** -Use with caution; this term can imply a degree of responsiveness or speed that may not be true. When needed, use _real time_ as a noun (for example “The request is sent in real time”). Use _real-time_ as an adjective (“A real-time feed is displayed...”). +Use with caution; this term can imply a degree of responsiveness or speed that may not be true. When needed, use _real time_ as a noun (for example "The request is sent in real time"). Use _real-time_ as an adjective ("A real-time feed is displayed..."). **recall** @@ -672,7 +672,7 @@ The following examples of settings illustrate key-value pairs with a colon separ **set up (v.), setup (n., adj.)** -Use _set up_ as a verb (“To set up a new user...”). Use _setup_ as a noun or adjective (“To begin setup...”). +Use _set up_ as a verb ("To set up a new user..."). Use _setup_ as a noun or adjective ("To begin setup..."). **shard** @@ -684,7 +684,7 @@ Don't use. Both *simple* and *simply* are not neutral in tone and might sound co **since** -Use only to describe time events. Don’t use in place of because. +Use only to describe time events. Don't use in place of *because*. **slave** @@ -702,7 +702,7 @@ You _start_ an application but _launch_ an instance, environment, or cluster. **startup (n.), start up (v.)** -Never hyphenated. Use _startup_ as a noun (for example, “The following startup procedure guides you through...”). Use _start up_ as a verb (“You can start up the instances by...”). +Never hyphenated. Use _startup_ as a noun (for example, "The following startup procedure guides you through..."). Use _start up_ as a verb ("You can start up the instances by..."). **Stochastic Gradient Descent (SGD)** @@ -716,7 +716,7 @@ A numerical statistic that is intended to reflect how important a word is to a d **time out (verb), timeout (noun, adjective)** -Never hyphenate. Use _time out_ as a verb (“The request will time out if the server doesn’t respond”). Use _timeout_ as a noun or adjective (“You can set the timeout interval by entering a number into...”). +Never hyphenate. Use _time out_ as a verb ("The request will time out if the server doesn't respond"). Use _timeout_ as a noun or adjective ("You can set the timeout interval by entering a number into..."). **time frame** @@ -792,7 +792,7 @@ Never _web site_. **while, although, whereas** -Only use _while_ to mean “during an interval of time.” Don’t use it to mean although because it is often ambiguous. _Whereas_ is a better alternative to although in many cases, but it can sound overly formal. +Only use _while_ to mean "during an interval of time." Don't use it to mean although because it is often ambiguous. _Whereas_ is a better alternative to although in many cases, but it can sound overly formal. **white day** @@ -806,9 +806,9 @@ Do not use. Use *allow list* instead. **wish, want, desire, need** -_Wish_ and _desire_ are indirect and nuanced versions of _want_. Don’t use them. Be direct. +_Wish_ and _desire_ are indirect and nuanced versions of _want_. Don't use them. Be direct. -Do not confuse wants with needs. Use the term that’s appropriate to the situation. _Need_ connotes a requirement or obligation, whereas _want_ indicates that you have an intent but still a choice of valid actions. +Do not confuse wants with needs. Use the term that's appropriate to the situation. _Need_ connotes a requirement or obligation, whereas _want_ indicates that you have an intent but still a choice of valid actions. ## Y From e20114bd499c93c7cf9429921ff0600a024be8eb Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Mon, 3 Jun 2024 16:07:43 +0100 Subject: [PATCH 12/27] adding details to rest API calls for role updates #7234 (#7254) * adding details to rest API calls for role updates #7234 Signed-off-by: AntonEliatra * Update _security/access-control/api.md Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: AntonEliatra Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _security/access-control/api.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/_security/access-control/api.md b/_security/access-control/api.md index bea080a078..9473c16c20 100644 --- a/_security/access-control/api.md +++ b/_security/access-control/api.md @@ -815,18 +815,24 @@ Creates, updates, or deletes multiple roles in a single call. PATCH _plugins/_security/api/roles [ { - "op": "replace", "path": "/role1/index_permissions/0/fls", "value": ["test1", "test2"] + "op": "replace", "path": "/role1/index_permissions/0/fls", "value": ["myfield*", "~myfield1"] }, { "op": "remove", "path": "/role1/index_permissions/0/dls" }, { - "op": "add", "path": "/role2/cluster_permissions", "value": ["manage_snapshots"] + "op": "add", "path": "/role2/cluster_permissions/-", "value": { + "index_patterns": ["test_index"], + "allowed_actions": ["indices:data/read/scroll/clear"] + } } ] ``` {% include copy-curl.html %} +You can use `-` to insert a new permission to the end of the array of permissions. +{: .note} + #### Example response ```json From d01e74fa72e6bb586708f487307d75acfcba9fd6 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:18:59 -0400 Subject: [PATCH 13/27] Add ML tutorials (#7180) * Add ML tutorials Signed-off-by: Fanit Kolchina * Writing Signed-off-by: Fanit Kolchina * Conversational search Signed-off-by: Fanit Kolchina * Add rag chatbot Signed-off-by: Fanit Kolchina * Writing Signed-off-by: Fanit Kolchina * Add RAG chatbot and convo agent Signed-off-by: Fanit Kolchina * Add reranking cohere tutorial Signed-off-by: Fanit Kolchina * Add semantic search tutorial Signed-off-by: Fanit Kolchina * Add generating embeddings Signed-off-by: Fanit Kolchina * Add generate embeddings to index Signed-off-by: Fanit Kolchina * Rewriting Signed-off-by: Fanit Kolchina * Rewriting Signed-off-by: Fanit Kolchina * Rewriting Signed-off-by: Fanit Kolchina * Reword Signed-off-by: Fanit Kolchina * Apply suggestions from code review Co-authored-by: Melissa Vagi Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> * Consistency Signed-off-by: Fanit Kolchina * Add tutorials section to index page Signed-off-by: Fanit Kolchina --------- Signed-off-by: Fanit Kolchina Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Co-authored-by: Melissa Vagi Co-authored-by: Nathan Bower --- .../Vocab/OpenSearch/Products/accept.txt | 1 + _dashboards/dashboards-assistant/index.md | 1 + _ml-commons-plugin/agents-tools/index.md | 2 +- _ml-commons-plugin/index.md | 6 +- _ml-commons-plugin/tutorials/build-chatbot.md | 910 ++++++++++++++++++ .../tutorials/conversational-search-cohere.md | 228 +++++ .../tutorials/generate-embeddings.md | 336 +++++++ _ml-commons-plugin/tutorials/index.md | 26 + _ml-commons-plugin/tutorials/rag-chatbot.md | 346 +++++++ .../tutorials/rag-conversational-agent.md | 838 ++++++++++++++++ .../tutorials/reranking-cohere.md | 344 +++++++ .../tutorials/semantic-search-byte-vectors.md | 314 ++++++ images/dashboards/os-assistant-chat.png | Bin 0 -> 200350 bytes images/dashboards/os-assistant-icon.png | Bin 0 -> 11174 bytes 14 files changed, 3350 insertions(+), 2 deletions(-) create mode 100644 _ml-commons-plugin/tutorials/build-chatbot.md create mode 100644 _ml-commons-plugin/tutorials/conversational-search-cohere.md create mode 100644 _ml-commons-plugin/tutorials/generate-embeddings.md create mode 100644 _ml-commons-plugin/tutorials/index.md create mode 100644 _ml-commons-plugin/tutorials/rag-chatbot.md create mode 100644 _ml-commons-plugin/tutorials/rag-conversational-agent.md create mode 100644 _ml-commons-plugin/tutorials/reranking-cohere.md create mode 100644 _ml-commons-plugin/tutorials/semantic-search-byte-vectors.md create mode 100644 images/dashboards/os-assistant-chat.png create mode 100644 images/dashboards/os-assistant-icon.png diff --git a/.github/vale/styles/Vocab/OpenSearch/Products/accept.txt b/.github/vale/styles/Vocab/OpenSearch/Products/accept.txt index 16e7562dcc..83e9aee603 100644 --- a/.github/vale/styles/Vocab/OpenSearch/Products/accept.txt +++ b/.github/vale/styles/Vocab/OpenSearch/Products/accept.txt @@ -8,6 +8,7 @@ Amazon SageMaker Ansible Auditbeat AWS Cloud +Cohere Command Cognito Dashboards Query Language Data Prepper diff --git a/_dashboards/dashboards-assistant/index.md b/_dashboards/dashboards-assistant/index.md index d44e6b58e8..d928f58659 100644 --- a/_dashboards/dashboards-assistant/index.md +++ b/_dashboards/dashboards-assistant/index.md @@ -122,3 +122,4 @@ The following screenshot shows a saved conversation, along with actions you can - [Getting started guide for OpenSearch Assistant in OpenSearch Dashboards](https://github.com/opensearch-project/dashboards-assistant/blob/main/GETTING_STARTED_GUIDE.md) - [OpenSearch Assistant configuration through the REST API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/opensearch-assistant/) +- [Build your own chatbot]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/build-chatbot/) \ No newline at end of file diff --git a/_ml-commons-plugin/agents-tools/index.md b/_ml-commons-plugin/agents-tools/index.md index ba88edef2f..009906d4cf 100644 --- a/_ml-commons-plugin/agents-tools/index.md +++ b/_ml-commons-plugin/agents-tools/index.md @@ -18,7 +18,7 @@ An _agent_ is a coordinator that uses a large language model (LLM) to solve a pr - [_Flow agent_](#flow-agents): Runs tools sequentially, in the order specified in its configuration. The workflow of a flow agent is fixed. Useful for retrieval-augmented generation (RAG). - [_Conversational flow agent_](#conversational-flow-agents): Runs tools sequentially, in the order specified in its configuration. The workflow of a conversational flow agent is fixed. Stores conversation history so that users can ask follow-up questions. Useful for creating a chatbot. -- [_Conversational agent_](#conversational-agents): Reasons in order to provide a response based on the available knowledge, including the LLM knowledge base and a set of tools provided to the LLM. Stores conversation history so that users can ask follow-up questions. The workflow of a conversational agent is variable, based on follow-up questions. For specific questions, uses the Chain-of-Thought (CoT) process to select the best tool from the configured tools for providing a response to the question. Useful for creating a chatbot that employs RAG. +- [_Conversational agent_](#conversational-agents): Reasons in order to provide a response based on the available knowledge, including the LLM knowledge base and a set of tools provided to the LLM. The LLM reasons iteratively to decide what action to take until it obtains the final answer or reaches the iteration limit. Stores conversation history so that users can ask follow-up questions. The workflow of a conversational agent is variable, based on follow-up questions. For specific questions, uses the Chain-of-Thought (CoT) process to select the best tool from the configured tools for providing a response to the question. Useful for creating a chatbot that employs RAG. ### Flow agents diff --git a/_ml-commons-plugin/index.md b/_ml-commons-plugin/index.md index c936f36161..f0355b6be3 100644 --- a/_ml-commons-plugin/index.md +++ b/_ml-commons-plugin/index.md @@ -30,4 +30,8 @@ ML Commons supports various algorithms to help train ML models and make predicti ## ML Commons API -ML Commons provides its own set of REST APIs. For more information, see [ML Commons API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/index/). \ No newline at end of file +ML Commons provides its own set of REST APIs. For more information, see [ML Commons API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/index/). + +## Tutorials + +Using the OpenSearch ML framework, you can build various applications, from implementing conversational search to building your own chatbot. For more information, see [Tutorials]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/index/). \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/build-chatbot.md b/_ml-commons-plugin/tutorials/build-chatbot.md new file mode 100644 index 0000000000..1e51298106 --- /dev/null +++ b/_ml-commons-plugin/tutorials/build-chatbot.md @@ -0,0 +1,910 @@ +--- +layout: default +title: Build your own chatbot +parent: Tutorials +nav_order: 60 +--- + +# Build your own chatbot + +This is an experimental feature and is not recommended for use in a production environment. For updates on the progress of the feature or if you want to leave feedback, see the associated [GitHub issue](https://github.com/opensearch-project/ml-commons/issues/1161). +{: .warning} + +Sometimes a large language model (LLM) cannot answer a question right away. For example, an LLM can't tell you how many errors there are in your log index for last week because its knowledge base does not contain your proprietary data. In this case, you need to provide additional information to an LLM in a subsequent call. You can use an agent to solve such complex problems. The agent can run tools to obtain more information from configured data sources and send the additional information to the LLM as context. + +This tutorial describes how to build your own chatbot in OpenSearch using a `conversational` agent. For more information about agents, see [Agents and tools]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agents-tools/index/). + +Replace the placeholders starting with the prefix `your_` with your own values. +{: .note} + +## Prerequisite + +Log in to the OpenSearch Dashboards home page, select **Add sample data**, and add the **Sample eCommerce orders** data. + +## Step 1: Configure a knowledge base + +Meet the prerequisite and follow Step 1 of the [RAG with a conversational flow agent tutorial]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/rag-conversational-agent/) to configure the `test_population_data` knowledge base index, which contains US city population data. + +Create an ingest pipeline: + +```json +PUT /_ingest/pipeline/test_stock_price_data_pipeline +{ + "description": "text embedding pipeline", + "processors": [ + { + "text_embedding": { + "model_id": "your_text_embedding_model_id", + "field_map": { + "stock_price_history": "stock_price_history_embedding" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +Create the `test_stock_price_data` index, which contains historical stock price data: + +```json +PUT test_stock_price_data +{ + "mappings": { + "properties": { + "stock_price_history": { + "type": "text" + }, + "stock_price_history_embedding": { + "type": "knn_vector", + "dimension": 384 + } + } + }, + "settings": { + "index": { + "knn.space_type": "cosinesimil", + "default_pipeline": "test_stock_price_data_pipeline", + "knn": "true" + } + } +} +``` +{% include copy-curl.html %} + +Ingest data into the index: + +```json +POST _bulk +{"index": {"_index": "test_stock_price_data"}} +{"stock_price_history": "This is the historical montly stock price record for Amazon.com, Inc. (AMZN) with CSV format.\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,93.870003,103.489998,88.120003,103.290001,103.290001,1349240300\n2023-04-01,102.300003,110.860001,97.709999,105.449997,105.449997,1224083600\n2023-05-01,104.949997,122.919998,101.150002,120.580002,120.580002,1432891600\n2023-06-01,120.690002,131.490005,119.930000,130.360001,130.360001,1242648800\n2023-07-01,130.820007,136.649994,125.919998,133.679993,133.679993,1058754800\n2023-08-01,133.550003,143.630005,126.410004,138.009995,138.009995,1210426200\n2023-09-01,139.460007,145.860001,123.040001,127.120003,127.120003,1120271900\n2023-10-01,127.279999,134.479996,118.349998,133.089996,133.089996,1224564700\n2023-11-01,133.960007,149.259995,133.710007,146.089996,146.089996,1025986900\n2023-12-01,146.000000,155.630005,142.809998,151.940002,151.940002,931128600\n2024-01-01,151.539993,161.729996,144.050003,155.199997,155.199997,953344900\n2024-02-01,155.869995,175.000000,155.619995,174.449997,174.449997,437720800\n"} +{"index": {"_index": "test_stock_price_data"}} +{"stock_price_history": "This is the historical montly stock price record for Apple Inc. (AAPL) with CSV format.\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,146.830002,165.000000,143.899994,164.899994,164.024475,1520266600\n2023-04-01,164.270004,169.850006,159.779999,169.679993,168.779099,969709700\n2023-05-01,169.279999,179.350006,164.309998,177.250000,176.308914,1275155500\n2023-06-01,177.699997,194.479996,176.929993,193.970001,193.207016,1297101100\n2023-07-01,193.779999,198.229996,186.600006,196.449997,195.677261,996066400\n2023-08-01,196.240005,196.729996,171.960007,187.869995,187.130997,1322439400\n2023-09-01,189.490005,189.979996,167.619995,171.210007,170.766846,1337586600\n2023-10-01,171.220001,182.339996,165.669998,170.770004,170.327972,1172719600\n2023-11-01,171.000000,192.929993,170.119995,189.949997,189.458313,1099586100\n2023-12-01,190.330002,199.619995,187.449997,192.529999,192.284637,1062774800\n2024-01-01,187.149994,196.380005,180.169998,184.399994,184.164993,1187219300\n2024-02-01,183.990005,191.050003,179.250000,188.850006,188.609329,420063900\n"} +{"index": {"_index": "test_stock_price_data"}} +{"stock_price_history": "This is the historical montly stock price record for NVIDIA Corporation (NVDA) with CSV format.\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,231.919998,278.339996,222.970001,277.769989,277.646820,1126373100\n2023-04-01,275.089996,281.100006,262.200012,277.489990,277.414032,743592100\n2023-05-01,278.399994,419.380005,272.399994,378.339996,378.236420,1169636000\n2023-06-01,384.890015,439.899994,373.559998,423.019989,422.904175,1052209200\n2023-07-01,425.170013,480.880005,413.459991,467.290009,467.210449,870489500\n2023-08-01,464.600006,502.660004,403.109985,493.549988,493.465942,1363143600\n2023-09-01,497.619995,498.000000,409.799988,434.989990,434.915924,857510100\n2023-10-01,440.299988,476.089996,392.299988,407.799988,407.764130,1013917700\n2023-11-01,408.839996,505.480011,408.690002,467.700012,467.658905,914386300\n2023-12-01,465.250000,504.329987,450.100006,495.220001,495.176453,740951700\n2024-01-01,492.440002,634.929993,473.200012,615.270020,615.270020,970385300\n2024-02-01,621.000000,721.849976,616.500000,721.330017,721.330017,355346500\n"} +{"index": {"_index": "test_stock_price_data"}} +{"stock_price_history": "This is the historical montly stock price record for Meta Platforms, Inc. (META) with CSV format.\n\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,174.589996,212.169998,171.429993,211.940002,211.940002,690053000\n2023-04-01,208.839996,241.690002,207.130005,240.320007,240.320007,446687900\n2023-05-01,238.619995,268.649994,229.850006,264.720001,264.720001,486968500\n2023-06-01,265.899994,289.790009,258.880005,286.980011,286.980011,480979900\n2023-07-01,286.700012,326.200012,284.850006,318.600006,318.600006,624605100\n2023-08-01,317.540009,324.140015,274.380005,295.890015,295.890015,423147800\n2023-09-01,299.369995,312.869995,286.790009,300.209991,300.209991,406686600\n2023-10-01,302.739990,330.540009,279.399994,301.269989,301.269989,511307900\n2023-11-01,301.850006,342.920013,301.850006,327.149994,327.149994,329270500\n2023-12-01,325.480011,361.899994,313.660004,353.959991,353.959991,332813800\n2024-01-01,351.320007,406.359985,340.010010,390.140015,390.140015,347020200\n2024-02-01,393.940002,485.959991,393.049988,473.279999,473.279999,294260900\n"} +{"index": {"_index": "test_stock_price_data"}} +{"stock_price_history": "This is the historical montly stock price record for Microsoft Corporation (MSFT) with CSV format.\n\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,250.759995,289.269989,245.610001,288.299988,285.953064,747635000\n2023-04-01,286.519989,308.929993,275.369995,307.260010,304.758759,551497100\n2023-05-01,306.970001,335.940002,303.399994,328.390015,325.716766,600807200\n2023-06-01,325.929993,351.470001,322.500000,340.540009,338.506226,547588700\n2023-07-01,339.190002,366.779999,327.000000,335.920013,333.913818,666764400\n2023-08-01,335.190002,338.540009,311.549988,327.760010,325.802582,479456700\n2023-09-01,331.309998,340.859985,309.450012,315.750000,314.528809,416680700\n2023-10-01,316.279999,346.200012,311.209991,338.109985,336.802307,540907000\n2023-11-01,339.790009,384.299988,339.649994,378.910004,377.444519,563880300\n2023-12-01,376.760010,378.160004,362.899994,376.040009,375.345886,522003700\n2024-01-01,373.859985,415.320007,366.500000,397.579987,396.846130,528399000\n2024-02-01,401.829987,420.820007,401.799988,409.489990,408.734131,237639700\n"} +{"index": {"_index": "test_stock_price_data"}} +{"stock_price_history": "This is the historical montly stock price record for Alphabet Inc. (GOOG) with CSV format.\n\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,90.160004,107.510002,89.769997,104.000000,104.000000,725477100\n2023-04-01,102.669998,109.629997,102.379997,108.220001,108.220001,461670700\n2023-05-01,107.720001,127.050003,104.500000,123.370003,123.370003,620317400\n2023-06-01,123.500000,129.550003,116.910004,120.970001,120.970001,521386300\n2023-07-01,120.320000,134.070007,115.830002,133.110001,133.110001,525456900\n2023-08-01,130.854996,138.399994,127.000000,137.350006,137.350006,463482000\n2023-09-01,138.429993,139.929993,128.190002,131.850006,131.850006,389593900\n2023-10-01,132.154999,142.380005,121.459999,125.300003,125.300003,514877100\n2023-11-01,125.339996,141.100006,124.925003,133.919998,133.919998,405635900\n2023-12-01,133.320007,143.945007,129.399994,140.929993,140.929993,482059400\n2024-01-01,139.600006,155.199997,136.850006,141.800003,141.800003,428771200\n2024-02-01,143.690002,150.695007,138.169998,147.139999,147.139999,231934100\n"} +``` +{% include copy-curl.html %} + +## Step 2: Prepare an LLM + +This tutorial uses the [Amazon Bedrock Claude model](https://aws.amazon.com/bedrock/claude/). You can also use other LLMs. For more information, see [Connecting to externally hosted models]({{site.url}}{{site.baseurl}}/ml-commons-plugin/remote-models/index/). + +Create a connector for the model: + +```json +POST /_plugins/_ml/connectors/_create +{ + "name": "BedRock Claude instant-v1 Connector ", + "description": "The connector to BedRock service for claude model", + "version": 1, + "protocol": "aws_sigv4", + "parameters": { + "region": "us-east-1", + "service_name": "bedrock", + "anthropic_version": "bedrock-2023-05-31", + "max_tokens_to_sample": 8000, + "temperature": 0.0001, + "response_filter": "$.completion", + "stop_sequences": ["\n\nHuman:","\nObservation:","\n\tObservation:","\nObservation","\n\tObservation","\n\nQuestion"] + }, + "credential": { + "access_key": "your_aws_access_key", + "secret_key": "your_aws_secret_key", + "session_token": "your_aws_session_token" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-instant-v1/invoke", + "headers": { + "content-type": "application/json", + "x-amz-content-sha256": "required" + }, + "request_body": "{\"prompt\":\"${parameters.prompt}\", \"stop_sequences\": ${parameters.stop_sequences}, \"max_tokens_to_sample\":${parameters.max_tokens_to_sample}, \"temperature\":${parameters.temperature}, \"anthropic_version\":\"${parameters.anthropic_version}\" }" + } + ] +} +``` +{% include copy-curl.html %} + +Note the connector ID; you'll use it to register the model. + +Register the model: + +```json +POST /_plugins/_ml/models/_register +{ + "name": "Bedrock Claude Instant model", + "function_name": "remote", + "description": "Bedrock Claude instant-v1 model", + "connector_id": "your_connector_id" +} +``` +{% include copy-curl.html %} + +Note the LLM model ID; you'll use it in the following steps. + +Deploy the model: + +```json +POST /_plugins/_ml/models/your_LLM_model_id/_deploy +``` +{% include copy-curl.html %} + +Test the model: + +```json +POST /_plugins/_ml/models/your_LLM_model_id/_predict +{ + "parameters": { + "prompt": "\n\nHuman: how are you? \n\nAssistant:" + } +} +``` +{% include copy-curl.html %} + +## Step 3: Create an agent with the default prompt + +Next, create and test an agent. + +### Create an agent + +Create an agent of the `conversational` type. + +The agent is configured with the following information: + +- Meta information: `name`, `type`, `description`. +- LLM information: The agent uses an LLM to reason and select the next step, including choosing an appropriate tool and preparing the tool input. +- Tools: A tool is a function that can be executed by the agent. Each tool can define its own `name`, `description`, and `parameters`. +- Memory: Stores chat messages. Currently, OpenSearch only supports one memory type: `conversation_index`. + +The agent contains the following parameters: + +- `conversational`: This agent type has a built-in prompt. To override it with your own prompt, see [Step 4](#step-4-optional-create-an-agent-with-a-custom-prompt). +- `app_type`: Specify this parameter for reference purposes in order to differentiate between multiple agents. +- `llm`: Defines the LLM configuration: + - `"max_iteration": 5`: The agent runs the LLM a maximum of five times. + - `"response_filter": "$.completion"`: Needed to retrieve the LLM answer from the Bedrock Claude model response. + - `"message_history_limit": 5`: The agent retrieves a maximum of the five most recent historical messages and adds them to the LLM context. Set this parameter to `0` to omit message history in the context. + - `disable_trace`: If `true`, then the agent does not store trace data in memory. Trace data is included in each message and provides a detailed recount of steps performed while generating the message. +- `memory`: Defines how to store messages. Currently, OpenSearch only supports the `conversation_index` memory, which stores messages in a memory index. +- Tools: + - An LLM will reason to decide which tool to run and will prepare the tool's input. + - To include the tool's output in the response, specify `"include_output_in_agent_response": true`. In this tutorial, you will include the `PPLTool` output in the response (see the example response in [Test the agent](#test-the-agent)). + - By default, the tool's `name` is the same as the tool's `type`, and each tool has a default description. You can override the tool's `name` and `description`. + - Each tool in the `tools` list must have a unique name. For example, the following demo agent defines two tools of the `VectorDBTool` type with different names (`population_data_knowledge_base` and `stock_price_data_knowledge_base`). Each tool has a custom description so that the LLM can easily understand what the tool does. + + For more information about tools, see [Tools]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agents-tools/tools/index/). + +This example request configures several sample tools in an agent. You can configure other tools that are relevant to your use case as needed. +{: .note} + +Register the agent: + +```json +POST _plugins/_ml/agents/_register +{ + "name": "Chat Agent with Claude", + "type": "conversational", + "description": "this is a test agent", + "app_type": "os_chat", + "llm": { + "model_id": "your_llm_model_id_from_step2", + "parameters": { + "max_iteration": 5, + "response_filter": "$.completion", + "message_history_limit": 5, + "disable_trace": false + } + }, + "memory": { + "type": "conversation_index" + }, + "tools": [ + { + "type": "PPLTool", + "parameters": { + "model_id": "your_llm_model_id_from_step2", + "model_type": "CLAUDE", + "execute": true + }, + "include_output_in_agent_response": true + }, + { + "type": "VisualizationTool", + "parameters": { + "index": ".kibana" + }, + "include_output_in_agent_response": true + }, + { + "type": "VectorDBTool", + "name": "population_data_knowledge_base", + "description": "This tool provide population data of US cities.", + "parameters": { + "input": "${parameters.question}", + "index": "test_population_data", + "source_field": [ + "population_description" + ], + "model_id": "your_embedding_model_id_from_step1", + "embedding_field": "population_description_embedding", + "doc_size": 3 + } + }, + { + "type": "VectorDBTool", + "name": "stock_price_data_knowledge_base", + "description": "This tool provide stock price data.", + "parameters": { + "input": "${parameters.question}", + "index": "test_stock_price_data", + "source_field": [ + "stock_price_history" + ], + "model_id": "your_embedding_model_id_from_step1", + "embedding_field": "stock_price_history_embedding", + "doc_size": 3 + } + }, + { + "type": "CatIndexTool", + "description": "Use this tool to get OpenSearch index information: (health, status, index, uuid, primary count, replica count, docs.count, docs.deleted, store.size, primary.store.size). \nIt takes 2 optional arguments named `index` which is a comma-delimited list of one or more indices to get information from (default is an empty list meaning all indices), and `local` which means whether to return information from the local node only instead of the cluster manager node (default is false)." + }, + { + "type": "SearchAnomalyDetectorsTool" + }, + { + "type": "SearchAnomalyResultsTool" + }, + { + "type": "SearchMonitorsTool" + }, + { + "type": "SearchAlertsTool" + } + ] +} +``` +{% include copy-curl.html %} + +Note the agent ID; you'll use it in the next step. + +### Test the agent + +Note the following testing tips: + +- You can view the detailed steps of an agent execution in one of the following ways: + - Enable verbose mode: `"verbose": true`. + - Call the Get Trace API: `GET _plugins/_ml/memory/message/your_message_id/traces`. + +- An LLM may hallucinate. It may choose the wrong tool to solve your problem, especially when you have configured many tools. To avoid hallucinations, try the following options: + - Avoid configuring many tools in an agent. + - Provide a detailed tool description clarifying what the tool can do. + - Specify the tool to use in the LLM question, for example, `Can you use the PPLTool to query the opensearch_dashboards_sample_data_ecommerce index so it can calculate how many orders were placed last week?`. + - Specify the tool to use when executing an agent. For example, specify that only `PPLTool` and `CatIndexTool` should be used to process the current request. + +Test the agent: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "Can you query with index opensearch_dashboards_sample_data_ecommerce to calculate how many orders in last week?", + "verbose": false, + "selected_tools": ["PPLTool", "CatIndexTool"] + } +} +``` +{% include copy-curl.html %} + + +#### Test the PPLTool + + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "Can you query with index opensearch_dashboards_sample_data_ecommerce to calculate how many orders in last week?", + "verbose": false + } +} +``` +{% include copy-curl.html %} + +Because you specified `"include_output_in_agent_response": true` for the `PPLTool`, the response contains `PPLTool.output` in the `additional_info` object: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "TkJwyI0Bn3OCesyvzuH9" + }, + { + "name": "parent_interaction_id", + "result": "T0JwyI0Bn3OCesyvz-EI" + }, + { + "name": "response", + "dataAsMap": { + "response": "The tool response from the PPLTool shows that there were 3812 orders in the opensearch_dashboards_sample_data_ecommerce index within the last week.", + "additional_info": { + "PPLTool.output": [ + """{"ppl":"source\u003dopensearch_dashboards_sample_data_ecommerce| where order_date \u003e DATE_SUB(NOW(), INTERVAL 1 WEEK) | stats COUNT() AS count","executionResult":"{\n \"schema\": [\n {\n \"name\": \"count\",\n \"type\": \"integer\"\n }\n ],\n \"datarows\": [\n [\n 3812\n ]\n ],\n \"total\": 1,\n \"size\": 1\n}"}""" + ] + } + } + } + ] + } + ] +} +``` +{% include copy-curl.html %} + +Obtain trace data: + +```json +GET _plugins/_ml/memory/message/T0JwyI0Bn3OCesyvz-EI/traces +``` +{% include copy-curl.html %} + + +#### Test the population_data_knowledge_base VectorDBTool + + +To view detailed steps, set `verbose` to `true` when executing the agent: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's the population increase of Seattle from 2021 to 2023?", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +The response contains the execution steps: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "LkJuyI0Bn3OCesyv3-Ef" + }, + { + "name": "parent_interaction_id", + "result": "L0JuyI0Bn3OCesyv3-Er" + }, + { + "name": "response", + "result": """{ + "thought": "Let me check the population data tool", + "action": "population_data_knowledge_base", + "action_input": "{'question': 'What is the population increase of Seattle from 2021 to 2023?', 'cities': ['Seattle']}" +}""" + }, + { + "name": "response", + "result": """{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Seattle metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Seattle in 2023 is 3,519,000, a 0.86% increase from 2022.\\nThe metro area population of Seattle in 2022 was 3,489,000, a 0.81% increase from 2021.\\nThe metro area population of Seattle in 2021 was 3,461,000, a 0.82% increase from 2020.\\nThe metro area population of Seattle in 2020 was 3,433,000, a 0.79% increase from 2019."},"_id":"9EJsyI0Bn3OCesyvU-B7","_score":0.75154537} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."},"_id":"80JsyI0Bn3OCesyvU-B7","_score":0.6689899} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019."},"_id":"8EJsyI0Bn3OCesyvU-B7","_score":0.66782206} +""" + }, + { + "name": "response", + "result": "According to the population data tool, the population of Seattle increased by approximately 28,000 people from 2021 to 2023, which is a 0.82% increase from 2021 to 2022 and a 0.86% increase from 2022 to 2023." + } + ] + } + ] +} +``` + +Obtain trace data: + +```json +GET _plugins/_ml/memory/message/L0JuyI0Bn3OCesyv3-Er/traces +``` +{% include copy-curl.html %} + +#### Test conversational memory + +To continue the same conversation, specify the conversation's `memory_id` when executing the agent: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's the population of Austin 2023, compare with Seattle", + "memory_id": "LkJuyI0Bn3OCesyv3-Ef", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +In the response, note that the `population_data_knowledge_base` doesn't return the population of Seattle. Instead, the agent learns the population of Seattle by referencing historical messages: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "LkJuyI0Bn3OCesyv3-Ef" + }, + { + "name": "parent_interaction_id", + "result": "00J6yI0Bn3OCesyvIuGZ" + }, + { + "name": "response", + "result": """{ + "thought": "Let me check the population data tool first", + "action": "population_data_knowledge_base", + "action_input": "{\"city\":\"Austin\",\"year\":2023}" +}""" + }, + { + "name": "response", + "result": """{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."},"_id":"BhF5vo0BubpYKX5ER0fT","_score":0.69129956} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."},"_id":"6zrZvo0BVR2NrurbRIAE","_score":0.69129956} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019."},"_id":"AxF5vo0BubpYKX5ER0fT","_score":0.61015373} +""" + }, + { + "name": "response", + "result": "According to the population data tool, the population of Austin in 2023 is approximately 2,228,000 people, a 2.39% increase from 2022. This is lower than the population of Seattle in 2023 which is approximately 3,519,000 people, a 0.86% increase from 2022." + } + ] + } + ] +} +``` + +View all messages: + +```json +GET _plugins/_ml/memory/LkJuyI0Bn3OCesyv3-Ef/messages +``` +{% include copy-curl.html %} + +Obtain trace data: + +```json +GET _plugins/_ml/memory/message/00J6yI0Bn3OCesyvIuGZ/traces +``` +{% include copy-curl.html %} + +## Step 4 (Optional): Create an agent with a custom prompt + +All agents have the following default prompt: + +```json +"prompt": """ + +Human:${parameters.prompt.prefix} + +${parameters.prompt.suffix} + +Human: follow RESPONSE FORMAT INSTRUCTIONS + +Assistant:""" +``` + +The prompt consists of two parts: + +- `${parameters.prompt.prefix}`: A prompt prefix that describes what the AI assistant can do. You can change this parameter based on your use case, for example, `You are a professional data analyst. You will always answer questions based on the tool response first. If you don't know the answer, just say you don't know.` +- `${parameters.prompt.suffix}`: The main part of the prompt that defines the tools, chat history, prompt format instructions, a question, and a scratchpad. + +The default `prompt.suffix` is the following: + +```json +"prompt.suffix": """Human:TOOLS +------ +Assistant can ask Human to use tools to look up information that may be helpful in answering the users original question. The tool response will be listed in "TOOL RESPONSE of {tool name}:". If TOOL RESPONSE is enough to answer human's question, Assistant should avoid rerun the same tool. +Assistant should NEVER suggest run a tool with same input if it's already in TOOL RESPONSE. +The tools the human can use are: + +${parameters.tool_descriptions} + +${parameters.chat_history} + +${parameters.prompt.format_instruction} + + +Human:USER'S INPUT +-------------------- +Here is the user's input : +${parameters.question} + +${parameters.scratchpad}""" +``` + +The `prompt.suffix` consists of the following placeholders: + +- `${parameters.tool_descriptions}`: This placeholder will be filled with the agent's tool information: the tool name and description. If you omit this placeholder, the agent will not use any tools. +- `${parameters.prompt.format_instruction}`: This placeholder defines the LLM response format. This placeholder is critical, and we do not recommend removing it. +- `${parameters.chat_history}`: This placeholder will be filled with the message history of the current memory. If you don't set the `memory_id` when you run the agent, or if there are no history messages, then this placeholder will be empty. If you don't need chat history, you can remove this placeholder. +- `${parameters.question}`: This placeholder will be filled with the user question. +- `${parameters.scratchpad}`: This placeholder will be filled with the detailed agent execution steps. These steps are the same as those you can view by specifying verbose mode or obtaining trace data (see an example in [Test the agent](#test-the-agent)). This placeholder is critical in order for the LLM to reason and select the next step based on the outcome of the previous steps. We do not recommend removing this placeholder. + +### Custom prompt examples + +The following examples demonstrate how to customize the prompt. + +#### Example 1: Customize `prompt.prefix` + +Register an agent with a custom `prompt.prefix`: + +```json +POST _plugins/_ml/agents/_register +{ + "name": "Chat Agent with Custom Prompt", + "type": "conversational", + "description": "this is a test agent", + "app_type": "os_chat", + "llm": { + "model_id": "P0L8xI0Bn3OCesyvPsif", + "parameters": { + "max_iteration": 3, + "response_filter": "$.completion", + "prompt.prefix": "Assistant is a professional data analyst. You will always answer question based on the tool response first. If you don't know the answer, just say don't know.\n" + } + }, + "memory": { + "type": "conversation_index" + }, + "tools": [ + { + "type": "VectorDBTool", + "name": "population_data_knowledge_base", + "description": "This tool provide population data of US cities.", + "parameters": { + "input": "${parameters.question}", + "index": "test_population_data", + "source_field": [ + "population_description" + ], + "model_id": "xkJLyI0Bn3OCesyvf94S", + "embedding_field": "population_description_embedding", + "doc_size": 3 + } + }, + { + "type": "VectorDBTool", + "name": "stock_price_data_knowledge_base", + "description": "This tool provide stock price data.", + "parameters": { + "input": "${parameters.question}", + "index": "test_stock_price_data", + "source_field": [ + "stock_price_history" + ], + "model_id": "xkJLyI0Bn3OCesyvf94S", + "embedding_field": "stock_price_history_embedding", + "doc_size": 3 + } + } + ] +} +``` +{% include copy-curl.html %} + +Test the agent: + +```json +POST _plugins/_ml/agents/o0LDyI0Bn3OCesyvr-Zq/_execute +{ + "parameters": { + "question": "What's the stock price increase of Amazon from May 2023 to Feb 2023?", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +#### Example 2: OpenAI model with a custom prompt + +Create a connector for the OpenAI `gpt-3.5-turbo` model: + +```json +POST _plugins/_ml/connectors/_create +{ + "name": "My openai connector: gpt-3.5-turbo", + "description": "The connector to openai chat model", + "version": 1, + "protocol": "http", + "parameters": { + "model": "gpt-3.5-turbo", + "response_filter": "$.choices[0].message.content", + "stop": ["\n\nHuman:","\nObservation:","\n\tObservation:","\n\tObservation","\n\nQuestion"], + "system_instruction": "You are an Assistant which can answer kinds of questions." + }, + "credential": { + "openAI_key": "your_openAI_key" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://api.openai.com/v1/chat/completions", + "headers": { + "Authorization": "Bearer ${credential.openAI_key}" + }, + "request_body": "{ \"model\": \"${parameters.model}\", \"messages\": [{\"role\":\"system\",\"content\":\"${parameters.system_instruction}\"},{\"role\":\"user\",\"content\":\"${parameters.prompt}\"}] }" + } + ] +} +``` +{% include copy-curl.html %} + +Create a model using the connector ID from the response: + +```json +POST /_plugins/_ml/models/_register?deploy=true +{ + "name": "My OpenAI model", + "function_name": "remote", + "description": "test model", + "connector_id": "your_connector_id" +} +``` +{% include copy-curl.html %} + +Note the model ID and test the model by calling the Predict API: + +```json +POST /_plugins/_ml/models/your_openai_model_id/_predict +{ + "parameters": { + "system_instruction": "You are an Assistant which can answer kinds of questions.", + "prompt": "hello" + } +} +``` +{% include copy-curl.html %} + +Create an agent with a custom `system_instruction` and `prompt`. The `prompt` customizes the `tool_descriptions`, `chat_history`, `format_instruction`, `question`, and `scratchpad` placeholders: + +```json +POST _plugins/_ml/agents/_register +{ + "name": "My Chat Agent with OpenAI GPT 3.5", + "type": "conversational", + "description": "this is a test agent", + "app_type": "os_chat", + "llm": { + "model_id": "your_openai_model_id", + "parameters": { + "max_iteration": 3, + "response_filter": "$.choices[0].message.content", + "system_instruction": "You are an assistant which is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics.", + "prompt": "Assistant can ask Human to use tools to look up information that may be helpful in answering the users original question.\n${parameters.tool_descriptions}\n\n${parameters.chat_history}\n\n${parameters.prompt.format_instruction}\n\nHuman: ${parameters.question}\n\n${parameters.scratchpad}\n\nHuman: follow RESPONSE FORMAT INSTRUCTIONS\n\nAssistant:", + "disable_trace": true + } + }, + "memory": { + "type": "conversation_index" + }, + "tools": [ + { + "type": "VectorDBTool", + "name": "population_data_knowledge_base", + "description": "This tool provide population data of US cities.", + "parameters": { + "input": "${parameters.question}", + "index": "test_population_data", + "source_field": [ + "population_description" + ], + "model_id": "your_embedding_model_id_from_step1", + "embedding_field": "population_description_embedding", + "doc_size": 3 + } + }, + { + "type": "VectorDBTool", + "name": "stock_price_data_knowledge_base", + "description": "This tool provide stock price data.", + "parameters": { + "input": "${parameters.question}", + "index": "test_stock_price_data", + "source_field": [ + "stock_price_history" + ], + "model_id": "your_embedding_model_id_from_step1", + "embedding_field": "stock_price_history_embedding", + "doc_size": 3 + } + } + ] +} +``` +{% include copy-curl.html %} + +Note the agent ID from the response and test the model by running the agent: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's the stock price increase of Amazon from May 2023 to Feb 2023?", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +Test the agent by asking a question that requires the agent to use both configured tools: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's the population increase of Seattle from 2021 to 2023? Then check what's the stock price increase of Amazon from May 2023 to Feb 2023?", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +The response shows that the agent runs both the `population_data_knowledge_base` and `stock_price_data_knowledge_base` tools to obtain the answer: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "_0IByY0Bn3OCesyvJenb" + }, + { + "name": "parent_interaction_id", + "result": "AEIByY0Bn3OCesyvJerm" + }, + { + "name": "response", + "result": """{ + "thought": "I need to use a tool to find the population increase of Seattle from 2021 to 2023", + "action": "population_data_knowledge_base", + "action_input": "{\"city\": \"Seattle\", \"start_year\": 2021, \"end_year\": 2023}" +}""" + }, + { + "name": "response", + "result": """{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Seattle metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Seattle in 2023 is 3,519,000, a 0.86% increase from 2022.\\nThe metro area population of Seattle in 2022 was 3,489,000, a 0.81% increase from 2021.\\nThe metro area population of Seattle in 2021 was 3,461,000, a 0.82% increase from 2020.\\nThe metro area population of Seattle in 2020 was 3,433,000, a 0.79% increase from 2019."},"_id":"9EJsyI0Bn3OCesyvU-B7","_score":0.6542084} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019."},"_id":"8EJsyI0Bn3OCesyvU-B7","_score":0.5966786} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."},"_id":"80JsyI0Bn3OCesyvU-B7","_score":0.5883104} +""" + }, + { + "name": "response", + "result": """{ + "thought": "I need to use a tool to find the stock price increase of Amazon from May 2023 to Feb 2023", + "action": "stock_price_data_knowledge_base", + "action_input": "{\"company\": \"Amazon\", \"start_date\": \"May 2023\", \"end_date\": \"Feb 2023\"}" +}""" + }, + { + "name": "response", + "result": """{"_index":"test_stock_price_data","_source":{"stock_price_history":"This is the historical montly stock price record for Amazon.com, Inc. (AMZN) with CSV format.\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,93.870003,103.489998,88.120003,103.290001,103.290001,1349240300\n2023-04-01,102.300003,110.860001,97.709999,105.449997,105.449997,1224083600\n2023-05-01,104.949997,122.919998,101.150002,120.580002,120.580002,1432891600\n2023-06-01,120.690002,131.490005,119.930000,130.360001,130.360001,1242648800\n2023-07-01,130.820007,136.649994,125.919998,133.679993,133.679993,1058754800\n2023-08-01,133.550003,143.630005,126.410004,138.009995,138.009995,1210426200\n2023-09-01,139.460007,145.860001,123.040001,127.120003,127.120003,1120271900\n2023-10-01,127.279999,134.479996,118.349998,133.089996,133.089996,1224564700\n2023-11-01,133.960007,149.259995,133.710007,146.089996,146.089996,1025986900\n2023-12-01,146.000000,155.630005,142.809998,151.940002,151.940002,931128600\n2024-01-01,151.539993,161.729996,144.050003,155.199997,155.199997,953344900\n2024-02-01,155.869995,175.000000,155.619995,174.449997,174.449997,437720800\n"},"_id":"BUJsyI0Bn3OCesyvveHo","_score":0.63949186} +{"_index":"test_stock_price_data","_source":{"stock_price_history":"This is the historical montly stock price record for Alphabet Inc. (GOOG) with CSV format.\n\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,90.160004,107.510002,89.769997,104.000000,104.000000,725477100\n2023-04-01,102.669998,109.629997,102.379997,108.220001,108.220001,461670700\n2023-05-01,107.720001,127.050003,104.500000,123.370003,123.370003,620317400\n2023-06-01,123.500000,129.550003,116.910004,120.970001,120.970001,521386300\n2023-07-01,120.320000,134.070007,115.830002,133.110001,133.110001,525456900\n2023-08-01,130.854996,138.399994,127.000000,137.350006,137.350006,463482000\n2023-09-01,138.429993,139.929993,128.190002,131.850006,131.850006,389593900\n2023-10-01,132.154999,142.380005,121.459999,125.300003,125.300003,514877100\n2023-11-01,125.339996,141.100006,124.925003,133.919998,133.919998,405635900\n2023-12-01,133.320007,143.945007,129.399994,140.929993,140.929993,482059400\n2024-01-01,139.600006,155.199997,136.850006,141.800003,141.800003,428771200\n2024-02-01,143.690002,150.695007,138.169998,147.139999,147.139999,231934100\n"},"_id":"CkJsyI0Bn3OCesyvveHo","_score":0.6056718} +{"_index":"test_stock_price_data","_source":{"stock_price_history":"This is the historical montly stock price record for Apple Inc. (AAPL) with CSV format.\nDate,Open,High,Low,Close,Adj Close,Volume\n2023-03-01,146.830002,165.000000,143.899994,164.899994,164.024475,1520266600\n2023-04-01,164.270004,169.850006,159.779999,169.679993,168.779099,969709700\n2023-05-01,169.279999,179.350006,164.309998,177.250000,176.308914,1275155500\n2023-06-01,177.699997,194.479996,176.929993,193.970001,193.207016,1297101100\n2023-07-01,193.779999,198.229996,186.600006,196.449997,195.677261,996066400\n2023-08-01,196.240005,196.729996,171.960007,187.869995,187.130997,1322439400\n2023-09-01,189.490005,189.979996,167.619995,171.210007,170.766846,1337586600\n2023-10-01,171.220001,182.339996,165.669998,170.770004,170.327972,1172719600\n2023-11-01,171.000000,192.929993,170.119995,189.949997,189.458313,1099586100\n2023-12-01,190.330002,199.619995,187.449997,192.529999,192.284637,1062774800\n2024-01-01,187.149994,196.380005,180.169998,184.399994,184.164993,1187219300\n2024-02-01,183.990005,191.050003,179.250000,188.850006,188.609329,420063900\n"},"_id":"BkJsyI0Bn3OCesyvveHo","_score":0.5960163} +""" + }, + { + "name": "response", + "result": "The population increase of Seattle from 2021 to 2023 is 0.86%. The stock price increase of Amazon from May 2023 to Feb 2023 is from $120.58 to $174.45, which is a percentage increase." + } + ] + } + ] +} +``` + +## Step 5: Configure a root chatbot agent in OpenSearch Dashboards + +To use the [OpenSearch Assistant for OpenSearch Dashboards]({{site.url}}{{site.baseurl}}/dashboards/dashboards-assistant/index/), you need to configure a root chatbot agent. + +A root chatbot agent consists of the following parts: + +- A `conversational` agent: Within the `AgentTool`, you can use any `conversational` agent created in the previous steps. +- An `MLModelTool`: This tool is used for suggesting new questions based on your current question and the model response. + +Configure a root agent: + +```json +POST /_plugins/_ml/agents/_register +{ + "name": "Chatbot agent", + "type": "flow", + "description": "this is a test chatbot agent", + "tools": [ + { + "type": "AgentTool", + "name": "LLMResponseGenerator", + "parameters": { + "agent_id": "your_conversational_agent_created_in_prevous_steps" + }, + "include_output_in_agent_response": true + }, + { + "type": "MLModelTool", + "name": "QuestionSuggestor", + "description": "A general tool to answer any question", + "parameters": { + "model_id": "your_llm_model_id_created_in_previous_steps", + "prompt": "Human: You are an AI that only speaks JSON. Do not write normal text. Output should follow example JSON format: \n\n {\"response\": [\"question1\", \"question2\"]}\n\n. \n\nHuman:You will be given a chat history between OpenSearch Assistant and a Human.\nUse the context provided to generate follow up questions the Human would ask to the Assistant.\nThe Assistant can answer general questions about logs, traces and metrics.\nAssistant can access a set of tools listed below to answer questions given by the Human:\nQuestion suggestions generator tool\nHere's the chat history between the human and the Assistant.\n${parameters.LLMResponseGenerator.output}\nUse the following steps to generate follow up questions Human may ask after the response of the Assistant:\nStep 1. Use the chat history to understand what human is trying to search and explore.\nStep 2. Understand what capabilities the assistant has with the set of tools it has access to.\nStep 3. Use the above context and generate follow up questions.Step4:You are an AI that only speaks JSON. Do not write normal text. Output should follow example JSON format: \n\n {\"response\": [\"question1\", \"question2\"]} \n \n----------------\n\nAssistant:" + }, + "include_output_in_agent_response": true + } + ], + "memory": { + "type": "conversation_index" + } +} +``` +{% include copy-curl.html %} + +Note the root chatbot agent ID, log in to your OpenSearch server, go to the OpenSearch config folder (`$OS_HOME/config`), and run the following command: + +```bashx + curl -k --cert ./kirk.pem --key ./kirk-key.pem -X PUT https://localhost:9200/.plugins-ml-config/_doc/os_chat -H 'Content-Type: application/json' -d' + { + "type":"os_chat_root_agent", + "configuration":{ + "agent_id": "your_root_chatbot_agent_id" + } + }' +``` +{% include copy.html %} + +Go to your OpenSearch Dashboards config folder (`$OSD_HOME/config`) and edit `opensearch_dashboards.yml` by adding the following line to the end of the file: `assistant.chat.enabled: true`. + +Restart OpenSearch Dashboards and then select the chat icon in the upper-right corner, shown in the following image. + +OpenSearch Assistant icon + +You can now chat in OpenSearch Dashboards, as shown in the following image. + +OpenSearch Assistant chat \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/conversational-search-cohere.md b/_ml-commons-plugin/tutorials/conversational-search-cohere.md new file mode 100644 index 0000000000..e02f576b7c --- /dev/null +++ b/_ml-commons-plugin/tutorials/conversational-search-cohere.md @@ -0,0 +1,228 @@ +--- +layout: default +title: Conversational search with Cohere Command +parent: Tutorials +nav_order: 20 +--- + +# Conversational search using the Cohere Command model + +This tutorial illustrates how to configure conversational search using the Cohere Command model. For more information, see [Conversational search]({{site.url}}{{site.baseurl}}/search-plugins/conversational-search/). + +Replace the placeholders beginning with the prefix `your_` with your own values. +{: .note} + +Alternatively, you can build a RAG/conversational search using agents and tools. For more information, see [Retrieval-augmented generation chatbot]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/rag-conversational-agent/). + +## Prerequisite + +Ingest test data: + +```json +POST _bulk +{"index": {"_index": "qa_demo", "_id": "1"}} +{"text": "Chart and table of population level and growth rate for the Ogden-Layton metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\nThe current metro area population of Ogden-Layton in 2023 is 750,000, a 1.63% increase from 2022.\nThe metro area population of Ogden-Layton in 2022 was 738,000, a 1.79% increase from 2021.\nThe metro area population of Ogden-Layton in 2021 was 725,000, a 1.97% increase from 2020.\nThe metro area population of Ogden-Layton in 2020 was 711,000, a 2.16% increase from 2019."} +{"index": {"_index": "qa_demo", "_id": "2"}} +{"text": "Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019."} +{"index": {"_index": "qa_demo", "_id": "3"}} +{"text": "Chart and table of population level and growth rate for the Chicago metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Chicago in 2023 is 8,937,000, a 0.4% increase from 2022.\\nThe metro area population of Chicago in 2022 was 8,901,000, a 0.27% increase from 2021.\\nThe metro area population of Chicago in 2021 was 8,877,000, a 0.14% increase from 2020.\\nThe metro area population of Chicago in 2020 was 8,865,000, a 0.03% increase from 2019."} +{"index": {"_index": "qa_demo", "_id": "4"}} +{"text": "Chart and table of population level and growth rate for the Miami metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Miami in 2023 is 6,265,000, a 0.8% increase from 2022.\\nThe metro area population of Miami in 2022 was 6,215,000, a 0.78% increase from 2021.\\nThe metro area population of Miami in 2021 was 6,167,000, a 0.74% increase from 2020.\\nThe metro area population of Miami in 2020 was 6,122,000, a 0.71% increase from 2019."} +{"index": {"_index": "qa_demo", "_id": "5"}} +{"text": "Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."} +{"index": {"_index": "qa_demo", "_id": "6"}} +{"text": "Chart and table of population level and growth rate for the Seattle metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Seattle in 2023 is 3,519,000, a 0.86% increase from 2022.\\nThe metro area population of Seattle in 2022 was 3,489,000, a 0.81% increase from 2021.\\nThe metro area population of Seattle in 2021 was 3,461,000, a 0.82% increase from 2020.\\nThe metro area population of Seattle in 2020 was 3,433,000, a 0.79% increase from 2019."} +``` +{% include copy-curl.html %} + +## Step 1: Create a connector and register a model + +Conversational search only supports the [OpenAI](https://github.com/opensearch-project/ml-commons/blob/2.x/docs/remote_inference_blueprints/open_ai_connector_chat_blueprint.md) +and [Amazon Bedrock Claude](https://github.com/opensearch-project/ml-commons/blob/2.x/docs/remote_inference_blueprints/bedrock_connector_anthropic_claude_blueprint.md) input/output styles. +{: .important} + +This tutorial follows the Amazon Bedrock Claude model input/output style by: +- Mapping the Cohere Command `message` input parameter to the `inputs` parameter in order to match the Cohere Claude model input style. +- Using a post-processing function to convert the Cohere Command model output to the Claude model output style. + +Create a connector for the Cohere Command model: + +```json +POST _plugins/_ml/connectors/_create +{ + "name": "Cohere Chat Model", + "description": "The connector to Cohere's public chat API", + "version": "1", + "protocol": "http", + "credential": { + "cohere_key": "your_cohere_api_key" + }, + "parameters": { + "model": "command" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://api.cohere.ai/v1/chat", + "headers": { + "Authorization": "Bearer ${credential.cohere_key}", + "Request-Source": "unspecified:opensearch" + }, + "request_body": "{ \"message\": \"${parameters.inputs}\", \"model\": \"${parameters.model}\" }", + "post_process_function": "\n String escape(def input) { \n if (input.contains(\"\\\\\")) {\n input = input.replace(\"\\\\\", \"\\\\\\\\\");\n }\n if (input.contains(\"\\\"\")) {\n input = input.replace(\"\\\"\", \"\\\\\\\"\");\n }\n if (input.contains('\r')) {\n input = input = input.replace('\r', '\\\\r');\n }\n if (input.contains(\"\\\\t\")) {\n input = input.replace(\"\\\\t\", \"\\\\\\\\\\\\t\");\n }\n if (input.contains('\n')) {\n input = input.replace('\n', '\\\\n');\n }\n if (input.contains('\b')) {\n input = input.replace('\b', '\\\\b');\n }\n if (input.contains('\f')) {\n input = input.replace('\f', '\\\\f');\n }\n return input;\n }\n def name = 'response';\n def result = params.text;\n def json = '{ \"name\": \"' + name + '\",' +\n '\"dataAsMap\": { \"completion\": \"' + escape(result) +\n '\"}}';\n return json;\n \n " + } + ] +} +``` +{% include copy-curl.html %} + +Starting in OpenSearch 2.12, you can use the default `escape` function directly in the `post_process_function`: + +```json +"post_process_function": " \n def name = 'response';\n def result = params.text;\n def json = '{ \"name\": \"' + name + '\",' +\n '\"dataAsMap\": { \"completion\": \"' + escape(result) +\n '\"}}';\n return json;" +``` +{% include copy-curl.html %} + +Note the connector ID; you'll use it to register the model. + +Register the Cohere Command model: + +```json +POST /_plugins/_ml/models/_register?deploy=true +{ + "name": "Cohere command model", + "function_name": "remote", + "description": "Cohere command model", + "connector_id": "your_connector_id" +} +``` +{% include copy-curl.html %} + +Note the model ID; you'll use it in the following steps. + +Test the model: + +```json +POST /_plugins/_ml/models/your_model_id/_predict +{ + "parameters": { + "inputs": "What is the weather like in Seattle?" + } +} +``` +{% include copy-curl.html %} + +The response contains the LLM completion: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "response", + "dataAsMap": { + "completion": """It is difficult to provide a comprehensive answer without a specific location or time frame in mind. + +As an AI language model, I have no access to real-time data or the ability to provide live weather reports. Instead, I can offer some general information about Seattle's weather, which is known for its mild, wet climate. + +Located in the Pacific Northwest region of the United States, Seattle experiences a maritime climate with cool, dry summers and mild, wet winters. While it is best known for its rainy days, Seattle's annual rainfall is actually less than New York City and Boston. + +Would you like me to provide more details on Seattle's weather? Or, if you have a specific date or location in mind, I can try to retrieve real-time or historical weather information for you.""" + } + } + ], + "status_code": 200 + } + ] +} +``` + +## Step 2: Configure conversational search + +Create a search pipeline containing a RAG processor: + +```json +PUT /_search/pipeline/my-conversation-search-pipeline-cohere +{ + "response_processors": [ + { + "retrieval_augmented_generation": { + "tag": "Demo pipeline", + "description": "Demo pipeline Using Cohere", + "model_id": "your_model_id_created_in_step1", + "context_field_list": [ + "text" + ], + "system_prompt": "You are a helpful assistant", + "user_instructions": "Generate a concise and informative answer in less than 100 words for the given question" + } + } + ] +} +``` +{% include copy-curl.html %} + +To run a conversational search, specify its parameters in the `generative_qa_parameters` object: + +```json +GET /qa_demo/_search?search_pipeline=my-conversation-search-pipeline-cohere +{ + "query": { + "match": { + "text": "What's the population increase of New York City from 2021 to 2023?" + } + }, + "size": 1, + "_source": [ + "text" + ], + "ext": { + "generative_qa_parameters": { + "llm_model": "bedrock/claude", + "llm_question": "What's the population increase of New York City from 2021 to 2023?", + "context_size": 5, + "timeout": 15 + } + } +} +``` +{% include copy-curl.html %} + +The response contains the model's answer: + +```json +{ + "took": 1, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 6, + "relation": "eq" + }, + "max_score": 9.042081, + "hits": [ + { + "_index": "qa_demo", + "_id": "2", + "_score": 9.042081, + "_source": { + "text": """Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019.""" + } + } + ] + }, + "ext": { + "retrieval_augmented_generation": { + "answer": "The population of the New York City metro area increased by about 210,000 from 2021 to 2023. The 2021 population was 18,823,000, and in 2023 it was 18,937,000. The average growth rate is 0.23% yearly." + } + } +} +``` \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/generate-embeddings.md b/_ml-commons-plugin/tutorials/generate-embeddings.md new file mode 100644 index 0000000000..92b62b9fe8 --- /dev/null +++ b/_ml-commons-plugin/tutorials/generate-embeddings.md @@ -0,0 +1,336 @@ +--- +layout: default +title: Generating embeddings +parent: Tutorials +nav_order: 5 +--- + +# Generating embeddings for arrays of objects + +This tutorial illustrates how to generate embeddings for arrays of objects. + +Replace the placeholders beginning with the prefix `your_` with your own values. +{: .note} + +## Step 1: Register an embedding model + +For this tutorial, you will use the [Amazon Bedrock Titan Embedding model](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html). + +First, follow the [Amazon Bedrock Titan blueprint example](https://github.com/opensearch-project/ml-commons/blob/2.x/docs/remote_inference_blueprints/bedrock_connector_titan_embedding_blueprint.md) to register and deploy the model. + +Test the model, providing the model ID: + +```json +POST /_plugins/_ml/models/your_embedding_model_id/_predict +{ + "parameters": { + "inputText": "hello world" + } +} +``` +{% include copy-curl.html %} + +The response contains inference results: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "sentence_embedding", + "data_type": "FLOAT32", + "shape": [ 1536 ], + "data": [0.7265625, -0.0703125, 0.34765625, ...] + } + ], + "status_code": 200 + } + ] +} +``` + +## Step 2: Create an ingest pipeline + +Follow the next set of steps to create an ingest pipeline for generating embeddings. + +### Step 2.1: Create a k-NN index + +First, create a k-NN index: + +```json +PUT my_books +{ + "settings" : { + "index.knn" : "true", + "default_pipeline": "bedrock_embedding_foreach_pipeline" + }, + "mappings": { + "properties": { + "books": { + "type": "nested", + "properties": { + "title_embedding": { + "type": "knn_vector", + "dimension": 1536 + }, + "title": { + "type": "text" + }, + "description": { + "type": "text" + } + } + } + } + } +} +``` +{% include copy-curl.html %} + +### Step 2.2: Create an ingest pipeline + +Then create an inner ingest pipeline to generate an embedding for one array element. + +This pipeline contains three processors: + +- `set` processor: The `text_embedding` processor is unable to identify the `_ingest._value.title` field. You must copy `_ingest._value.title` to a non-existing temporary field so that the `text_embedding` processor can process it. +- `text_embedding` processor: Converts the value of the temporary field to an embedding. +- `remove` processor: Removes the temporary field. + +To create such a pipeline, send the following request: + +```json +PUT _ingest/pipeline/bedrock_embedding_pipeline +{ + "processors": [ + { + "set": { + "field": "title_tmp", + "value": "{{_ingest._value.title}}" + } + }, + { + "text_embedding": { + "model_id": your_embedding_model_id, + "field_map": { + "title_tmp": "_ingest._value.title_embedding" + } + } + }, + { + "remove": { + "field": "title_tmp" + } + } + ] +} +``` +{% include copy-curl.html %} + +Create an ingest pipeline with a `foreach` processor that will apply the `bedrock_embedding_pipeline` to each element of the `books` array: + +```json +PUT _ingest/pipeline/bedrock_embedding_foreach_pipeline +{ + "description": "Test nested embeddings", + "processors": [ + { + "foreach": { + "field": "books", + "processor": { + "pipeline": { + "name": "bedrock_embedding_pipeline" + } + }, + "ignore_failure": true + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2.3: Simulate the pipeline + +First, you'll test the pipeline on an array that contains two book objects, both with a `title` field: + +```json +POST _ingest/pipeline/bedrock_embedding_foreach_pipeline/_simulate +{ + "docs": [ + { + "_index": "my_books", + "_id": "1", + "_source": { + "books": [ + { + "title": "first book", + "description": "This is first book" + }, + { + "title": "second book", + "description": "This is second book" + } + ] + } + } + ] +} +``` +{% include copy-curl.html %} + +The response contains generated embeddings for both objects in their `title_embedding` fields: + +```json +{ + "docs": [ + { + "doc": { + "_index": "my_books", + "_id": "1", + "_source": { + "books": [ + { + "title": "first book", + "title_embedding": [-1.1015625, 0.65234375, 0.7578125, ...], + "description": "This is first book" + }, + { + "title": "second book", + "title_embedding": [-0.65234375, 0.21679688, 0.7265625, ...], + "description": "This is second book" + } + ] + }, + "_ingest": { + "_value": null, + "timestamp": "2024-05-28T16:16:50.538929413Z" + } + } + } + ] +} +``` + +Next, you'll test the pipeline on an array that contains two book objects, one with a `title` field and one without: + +```json +POST _ingest/pipeline/bedrock_embedding_foreach_pipeline/_simulate +{ + "docs": [ + { + "_index": "my_books", + "_id": "1", + "_source": { + "books": [ + { + "title": "first book", + "description": "This is first book" + }, + { + "description": "This is second book" + } + ] + } + } + ] +} +``` +{% include copy-curl.html %} + +The response contains generated embeddings for the object that contains the `title` field: + +```json +{ + "docs": [ + { + "doc": { + "_index": "my_books", + "_id": "1", + "_source": { + "books": [ + { + "title": "first book", + "title_embedding": [-1.1015625, 0.65234375, 0.7578125, ...], + "description": "This is first book" + }, + { + "description": "This is second book" + } + ] + }, + "_ingest": { + "_value": null, + "timestamp": "2024-05-28T16:19:03.942644042Z" + } + } + } + ] +} +``` +### Step 2.4: Test data ingestion + +Ingest one document: + +```json +PUT my_books/_doc/1 +{ + "books": [ + { + "title": "first book", + "description": "This is first book" + }, + { + "title": "second book", + "description": "This is second book" + } + ] +} +``` +{% include copy-curl.html %} + +Get the document: + +```json +GET my_books/_doc/1 +``` +{% include copy-curl.html %} + +The response contains the generated embeddings: + +```json +{ + "_index": "my_books", + "_id": "1", + "_version": 1, + "_seq_no": 0, + "_primary_term": 1, + "found": true, + "_source": { + "books": [ + { + "description": "This is first book", + "title": "first book", + "title_embedding": [-1.1015625, 0.65234375, 0.7578125, ...] + }, + { + "description": "This is second book", + "title": "second book", + "title_embedding": [-0.65234375, 0.21679688, 0.7265625, ...] + } + ] + } +} +``` + +You can also ingest several documents in bulk and test the generated embeddings by calling the Get Document API: + +```json +POST _bulk +{ "index" : { "_index" : "my_books" } } +{ "books" : [{"title": "first book", "description": "This is first book"}, {"title": "second book", "description": "This is second book"}] } +{ "index" : { "_index" : "my_books" } } +{ "books" : [{"title": "third book", "description": "This is third book"}, {"description": "This is fourth book"}] } +``` +{% include copy-curl.html %} \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/index.md b/_ml-commons-plugin/tutorials/index.md new file mode 100644 index 0000000000..4479d0878f --- /dev/null +++ b/_ml-commons-plugin/tutorials/index.md @@ -0,0 +1,26 @@ +--- +layout: default +title: Tutorials +has_children: true +has_toc: false +nav_order: 140 +--- + +# Tutorials + +Using the OpenSearch machine learning (ML) framework, you can build various applications, from implementing conversational search to building your own chatbot. To learn more, explore the following ML tutorials: + +- **Semantic search**: + - [Generating embeddings for arrays of objects]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/generate-embeddings/) + - [Semantic search using byte-quantized vectors]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/semantic-search-byte-vectors/) + +- **Conversational search**: + - [Conversational search using the Cohere Command model]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/conversational-search-cohere/) + +- **Reranking search results**: + - [Reranking search results using the Cohere Rerank model]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/reranking-cohere/) + +- **Agents and tools**: + - [Retrieval-augmented generation (RAG) chatbot]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/rag-chatbot/) + - [RAG with a conversational flow agent]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/rag-conversational-agent/) + - [Build your own chatbot]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/build-chatbot/) \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/rag-chatbot.md b/_ml-commons-plugin/tutorials/rag-chatbot.md new file mode 100644 index 0000000000..5dddded23a --- /dev/null +++ b/_ml-commons-plugin/tutorials/rag-chatbot.md @@ -0,0 +1,346 @@ +--- +layout: default +title: RAG chatbot +parent: Tutorials +nav_order: 50 +--- + +# RAG chatbot + +One of the known limitations of large language models (LLMs) is that their knowledge base only contains information from the period of time during which they were trained. LLMs have no knowledge of recent events or of your internal data. You can augment the LLM knowledge base by using retrieval-augmented generation (RAG). + +This tutorial illustrates how to build your own chatbot using [agents and tools](https://opensearch.org/docs/latest/ml-commons-plugin/agents-tools/index/) and RAG. RAG supplements the LLM knowledge base with information contained in OpenSearch indexes. + +Replace the placeholders beginning with the prefix `your_` with your own values. +{: .note} + +## Prerequisite + +Meet the prerequisite and follow Step 1 of the [RAG with a conversational flow agent tutorial]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/rag-conversational-agent/) to set up the `test_population_data` knowledge base index, which contains US city population data. + +Note the embedding model ID; you'll use it in the following steps. + +## Step 1: Set up a knowledge base + +First, create an ingest pipeline: + +```json +PUT /_ingest/pipeline/test_tech_news_pipeline +{ + "description": "text embedding pipeline for tech news", + "processors": [ + { + "text_embedding": { + "model_id": "your_text_embedding_model_id", + "field_map": { + "passage": "passage_embedding" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +Next, create an index named `test_tech_news`, which contains recent tech news: + +```json +PUT test_tech_news +{ + "mappings": { + "properties": { + "passage": { + "type": "text" + }, + "passage_embedding": { + "type": "knn_vector", + "dimension": 384 + } + } + }, + "settings": { + "index": { + "knn.space_type": "cosinesimil", + "default_pipeline": "test_tech_news_pipeline", + "knn": "true" + } + } +} +``` +{% include copy-curl.html %} + +Ingest data into the index: + +```json +POST _bulk +{"index":{"_index":"test_tech_news"}} +{"c":"Apple Vision Pro is a mixed-reality headset developed by Apple Inc. It was announced on June 5, 2023, at Apple's Worldwide Developers Conference, and pre-orders began on January 19, 2024. It became available for purchase on February 2, 2024, in the United States.[10] A worldwide launch has yet to be scheduled. The Vision Pro is Apple's first new major product category since the release of the Apple Watch in 2015.[11]\n\nApple markets the Vision Pro as a \"spatial computer\" where digital media is integrated with the real world. Physical inputs—such as motion gestures, eye tracking, and speech recognition—can be used to interact with the system.[10] Apple has avoided marketing the device as a virtual reality headset, along with the use of the terms \"virtual reality\" and \"augmented reality\" when discussing the product in presentations and marketing.[12]\n\nThe device runs visionOS,[13] a mixed-reality operating system derived from iOS frameworks using a 3D user interface; it supports multitasking via windows that appear to float within the user's surroundings,[14] as seen by cameras built into the headset. A dial on the top of the headset can be used to mask the camera feed with a virtual environment to increase immersion. The OS supports avatars (officially called \"Personas\"), which are generated by scanning the user's face; a screen on the front of the headset displays a rendering of the avatar's eyes (\"EyeSight\"), which are used to indicate the user's level of immersion to bystanders, and assist in communication.[15]"} +{"index":{"_index":"test_tech_news"}} +{"passage":"LLaMA (Large Language Model Meta AI) is a family of autoregressive large language models (LLMs), released by Meta AI starting in February 2023.\n\nFor the first version of LLaMA, four model sizes were trained: 7, 13, 33, and 65 billion parameters. LLaMA's developers reported that the 13B parameter model's performance on most NLP benchmarks exceeded that of the much larger GPT-3 (with 175B parameters) and that the largest model was competitive with state of the art models such as PaLM and Chinchilla.[1] Whereas the most powerful LLMs have generally been accessible only through limited APIs (if at all), Meta released LLaMA's model weights to the research community under a noncommercial license.[2] Within a week of LLaMA's release, its weights were leaked to the public on 4chan via BitTorrent.[3]\n\nIn July 2023, Meta released several models as Llama 2, using 7, 13 and 70 billion parameters.\n\nLLaMA-2\n\nOn July 18, 2023, in partnership with Microsoft, Meta announced LLaMA-2, the next generation of LLaMA. Meta trained and released LLaMA-2 in three model sizes: 7, 13, and 70 billion parameters.[4] The model architecture remains largely unchanged from that of LLaMA-1 models, but 40% more data was used to train the foundational models.[5] The accompanying preprint[5] also mentions a model with 34B parameters that might be released in the future upon satisfying safety targets.\n\nLLaMA-2 includes both foundational models and models fine-tuned for dialog, called LLaMA-2 Chat. In further departure from LLaMA-1, all models are released with weights, and are free for many commercial use cases. However, due to some remaining restrictions, the description of LLaMA as open source has been disputed by the Open Source Initiative (known for maintaining the Open Source Definition).[6]\n\nIn November 2023, research conducted by Patronus AI, an artificial intelligence startup company, compared performance of LLaMA-2, OpenAI's GPT-4 and GPT-4-Turbo, and Anthropic's Claude2 on two versions of a 150-question test about information in SEC filings (e.g. Form 10-K, Form 10-Q, Form 8-K, earnings reports, earnings call transcripts) submitted by public companies to the agency where one version of the test required the generative AI models to use a retrieval system to locate the specific SEC filing to answer the questions while the other version provided the specific SEC filing to the models to answer the question (i.e. in a long context window). On the retrieval system version, GPT-4-Turbo and LLaMA-2 both failed to produce correct answers to 81% of the questions, while on the long context window version, GPT-4-Turbo and Claude-2 failed to produce correct answers to 21% and 24% of the questions respectively.[7][8]"} +{"index":{"_index":"test_tech_news"}} +{"passage":"Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs) from leading AI companies like AI21 Labs, Anthropic, Cohere, Meta, Stability AI, and Amazon via a single API, along with a broad set of capabilities you need to build generative AI applications with security, privacy, and responsible AI. Using Amazon Bedrock, you can easily experiment with and evaluate top FMs for your use case, privately customize them with your data using techniques such as fine-tuning and Retrieval Augmented Generation (RAG), and build agents that execute tasks using your enterprise systems and data sources. Since Amazon Bedrock is serverless, you don't have to manage any infrastructure, and you can securely integrate and deploy generative AI capabilities into your applications using the AWS services you are already familiar with."} +``` +{% include copy-curl.html %} + +## Step 2: Prepare an LLM + +Follow [step 2 of the RAG with a conversational flow agent tutorial]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/rag-conversational-agent/#step-2-prepare-an-llm) to configure the Amazon Bedrock Claude model. + +Note the model ID; you'll use it in the following steps. + +## Step 3: Create an agent + +For this tutorial, you will create an agent of the `conversational` type. + +Both the `conversational_flow` and `conversational` agents support conversation history. + +The `conversational_flow` and `conversational` agents differ in the following ways: + +- A `conversational_flow` agent runs tools sequentially, in a predefined order. +- A `conversational` agent dynamically chooses which tool to run next. + +In this tutorial, the agent includes two tools: One provides recent population data, and the other contains tech news. + +The agent has the following parameters: + +- `"max_iteration": 5`: The agent runs the LLM a maximum of five times. +- `"response_filter": "$.completion"`: Needed to retrieve the LLM answer from the Amazon Bedrock Claude model response. +- `"doc_size": 3` (in `population_data_knowledge_base`): Specifies to return the top three documents. + +Create an agent with the preceding specifications: + +```json +POST _plugins/_ml/agents/_register +{ + "name": "Chat Agent with RAG", + "type": "conversational", + "description": "this is a test agent", + "llm": { + "model_id": "your_llm_model_id", + "parameters": { + "max_iteration": 5, + "response_filter": "$.completion" + } + }, + "memory": { + "type": "conversation_index" + }, + "tools": [ + { + "type": "VectorDBTool", + "name": "population_data_knowledge_base", + "description": "This tool provides population data of US cities.", + "parameters": { + "input": "${parameters.question}", + "index": "test_population_data", + "source_field": [ + "population_description" + ], + "model_id": "your_text_embedding_model_id", + "embedding_field": "population_description_embedding", + "doc_size": 3 + } + }, + { + "type": "VectorDBTool", + "name": "tech_news_knowledge_base", + "description": "This tool provides recent tech news.", + "parameters": { + "input": "${parameters.question}", + "index": "test_tech_news", + "source_field": [ + "passage" + ], + "model_id": "your_text_embedding_model_id", + "embedding_field": "passage_embedding", + "doc_size": 2 + } + } + ], + "app_type": "chat_with_rag" +} +``` +{% include copy-curl.html %} + +Note the agent ID; you'll use it in the next step. + +## Step 4: Test the agent + +The `conversational` agent supports a `verbose` option. You can set `verbose` to `true` to obtain detailed steps. + +Alternatively, you can call the [Get Message Traces API](ml-commons-plugin/api/memory-apis/get-message-traces/): + +```json +GET _plugins/_ml/memory/message/message_id/traces +``` +{% include copy-curl.html %} + +### Start a conversation + +Ask a question related to tech news: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's vision pro", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +In the response, note that the agent runs the `tech_news_knowledge_base` tool to obtain the top two documents. The agent then passes these documents as context to the LLM. The LLM uses the context to produce the answer: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "eLVSxI0B8vrNLhb9nxto" + }, + { + "name": "parent_interaction_id", + "result": "ebVSxI0B8vrNLhb9nxty" + }, + { + "name": "response", + "result": """{ + "thought": "I don't have enough context to answer the question directly. Let me check the tech_news_knowledge_base tool to see if it can provide more information.", + "action": "tech_news_knowledge_base", + "action_input": "{\"query\":\"What's vision pro\"}" +}""" + }, + { + "name": "response", + "result": """{"_index":"test_tech_news","_source":{"passage":"Apple Vision Pro is a mixed-reality headset developed by Apple Inc. It was announced on June 5, 2023, at Apple\u0027s Worldwide Developers Conference, and pre-orders began on January 19, 2024. It became available for purchase on February 2, 2024, in the United States.[10] A worldwide launch has yet to be scheduled. The Vision Pro is Apple\u0027s first new major product category since the release of the Apple Watch in 2015.[11]\n\nApple markets the Vision Pro as a \"spatial computer\" where digital media is integrated with the real world. Physical inputs—such as motion gestures, eye tracking, and speech recognition—can be used to interact with the system.[10] Apple has avoided marketing the device as a virtual reality headset, along with the use of the terms \"virtual reality\" and \"augmented reality\" when discussing the product in presentations and marketing.[12]\n\nThe device runs visionOS,[13] a mixed-reality operating system derived from iOS frameworks using a 3D user interface; it supports multitasking via windows that appear to float within the user\u0027s surroundings,[14] as seen by cameras built into the headset. A dial on the top of the headset can be used to mask the camera feed with a virtual environment to increase immersion. The OS supports avatars (officially called \"Personas\"), which are generated by scanning the user\u0027s face; a screen on the front of the headset displays a rendering of the avatar\u0027s eyes (\"EyeSight\"), which are used to indicate the user\u0027s level of immersion to bystanders, and assist in communication.[15]"},"_id":"lrU8xI0B8vrNLhb9yBpV","_score":0.6700683} +{"_index":"test_tech_news","_source":{"passage":"Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs) from leading AI companies like AI21 Labs, Anthropic, Cohere, Meta, Stability AI, and Amazon via a single API, along with a broad set of capabilities you need to build generative AI applications with security, privacy, and responsible AI. Using Amazon Bedrock, you can easily experiment with and evaluate top FMs for your use case, privately customize them with your data using techniques such as fine-tuning and Retrieval Augmented Generation (RAG), and build agents that execute tasks using your enterprise systems and data sources. Since Amazon Bedrock is serverless, you don\u0027t have to manage any infrastructure, and you can securely integrate and deploy generative AI capabilities into your applications using the AWS services you are already familiar with."},"_id":"mLU8xI0B8vrNLhb9yBpV","_score":0.5604863} +""" + }, + { + "name": "response", + "result": "Vision Pro is a mixed-reality headset developed by Apple that was announced in 2023. It uses cameras and sensors to overlay digital objects and information on the real world. The device runs an operating system called visionOS that allows users to interact with windows and apps in a 3D environment using gestures, eye tracking, and voice commands." + } + ] + } + ] +} +``` + +You can trace the detailed steps by using the Get Traces API: + +``` +GET _plugins/_ml/memory/message/ebVSxI0B8vrNLhb9nxty/traces +``` +{% include copy-curl.html %} + +Ask a question related to the population data: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's the population of Seattle 2023", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +In the response, note that the agent runs the `population_data_knowledge_base` tool to obtain the top three documents. The agent then passes these documents as context to the LLM. The LLM uses the context to produce the answer: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "l7VUxI0B8vrNLhb9sRuQ" + }, + { + "name": "parent_interaction_id", + "result": "mLVUxI0B8vrNLhb9sRub" + }, + { + "name": "response", + "result": """{ + "thought": "Let me check the population data tool to find the most recent population estimate for Seattle", + "action": "population_data_knowledge_base", + "action_input": "{\"city\":\"Seattle\"}" +}""" + }, + { + "name": "response", + "result": """{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Seattle metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Seattle in 2023 is 3,519,000, a 0.86% increase from 2022.\\nThe metro area population of Seattle in 2022 was 3,489,000, a 0.81% increase from 2021.\\nThe metro area population of Seattle in 2021 was 3,461,000, a 0.82% increase from 2020.\\nThe metro area population of Seattle in 2020 was 3,433,000, a 0.79% increase from 2019."},"_id":"BxF5vo0BubpYKX5ER0fT","_score":0.65775126} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Seattle metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Seattle in 2023 is 3,519,000, a 0.86% increase from 2022.\\nThe metro area population of Seattle in 2022 was 3,489,000, a 0.81% increase from 2021.\\nThe metro area population of Seattle in 2021 was 3,461,000, a 0.82% increase from 2020.\\nThe metro area population of Seattle in 2020 was 3,433,000, a 0.79% increase from 2019."},"_id":"7DrZvo0BVR2NrurbRIAE","_score":0.65775126} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019."},"_id":"AxF5vo0BubpYKX5ER0fT","_score":0.56461215} +""" + }, + { + "name": "response", + "result": "According to the population data tool, the population of Seattle in 2023 is approximately 3,519,000 people, a 0.86% increase from 2022." + } + ] + } + ] +} +``` + +### Continue a conversation + +To continue a previous conversation, provide its conversation ID in the `memory_id` parameter: + +```json +POST _plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's the population of Austin 2023, compared with Seattle", + "memory_id": "l7VUxI0B8vrNLhb9sRuQ", + "verbose": true + } +} +``` +{% include copy-curl.html %} + +In the response, note that the `population_data_knowledge_base` doesn't return the population of Seattle. Instead, the agent learns the population of Seattle by referencing historical messages: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "l7VUxI0B8vrNLhb9sRuQ" + }, + { + "name": "parent_interaction_id", + "result": "B7VkxI0B8vrNLhb9mxy0" + }, + { + "name": "response", + "result": """{ + "thought": "Let me check the population data tool first", + "action": "population_data_knowledge_base", + "action_input": "{\"city\":\"Austin\",\"year\":2023}" +}""" + }, + { + "name": "response", + "result": """{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."},"_id":"BhF5vo0BubpYKX5ER0fT","_score":0.69129956} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."},"_id":"6zrZvo0BVR2NrurbRIAE","_score":0.69129956} +{"_index":"test_population_data","_source":{"population_description":"Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019."},"_id":"AxF5vo0BubpYKX5ER0fT","_score":0.61015373} +""" + }, + { + "name": "response", + "result": "According to the population data tool, the population of Austin in 2023 is approximately 2,228,000 people, a 2.39% increase from 2022. This is lower than the population of Seattle in 2023 which is approximately 3,519,000 people, a 0.86% increase from 2022." + } + ] + } + ] +} +``` \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/rag-conversational-agent.md b/_ml-commons-plugin/tutorials/rag-conversational-agent.md new file mode 100644 index 0000000000..86fe38416a --- /dev/null +++ b/_ml-commons-plugin/tutorials/rag-conversational-agent.md @@ -0,0 +1,838 @@ +--- +layout: default +title: RAG chatbot with a conversational flow agent +parent: Tutorials +nav_order: 40 +--- + +# RAG chatbot with a conversational flow agent + +This tutorial explains how to use a conversational flow agent to build a retrieval-augmented generation (RAG) application with your OpenSearch data as a knowledge base. + +Replace the placeholders beginning with the prefix `your_` with your own values. +{: .note} + +An alternative way to build RAG conversational search is to use a RAG pipeline. For more information, see [Conversational search using the Cohere Command model]({{site.url}}{{site.baseurl}}/ml-commons-plugin/tutorials/conversational-search-cohere/). + +## Prerequisite + +In this tutorial, you'll build a RAG application that provides an OpenSearch [k-NN index]({{site.url}}{{site.baseurl}}/search-plugins/knn/knn-index/) as a knowledge base for a large language model (LLM). For data retrieval, you'll use [semantic search]({{site.url}}{{site.baseurl}}/search-plugins/semantic-search/). For a comprehensive semantic search tutorial, see [Neural search tutorial]({{site.url}}{{site.baseurl}}/search-plugins/neural-search-tutorial/). + +First, you'll need to update your cluster settings. If you don't have a dedicated machine learning (ML) node, set `"plugins.ml_commons.only_run_on_ml_node": false`. To avoid triggering a native memory circuit breaker, set `"plugins.ml_commons.native_memory_threshold"` to 100%: + +```json +PUT _cluster/settings +{ + "persistent": { + "plugins.ml_commons.only_run_on_ml_node": false, + "plugins.ml_commons.native_memory_threshold": 100, + "plugins.ml_commons.agent_framework_enabled": true + } +} +``` +{% include copy-curl.html %} + +## Step 1: Prepare the knowledge base + +Use the following steps to prepare the knowledge base that will supplement the LLM's knowledge. + +### Step 1.1: Register a text embedding model + +Register a text embedding model that will translate text into vector embeddings: + +```json +POST /_plugins/_ml/models/_register +{ + "name": "huggingface/sentence-transformers/all-MiniLM-L12-v2", + "version": "1.0.1", + "model_format": "TORCH_SCRIPT" +} +``` +{% include copy-curl.html %} + +Note the text embedding model ID; you'll use it in the following steps. + +As an alternative, you can get the model ID by calling the [Get Task API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/tasks-apis/get-task/): + +```json +GET /_plugins/_ml/tasks/your_task_id +``` +{% include copy-curl.html %} + +Deploy the model: + +```json +POST /_plugins/_ml/models/your_text_embedding_model_id/_deploy +``` +{% include copy-curl.html %} + +Test the model: + +```json +POST /_plugins/_ml/models/your_text_embedding_model_id/_predict +{ + "text_docs":[ "today is sunny"], + "return_number": true, + "target_response": ["sentence_embedding"] +} +``` +{% include copy-curl.html %} + +For more information about using models within your OpenSearch cluster, see [Pretrained models]({{site.url}}{{site.baseurl}}/ml-commons-plugin/pretrained-models/). + +### Step 1.2: Create an ingest pipeline + +Create an ingest pipeline with a text embedding processor, which can invoke the model created in the previous step to generate embeddings from text fields: + +```json +PUT /_ingest/pipeline/test_population_data_pipeline +{ + "description": "text embedding pipeline", + "processors": [ + { + "text_embedding": { + "model_id": "your_text_embedding_model_id", + "field_map": { + "population_description": "population_description_embedding" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +For more information about ingest pipelines, see [Ingest pipelines]({{site.url}}{{site.baseurl}}/ingest-pipelines/). + +### Step 1.3: Create a k-NN index + +Create a k-NN index specifying the ingest pipeline as a default pipeline: + +```json +PUT test_population_data +{ + "mappings": { + "properties": { + "population_description": { + "type": "text" + }, + "population_description_embedding": { + "type": "knn_vector", + "dimension": 384 + } + } + }, + "settings": { + "index": { + "knn.space_type": "cosinesimil", + "default_pipeline": "test_population_data_pipeline", + "knn": "true" + } + } +} +``` +{% include copy-curl.html %} + +For more information about k-NN indexes, see [k-NN index]({{site.url}}{{site.baseurl}}/search-plugins/knn/knn-index/). + +### Step 1.4: Ingest data + +Ingest test data into the k-NN index: + +```json +POST _bulk +{"index": {"_index": "test_population_data"}} +{"population_description": "Chart and table of population level and growth rate for the Ogden-Layton metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\nThe current metro area population of Ogden-Layton in 2023 is 750,000, a 1.63% increase from 2022.\nThe metro area population of Ogden-Layton in 2022 was 738,000, a 1.79% increase from 2021.\nThe metro area population of Ogden-Layton in 2021 was 725,000, a 1.97% increase from 2020.\nThe metro area population of Ogden-Layton in 2020 was 711,000, a 2.16% increase from 2019."} +{"index": {"_index": "test_population_data"}} +{"population_description": "Chart and table of population level and growth rate for the New York City metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of New York City in 2023 is 18,937,000, a 0.37% increase from 2022.\\nThe metro area population of New York City in 2022 was 18,867,000, a 0.23% increase from 2021.\\nThe metro area population of New York City in 2021 was 18,823,000, a 0.1% increase from 2020.\\nThe metro area population of New York City in 2020 was 18,804,000, a 0.01% decline from 2019."} +{"index": {"_index": "test_population_data"}} +{"population_description": "Chart and table of population level and growth rate for the Chicago metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Chicago in 2023 is 8,937,000, a 0.4% increase from 2022.\\nThe metro area population of Chicago in 2022 was 8,901,000, a 0.27% increase from 2021.\\nThe metro area population of Chicago in 2021 was 8,877,000, a 0.14% increase from 2020.\\nThe metro area population of Chicago in 2020 was 8,865,000, a 0.03% increase from 2019."} +{"index": {"_index": "test_population_data"}} +{"population_description": "Chart and table of population level and growth rate for the Miami metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Miami in 2023 is 6,265,000, a 0.8% increase from 2022.\\nThe metro area population of Miami in 2022 was 6,215,000, a 0.78% increase from 2021.\\nThe metro area population of Miami in 2021 was 6,167,000, a 0.74% increase from 2020.\\nThe metro area population of Miami in 2020 was 6,122,000, a 0.71% increase from 2019."} +{"index": {"_index": "test_population_data"}} +{"population_description": "Chart and table of population level and growth rate for the Austin metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Austin in 2023 is 2,228,000, a 2.39% increase from 2022.\\nThe metro area population of Austin in 2022 was 2,176,000, a 2.79% increase from 2021.\\nThe metro area population of Austin in 2021 was 2,117,000, a 3.12% increase from 2020.\\nThe metro area population of Austin in 2020 was 2,053,000, a 3.43% increase from 2019."} +{"index": {"_index": "test_population_data"}} +{"population_description": "Chart and table of population level and growth rate for the Seattle metro area from 1950 to 2023. United Nations population projections are also included through the year 2035.\\nThe current metro area population of Seattle in 2023 is 3,519,000, a 0.86% increase from 2022.\\nThe metro area population of Seattle in 2022 was 3,489,000, a 0.81% increase from 2021.\\nThe metro area population of Seattle in 2021 was 3,461,000, a 0.82% increase from 2020.\\nThe metro area population of Seattle in 2020 was 3,433,000, a 0.79% increase from 2019."} +``` +{% include copy-curl.html %} + +## Step 2: Prepare an LLM + +This tutorial uses the [Amazon Bedrock Claude model](https://aws.amazon.com/bedrock/claude/) for conversational search. You can also use other LLMs. For more information about using externally hosted models, see [Connecting to externally hosted models]({{site.url}}{{site.baseurl}}/ml-commons-plugin/remote-models/index/). + +### Step 2.1: Create a connector + +Create a connector for the Claude model: + +```json +POST /_plugins/_ml/connectors/_create +{ + "name": "BedRock Claude instant-v1 Connector ", + "description": "The connector to BedRock service for claude model", + "version": 1, + "protocol": "aws_sigv4", + "parameters": { + "region": "us-east-1", + "service_name": "bedrock", + "anthropic_version": "bedrock-2023-05-31", + "max_tokens_to_sample": 8000, + "temperature": 0.0001, + "response_filter": "$.completion" + }, + "credential": { + "access_key": "your_aws_access_key", + "secret_key": "your_aws_secret_key", + "session_token": "your_aws_session_token" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-instant-v1/invoke", + "headers": { + "content-type": "application/json", + "x-amz-content-sha256": "required" + }, + "request_body": "{\"prompt\":\"${parameters.prompt}\", \"max_tokens_to_sample\":${parameters.max_tokens_to_sample}, \"temperature\":${parameters.temperature}, \"anthropic_version\":\"${parameters.anthropic_version}\" }" + } + ] +} +``` +{% include copy-curl.html %} + +Note the connector ID; you'll use it to register the model. + +### Step 2.2: Register the model + +Register the Claude model hosted on Amazon Bedrock: + +```json +POST /_plugins/_ml/models/_register +{ + "name": "Bedrock Claude Instant model", + "function_name": "remote", + "description": "Bedrock Claude instant-v1 model", + "connector_id": "your_LLM_connector_id" +} +``` +{% include copy-curl.html %} + +Note the LLM model ID; you'll use it in the following steps. + +### Step 2.3: Deploy the model + +Deploy the Claude model: + +```json +POST /_plugins/_ml/models/your_LLM_model_id/_deploy +``` +{% include copy-curl.html %} + +### Step 2.4: Test the model + +To test the model, send a Predict API request: + +```json +POST /_plugins/_ml/models/your_LLM_model_id/_predict +{ + "parameters": { + "prompt": "\n\nHuman: how are you? \n\nAssistant:" + } +} +``` +{% include copy-curl.html %} + +## Step 3: Register an agent + +OpenSearch provides the following agent types: `flow`, `conversational_flow`, and `conversational`. For more information about agents, see [Agents]({{site.url}}{{site.baseurl}}/ml-commons-plugin/agents-tools/index/#agents). + +You will use a `conversational_flow` agent in this tutorial. The agent consists of the following: + +- Meta info: `name`, `type`, and `description`. +- `app_type`: Differentiates between application types. +- `memory`: Stores user questions and LLM responses as a conversation so that an agent can retrieve conversation history from memory and continue the same conversation. +- `tools`: Defines a list of tools to use. The agent will run these tools sequentially. + +To register an agent, send the following request: + +```json +POST /_plugins/_ml/agents/_register +{ + "name": "population data analysis agent", + "type": "conversational_flow", + "description": "This is a demo agent for population data analysis", + "app_type": "rag", + "memory": { + "type": "conversation_index" + }, + "tools": [ + { + "type": "VectorDBTool", + "name": "population_knowledge_base", + "parameters": { + "model_id": "your_text_embedding_model_id", + "index": "test_population_data", + "embedding_field": "population_description_embedding", + "source_field": [ + "population_description" + ], + "input": "${parameters.question}" + } + }, + { + "type": "MLModelTool", + "name": "bedrock_claude_model", + "description": "A general tool to answer any question", + "parameters": { + "model_id": "your_LLM_model_id", + "prompt": "\n\nHuman:You are a professional data analysist. You will always answer question based on the given context first. If the answer is not directly shown in the context, you will analyze the data and find the answer. If you don't know the answer, just say don't know. \n\nContext:\n${parameters.population_knowledge_base.output:-}\n\n${parameters.chat_history:-}\n\nHuman:${parameters.question}\n\nAssistant:" + } + } + ] +} +``` +{% include copy-curl.html %} + +OpenSearch responds with an agent ID: + +```json +{ + "agent_id": "fQ75lI0BHcHmo_czdqcJ" +} +``` + +Note the agent ID; you'll use it in the next step. + +## Step 4: Run the agent + +You'll run the agent to analyze the increase in Seattle's population. When you run this agent, the agent will create a new conversation. Later, you can continue this conversation by asking other questions. + +### Step 4.1: Start a new conversation + +First, start a new conversation by asking the LLM a question: + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "what's the population increase of Seattle from 2021 to 2023?" + } +} +``` +{% include copy-curl.html %} + +The response contains the answer generated by the LLM: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "gQ75lI0BHcHmo_cz2acL" + }, + { + "name": "parent_message_id", + "result": "gg75lI0BHcHmo_cz2acZ" + }, + { + "name": "bedrock_claude_model", + "result": """ Based on the context given: +- The metro area population of Seattle in 2021 was 3,461,000 +- The current metro area population of Seattle in 2023 is 3,519,000 +- So the population increase of Seattle from 2021 to 2023 is 3,519,000 - 3,461,000 = 58,000""" + } + ] + } + ] +} +``` + +The response contains the following fields: + +- `memory_id` is the identifier for the memory (conversation) that groups all messages within a single conversation. Note this ID; you'll use it in the next step. +- `parent_message_id` is the identifier for the current message (one question/answer) between the human and the LLM. One memory can contain multiple messages. + +To obtain memory details, call the [Get Memory API](ml-commons-plugin/api/memory-apis/get-memory/): + +```json +GET /_plugins/_ml/memory/gQ75lI0BHcHmo_cz2acL +``` +{% include copy-curl.html %} + +To obtain all messages within a memory, call the [Get Messages API](ml-commons-plugin/api/memory-apis/get-message/): + +```json +GET /_plugins/_ml/memory/gQ75lI0BHcHmo_cz2acL/messages +``` +{% include copy-curl.html %} + +To obtain message details, call the [Get Message API](ml-commons-plugin/api/memory-apis/get-message/): + +```json +GET /_plugins/_ml/memory/message/gg75lI0BHcHmo_cz2acZ +``` +{% include copy-curl.html %} + +For debugging purposes, you can obtain trace data for a message by calling the [Get Message Traces API](ml-commons-plugin/api/memory-apis/get-message-traces/): + +```json +GET /_plugins/_ml/memory/message/gg75lI0BHcHmo_cz2acZ/traces +``` +{% include copy-curl.html %} + +### 4.2 Continue a conversation by asking new questions + +To continue the same conversation, provide the memory ID from the previous step. + +Additionally, you can provide the following parameters: + +- `message_history_limit`: Specify how many historical messages you want included in the new question/answer round for an agent. +- `prompt`: Use this parameter to customize the LLM prompt. For example, the following example adds a new instruction `always learn useful information from chat history` +and a new parameter `next_action`: + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What's the population of New York City in 2023?", + "next_action": "then compare with Seattle population of 2023", + "memory_id": "gQ75lI0BHcHmo_cz2acL", + "message_history_limit": 5, + "prompt": "\n\nHuman:You are a professional data analysist. You will always answer question based on the given context first. If the answer is not directly shown in the context, you will analyze the data and find the answer. If you don't know the answer, just say don't know. \n\nContext:\n${parameters.population_knowledge_base.output:-}\n\n${parameters.chat_history:-}\n\nHuman:always learn useful information from chat history\nHuman:${parameters.question}, ${parameters.next_action}\n\nAssistant:" + } +} +``` +{% include copy-curl.html %} + +The response contains the answer generated by the LLM: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "gQ75lI0BHcHmo_cz2acL" + }, + { + "name": "parent_message_id", + "result": "wQ4JlY0BHcHmo_cz8Kc-" + }, + { + "name": "bedrock_claude_model", + "result": """ Based on the context given: +- The current metro area population of New York City in 2023 is 18,937,000 +- The current metro area population of Seattle in 2023 is 3,519,000 +- So the population of New York City in 2023 (18,937,000) is much higher than the population of Seattle in 2023 (3,519,000)""" + } + ] + } + ] +} +``` + +If you know which tool the agent should use to execute a particular Predict API request, you can specify the tool when executing the agent. For example, if you want to translate the preceding answer into Chinese, you don't need to retrieve any data from the knowledge base. To run only the Claude model, specify the `bedrock_claude_model` tool in the `selected_tools` parameter: + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "Translate last answer into Chinese?", + "selected_tools": ["bedrock_claude_model"] + } +} +``` +{% include copy-curl.html %} + +The agent will run the tools one by one in the new order defined in `selected_tools`. +{: .note} + +## Configuring multiple knowledge bases + +You can configure multiple knowledge bases for an agent. For example, if you have both product description and comment data, you can configure the agent with the following two tools: + +```json +{ + "name": "My product agent", + "type": "conversational_flow", + "description": "This is an agent with product description and comments knowledge bases.", + "memory": { + "type": "conversation_index" + }, + "app_type": "rag", + "tools": [ + { + "type": "VectorDBTool", + "name": "product_description_vectordb", + "parameters": { + "model_id": "your_embedding_model_id", + "index": "product_description_data", + "embedding_field": "product_description_embedding", + "source_field": [ + "product_description" + ], + "input": "${parameters.question}" + } + }, + { + "type": "VectorDBTool", + "name": "product_comments_vectordb", + "parameters": { + "model_id": "your_embedding_model_id", + "index": "product_comments_data", + "embedding_field": "product_comment_embedding", + "source_field": [ + "product_comment" + ], + "input": "${parameters.question}" + } + }, + { + "type": "MLModelTool", + "description": "A general tool to answer any question", + "parameters": { + "model_id": "{{llm_model_id}}", + "prompt": "\n\nHuman:You are a professional product recommendation engine. You will always recommend product based on the given context. If you don't have enough context, you will ask Human to provide more information. If you don't see any related product to recommend, just say we don't have such product. \n\n Context:\n${parameters.product_description_vectordb.output}\n\n${parameters.product_comments_vectordb.output}\n\nHuman:${parameters.question}\n\nAssistant:" + } + } + ] +} +``` +{% include copy-curl.html %} + +When you run the agent, the agent will query product description and comment data and then send the query results and the question to the LLM. + +To query a specific knowledge base, specify it in `selected_tools`. For example, if the question relates only to product comments, you can retrieve information only from `product_comments_vectordb`: + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "What feature people like the most for Amazon Echo Dot", + "selected_tools": ["product_comments_vectordb", "MLModelTool"] + } +} +``` +{% include copy-curl.html %} + +## Running queries on an index + +Use `SearchIndexTool` to run any OpenSearch query on any index. + +### Setup: Register an agent + +```json +POST /_plugins/_ml/agents/_register +{ + "name": "Demo agent", + "type": "conversational_flow", + "description": "This agent supports running any search query", + "memory": { + "type": "conversation_index" + }, + "app_type": "rag", + "tools": [ + { + "type": "SearchIndexTool", + "parameters": { + "input": "{\"index\": \"${parameters.index}\", \"query\": ${parameters.query} }" + } + }, + { + "type": "MLModelTool", + "description": "A general tool to answer any question", + "parameters": { + "model_id": "your_llm_model_id", + "prompt": "\n\nHuman:You are a professional data analysist. You will always answer question based on the given context first. If the answer is not directly shown in the context, you will analyze the data and find the answer. If you don't know the answer, just say don't know. \n\n Context:\n${parameters.SearchIndexTool.output:-}\n\nHuman:${parameters.question}\n\nAssistant:" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Run a BM25 query + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "what's the population increase of Seattle from 2021 to 2023?", + "index": "test_population_data", + "query": { + "query": { + "match": { + "population_description": "${parameters.question}" + } + }, + "size": 2, + "_source": "population_description" + } + } +} +``` +{% include copy-curl.html %} + +### Exposing only the `question` parameter + +To expose only the `question` parameter, define the agent as follows: + +```json +POST /_plugins/_ml/agents/_register +{ + "name": "Demo agent", + "type": "conversational_flow", + "description": "This is a test agent support running any search query", + "memory": { + "type": "conversation_index" + }, + "app_type": "rag", + "tools": [ + { + "type": "SearchIndexTool", + "parameters": { + "input": "{\"index\": \"${parameters.index}\", \"query\": ${parameters.query} }", + "index": "test_population_data", + "query": { + "query": { + "match": { + "population_description": "${parameters.question}" + } + }, + "size": 2, + "_source": "population_description" + } + } + }, + { + "type": "MLModelTool", + "description": "A general tool to answer any question", + "parameters": { + "model_id": "your_llm_model_id", + "prompt": "\n\nHuman:You are a professional data analyst. You will always answer question based on the given context first. If the answer is not directly shown in the context, you will analyze the data and find the answer. If you don't know the answer, just say don't know. \n\n Context:\n${parameters.SearchIndexTool.output:-}\n\nHuman:${parameters.question}\n\nAssistant:" + } + } + ] +} +``` +{% include copy-curl.html %} + +Now you can run the agent specifying only the `question` parameter: + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "what's the population increase of Seattle from 2021 to 2023?" + } +} +``` +{% include copy-curl.html %} + +### Run a neural search query + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "what's the population increase of Seattle from 2021 to 2023??", + "index": "test_population_data", + "query": { + "query": { + "neural": { + "population_description_embedding": { + "query_text": "${parameters.question}", + "model_id": "your_embedding_model_id", + "k": 10 + } + } + }, + "size": 2, + "_source": ["population_description"] + } + } +} +``` +{% include copy-curl.html %} + +To expose the `question` parameter, see [Exposing only the `question` parameter](#exposing-only-the-question-parameter). + +### Run a hybrid search query + +Hybrid search combines keyword and neural search to improve search relevance. For more information, see [Hybrid search]({{site.url}}{{site.baseurl}}/search-plugins/hybrid-search/). + +Configure a search pipeline: + +```json +PUT /_search/pipeline/nlp-search-pipeline +{ + "description": "Post processor for hybrid search", + "phase_results_processors": [ + { + "normalization-processor": { + "normalization": { + "technique": "min_max" + }, + "combination": { + "technique": "arithmetic_mean", + "parameters": { + "weights": [ + 0.3, + 0.7 + ] + } + } + } + } + ] + } +``` +{% include copy-curl.html %} + +Run an agent with a hybrid query: + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "what's the population increase of Seattle from 2021 to 2023??", + "index": "test_population_data", + "query": { + "_source": { + "exclude": [ + "population_description_embedding" + ] + }, + "size": 2, + "query": { + "hybrid": { + "queries": [ + { + "match": { + "population_description": { + "query": "${parameters.question}" + } + } + }, + { + "neural": { + "population_description_embedding": { + "query_text": "${parameters.question}", + "model_id": "your_embedding_model_id", + "k": 10 + } + } + } + ] + } + } + } + } +} +``` +{% include copy-curl.html %} + +To expose the `question` parameter, see [Exposing only the `question` parameter](#exposing-only-the-question-parameter). + +### Natural language query + +The `PPLTool` can translate a natural language query (NLQ) to [Piped Processing Language (PPL)]({{site.url}}{{site.baseurl}}/search-plugins/sql/ppl/index/) and execute the generated PPL query. + +#### Setup + +Before you start, go to the OpenSearch Dashboards home page, select `Add sample data`, and then add `Sample eCommerce orders`. + + +#### Step 1: Register an agent with the PPLTool + + +The `PPLTool` has the following parameters: + +- `model_type` (Enum): `CLAUDE`, `OPENAI`, or `FINETUNE`. +- `execute` (Boolean): If `true`, executes the generated PPL query. +- `input` (String): You must provide the `index` and `question` as inputs. + +For this tutorial, you'll use Bedrock Claude, so set the `model_type` to `CLAUDE`: + +```json +POST /_plugins/_ml/agents/_register +{ + "name": "Demo agent for NLQ", + "type": "conversational_flow", + "description": "This is a test flow agent for NLQ", + "memory": { + "type": "conversation_index" + }, + "app_type": "rag", + "tools": [ + { + "type": "PPLTool", + "parameters": { + "model_id": "your_ppl_model_id", + "model_type": "CLAUDE", + "execute": true, + "input": "{\"index\": \"${parameters.index}\", \"question\": ${parameters.question} }" + } + }, + { + "type": "MLModelTool", + "description": "A general tool to answer any question", + "parameters": { + "model_id": "your_llm_model_id", + "prompt": "\n\nHuman:You are a professional data analysist. You will always answer question based on the given context first. If the answer is not directly shown in the context, you will analyze the data and find the answer. If you don't know the answer, just say don't know. \n\n Context:\n${parameters.PPLTool.output:-}\n\nHuman:${parameters.question}\n\nAssistant:" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2: Run the agent with an NLQ + +Run the agent: + +```json +POST /_plugins/_ml/agents/your_agent_id/_execute +{ + "parameters": { + "question": "How many orders do I have in last week", + "index": "opensearch_dashboards_sample_data_ecommerce" + } +} +``` +{% include copy-curl.html %} + +The response contains the answer generated by the LLM: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "memory_id", + "result": "sqIioI0BJhBwrVXYeYOM" + }, + { + "name": "parent_message_id", + "result": "s6IioI0BJhBwrVXYeYOW" + }, + { + "name": "MLModelTool", + "result": " Based on the given context, the number of orders in the last week is 3992. The data shows a query that counts the number of orders where the order date is greater than 1 week ago. The query result shows the count as 3992." + } + ] + } + ] +} +``` + +For more information, obtain trace data by calling the [Get Message Traces API]({{site.url}}{{site.baseurl}}/ml-commons-plugin/api/memory-apis/get-message-traces/): + +```json +GET _plugins/_ml/memory/message/s6IioI0BJhBwrVXYeYOW/traces +``` +{% include copy-curl.html %} \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/reranking-cohere.md b/_ml-commons-plugin/tutorials/reranking-cohere.md new file mode 100644 index 0000000000..412180066f --- /dev/null +++ b/_ml-commons-plugin/tutorials/reranking-cohere.md @@ -0,0 +1,344 @@ +--- +layout: default +title: Reranking with Cohere Rerank +parent: Tutorials +nav_order: 30 +--- + +# Reranking search results using the Cohere Rerank model + +A [reranking pipeline]({{site.url}}{{site.baseurl}}/search-plugins/search-relevance/reranking-search-results/) can rerank search results, providing a relevance score for each document in the search results with respect to the search query. The relevance score is calculated by a cross-encoder model. + +This tutorial illustrates how to use the [Cohere Rerank](https://docs.cohere.com/reference/rerank-1) model in a reranking pipeline. + +Replace the placeholders beginning with the prefix `your_` with your own values. +{: .note} + +## Step 1: Register a Cohere Rerank model + +Create a connector for the Cohere Rerank model: + +```json +POST /_plugins/_ml/connectors/_create +{ + "name": "cohere-rerank", + "description": "The connector to Cohere reanker model", + "version": "1", + "protocol": "http", + "credential": { + "cohere_key": "your_cohere_api_key" + }, + "parameters": { + "model": "rerank-english-v2.0" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://api.cohere.ai/v1/rerank", + "headers": { + "Authorization": "Bearer ${credential.cohere_key}" + }, + "request_body": "{ \"documents\": ${parameters.documents}, \"query\": \"${parameters.query}\", \"model\": \"${parameters.model}\", \"top_n\": ${parameters.top_n} }", + "pre_process_function": "connector.pre_process.cohere.rerank", + "post_process_function": "connector.post_process.cohere.rerank" + } + ] +} +``` +{% include copy-curl.html %} + +Use the connector ID from the response to register a Cohere Rerank model: + +```json +POST /_plugins/_ml/models/_register?deploy=true +{ + "name": "cohere rerank model", + "function_name": "remote", + "description": "test rerank model", + "connector_id": "your_connector_id" +} +``` +{% include copy-curl.html %} + +Note the model ID in the response; you'll use it in the following steps. + +Test the model by calling the Predict API: + +```json +POST _plugins/_ml/models/your_model_id/_predict +{ + "parameters": { + "query": "What is the capital of the United States?", + "documents": [ + "Carson City is the capital city of the American state of Nevada.", + "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.", + "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district.", + "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states." + ], + "top_n": 4 + } +} +``` + +To ensure compatibility with the rerank pipeline, the `top_n` value must be the same as the length of the `documents` list. +{: .important} + +You can customize the number of top documents returned in the response by providing the `size` parameter. For more information, see [Step 2.3](#step-23-test-the-reranking). + +OpenSearch responds with the inference results: + +```json +{ + "inference_results": [ + { + "output": [ + { + "name": "similarity", + "data_type": "FLOAT32", + "shape": [ + 1 + ], + "data": [ + 0.10194652 + ] + }, + { + "name": "similarity", + "data_type": "FLOAT32", + "shape": [ + 1 + ], + "data": [ + 0.0721122 + ] + }, + { + "name": "similarity", + "data_type": "FLOAT32", + "shape": [ + 1 + ], + "data": [ + 0.98005307 + ] + }, + { + "name": "similarity", + "data_type": "FLOAT32", + "shape": [ + 1 + ], + "data": [ + 0.27904198 + ] + } + ], + "status_code": 200 + } + ] +} +``` + +The response contains four `similarity` objects. For each `similarity` object, the `data` array contains a relevance score for each document with respect to the query. The `similarity` objects are provided in the order of the input documents; the first object pertains to the first document. This differs from the default output of the Cohere Rerank model, which orders documents by relevance score. The document order is changed in the `connector.post_process.cohere.rerank` post-processing function in order to make the output compatible with a reranking pipeline. + +## Step 2: Configure a reranking pipeline + +Follow these steps to configure a reranking pipeline. + +### Step 2.1: Ingest test data + +Send a bulk request to ingest test data: + +```json +POST _bulk +{ "index": { "_index": "my-test-data" } } +{ "passage_text" : "Carson City is the capital city of the American state of Nevada." } +{ "index": { "_index": "my-test-data" } } +{ "passage_text" : "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan." } +{ "index": { "_index": "my-test-data" } } +{ "passage_text" : "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district." } +{ "index": { "_index": "my-test-data" } } +{ "passage_text" : "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states." } +``` +{% include copy-curl.html %} + +### Step 2.2: Create a reranking pipeline + +Create a reranking pipeline with the Cohere Rerank model: + +```json +PUT /_search/pipeline/rerank_pipeline_cohere +{ + "description": "Pipeline for reranking with Cohere Rerank model", + "response_processors": [ + { + "rerank": { + "ml_opensearch": { + "model_id": "your_model_id_created_in_step1" + }, + "context": { + "document_fields": ["passage_text"] + } + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2.3: Test the reranking + +To limit the number of returned results, you can specify the `size` parameter. For example, set `"size": 2` to return the top two documents: + +```json +GET my-test-data/_search?search_pipeline=rerank_pipeline_cohere +{ + "query": { + "match_all": {} + }, + "size": 4, + "ext": { + "rerank": { + "query_context": { + "query_text": "What is the capital of the United States?" + } + } + } +} +``` +{% include copy-curl.html %} + +The response contains the two most relevant documents: + +```json +{ + "took": 0, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4, + "relation": "eq" + }, + "max_score": 0.98005307, + "hits": [ + { + "_index": "my-test-data", + "_id": "zbUOw40B8vrNLhb9vBif", + "_score": 0.98005307, + "_source": { + "passage_text": "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district." + } + }, + { + "_index": "my-test-data", + "_id": "zrUOw40B8vrNLhb9vBif", + "_score": 0.27904198, + "_source": { + "passage_text": "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states." + } + }, + { + "_index": "my-test-data", + "_id": "y7UOw40B8vrNLhb9vBif", + "_score": 0.10194652, + "_source": { + "passage_text": "Carson City is the capital city of the American state of Nevada." + } + }, + { + "_index": "my-test-data", + "_id": "zLUOw40B8vrNLhb9vBif", + "_score": 0.0721122, + "_source": { + "passage_text": "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan." + } + } + ] + }, + "profile": { + "shards": [] + } +} +``` + +To compare these results to results without reranking, run the search without a reranking pipeline: + +```json +GET my-test-data/_search +{ + "query": { + "match_all": {} + }, + "ext": { + "rerank": { + "query_context": { + "query_text": "What is the capital of the United States?" + } + } + } +} +``` +{% include copy-curl.html %} + +The first document in the response pertains to Carson City, which is not the capital of the United States: + +```json +{ + "took": 0, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4, + "relation": "eq" + }, + "max_score": 1, + "hits": [ + { + "_index": "my-test-data", + "_id": "y7UOw40B8vrNLhb9vBif", + "_score": 1, + "_source": { + "passage_text": "Carson City is the capital city of the American state of Nevada." + } + }, + { + "_index": "my-test-data", + "_id": "zLUOw40B8vrNLhb9vBif", + "_score": 1, + "_source": { + "passage_text": "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan." + } + }, + { + "_index": "my-test-data", + "_id": "zbUOw40B8vrNLhb9vBif", + "_score": 1, + "_source": { + "passage_text": "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district." + } + }, + { + "_index": "my-test-data", + "_id": "zrUOw40B8vrNLhb9vBif", + "_score": 1, + "_source": { + "passage_text": "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states." + } + } + ] + } +} +``` \ No newline at end of file diff --git a/_ml-commons-plugin/tutorials/semantic-search-byte-vectors.md b/_ml-commons-plugin/tutorials/semantic-search-byte-vectors.md new file mode 100644 index 0000000000..7061d3cb5a --- /dev/null +++ b/_ml-commons-plugin/tutorials/semantic-search-byte-vectors.md @@ -0,0 +1,314 @@ +--- +layout: default +title: Semantic search using byte vectors +parent: Tutorials +nav_order: 10 +--- + +# Semantic search using byte-quantized vectors + +This tutorial illustrates how to build a semantic search using the [Cohere Embed model](https://docs.cohere.com/reference/embed) and byte-quantized vectors. For more information about using byte-quantized vectors, see [Lucene byte vector]({{site.url}}{{site.baseurl}}/field-types/supported-field-types/knn-vector/#lucene-byte-vector). + +The Cohere Embed v3 model supports several `embedding_types`. For this tutorial, you'll use the `INT8` type to encode byte-quantized vectors. + +The Cohere Embed v3 model supports several input types. This tutorial uses the following input types: + +- `search_document`: Use this input type when you have text (in the form of documents) that you want to store in a vector database. +- `search_query`: Use this input type when structuring search queries to find the most relevant documents in your vector database. + +For more information about input types, see the [Cohere documentation](https://docs.cohere.com/docs/embed-api#the-input_type-parameter). + +In this tutorial, you will create two models: + +- A model used for ingestion, whose `input_type` is `search_document` +- A model used for search, whose `input_type` is `search_query` + +Replace the placeholders beginning with the prefix `your_` with your own values. +{: .note} + +## Step 1: Create an embedding model for ingestion + +Create a connector for the Cohere model, specifying the `search_document` input type: + +```json +POST /_plugins/_ml/connectors/_create +{ + "name": "Cohere embedding connector with int8 embedding type for ingestion", + "description": "Test connector for Cohere embedding model", + "version": 1, + "protocol": "http", + "credential": { + "cohere_key": "your_cohere_api_key" + }, + "parameters": { + "model": "embed-english-v3.0", + "embedding_types": ["int8"], + "input_type": "search_document" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "headers": { + "Authorization": "Bearer ${credential.cohere_key}", + "Request-Source": "unspecified:opensearch" + }, + "url": "https://api.cohere.ai/v1/embed", + "request_body": "{ \"model\": \"${parameters.model}\", \"texts\": ${parameters.texts}, \"input_type\":\"${parameters.input_type}\", \"embedding_types\": ${parameters.embedding_types} }", + "pre_process_function": "connector.pre_process.cohere.embedding", + "post_process_function": "\n def name = \"sentence_embedding\";\n def data_type = \"FLOAT32\";\n def result;\n if (params.embeddings.int8 != null) {\n data_type = \"INT8\";\n result = params.embeddings.int8;\n } else if (params.embeddings.uint8 != null) {\n data_type = \"UINT8\";\n result = params.embeddings.uint8;\n } else if (params.embeddings.float != null) {\n data_type = \"FLOAT32\";\n result = params.embeddings.float;\n }\n \n if (result == null) {\n return \"Invalid embedding result\";\n }\n \n def embedding_list = new StringBuilder(\"[\");\n \n for (int m=0; mUA+W|Umy@U zLVol?M}mX%@R1@iU}Jc;_IJVN&>#Abqzpf8_pE-63PUE>z*8%DTme($hTo77rng~6banCYM%g}$7gEmY_ShHZvCh{X& zzkMqj+8Y~NJDAxx{`?3kduwXJTuIGQO&;~@OZ)9U&Oy>%+{euLI%a!Bp7G&(G zPv8o&vUcEbw;Y(UO};O){gqFwAK#9 z|8C@e+7U8#Ftj(fbu_oJCitUWeFGaOM{XjbKN$V*&%fW(*wy_1I9WUVr(16wr2pdy zJtG|h{r|T8M#}X^DTkc7tFe{3kU8j0Ja0UB7#WyYx&B4)mq-7{^1n#c{)?2E{quj5 z{@0^FlPWtH+Y8!&-k3V_{NLF8hxorA{)do@{*S)@mn{A*=zo>INty?qi~fH{jR$^< z(C-Wkj1Np)h+oMS{3!jMo>Cv+t1N4(z#eXX0D(eY0D;(mPF2nVhFtYwtxG17hfR^5 zu%`h(Ka`S^ko##nW&8XL+v`gD=+WqjNt_kcYn#jMSUS+baIWy}cif!qXz4#C(5$@u8m?85yBK|5IDhe1s@xy$t>hC{+#;XawND zy-@U_XxE`h_#l3J$%#RIdjSd(i0~|0lC!kL-}lV-jnnY$ zKMM&B+(t@Dicosg93i%AF-beWP_sd=f<~92lAv(xT#Ht-;VkMKn~h&5URf;qvfdU# zO*wmcJwdZA-QUZAJq6A+*Xh!Q`#ILR-ZPu{Wk(rSs$yD8!D7p-;JF{9mN%YX7#=XeKeqMVir``uzlK|fvNuhNP(sgX99 zHK)!msi*mvndh?8rMK!Y?($`I7PA5sZJSK(by2+cS|Z_hZ4`8ImPEf19R(kRJgOPt zm;jUzqbaOEujhI7wn)(z_SV+dIPi}XCKB>!=Eoib4cWRWw3D^4 zo`(w$q+po#11fT&&z-+=eEwdHe~fSl6j$sx(2Q_xS1FGrOH%e;p#%Eq+p$!PC@0FJ5qALg>+Txid9uz92XJ5+hYb$2lum6Y3+G$_DsBX( zb4P`Yu^2}1-c^BC<-a@Y%Itr+ny7MGmQ{0x?)+@XWN zn|Ny?vfbP2+h8)kAN$C*8;1IF-jLoJsZ~XWZ#9grd3V`M6goFn82VH_*a)BMc9|~S zapZj$(c3@rUUNGiX=iu=tFwQOyRbT}H?y55{QHWv51G5`iB?e9<5I>uR`!avo-(3K ztD_OFe%%6S%im2w(geOpC=KG!B?f{se%le1JmpOzXXsS_Zw8_xDGhm@9aji-DC)R= zbEukI^K8?pp^Fd|gQN=j+uxUvc?dd|AQ+ay+fmJ>JC`%7S$b$Ih-pQ{SZ?$JzXMd( zahZQOK8~ZvfjGZ6RU0(7^>O)FgluWnO+l1;;Dry=+F@$;299de>cFdoE%Pi~2S@Q@ z#$^Ho$|%hOHq1hciKphQHUn9i0nXi4Bd#9b84QDX8r4EsVe`mxFdNh`bG)VbH5q)Y zIhUij0UgKE!y{TmnU?%ZT2UYER1)j?lIw}1B6M%H)N-qloTE%WjsI2%0lwtK?|^2< zW(totsEnbNw}Oa>WUxH|0X=U3Y&nW5;nGr{&~S?Pv?)T@N7FAF(>6zf_spoJ{cA#v zDV7d%F|{2YYz7UThfZ+GE}or;5p|BivBATn z$%EcIz%Cd%19LILh%34>LaSb1WLJHz!Lf6=L)1VEBmUB{UH9&wIZh7Gapj&M0n27# zNL{_KBKl8kI*l2jwIv8@&!#>p*tRpnaX5YqhmUq2Zw-&+*mxNG@4o1ADgd10$X`7`?#v9=Hjd$StK^~n8tFhsmWqwij^*bc^gwQNZZ2-RJ zk>fHS*Qhz8IlLA4;)uQiW>;bL@K|ScK&Kv$T@={`M#qYOA#FIeKDP7Vy#@Rfda}WC zGOI&_S2ofmwnyuGP{C@rQ)acbOSMu-={i_H->3N zB+at1c1NA2+d2-gIc@==o38>stQr_rDS`E`jp`JaQM&SXq;BYX7& zyvW=!oj3#i+5uPJ{Ht%c7dpZtypS6KhNhM4>n7#slWoq9mDNAL{l<@GZKKJhelgxY zI3bwY^hLpL)R$i!}&>Q6tEUzzB_Y3ZOylLV+vV? ze_!0rVrj@8JJTOWAEHY+?iP}TO)bF9$w$?aeo^u8Cg#KDN86I>P+O+!Jap!O5Nc}a zQ;!Rq1IXvLla1()TPA-LnaqfK`R%(RgWHIRoDi7>ton<4`94*jz^bBihuJkGTRz%d zjOpa0hRa#rE;|J`pZGJH=yTZ9 zgh|ix#R2J0Z>zJQ7OBDajvWz667mrV05-`(QC+Tu>2w&@M$8`tG^?&47Nz|jnOM6B)=#o6hKcUzO7H1J^)h@{YN+b{DH|fpnN2m%SU}lh4fvX7^@rX4 zFk2r-B0;~k0NuuMb*C3D<1 zRhGA04M;>Dx$^#Aqw7NBEziU~fS?fA`p1}yF8tXNsHwEz%m#cH+`|CfckS!=qg$Lq@hat2r}-rr{)AW}Zb(mb-cS73BYh``Mn9kPAG&00izD*WTDu~t7 zSjnC>{RyZ}AP-GGVa_jFABcBTs(+4Itf-G}%2rE(yDj5qcYoS#fGP6BDs&vm8&h;?=);N=( zET7$(M1dAtXhmHxTTM$z=$@1#LMAoC^Ajoy1_YsKIZvGUJ6n@KK%?kN(mwUA;@aX? z+An;)0EIPvG!oJ+19vD)DkUp=rzuI%`QQzBE}4(GLTnmq6E!tA-zh!x5azsCuXu3q z@9kyBDO0Y{5yat7PJb94XL?G=9wWGJF+2o(<1qJ>TmD&zJo{YbM^C)e6Sb&RE>VxY z1cTaKfb5~QWff|^T)bq(E7HVbC~Z`U6;y}#F%0t1F-b*2Y_-PNvg`cvi?};v)%g}V zbn{d8f#%NTsZiCBsm5_V*)yWB30RF@|!1+tjJR@&oq4M_{zc-dlMK zjfz-~UJ3rj!Tl@l3l!&rtHYB2whl1;8I4*i-z`D@ySe*TPMYkCW|kgNap4~I78s3` zBK+1O{i_SPp}wk2Iq3YFYBSLSeu4k^^2Ffv;%atU&bujC&8a{9S^gm)*c9%Hi_vK# zHVebFuK350k_pb0q#!wH!ekBMw=3*Fx+_-(>&g`BhP-4ToEk>@o4Ni+m%zh($-ko3 z0gzS1{~=1x+JJovzcEQgR{*|^4{qaonEWy1oXu$NNxJNJdj9Y7 z?Qg!w38RB3v=cCcH4=G9G$)5$Iyy1`7|L%)`=Xf&n^#L@jmnvl_LRWT&X^RnP@ns4 zr3iqKQK!@XNmpvnZ0poXe^5y~KaOc*?eFgoQONl^L73OtFs}tHrt;jXBlMi~Yw6gm zR}&`@hxJ!cB~W8V1lnT!gJwg^bU{{sn5>htJVq-)LT&60wRB}b_eu4j^La=84|F89 zA5hQo@(JDNC?jpLDY{zvlthD(>KL~i?42J+f2b|R{(~I�fS+B9!4w%gnrTy ztQlG2#SR;W0nwHjR>$W5uru>E7lVDtX<4MnX{-8_aq$n)WRJa9d_D^;wGI4C%p&jB z{0CKFKY(@ZU0ju1Z=&rHp_?)>HhKK9Gz|ES4*q94pqvqWm;)$59;C?YrIoHmS>M*< zp%tuNUH)zX{jawdO|K3Irbiy7jI}52uuah0_+NesvHXNL9C}(bQ!MQJmZzBS$ScGpt ztF(u|j+NjgT4q4%!q^R!RK}$otoQXicY*Ah9a82Ym+qGi*biiWM3P|O1+6zn)XfmI zU(_%L;N4UVJPk@5>^F5G531~Ft|7yO)STMPF01Hc?%)r7)Bj$Vqah)}$U%ua)2xzK z;i{Wl!6OJS()y(HB^4h-0#84-nGAk)Yc)PzIi6}VL^5A0g>k+OD49_^5#{`RFq8vnT3s`+=xqMi|5yd z!Z&kN#&~FSnn;X>kE4-{#2tiY$z4q~HAhUXe04nMhMIaCT{L5D{VM!GSk*vnshSrFtZ_Fx=CB+k29&d(Zwllj01>nHx z*A#7mc4>hhj#Vo+<=d5FXQ;GAyf`FSg&)oBoc=5!6fX!)R=oI5(elJABh*TA8%jtN zbp-s!2D0JP%!+6kOqOVg`q-RKGk20V6FUjjhN2WvWHI&->2+XL#Tq15K=e^>3sI%g zPZy!8Psfx?*Fg|$^Qa7@Nh%Fq4|iDjbK2q<<=3_&8A>BCAc)u+Ke0i%WKp{;?k|=j z-WDP_Es#Amri@5Wxnj{QK`r5i;8`r?>3P;--`$N^WJ&~q<~h1cUZCq$J_c3&*%Z19 z*0*~Zg4h(K^T7~=Jy|ZO^<_JBboB_$)%dmwFT5*b+X$GJNEK53g72qiVnzHH-$%g@ z6+H(QQ#<*=6bUa1&3rmuEDYG9yS(uEZQ|bx=)Jx`)>z)^Bs6@c)qTSJI=!%5Jeq1u`h4iQgT6(WbL{@cqrtf=Y`kBFQ zFHLEyN1^T*5z2jtFiSIWXnDST0339iPAY5HV8Mu)ceb_v3uYu?K>*_W)Mo7C5jL zxdxu!Zs#BH*CkEX9a^vwCZ9zB#&lzmXLa?xckKd~)qUNlKaiL498aXc?myQbm=RVY{j`v#Jb zTE5W7AC^M4pS=^|x6a+Addo6a@a4^j5VTD@gDq)idC3%4SlYY$RJNwoFh(y;b30;~ z>Dwf1=@g*bX0sO`4mi>p#As{w=nF*9;uVarlFr1M$$LR+6?QGz4B zGt8G@E$7tU!M1zmUv2ank@k~O|C9--*iB1LE2->kt;$$&P*8SKiox*^S$D#m{$C{&<$uq2eZTzALlg%Q+aSNm$ zMlxE0LbTQ0v-5dnA{%xMjym~Nsvdq(4W4t<(g1RtHIUk`%?f+r@$x zP6ZzBz>rpe?1!CCF>%rU7k?=V={G~XU*r7Z9CF)>xp3^xg{RJD6ZHAitITyPw-HOd z2}exIFL4vfiU0H93=&hG;cAWg*T{o)xr#fhKq=kM_4%Y`towr|f5QZ0{SFu*g_ryJ z^{Krk82cl}fiFzdHOCVTv9m91aBy(K&JkK_Mo=cB+64IFcUrb0=F264b@Bt(s@i~W z0kCCU59BKC<^+vY;&UM*LP!Tz(X}cdr`8kXht2RSCul7w7J8-#1OV-bk1>UyIt#qnY zxek09jIi$aR@8}}kY1N_cIk>@i-=6oXZ)BWTRBuO=YLf$(AvBvvPf@($mgSbiDjT@ zbIeBl#FoqwuW?w1_D=f;hm=c+ol{d%sB|M_XL^x|RVJ%2&AVSYp~i{njlUQ880`()?Dh#~hqv!XKGw5) zq@x)YK0gHT$cbVV2rZjo;PN9NDT9y->Qt}CgC2sRH!~yk0>w&+S#c4hQq;c-f0g=n zMw^1`p^Z@1`slCy_!Z*-Zu7`1iibE!+cTpn*VqoEfXDTC_dJ!O9w(q#8bZ?_WN?uE z^VN+G!E_`B^lBC3yvp(JPNUhjSnBznZQora)Awf6yL)eO^98Ee`_F5i;IytAr^f^n zRJRY7?jkK#tM%ek3FTDhcHeLqK#TSAL!`>_3UD#U3{k+w;$}K_)P_ETAc&sc9v;jozN9tn`apM7R6v z+xXz4;8kv!eVSLVE1n;6>m~S(!`v60<7@Bl!QRXp$+XExq7ugB(KV5@*vXJh=PRjr zzQm5TEn$Q|n5F0tk-zg;8#ISKK)!A-$oFw>_}e&lqvSP#8_O4!r>E@XLG{($&&K=A ztQ~}YTc5}~V!CBbJkNA=OV#Dna`zksJOkMg&Sz=IQm`hF&jHSXDMfuit&Z*>rePN)Cp)zKHa;s_yd1+Z681ot*rP;=e03GZ29?xKLqi$ zDLY4;Af{|xG+k_BTi2TT8jjXJo{t)gty-M;Q^8TamN~Ga)*T-t#<*1S>R2Cmhc7XV zm<#GG$$D$hyL7aRiAXIF;E`?;i5^Gr;KVfoV(KQTD5);&&tr|Jiu56AC-4wayRm4c zvk0D=e!)7+7CP?JupxEmM4$9%uWYS8Pqyh~eL43NJOKpPbUt6z^Bl}nkWx}o)_9!q zbeg3u15HO#N_+P+YjJt@8+r3J=Vca`Ffbs|aV@7lZeVZHzzeB=m^M|w-|N@va8*=L zoRNcC<0L8%NgEvzJ&Y`1Vdl?J&W#r{%i)0Z7*ReMi@aTl7~L-#7xgObTRb8%B%Ipp z+SSkgvXVBIZulrL#@}jm<9Y9}BBb8#+B^eVk!wj?lT2|j%e5gU1q!dZU#SDzU++1L zd;HC7^Hc)dr%qeI?QLJZv0isvs1#gk9l2|x((G06L&99_mp%a<)%v{{H2}q8I(KFc zL*n)i`M(|?9Y!cdz(mHs`hEXP%9O$n3i(8QOdblPWtDaX@`!mVfm)hgx}6u=ExS{U zELIXdQ0C>a7dSX|8PS%52AU;E5i@EOqGI44j(AFEYWQzWpX0={xAL=aG@FP#>I1aCTgVk!uz!ndulU3zoOyx~=VMg2w=1m-Lfm8w^8{$>e zu;fm+b9H;#k=v&8a^+`$28_gn78GOSb3HGwT8K~0J68YfID?^ETpm+b zc)4c7M6;>rh9O(IQUHl#5GRS zyS^FE2XP--X;K^Ic)ZVP)8Vrpy$XM9s$Xu`s;7KGtmyo zXmW_R)Ugwb@?@3eeUcfTOm6!~{92aVL=Y`ww4HoCbrHGios^WRp=rx?HN>UfWDr#| z8eQ3`OB^B*uAOH;L*zb(cZ#1pFtk2xI>g9gA$Dz{T)zQyT0-pP^SC?H$6|d#uSZoA zY`!k&t~t;2F}khb<~RM)73x{%vSP0Yuya0a09_2>*huc@k?i|SLi<-yADc3z)!%(!a84*ho3wU=#^>3Xx=t_NHS!6L#P%^>MR(l|&xbn0L)LqTBcbN!X3_XP_cCw# z(G)J_IU4P3=q0CNRdakcZtuz&o{A1v*98i65GFn@9?oh%u>(A?p|{a z&h#CeZcHT@-}k#QosGQvjs!RP&Nx?3!hdRjpyfa?1ZEg;nkX-k*M4`z0ghlcTh&&k z)h79aSBB&`&~!d-XV_UhXGj>Ky!#xLfn**t7+$;0sF?3e=*@s7cyjKFE>`B~4aZZ` zwk8C_#Z>aQw@fun+C9LsF2)y6$49ek)3t63m@jlqB3retzdlJmHeWUnaxh_dogu`q zPT8wLS3JKhTRGfsXL#DL7=*3_l=@)QU!M-4>iM}qa-A7~goxATgVIyYMv$`ZPu|7P z#=uwh6H{?9v1l9^ozh2*^aX(vpF6vI{S{Z*i4^c)x)Yzt`&}kk)3d$;y#YK$?yyYz zogs{ea(j zlyJQ$oI9s`=B?m1_=|J;EHl+okCR~$OJ=!5Dw=N-!G?7^amg=VQnHt)W_U%!EPV&n zT0eu96}%r`$oZiv+%hnzR4-Cz^6{?Em3kxFSg&!X=C186=iy|VdpE|37GNMRBC1E3*FtDjPX`xoM^T4OmOVmxh==c z9|{%kc8OUMyx`R0eYTx z_%tOlUh`{YmZZ~{)BTI^rQ2e5455)XvRgXyQ4kJsa( zyU_6(vzzh0H&tt+!F;)c1MecY62ib<{N#gBnYG)SJHk~nH~G}+qe!mJ6Di5^xT}>+ zglHc&y;@NV@?qY)MZv`)a?YiGBPSE~O8Khr_A8(chF#`pLFPfnvEo64ug;rvk1|h0 zVfUGx*7L8M{S^C95~{)PXE=!3e$GX-NZ22s-g{1!PSxLkLyfE%i7$v%T3ht!vlwl( zFpy|jLZlHyn?XJ0+aJ@BGmP-Z>~KJ2uB^*7>@|YA>(_&-7B7K;_OVC{f&? zFeFNeAs%M7WEO8eZEC^BY%-c?$15GZ&up?^*-Sj@N7;zn%wN%dU|;Uw$-b%R_UI*g z$NY?9O4uf_B5XaUx6{=P*`4Ar^VLi@{$giXqTJB(Ah%kgeWG@mQlZ)ano_f&jO%8? zi;DbEy~cVK#_eTietZ6hkI-k)5_)Mq%A^AS&+rstugSXk+Np_5=ktSe-|#Jyi>C}T zR*KtYuww9EE{6+$&{{sSrG9B%Ub>sYUpNMqdsL@tVPDvpF#>ur$Du0-p*9s}rQdx~ zp+$aJCK#lmUa#*|e}B2yBU)vE^X|}8Qw2h=Ahh7bM@@Y+XB7e*6UUo2gIo4N%Q15O zxuzMRzDYwO@ENCib{&hqFOQ{2zO|rkZ-LbxwVAm%ExO1aSy=UKbX9^q^e=gUECM*C zq70J9hLgk*x$Hrc0TX?%854a&CAy^>X?G#k!5W31whGxIv9qzAv%N-{MF(-_Gm(NC z(~OkwZ9AUN8%jneF}xq_1Isk(6wy43k)toV0(kN{U6r^icY;IVg~t{$R%K%@$DO7K zddL+%Nj>ebEwkhEH7=lJHWAUs10d3QC@#>f*=Su)C;lrB%~t*M=V-Fi7>-5MM1C%>AEA@ z{PTf&XR6dDetzwRj}EB24-CCsK7F55LGBV?AdP)USzSREfg|qeIae2L!pypz(&yEL zFr`S6K(%9iqx<^s_+fvRn<3D@vldnr1^Czvi<-yMqyvI7ffv$1R}SO((7L{`b9JJuE1kZ4>k-II2shXA{GuFIZKmHGwR!MU#SUPzs{A7Xnl z+Y}Rv`$+NL@CeGhf)!e`q*r}An)`7`(!M?PK{_%{Vzy*Zmbh~=^COGdk{WBdI{i7^ zxxLmUy8ZDych%QZd@~o$zu7yNVD+PN8XkPO=S&EWQ4CQb0q#_NE?t&+i^~%Y#jvQ- zjg(nj_!C415@V4Ll}L(Zv-;YiQL^f>&z&|gE%1{AIqPf@7y&q6%)wQcIsECCmK{rC zei@Iack(^j`jq3(qnMeFW#k6V0Udq~V-;S{>h`U-8JFT2_Nr?+0`EykKlKljVrEn- z;L^m(c77yfLu!#({+2|SE;9vjjC0yXZ<^`WajxAQW8_XVJtuhe*{aPo zJ6C60Gfk9=uYm2I@=B;-j(SV1#utZ1I&w=p_mL=G@t_-!ab(K7)cBukm$h|DkMG(a zm*m{8%k?kDs-muy+C3bV?|n5T+(0SoDb79lEu1>kxB}WsL!{} zs@u)vwY&OXeLi)=3tgPYkqy5^B2b5P7a*JVr*ME7VJ}g;AF4sj+y-POJ3^}22MqpF z9>q!gnhiy?CtjfsK@F zeu!^$WU7n6iXuqmKUR2CHEORUtJJff6?76CW8+PbyY@23d$VFm7m>nO>WwdctpI&n zoc~Iu_SgIlg(CzDc7kGc9tsc9Jd^XuGF908So}G2XS~N_XX2IzU(X;Sske%%1E$ix zLwEJXW07=?etq1p*^w|Y4btbmv|1cf5N5(zce9GAXw$XuQpvSITDeMT-Bkx;(}szs zhAY#W4^h9p*DJ5>vyI?(sn7JK^=jden&MV(H4lB-=v+#-Nh78}Y z6^`FXo@T7>O#J-5bbA*P=UDRYj(cl{r$N1A29-7pGW9EIi7$Jc26!Cz^@5lEX}aJO zD9ltESq(DBg8cQW9xZ>C&JjoxmPC{EAaPfomlG6b=|_R!2k<|VcDdtml$)#<^V1_T z&$Y8*ES)bK7E{f^UyJ_sPAjNmuD!~I%0jDv+k)Z-hf8S{RQRq%=cu}ZPQCLdK1NFh zOWP7CY4!S#ZRc@OAg|EkV$N$#*mxW`=51K-=d#i60MVI3sxdMReWda+-Iql(Z44O< zb2?|6b;=!T)tT2UAxjL=8D6D#x46MA>xS{Lo&td*5yLt4IuC!N16mLh&_wsG?k7<= z#@{^?cDvBkKO9fc+r-tUCjD?D*QNGa)=t|>m6DW=TcV{FD`l<`QGjtsT=lYAuNN&T z?(8O}EHyobw}v?uy|zkjl{BHm^Zu!V(12y%@1wEz;!eK9rNMspC8iON!%-}sYk_{Y zLQ$!IIoJB&mFmLHHc-bd)^W! zv^Iuoxh)E#R@zMF7*nON-HngV;kM>U^j+wC8Q<1^aJD6McCT+KS^Fd-Z~E+mP8p`c zX%9#m>bl*B5cwIjyuMvo)F0g!4sR~Efs3M>)Nt>!kKG(_sv4g-@9!BEl4{ zlZ;SU`9!lMwIB6oHyeuX_Z7EfXvu9^`Ad=L{zr}>nxl);=Iz<4=bs#eYx%}`@VhkO zQ&}Ka!ba~_3ADnxz~=R`>5E9f8^-+*)1SfeE?xmBWw%{VuvF__f3`B5G|p3k<)&&m zQ-tRT#Gv{DfUS;EbB0cVx8KD>%6}F9RXl^?=2IaGNsZBdf^ezIn>O)6kogc+IqHK{ zJ*U;Rpj^|?gg9dyt*;)jfEMdZPR4M|Cno4@27$4uOmxDK8H$Sj7fiz&09CHR!vFJu znl>mlBk`0i42bPgMrd66C30fqR@jJ{ztC^s2T-;ywpS^p_d`M$D+`)1IZojbog4&8 z%=_Zc@xskYD$SP1j_`}+7`KM3q{CwkdDn-q8VOr`K|LnkU-K%?eew^MLEnI8xP z_0L2e{V60Bnp9Cm|11 zjCgPV+`L34V+jQ0#900=9Gt_Lx|z?Ucg9pT-(_0AB=UqbhP0#l%d z`uikHhm?)JNw|A4NPA%trlM8mh(#5|emqRDry6Ri88X%c5!HEl;c?mXM2x6$=|w53VKGp2-p`WTh}4kGmCwAPw}LM>Va5uOQ0-^px`| zf}bn_^p0Di#PmLa#JiR1PI7b5@MKGLt~%-D>x%4H{+uR$OFj`OG^@m>*}D;cV=+lT zTD2E4;I5YAu@8jdye9ZXGlfmSfeFEY@Q0Re$NMfh3_TG;nJeR>XwPQLs<-{j%ovGP zA1^b$sS){mWQ1@O*q51;dDg@&4yA1Zgx(o_N1l7dAS)}(&fU0~G!a{p+Zoki6b z>`%G6{^ir${_}q`)Ni95%?yE}Wyw;R6_pk-;&Z3~cm790I8p^-e;;e}ywIH1?Sp3{ z43-kEoW!%JiDKgN*XB+mo#YhYqqLl#u5NWn0p)TnGsm=O1EkqsGME3yQtk*Y-pCH{ zUNm3%yop6Uy#q|a%oeC*s&me}Al5gx(eY$WR4UXbZlpN**J$a#Kx!AMz7qVY-|N6X zh+jT_o8bM6vHV}S!5{d6xQvX90L`}qBT{&EzI`fW^yj}u%Kru9I5#RHX^ot~meIRj zY?3~*Y{dg(H4^p3p#LY>+J+hNM#9I0O}?S0;Nlj_{vV>HQVz)|-ILx9Tzh67 zh;6!muNr^w_REd&B_}Wk?^T=Er>pIO`-o*E$XI^VvXtIX*0J!9@FG!szvQ<)_+j?g zv@!^DLx}giLoLgWQ0248I!hv|SFVLW;JyjlR)jPM`Y0Z?G*mr=M?99Pm})h*x=R23 z&xoy~`AmJVS{#Nx zxF1`(;LcMl!q-{Q()^#+{#Bvn6kuKLi19FlOLI*Vzz%}zBGs_9iuzzB)2`nR&Z*pL zfc)mi)LN086w~yxg%^$ zoZhbc<~~t79jr=vzDX(!1TXY1I!M8X|ANxRkP{4Hftv+qtQm22{lQr_cX3AgG*)K=B zEAA({E@ZFbD>PLHLMgVZr1w^r$^QpI`Gs`jx~R9(94m4#RMkgC?XjmK$O*GrUyc6g z4Tkh5hV1Te;dq_=&vq$4^L}1Fw;%K(|Fn@&o+v4`EjJl|L!ao3YOm~nRV=N(vd^>c~J55uaXWGq=T|sS;pssm4k2J zrg#`dblP%3M(8_T2fxJQc5XcIVs6(x`DJ*Dy^uvjtRIGOP9+r-D6t%mcVgZT_16&0 zrH`V)=RbbK)Jc*R^`>Qhy%T319VWUtStYORyv%mw&3EYFeq5=r={3#Wj&R|Lg?#s3 z;#b_#k194@!~-g6ucj)yEnbBNE39IrY5}>_oRx(-P-E7xCYPoCgfr_duirlNuc7co z2c!P7W`Fk}=klvOHl((G-KZO?g3{(S_fukSU6PLKf>fB|>pG`7z>nmf26>>_xh8GZ zLMP-XeXnl=pTpk`C0_J*IYx+QEPL^QR@zrawSQAoML{s z1$|sv&KPA46ORr6PYeP0Iz8S=hT{H)#6Z<^H~=Oc+-SGn-Yrj0O*t;l2H4Dlii?Y1 zI@(kUWKu~3tY4x&ak)}-&E6Ug3@6*6Lo5-sFEn@s-2iR6H+=~wwOo9+h`eKBhkvm= zP1EoEFx5qF;$(O}8lGg@oR$Ya4)pT79e1E_ryO7@Bgb#Mj(y8cj;bd@>EGbns@N=xHFL_g`E&GdV9nSt-MNoTl^jAX8o)$y$zj&vvNkP5xrQQ}t z`Qqs)Yw=nTd3j&(({sY?k4nG#ZE351^HVx4h|$^jmT)20hXy z@9+)UpM;e9GI@QEfGOEXm+t6w%#onOpMaj4nKeG?sov@x@C0;fY3iH4*clQxFc7FH zSZcOxya8st-Zx{9^CN=?SK}z<6>D=e*btBNUI%~X&aPh7+PhwKz}#cG>OAAxpJ8;J zxjVG+Avk^iDrNh=@b;K8=skX-O$R>tEzeW@Or@%73LAIF#AyHmg$@)!2a&}b?2Z~Y z&mGe|J5G#qaHvj%34h+nnTu}3!p_?k%wD}P7lFWcwsmCzyVf~EVh7ZHPQ%?#-A_Q; zjE_;|+aCu@*n2Qqd22J&rwz4}em+(IC5#i>*dvL~%Q2CLI`(-3{D@BJ+)grmYo1Gb z;LF}uDh-d!qAkxcMdc5p&`cA=Ag-d-?3l7)a(NN(@w)jWdM5C_2BXx`X3WWs_*>7Y&!XCroJG>=VX?+K{NIIA+;H((A*y zvCApJ(NvrPk6R!wf^vxxJkWFHPB+g{A-3&#h}r)9-gG!2Nt;C1`>CSs_TiGSZ_%uR zal2p{pEcrB_m2sj`=h(b%w0q>H(IxQZ=ass{)3Kx%(FvS% znwaLa-QI*g*26{WgXQLNYX;qWUZ9dFY03+?$L-Macag!J`L(y4x6{1ypl)b997V9! zr~BE)7M`=+*80_L-ok@Jsst)%*t%EiwVck3$Pb9gmV>yr!##b&b>+`*Nnzge@XPwA z!k~@T?*n0OWDF4Z*34M6+D z^Tu`8rig+ko;<_*hx%b62;7^Xj;ID3Xo`-;L|jYA?y|o^*aa9jTR0ov=>dnl#y&5T z`eN;?57)u>fr5y*mbwZq0iCV2y087U{FZk}C5`2l-RNwVL$th^PTX|64)+S0k9|lMp$Dn~r#}j<1bp~k+g^bFec(&sT zkiqv#2;t3f+$4j!G_E~I^GR`S&8%tMJv=Pq@7z*OKe5ucJw&#>+yp6fdgC^4CGsn@ zYbd)tKBHa?=uX#5uX0$F=Sv>CPoqaB0jL56fc19hL;0AuaR`s;`G>B4dk^JjT$WIpkE$gcyosQOORom9o%(FT!qD&(p z5$%eQJbG6STaa*`t-BA04sDdpN#oDluZ^b~UD)pLE-SZNQcuu=uL?HWZ-!D&hr0+} zBqI)Hx67v85vEMJehxWU9@+KZS=uxSc6GW$RXn)qWt6^KtyoX0>SiABK**QRg>f&p zlW?t<44-mycRtNgbY00=Yb>3wXl#9UdaLBVf9;b0&0TiQ_Bp{~q|^T1i7n{U7QPlA zhKr1%)ys$Dy%EBI3YBKXpdbu$!m%D0h);RvYVl&!; zi$3BgdKTO7{M9rGf8{GCraCQqKt~4}i@sFxYpkwGRdCJTy93AGw{z$MsO)YgRey=c zYFtlYv?`4)eSTIomy){c1Zk!(XdzU;*$S#)k}qm7;C1@XEe|i4G&&`$+2)j&yYzEC zv_f>PTDEK7F7}>XsQAaJA?fzX+sE3}!ce?CoTg1&)|k{zTCQP==SI43$ zseW1AR;166aj3$vUil}x4n+GYRA{MpdT`?Jt+hWw#nhwh5hQ188HF^y@R!*47q?&S z)M3@`Y$A-E|C+1^)o9*+mqnc2)eU#mL>O;!wb(0lcPQhu#w+XX;(G%&ow(bJx;gEk zQ@L2@SeK|(w*iV!_uYR4{u)xkh~ao>t)4nPn%*fMIGJXB19Wzz&A(#fdLddHdqJ>p z#MGjOci|E}ut*d^1!24;vU9&)U*D~?xhiQ4=-YHsu*4U^W#d6Z3@1R^BB)d@E7WKBtUKHoDGE4CJ}f;_b#as9&0^xNJaF&)I+K2bu) z#!CDsnlv;zY&h01t3Q@|-P@vf3XL$u^Whs)?qP$^uv>R{7~YcYEK>ZiF#a?ZHWn8r zYf*W;LjQildI0Wwv#%a<2t^N^z``N98TqB=lnyd>HKpYg3{IL{Kc7< zOVcV~h=qtry4yr4c=u?6&Ru|V&11LpFnJ)I_wB@}0}ihFoUYcm4Dy1CdgA-;6(Nk= z$Pe$>b-CFs;eBZW!t4TQr#4wH(14ZQ|KV*^sJHVdM!Q4yXfxv}lrj@PcXs2@r16iaQeTvgDhH!C=FnCX{%eQFgB zvk7i+N}7JSn%JaSLPgamRuKM{n$2l2DPJg)yO zrOfLH51z+4C<)hi;`R7`Z*XLeHgy;q!x(5e=IN!kO5wa+F0n*m$Xb=rXw@lo$eELMJleH6 z0|oydVOIea)z6Y%2?vA0mOA(|yqy*`%p-Z~EyJP4WI{xG5tMB>V z_g{-O>#pI>J?HGb&)NOE*>ybj)GYd^mn_Z7m zh)vVSb~YX5{3N@ZI-Gw30I3tvcniqf4tRraPW>$txa_g+cD+)ZP{l9ILCU`+B*#N3 z77fWFa5|)3G9GsFcv!}M+HUd8KEo$XWo+4x9G+OR?Gqjm}p ze&tWchS09hcG6bq$1632=sK>fldhG^*@_VBM@X|}>8$bh*eN99;u|55Z*&($z4lpR z+Q-8CaZ)2LL`7sWQ=#dO!yQPLFFt#AGns%h*=+qC#VL&6Tz@?@z`V@BzR%!Anb++F z;DseYJQ^V*c!EM6E$<7yPl|Q-bW&F@TO-wE&JOb&B1q+OdxTvQ`e?{h31RY*MD^tOuR z9Z+gsg%Z?H-XAZO8y=nqxJ-6$;*#BXJB9`c@gMCyiQpwoRu;Al11ZOufh?h1cFJO= zNE9Cnr-~+XQspeL-@rz-qA5htJTZTQ2E;3hrC!gf6@OlVjn+l1{iH*eyB^n@$Xf_U zr_Q2Sb`pKSC6Db*m!IkVj1nU#Xx3VrOCmijx2KI<3S8De=t|Omd@tF^xbJ@s?|fdQ zjyjk5S_FAnG%Fy|Dp&$wTO~S#i*lemaSid?o@%@DmK!ok$UJ}4$8X+z<1{`}F)oFs_`T`bCiOV=-(3&7w+28g-Q>50 zMD%#^F)R!gMEqVrNfn0K{4sngre`q4T&9=U^!BZpblTO5)z*s)XtP8^nJa*`bT3Yg zT((x$kMk)nH#9}k#NQug4Cp2h%%(M8=Q*XyABYJ(TTNK1R4PH=Q5j-5y zM%4%PlsFd~0$(>jJly#Z4l(CAno~xzNr^>%x<*denGyx# zrP(BQ3X!=p3_;=nzQMpx7^YUYdQPG3u2-UIi^2d0#*HQsreWHdtNpofC36y;_ec2z zxw=FzC}gH!=O`dH+1=v8z1?o1>HnijIL zjltG)5=gGlkA(=!SZIUzplUtSOmm^xjcQ(QJ1lR~^4^0x9Mh0W(c11?vQm;_NQdJY zww?}FPx-Gplq?b(#`L0ySfY2=BBsnykWWK+Voh98%(DOjs2V5tSUY^j54$5*e+4XlE&KL4ksZNg;D9W>DfB z_{uc>U`IVG+;<1T4l639M+gr@ds!W^Fa;DZY_2ao%P+WAi?uCQ#3HhPHB78EN>Wlm zn@^46|G9ES|D#t>RavjhNmrh(;;UCzfpO^Nb;mGHmb=`>uCb@=E^X)1ny(?<{ zmi?osft8p45qG<&_$LfoV*8epp+J+`M@!*>mYl%}ykelT+b0FS$5TA&Y__+zF8wOx z=V8b5$$h=4Ts_?FeDSKwxH;+#a7Km*-|wax2&2E(ERjAbeMN*2TXw(DZ?oH2oS<#r z%+x>W=8O9{c)7mOES4VcVcjCp7b_8sDi%7?d|2xy-@%t#5R2l<_bsmUY*N&9#^`xe z|8nTH#4SgYjhvV0p%xD*Ps3uvWe=z6d7LAdGxb9Ur@6A3Xd zc6(?cj_w>iig%DOiMWp*N!mfm_t8(E&fzWsIltevQ?&Fw0}HC>aObVA*&hl&FE~z-g;IINT;-`Wr zoMH#|(ZQMfOTZYOhXJVv6VLm*fLubb?mVU?ywl@iO;3a5RlrEFcz^jfexOX-)a}I~ z10&yAUSmnekm{1_vW>?mE_BKU754}CVwckjD7I4=@G_Uo;Mj`NY+gd^`jiy-wddGCGuCH36i z=xo|5-|!37G!Ly!4<{07tA-`sdJCP}+~gxqd6!k*vyS#laj#B3MmTdqzS|ucxAT_a z{vUA!ZQ{0T$ihddkwTAbqXx@3m3hY*Kef;g)2`yYE2a*5=Y1bQnO%3pn-=?ClEqVa zkllOD=Ej$PDMk#1aI~F=fHHWFHy455x1d&uD-mFmxC7vQGZ6AQ?KyV9=jNJ;`8Yhq z0O%xCZ8Y7e!`7@XweDq=#5jDOPZ{&Jt(aVwX3FTt7Ane~Uq196SbWMu9csfwtgJ&f zU1c$u4UT(tH9;$SZ`;}7dd}Z`g}`z9B;B-tP`qD-y91~sh@I0Ka$%aj=;U?qc8%4H z$@_zIMbzKxBE-LnW2ix@U7AWv1}m05X&dm=*sYSKZ}svm)fP4E#rdCy`j@VosUK-l)OGa2wOp{Mo*@%WHFV=c74NNN^0J zCUL9!xl@x@x9s;W_3na+rx~7>3M9@Ekl-p_NsFR&-KvIH3+9CCBYG2Ffu0qPYT*`> zI&2f95Z_$k_MTlgGMom+<-?k)=4?Q)|9mzH?a@{{yIP?L%T z(N-XhYm1+kah&LPuOo4F`3z6DmMd#dUrI|E2GenrPd&Pp+oP;rQ7OdrCYQ?Y>j)6w ztKlFS7S!PYDJb(Nk=Pt#3+-s8Hi`!J)}3Q*wVI6Hh+fea`UAt9|B&V(GJtX7R>8#Q z*Ln=>s6G8Ex=)t=44qdk*Ll#gNoe31rCA#^Y@fJiQZG^Trt*N0?3_4qhZ(H1NQE6H zBnscOl2#e=E?~z3x%B%Kvi=A?${Oxy-fUdT24RT~ygqUstO+}X8b2RZ zPdIf~fT3=&#qsSkFtm^?4!kBy2e7k}0%6Z!UO`eoET^EcmAuXsJdOE2{>eW3QvEmc!Ar`1Frj*I)){xuTn= z@c$_k_*du+f$uMliEN(z|2G*Ky@9s+Znz!#tLFXl8=|$*Z*qYjgoW2>2)> zsTpz#7v--hroUvIL|TQs;*XyEJqh)fJ20s5VWjQkITko+|JRU+>`3d$e@S8gn!kJc z*;mZqskw=))c?xLig7}Vju33xKrQk8`)~rQ=*L}M?&S2Wz+;uxq<@l0L_bIpOoA_( z4wB(7z()9;_Ai}anAT1@#690NLZX5qRcbaC7Qub?tvm?mqnYTxj40rT#!>d@i(xX3 zY{Ve@dzyNbG%|x>;w;F==eJUd6wJQj-+-zj*L7M=v0VKGR~u-#P2=Kj`+8 zD5qgid}bX-Rr|q|A9P>zhNIv9x`3xaP$Z@-%!xx^eQh0Z|HB?XPYT~<^Q%Ryk~jWr z_^&U1MQwSB1HF-AtnmS+eem()2s$qY&l6^u;eJ&2K8iG z|2j`=x;%!*N0z^#uwyb%_Dfzs^HC{b(a}MjE(Kz^hsOoA-KB<>4I&8_3Sxz;tScXy z<NnE@91Q6m# zUVUjQ+xa(2p*athEWrJky?eUGPTw>PF&iJB&f41gsS^R$kFIpTZ{fZ-+A9i#?yEsc zk3td(9y;Wdl-MDIluZx1CSr>*>(BoTiU=Gc3e(hYy&nLoo?Ynr^nBnCk5aT&`F8BHP!=?Da{%7f`)I}@_uhRm|(;LDV;|^n0@!hMB;-6jyKZoUeDXjnQiUks_ zA{wYqHi(YHLN{M8!qs$>*{pMRyspSs9@`QTHv${_23cV$Xxo*d z_Mx(wbm0?0s#k=YNf}*CB+;M+KB*O8GM>BsGPX~0V&Ld>E4`(EE|pvIE|9ox)%8&f zu(6H<4s=m$lyi0R=zXaZ1_jXMFeqLm@qewrHycabPGK3Vbf%CcBY%{A_3^5;*G=R< zM^&T^?6E!$isg{ghWMy#C1{e6X$vp7kyF&{ba~vQXC|Gp?+feR2BgeaF;_)~GvCmI zb1zgW+cJq+kP?U8+y4Oy96ElpQ?{jEx!q^uBOR6Br`q`xGs~$T%l_IJd#~QE%(GMS zdcXg%cdb*3oLJ#rYz7J9IC^i3udl!Jk$Jk8% z`}}y5uHXF!Zk)h5k|(cCiSx}ZD6YbIStnnI%~9C$+%zJsJ8r(FpOH3RD)^>eU4b4A ze2nS#Qh1o;>G-{}8)CK6RM8Zhl1gd(HrX~haG5SKLFDVb^(xFk3}Y0!FV5{k7LtZQ zjfh6f(n^|66PRV+-fQRaQ-mc&)Ow+E0v?x3l+Wd1R1)4)zoybdWQ3_~LTO38EZ0;q zc7C$ftUe!N6`eW~-Ap#q8h;hiwMlqg>t4 z=$iM!LhPH}+RjI_q%L>c&`Ep_)rSQ-OB0Vd@Y}vA0z(iW_6>{2I(!{C_V9G>sHJy?JY@3=3T4tEiJPd-yk4F8 zWdZ6;C2dBR2|EMseNeo}iCD+QvqMaSuew#rY+H4=-Ef3hBV6teT5g9$4&;KHslwmA zB2rjbS}A5kvOG!3X~mQEE~BT}g4SJ8&?bq$An;&sYF!p14G^ZxbKC$R1NcS<)(=MOP}Nr5fH?@3kxmhGugiXX{2{FFhc`yXT9rVH$4QN4+$;JQc) zSC)?9TVBlnvXaN`NZ@^Zo*@3@*#uly$@_eSOzlWRHfjlxE|wO{s?jmRADVR|2uhLg zM@KjWq-E@T3MZ}>RF@9fFl$TCQ@!S(9N${^Yv2dt_AVJod|vg#hFfR9+F&P=U@Vv& zsnRC$>1Ef##@nv`v22Gael?;F9lL|)8j)T*%?+CqOT2Jvw4TeCBkQO0M?QT)axIaN zQj4BrIlhFmAx@h<4CLPM zxKGCju&srgGm0BpiR*3YVMpHT&*5#z-nI)PSHtxeKUmmHZ}v`YEGB_((d5t#FH}^9 zuNu6`xGE7sFrHcV*+rZ+-<#?y-|X-AWjom9=k)-bfIgLqf*hfby;hI)F>)W;ufnZ) zvyM(E5lKD-%B53`PnX8|Odu==iF*oeR(GoQB3BR@ObHLqoqZ9Pse_M2!nH-G2XneIz80&q46$+Ge=5#m}IVH$8DW^CdbD=9N5rl{sZX z%^p!cXx{J&bYDY*;kv}1oTYuhb=t9Oy32Zntqg@J+?&>z(^sIjSHH70F+v?NpzkMi(bnD?{$_dvu)rO{h{dhH_8AV%v+QS0{)gC5^! zFt<2~Sr_@e^j;QMekSH}iY;2EsXUz|f4&(!j9=|%NIX(7GDr2OAKIfHT$yyI{sWT9 z?;Yh=KDzdnegqVJI{T<9f{$0Cl{>G;Cwv<9yWdti;Iff8lsp;UC2j_>op(-GaUhqU zcbcY8tQDyn<|}4z)I-n=lHql8lOf(e(rl$^=`IR~&CNF9^?AF?=2!?LB)h{Eg|(^< z{0`R#Io66hXEm*H$>ex1BC$+I2@1WVmN<4ac1KFWEN3f}Ax+?1U+&@7-gM0JHMt|g zs*w)0eYKorwitYjgY+*&rc+2qlS?Jsc9;KQ!pK)32}}qX5oXfb4Ov~=A6nACpDK}8 z7*g`b|HzBTo+A*{q%3m6Ti6WJZjx3fcfW|e?-koXATOCESLF7120}xT^SG2rsxnAg za6F~9cdpzzU9*WD?XkrY5=fH^)59>9@NqY4e?=e35pNWP6i4u63oVx}1s8bkm+E z`TIPsBR1SF)1L@|BzYwenzv5gQ(_AMjdavWgJAuj{aMhZT<45gUV6I#_n+ zzGh66s40Xzx_I9LdHB4Gs@@x{Eb_Yda)I%Otw4qAa9U_}p{Qa+Q*(sTiDB)h z08Z5#5({jr2UR&gQ_OHF2e980%VkwUqnXVJWm<6HOD9_8isVTIymHWXet~x>(IvgN ziLL7dVeFLvEI+$A(AH#p%l?*{_dJkv+w-^tYaom?sM7SghuGy}PUi86bkz9G9qRHl zUuf!WXx{za=z;GAbIB;>25zXGU5KO=^m~uHE(@kDurnG?-iY@fN=; z>rH*5IS5%LnQZe&2W4X?B z_a!_Y`~CWn*v%8+V-noU0MoP)g^CART{zt%JmxER`h9Omvpr{qgkeqZ<19C7D^)Oi zun%WAgLo9f7(jqaymahk)(2n2u`J2*uXoC53LXAInZ86sRGI#g3h3FF6k{GS6iwn$ zv5$>)1LbuK+6)033~uQ<&MwX-&RaFu(T%qk&m;bD%wSRk9I+6#M%~V+s!PWMTHyt^ zeInSVWWMA*<3wZ*dEcdj8W#|#mcygHO@od-8{1RVenikCeQuTa3zN~mRbY()p6ge!f*%ctD=~k^+53Q$>?4i#l zH;dH{F{D*l13hOlfmzD4`3@6Jpu7qtC0G&f_XGwDjn++$qV@$Zsn5wRsEDsmsMC8v z?SUyt$y;+Rm#?WdW6graO9@6rKW!%!s*>kcEccbdw(h9us>lTjF4z!gX^7_Ce(s4P z{@|Fzt0cMIS6pEi(i83FVKfylb1L~kR3k+s)s#jxE}BMN|FoVJsX8&Pu)AoF*H^Br zH>i3^DU_X$MAszntGj?#I!XNDG9Ep)QHJb%nXGC3w!%9u81Igt9MJ7N?(BQ!-ej5N z-Dm2aiGE%$zQx}aJz6EsR8C(n3>6wF-71AF@cs zB>y6(?Jn6QD;WUuDl5|+T(p;nItMO{)4ai-E4+~Arve|Ehu|_jr$hYIZT{+~XZj!p z^~krrZ0@7MF@Uwv#ZAqyLog7d`8m57sP}ZVD#qPuf8^q@&`VP}N2lN!vPE6j7=Qd; zX8a&Q%tD=geVQ`?X+O|odg$h=J0K)=b1Kyz?o<<%^kBR9z}2%v_{1QpuNWa`XqUw_ zwNup7`p(R&X00e+O@&_gldsH^55Gq}iFVQ7sC&$45Gr&@4|47c@>U^g@bg~B1bF>z18qIFSD*#ohje>vJ`XHlL%%Fh1vmlDV1u^ z+Ph;LY~L7)Z7#G-XM24dF##pgf1G?NnYVaxiIh~((PUH2dDxb;STr;AZh3u5XC{&HyS4)x+4CD$LLDF?tR4PA(}1~#E53}osgsseEW3S zlmf7bxQi13l=TcnUE_q2drsoH3gd@WPPFE68}GjC19Yk!avj(%e@%ENaE}VfH`;}qV4eD&E zzQ44F(MXPBQW=^7|B=f3I8J6mg?NqB<+{hCFb$ zi@h4l^$}tv`Vqc$-(A=oG{mqD2P3(sEG|8Dk#}eer)UanH^+}_#c*a}Lt6usN z88TC(#?fbLV0I%H@(w-stO#M?&vllN` z7oXkc2ghxKb+L8_#=ZNNvE>5l;F+T}Ws{5GfHw6!-{36i*5dZSs#=Aj9{aQa*S@yE z>Qz%h?%TVy-XRS&VQ6Nja@$FNf?-kh9>`m~P=;g=M^Tu~0q3@4fE5fheLF^Ndek+| ztkm0WHeS-o#8gJDNZ~OB`ZNUO2t&yg?J_`M^CP5d-@6$eRyk-#E#>*E+9{ESflG@A zI`uf)-5qprk>4dMGhj53+iq&2^PL%*(JCqLyX)sJs>q|n$#X{2U)(>}i8xzEJ`5A* zz4Kl4UMYyBUh9GKsbBrzKfRwm83P~OO$T(+LrUP}CyW^jIGTP{i;7*Zdm&p(malK~ zl~&+9%wxqnS2~XQ@O3%D&&aNaEYzb;`xX&TT8ye8i>*h{jeZ!R?E}X2q(xScH2Q4e zXc(kZ}$$o7k;8#tR*qFoUo+CYI=PiFV>k7u<%FsHKHqwsBb4$ zgo@BdR`ob4mfOUEsG5 zr2tnN!*gA(Mh_XD*2VA8cw~zG0Q%4c)^UV(!Idr(ZDq$SK+!EkpSd%Top;Q2pdIOo z6hI}e#Yp|GZ;p@nqI^a5CA5mMZhgFZH>9oAJC7-B3N>L8hMqZal@P>Goku4otm{-7 zhK8fb*IYqxKhHSUVDbEwd}Xzk3i3xm*Q%$bafE%jO(xssx6W5G$W|YTNA%)>X zP_hPj`ATy;264g-cg_&yI-E1sgX6Fhd@+`CoIoKA%t4AsclN+n#hV$=-H8tU%W~5r z@>wVO``he?u!Wk-3U}>j2AQ+ok|#7CSRpEf_PGLhs6JTpUgEIKh8sVD@QF^4R+bOA zK`sX!C=Jzcws)3N`{g@({Al_{r~qI5*-E$9FCNflfu^M;>xEzUfI@__CLXQ&Rgdp; zQ54G?Z*>d2c0MWp5j>AZgLXA=?~evgR-S9dr6PPE1KmHB?uiFvRLptHBO?STXOX?7P`l1F~2zAgyX z=OUOFRblMFyW_e(DZtbaV9ulktuG+-KSr{9->XO9@#O+1NoRGfm#7RU@xRcNE{1hS z>~c*#ys%hCbVfTK@M=rc7I8|=$QM>uLRfbwUhhxTR~=DbY}tFf&lO>Hu`1YN{2G#5 zq+5vfLMj_O!Tl1|xDGAkIDoYxW4MqEsWGsh_v)*TmNYU5RDKoR=iP?rd@S@}uWrQ*9NMfcG{Q!veja1LdrlH69*ql!-DnxwErFtPp(wE@{BzJCL5{_9e3a zYEzgD+&MY~7M4L)ss84jX8|6(8F5Y`3x(W<#Cf-CE zKi2t7T-vM9cdb3~lf|Sd=-g5S2ogAHKk_mkyZ|5{sjJ}=ea(IMb+KWuV&Hx^u?mnT zm!R1atDIDU;H*0a9)VJU&sLsI^Gc{PrJn=%O;Jy1Xm)$@i*$I9Zqd*B|3}XgF(Ixj zTk)ftS%HLvz`Ysi!iNjtx`)n3isCfzVOXgT7)oWPTtF zSTQUL19;=K&0!3Rdm}|(3G*4vwyjUlReZdk=@KiR6SV-1yQ9tS zRG(dJnzk31NmCCPDp6DJ5d&T!y&O@+TvvfdPA`HAz2eB>D(1tS5M>2=&f}zVPceZJ zbbK?ughe$w@+FyfPN3Mc4#KM_^D zM<>0J`hJdOig*Cm4-00m@w%HVV9ZLBA6-35CgWiuV4Lov8e&y(8Wqv)rFJ|VKY`)} zJo~9{5pHQpr!y<}eh(vCuRAUVNv=>+O`c z=A*K>{2IDlq3-mV`0T=y-&OA>sw~SRMB4Zs*QAoN@A~^k7hbXjs>LKR zd<20OLnFMx=O5Y#y*0?%#TxUyd)Q(Wu;fY@7H@mvt$g}QiuY2iv|XChZkrl8w|J9o zW?|j3>_w)>Z?1w)tGybHJ$7fB!=Cw==P_uOc1_>ULMvuN%9h#8{pX$O{zzPlr@>0c zlf&{NR`yh%E2o4#XX24@lr*l%k`Pu&f~YIty$pF{YNNfn>9jhbOU;xJ7I_g$ zq{7}olod~K7+eX_c~Y(8*{21S!hF)gsr0rRGoCKv3c%53lOwwiV!COGc8sTmo#t6X zRfBtTteaue-hg>Nr+$iO7HlKDNJzg=U%}d^deviMFYtDNM(o}4x3JU%zc{$Y_fdZ4 z1Q{-A=P|DxPzS$VtL+@<`0y_wGi;aWPS4htr0DC~3K{E@lFn-q-M)TxWrEH(U4PZw z5lFALF$`OgukaA*zU+3>%HGMz=!$u_9x^-9_WiG(uZNAaQcqeEtBZulol`toR7!cVCD< z+|6|1Kzd;)7BJ(Mc;`Q0q-v`O?iPFbzOx9#{5ZU&RL64k5xu4Exo17J6e*hRC<7DU8RNZZBc1 zw-qKb9bGn4RbdBMZMe*2>!gk4pU^_`GVUab4LAwH#8#d_0ai{Pc9yWGZa$Yscpye$ zwb8*`GP1?LWO{Ymw6eDQ0UNQpB(u1oe5-FqyVdDFO_=bwg4MhF@J4!{IYzLs>OK9$ z7f)rgqB1_;o_g0qbYK9czx!js`WiWl)c6z}cm#o`PFPV4v7(yjjvk*OoNt0HQ%g1U z06Vy{oV?rlhgluT0eiQxY{kt?b4R^$|Hi%JF>AOrgV}xHRxwKeIzLZX6Cf~q$iD+; zWixVDPNCSl*$A}uJW4(R@36=(x%o6=#Sq$zQS}*@&CK3BPtFo-;!+qY|7+1iYYUPZ zJ<6R&mK~vX5|5iBWz&{-b}!UD1y^g z+rMuxKM!dEMvN^R601Rwvhi_iGxOoM&tpIrAM@p$?1{XKpjK1oqlXWtoe7X0l1?dq z%Xw8DeESG=kpqII+DE*L#%gg_^DD%plg(+x1{Ejj_hr5x3&fgZtv(JY%(fhNr*Z?n zz$Vb417F1dSz+*_o}LgbPdhJYV1Giyd#O^YE3ar=Nwvgmc#P7YFrL_HPXc0!-0Q{S z!ys`WzU+%pk;QL7hYK96w@sYW<+}ZfPn#-2aZvk}#VATUSrRxlTY>(rWGGR&zN9yc z{!)C6(HYy=fbOS*C5{EVzj~F$L_lIpb5|SEvh(0*O07{FDV@ln-h6G{?47hUo-0TB z9Ajt%DLTt(d%5C9fZ3qmM%T!U86QJfGZO6GG6fh zR=gLZv$%8Nzk%IbNPPpckKYlwk(Q*@c(V(lQ1zmjjnLG1vO2c}UbmJ&9qFc^B&=%= zFApXv9{~b`Lg+LhW!SZyk6jOWuCVjF=3dobIedykUiE#7Gt_2x_p zzxrmIxH|kbFWjCR3#HN)PgnT2DC#lItHCrLH%V(+(mO3qhaG8UqwyQ56dosy2R=7Q zi%=bnoBeil;OQ2o70MCglMRkPY{%a&h|rNbrb+Zop7ytJm^f=!OS(>Sii2#lz%GZq zO{$ywqkABm_kjWcEX>M$2V;W4FgY?ZnyZj#fn|o`^h;+3`qJL>$v7h{VyyK6JMC68^H`Nq5qg+26e%)Oj zp6G2ho>OTVgN-os)ADZhL7Krg&wt<+2oL?`*S@f8@4nh!`j0twv`3%HCT1y?#uDq> z?S@~sD2&(&w1cNn~YEZx%;mF9OGv} z(_cPepFwM?sj0O-i+}RJ5J&v7gj;h+s0RZb{%}rz?#loA^z$YD%47DM$?``K<8K$A z1zW?P&B@Q0pYgY$$ZSy&1%zJ%9VKGZRHgay`r|J^cz}$l^V@>Ap=12W zkdg3VEiqmSWBvVyzvF52M*0?Vgh;E3RRi=X!{B2m`+^}c_U@VQa695vTu8J+M^lDB>t$HB8Ee( zN|?t8e2O*Efad&z4R)kxRe<4U+eCcWS^wC1XnfQ%@MiX0-lJZW9>|c&eYyOD64iHX zZR^GQ!#zVW;^zA2VlLqi3o3fVzpMhXDL5du4S|xaEmNss!w=);?7^E^sO#ASpKy2t ztbqv8W?4pkYN<|D&p-hEJI7gIIqoyOC;^@q;yvlSY2LCk`xh@yc<7zQ{-t;@QVXf| z2CDq@cy%M#_l&|12fZxt)5oqnXxcxEmi{8T5UGacgLWMt(NkLKrJS07r^m5f^12A! z%nR-62z7{P6>;W+B^wedvQ#bYspLOXPypt$XlwlyHawGLylWL{nftIt=L~!dKe%2z z5onz$6N^pZ2SxmKV)hv76-}VTzf2qr8>R<>CtQ-l7@HKTq4cR({n-E=)+gq3eamNw z)W{^yFoN))lj#HKt$E8>e*ELZb320Ne=)WHN;z0AXt@jck~4cu^Hml=pZs+F(Oi5tWX?>a zdZ^eYBVEs40seU(2&|wTL#W^+il(nhz*mF?!@YB!o&9D{@^d#CnhOjYsM0R5hu{yr z@Lw0Lx=0-zkl&&lr?ZB5XAl~VF+={pmf-sw3*p(nXd%BW2!u`(G_xgyiy#G0){u{W zvW_J5uv)HDnf#U~r`5G;V-!QPQNA{?mgni<=Pv=j*17&zxxiQSgbkXyCU4g1pI$Mom6lOg9g3BTW2JV~&*L#4Zq)1RZg%$t7KflCB4fOi)m= zR5>1@xk=c(L2?l|AvRoJY>1F6@6TAjQ17%j-5vhU(+My95dXfexb@wy6lo!~z3ZUn z%~@G7(CKzQ0I`;C-`^X33Vn6w6@;(LOtw;n!;#miSg7pB{j!Yw4Yc3nqQ4%RdK7tx zFBG13dY);Qub15Y8vX}Zj1L0u9`;Yp9opha0(hCWh5-MzMHhr%nrHCT7>_IrR~XhY zGmCq(oMx~+Ee_fani)w-=0DMq`5C?YkI|7N9XI}m6aHVy8XDtR7>W(PgrvAI40^6U zxa#nu)-(V|VcKZ9K5d%`iOdv+ExCeK@2@iY*K0C*7|~||n9v=nx^38{>gR@-=60z& z!gFzfsC)gIUgln}Q1>DQ?1}WI;j4P}b5cl=$dq7K6J2T_zDOh4&i&ks(1K8VxUd}p zoZ`Px7Gm6}cs!|eO?buG@!lr0_#}-LhOrvx0cILZ)tvZn{Iu}N1k*tZ<`0=zc>&GM zY}Ve^?pZMK<9zoMB3clM&6daGxC|%#Va@m%iL@eCg0|zYInYCfehBu@zqJToRsd!j zeaD1CMaQ1Mwy33u_B!j{kQCM$)|K?dtyCQDW6nR^z-7LqqMAxhec_;dLl|MF%pkttE)+UX;yS z(tL;~v9kAp7G_CGM6rEdG>{Ga&+>_&k<<>JFKA;S`~BEp>He-Dnl+7i z96?O*F|&VBZxE}~Ac@}AjAij!Q7%^wLk$^3FH@mvK#nZ|MPfZ>O&*iB8P~5$2ACSSK|(Vbqd37E-u=1x7aU*?g%v8%64B*|r`}Zo zO{O(Obd*p(IANM7d;;JQV|P!rat77UXx!FJt@0s}xxjFJV2gcK)qC&NdQC|TgJ!M| z@EuV9{qTgv^^=B_hSF)=@qFXJGjoYp|7pG+W5bBbgIQUy)<1~@I<&@@#yv+$PolwO z17E5~Q`y0_tY0=iS;U0PY~#Js#wLDYvN!0FmEAZ1C>;@W%>$M zV?5vh<7Us!lpE6iXTSJu$Uwo+R_GIV9)=Y!wPZ^@xO%*=3DUfi8d*~+hdv+%QreV1-vzfho@LFO3Ylql(3e zh!JNFE?#2bwgt3dz_67H!XOFyULPX~CQbi5d}^m*ug>;$UYI=oHng7r#3F`egz(kZ z=S7)93it@GWAvZ(#ZNn08>X5lnSOUDoaNWIC;sJt5S@(}$@(K0YSn z;g$U#PlLE$2~gNbXUiV#QU8k(b}-ep`tVj5E!`>q;`96vbKi7~GKF;TICs1L|9Y4k z%*EL@V_)(7qlCP?d{zQikmEiFW4*LjlLtzWqju8^yQPVS2B-JPZK4P_m7n&?!T1-G zm?r^(5H%10#gkortQ-82x^ZHS`Zq;#>;vQT=%r!FmVD+S^+?Dh_TN5YIjP?8tvmse zV#^&3i$>s%Mf=oAe_5f~-U-e$+X3lERnppe>(|P!B?~!CU)MKOB&O6%Z9El`NP!a!k&Y5R+m548qxas4u(`w3Q3BDVWq|2sGImPYpx;$b z2!ymDjM4i)592>r_}>=>;;mRY%!hv#0{ngN{l`B=l4~_ljfej?Gwo;W!0#iaUc(FA z_+!;g%>NNAfXH4M2Lp@QHQ!(M&~K#t^BcboBE4^LRt9?8tpDp-0*QkC?9l(>==|Kp zSdg#-4zJk@WWLgWG^x>eq0O*Ydsom`1X3xxWW1Q0JNwYn(uTX zrvHvD^;^6o9Vvy|M^I9^fOehI`_Lg-pa`eu%yKLYPLci8?>z}u$tLI+lZJNYBRTqLK?F9i7Ud>`WDc|zK3qkuW)+hn(>Pv>3p z>yFoiv?^jWN0X0m+TOPxJ4^ z-<<7=d3?SPj~&c@SIjqgVSe5bqgZN=_lnoK@y_AUQ>!67;ewy>TF8QD?j4BDoIZjL zz!(Q$jYH*6BWZ5#UN=V5;i^+TqGwFl$j8AboA(!v?&f!5B5MczqqK}-P-*4)mQPfA1-ji%)9J|*A$I^>z3=WaMBfe_wI1f zSKIwz|19ZrG*^k-G~ANeNWiA|3u|SOR+Eko(edr6GPBRn+d6(j(F7GFms*p13J5-% zcyBsmspH;)4)bEVLVU3XZA?odxq5|BG{ft8iUmE_Y+rIc)aS?vM0r*JBEmt$ex7b; zJ6H|QM-J7(ChMPL!o^0}t(Ugw_&qLCR+zx2#!}qDwj#cy8v7WrFKq2;!z9yUpSnlRD7k!+Vdc%=tqe@Md*I!aKQpSDG5#gyTP*ttoi6*hI8OT%(5VZ5gLg9OE@`sAU-ki?JbVZQ zSHJIFa}Qz6$jHzz1B~bA!m1X1Ym%;csB3py8XsCE?cM;hFONqwtdCy$4L{0-r*rHU zHE&#Ej&0ZJ&&g(ZB`h%;-&lIlE3HC8`(xKh4;6w)JxZpY^kHB4^9hx@zn$TX--KzN z8-j6OfL?Ptx4oU5PYm_gm?spE@D|z}dW}FhKDwssGwp@PG#_io?KP$4b$Aq$o7xGf zVmACf)d2x89basGx^M3eYc<(`_NN2m~%UGIXq)f1zzxudJDyxubKu{_<2p0z%^cHYiHE{nx zIZZRP?gSzrf&%9845 zwU35Nj~rvS+XJ&cDPA(k*TBOkNBP^jZbI`|Um4Xz)zpo>uLKE-Y;gq;l(k`=4FLDP)woA1iRYRuexp}_ze}{&= z-$QlX5A*Z2&fc6y=-ikk(0;#lV7|N1)Pm0k+L2OqyoSlB z@k%g-gE775V$D{)9D)Er66X?e=zfm-pyMgSpi@uA_zXnqMmRvaP9YsXE!sqz0s0ce z?eflO@8b2C&oS+30LCI{GN1Sr%R(+(5y5?0g6Yk(D4se8i<4Ji3YpYhd=cqx?ILkv z`q&bkn%-Fi=VlfE?a>!LQfXS8q;cy+szdwkp2^jByF4}GpQCV)t0}6frl>rK09Z;{ zHO?MSsT29rv*Z$FTO<=`h+StVlqU3`4v&1!lj>zr-0W<1$zgS(zB4v4EI*b}De`X- zEeoMa-kk4(^(CB2N3dO!T5;TItb6Re8ttBN7Qh~`Yc<*s3!o@Y`$qET>B?C-Fs&6| z!d>=j?=P+Q^jR-_wT>M(SInK7_8SStUK`u&|02?m>u$b^eiTxHiqKzkhHZuiGu}H{ zIGyFOoWXonXp^mcmUHSlE@r%2p*&Wuskch~Q5@Auu`z4!BZ&L3y+VI1D`&fe=?Ywf+Sc`Ri^(D;(^dj2oB_ld- zW_KP-n46^%c10K^(dQv>AioCTgJA7_UIniHLRhdtYHzq7d+@K}4TRjW>Wcp4lZJXu zvl4pSyW>s`0^@wY;%v|DdJ(Af9;rcZw9m&@Aex9RaM!H&4e`Y)=E`qxpi&c!vv`yj zfto(1JVWFrO>uv(%w$xn?c%VPQ)Wz5K9GF=C7MP-LPhZT{y`sL?PAqO>av>)W65K% z%%PpR;-ibLt;pNr)qM(MT>)iIvQIJOHq^+JcW$V&XfvckUZ{1~2(CZ;B&@t!s8K~K z_2WGR9D)JUGJvC|)387ETuGT>W-N$1%3Dri;b^1dP3Fc?>_z5mH&TqgxRE`4xq2d528|jKBaehZu!o0N zxl39uZ~F^01|(0x@a2$QE4V>C1!68gMa>tmTg+OV@Vtp)KNo1=f$bzU;R<)h_eK}n z$03eh8;urnaceuYGK@{BeR~%-=z4f$GzM3obEd;>Sx3#MS+89PbFkU-y3-_!vg?!S zCD?$J7e>RFN$nNmk(@2uZ*qM2dxU7pjqV`-0T>8&1P_8YqvXa%^%5qvR3M{A_G0vL z5Zo=CXScnCNqf^SQNP6Xl5t{+s2aV_`6PBQ(oD80OCeDhFQ^PYs&Mw#2&c3)X#ExL zc&Es{qF#x}1T{tldoWXwH|kxs0O@E5YSc7WD-@#J%sPU#wt+N99DqU zPYsS1_7e_jZ3ugCC3!wh`~W=j{YuBnz7)?rn}vgVl#NM`j!t|*mC#G{G&8L5ifyWW zKXzIYO-wq$$%4|;_DYutiP~UaiBw^JuP#?4-37yXeZyZvFDK+bpcfLyDbu5OSGtcM%JiYo?z$vgBouU@AraeW>h>lf|$zeNLZ zAQ*0Plomjbc?!|1Y8y=bBqTzC-;$gO(OsT!7NJ9*ED#smTyxmB9`}c4nQC4T;^1-b zqE5fe|0w2IdeHnqYxjqo3B=h^HEoeT!kAn z`R=;JOemm)L*-`y38Ldr<6Q)k{J_+lCjsknegxxsKz0dCb>&>h<+_37-2g@-?@3e%Ek*g=hPyuLQNnxT07rG+atU+ z>O_<_w*&+6;v=dJHAz@kJ6G2;C+37X%#`9gR1Xml zWtOcy^(l%0WbE9}SwI~A>}?c(z&rdaQIT90g(-cyHcQq<5^b<(%3-p9h`(vII{6Z} z+n41p!L%xRR5~*shUe<->jvTd{e7z51y6oJ6;-C%@Z6t!c6Ajv9Z5}mGE*M-!b}rZ zZI$HVqW)Ykpo$(&9SAO#4P4;hG6v**3cT5h@lR(IQQ12m)h$=ODTAGf(s5R7J~HJt zob}lvNb=8jeb3!N;Nk}VmINR~h39^&DKPuw_WI@}9buOAI%@65*o`WQ??PByaJwXe z=%uQ3;c(uA*GyrlJ1=7yIs=}h;kjuR!ZeKpO2&)}OFpQ6<{_hj7C^h!iHoqe^pQ|L zF`B9wM^oq~)ku>D!yHRm_D^I_JU4O|qEg4}~P4bmSek)<}yfbj! zYoie&IFt4Vk;d2S;IJDfNTrDJdS~6FiL)1yf~q8;#2dPTD%5ISHJFGw`?soY{ZRfz zRVT#C=aXKdyI-QR*IKx1>`EC9Fzfkri$^MKxs1q<3?{-DblvQ3h zcr@wIKpFRsGT3h}elr9(QCj3@rFm*mqDk|Fb!e@xdx^+5dXmPRk;4vGpOhpo4zyoTMgy>o##L#w^OqkcN>BbytY!+Ovq zoOhtB8W-3hRIc)Wz!8S3JU?B~*>hq_bGvX#NRSQqGqm}Vob2vjL zSO~(EOj&<*6qYPa*}A({vqo)ddk8joCPx4fU+y4NyIgf9U&Cm#)kubCS_DLYF9r5I z;n@q+D0Z;ta6c#T&Uv{1j<%97=DOQ1ekJf_Pc;J(zb#V|6o(i`-?O6a0o5~^&5H+2 zY#zCVjhKh-hd?iV@qJB>wN0`<{iEVgkF<}&nW<%v{${r&cUQSDo88LbI^8HxYUvbS z+XJ9MQjY1k3|~Kltib<<@@P&$_`WOjZDiu1!X)OU&8G#b3Q9S(%@wJfz^+n00DFL4 zPqeP=w`}eNIE47h*xv|g1%;zh0qMWcuffm<<3KPPg%Tu>b2_?XoYvlG23Rof5q+=o zXxkN;TnD+cvnfCrxOdm@%+b6!84)yO*RsRhv8(Ftb!CP{oJR(~^Qa`teoAMV8+g5G zs_ZH>gtv((q>2L~n9q;Ni-EnP#Z6N|+ z64LV1ACgUAn1U9Q(|mgC-Q^8nJn!~UIB77zwT&;!f7wnA-(M$tW;AK~1y*43aUpY9e z;cfx${fGmoq}j>x*64a_7E|@@$CTTj^+)F~t_`+*98pse+1PJgaP8Knj;x%9XQE|; zF&2Exre@yWWYN~rWf|wLvfJser^)P;+Ak)KVGuV~(**Z8n`-Sx@!Jr4btxVCz}$G> z1!nHXMMaIBa;Tt-$-+8z)~BRGS3ATAYtW`DOjF)asmtzeJ1XAYd~QF8>Ir$eDG4k7 zlLX9Hh7*Y_rJ#as~-H&^IAytAptgE)Y_7thKE(H+=^C^7U@ zy@Y4vXZGZ#7VZ!DIPyU_ZL61!Gdf@e#A;nR2ATNEkyo!g>yo+cMfiHdc+aQM>DV8J9j6auE|7d4%{Wq|o3W{f zN~c_PaZt#-N^@;Wl#8$58q1DDLJKZ%-tJPksxXD(eN)WhWGcZP`OC}Uen%eU!C3(C zumAPR{Qahs@Uf%YG3wSXJO?~-wved?$30Js@bhpg7p9h1eBM8N`NM$_B;b) zGcso9WiCrEmN~P&5#o0$^S4j^6vp;u9H?7_?jpeJ_4YKw1^n ze|0{a4XlQ1cdy6t7WRTQ*O_Ms{nHutPbyW6uo-@Lu0mzu<`?rG!b_M+nXv4qX3yW! zBZN0Mkm{&}K0i%ui1V9f`)weHkb89AQ z1e2lnT!F%%V_6WQGelpL=a%$uve#QGRivWV2Xl1+ol+&Me5r%bo1a7{kdfKl_~~Ay zbv>}$-Xf`XgPKgKE>c_SrI0R9Tjp#jiujX^sAFgRXp5Mi7*8drD{R=JZ>_5K6fA0Y+_~8{ot9=Yn3BXD~2(;?%pZ?W8JrcFa zY4kJ#!~iFx5mb@vrw-q^=;$o6EtA~+QJvz${_j%z5_*Y$AdzqEpBo=l8!n4lU#hFVF(Wqrb^&n{rPW%9`N7X3*uFVreic5uI-3| z$``g<_m`5g2HduTV}8HXab9$^iP+`$uCDA%z+&?gKLKh~H!!qEN-j*n!ZSn_+wes_ z|IgU}hY)`xvf(vhS?fZu%e`H1*liwpY~pvRE(6Aa3i$$5*bDhu5w?=+r>+MhD)Ju|QP_C?fbq{6rX9TzS%>$DJQ1M-FrX*CVjB6M zN~pe`fuQXb|6>S5Re-%gehZAD^(5>HeZU*bLI6K3{Um|79^At<(KCnvqG7Y4-g9#S ztBuF0^=4`bd>Kmf=9R0XQ)bU;%{^y(%IK_^ZUsU=3_Ht-1VA8m%pD zmntFDB03GJC<|D+5Pv1$>m$)3Skwd2On&X$Fa5ccKJ3lFPbq8 z_O1t9xUn$7l%g5pN$R5+%h$L#&znd&V5xm4W7o(#5FUk0eu3BB(X8eflFBq^{26-s zqc}e&F=wB?zt20F}cc-jV!~iA6;50}#lFt)-rH+s**6sU!HvK?$8nVZL&7B(8JPHiSl!81o>&|y& z_Qe?hbg#|UF^2nYp+{pNe)Q?q*p3UCCQlOp`HN<{LBYqOQ(&^xbZ6 zPWpo5BSJ0t{v5)H!QJx)+7u671!^Tq8jM~ZT3j~EDD1M$ZIizfyu*B8!#&a|+zjcM zUb`MT1X3zk2LzRqnz|yX$RBSh03!}yb`W_Yu=iq-U4$jW9RYjhr(YKlaTG|cd`f*a zV##b$aQ|OXpY#LlPYInn-DL)#Sjf4@&Ql~;y(TT0%nbR*`-08HqcB9z37-vs7KnaB zC2!0HXKOPSyQiLkH}^Bn&I~G#DVTBgW?>sxLbbu9Cc;2uf|!;&7ZHfC>g8RWgrnnAx_wX`i?+|G#*35lzixVPSw|bY&SDNKD^d5FXaS&g--h|@S+2R~{yU{p5HUl&os?pcs&ptm z7ZJ3`l>l=)TRKT%w3fTZ_B;W0^(kXRNzx$rlQ3$o&-xRA zLW&ZUan0kAJrYMzJbf>5pQfmR>^|nPp|U73 zAgW=K@9WR^Ku$yydT%*@Kcr|~C!9q`Oxu6wR1l(L>E+*wMH>DFVamtnmu2`HOWI8# zV9`t9?z!!mdY`%Xq_Kn$j=CV~52c0H@y=__YG`x+%Rjud2uGMoFDqbl@8-N-5f|7! zyqP>v{Lt`T4CO8B3-aCVkq4L|UJ_gt`dFyae4A*fWFJKx7v^h3WslOu{zOvSrbODa z?eTCKa+$au?S7_#bTd75&k=^EzV8=&vDfyd-P?;E;a9d<;wkuAX{WeQpKT=?$-2pb z)tOhbQC`~UfEeWAtiewCn2t$GRu1PeBnGA;gmE8k`(dB5p-Fd)wZZ-lR(DF{?uMC@3Cy5byK7eKF$QYjtJY@7c5ea3 zeJ5?|)kaY%h;W%UZLQXl&RWe-yrWVqa|+t+-WOl?(4M7HNd(hG4hYPzUMHxphy4exxxzv%7Gt*(*m{{`@`SeFi0Qy0 z#`c9zV4xE!ljDr$;T$_f$VFdFng1TKf&0kkF#4hQB}XfjGC#lcmv_9y#eH-EnaUe7rvd zTKF}A>DKv7q2|6mm+zd)QPqf+;Ng-kZw?OMA)sfBwuZ5G2NVvJTK1;RewvBp3>6eL zDFQ~=YB+An8n&trMXObfB>mUKa!C}b5_GM9)O_D2uY85bh#t$In^>bio4T3aKna4= z0Dz4x8Y8qob<7*h$lA@ugGS-)XN~=~3;oUbv4~zLs{@2N?34S3Cir50*q-mrMOrr* zVk17ERK(#f=fj#F$X3~&W+&^zFWumvz18gBNh}iPhU`^D5~TE_9$i)uphO5trokOn z5i&_0F*6&5?#dZDpKQ>XEi&5>|~DoVKRy`H~`hEl<+&6HIEwUlKLB?hO`awzr{lUZGqqj`!{;y z+g3FJ(TL=kqG5h9n${iCMJ8r_fwgDnx5+m41EYaR4tavD(M*i`KSP;0YgPb72W}q+ z02D}(z2iU(;WPd(CH=YO2!y{{2r27=>>Jbw%s89{tmyu0X#B@ngJ0)$Nq%5jObd+4 zOT^vT?hzn;UcUCmUmT2BbUllq?Y-&ID0jEdJb4c>kZQIB0ZvWYSwuDq_@b771oZy~ zcX&70^EcG5cxeweTd*JhC$B|RuRZ?!_%VHOoKgMPa*=3%LQ^5NkF8Pff^AWaH1ZoA zlR10^fB)gUUZ=h=_gdsfessq3Arx@92mwbXN`+a6@MHX4WNy0K_0J+DEc_d^9cnUu zs?xtXae9k3cPd#wJ;<_d2O^^P2f%NsME-3IF|fnFT@`gV0)aq2F@#2}@}CuXe?A&M z5}j=R9V17-Ho&YYW&>C99U7;>*5|d&k_ntb(s;2?K8NZqFEhEHh{Tr|#-JO@+>8hO z{xk&}>@}Zd2Mg3vO-ijA0{|5^>~y_!78G}&uEb5w{P;F_w*vy z(YvQzMEF{RM6mGSp&QMw{R+Il6d3jIf|n6c|dO9QJS+p=Z5MUwdP{If&<8SonoDFX0O^!ByyO zsaplde;S1R^`w^Y(Mo2`KcG8=aT-0AxBPh}BlzW`h>JF!H70VnO7GV(Gyw2+)OKCp z#&BZh)$zS(l>V6|U%N?X3{7;*aJZ~^0=ZPdOxgc*IVXJ&Gn*vaTZZr|3Ho2-De6>( zQ4`1rmrq@VZ)QK_WxJ-Ox9H7*y%5{;oilgrusTj?kye$%YlB5CT+>WLDcoKS675(3 zZ>t6;-82po)$XLW{n{3CEc`XNxO2}lKvcA9)4GBDuceiNGh>bU(D&B{5Oac+^w6d! z3&xD8q)X^I$E$l5HkJU4!Oea=Z2_(!2A=CzF-u?VPgf6=Jx77o7O`v#_!NLui7JzN zV&d!qk6phusQk`k-|h2aX4PD+`>Yx=u2(Exa>RkBW7Py=;k$#E)n))olneL33@;rp3d<3U!nM$OyG)^=4@ep7X%N?D_IKd+$~`Xq zrIUv!oUqE_yK@3g%_49^)=-T{3wKLjdkIv;G$HU23c5(mimg&`_Cgn31_v8#&hn55 z7)A2NJw9q%0>g>3Xd0dciz_sZ7qq-}zpr0IF-l5cv+Q~CntbcHx8g_dQXCj+%Zkw& z6OKQxs*^ozdD!Pt^y{IH0Q6(SrOs?!eG70CdC1s#WHmL@Ov1UDVcy>+a8KxY@rK>_ zA7Pb3>x&)s7d8{0F1%(BvtDlSvu@%vDY$iA4cJn)YWu%dz|kJ+ zMfbxmaq6sB`LAsTM&L*F4WBIWFwd3U*VS^|g2W86R$0tXuI!HSq(lMMfS$e~lwpt5 zF+-2N0^jSvFlt7?4}pC#C|2XN73s#bg}CXwgN%Q-SZZ1fG_W32t2dk{_fFNQ`J}dW zuK40^do;I@zRr3zYpo|n;r`|z^pt=}Q@KE$rWkZRVgZ zDRpDq!KIQ1psKT8b%sm|_`;IgN~Lf*wPW_h$yy{;N8{&em{&gFGrg^_ zt=dp-vXF{rURB5i7e;C};q&n}P3~9$iS@6*0f0{u7%pbxs8#dL#6P1cdcu=-d8eVt zA~hxXObMzmHVeYwXeVf<-mIA5&94TwOhAjOHG61jgBZ?>Y+gO`O+`twTw%pI1@b`= zs%F%H7iiFC_YJ)we{UQW9UuL*5)Nxl`p{h-6T{=_<9MT|P@6IlFcVzQeV1z^)2c0p z05l32#9}OMt}Rny+ZKqKAbo3KexW8o0s|l2y^@iZ^tExp+F*p2skinMr|0v=aKG@DNNF7K)c6`Y{S?y$Ts zQ1D`C)tF3+P=EBr(V73)E@aSi{w&4^Lm_lm%VIZq>|#$q_*_N_)_w5p^x6OvJi18) zu~3XJNRdgQ3!BW(>@eTCG*QkcJ^|XBPFl_plSN;euk<+3=^uK5uTj(X-%Q|h2O?SN z(?R79B(0dN*M1>1Xr!AMB%PTrAZ~E~e?0&58&^u9Bl21-+tmR(lXsIpbRo^#k5Iyxu_eJKFka$Sk=uFSNG)&m0~r8IX^c{4RETIez`} zwYNh}4qJP6XE}UDU)b#~a|XNZFnr{yK5j(<#vc2^);x~Tp6?^%q@+S&XBX@F24(0* zy53LF#-ZJXOx__BbWAd6*+ciQGp_)a&K_Gx>UGho=#^Yd4A(<;hkAX$a436*83Lg? zFkD1C&U9-s(-%_gqzT~(SAw=+YIoYC06q0#A~0F8P&Y;}+m8qF)Dy;BG>Vn& z=}KC`MCLQ^VSnPUYLt0IJsL`(-d1KIpn)IkC`b50|Le={g4DkAK*C0!SRMyYWh`Je0G6U`q2`^{O1D`Lz*xjvp*ee2b_;x2p^3q4N5Z>EPOXfvKwIp}bFSj+;h=Pvd9Z>2SBY^17?}eMqJG94O3Cb)W-uI#$fDyybaP%sS7oQ8rPV~3_*t89vtLKElt$Qq zK8uFhPt;fioTA;X&K!@8=hc}m`g*RD)y+Y&P-<@NhHZCU*6|^LS-U##6F=_EXMp!} za)KMuK4g|={Cji(?pMT`iyofW+>GnIt<$r81a@Mou<>JAF{1dr49_5jF}|BaJprp- zYZ88`(Iq5b<|iPTO)~g2a`Mvo{$K9d?fCa88L3e}wWo({(sXO*jlEa4Urc^Uz59odV$SW+F=5KgxavH=)xHH-+fqc1DVo~HF<>qJA z$6LmDA3!A{{**}WhFvzFEO3(|AnO+duMx)U6M+G>nT;K5{O8L0NzEJ`E`aUXk9Z{8WGGZZ%1gJZMs zvW^}-;6f$=6ecMd;~p$Lo0WrtSu*q&X8Q96s1dJ|`cfA0a2li$XeRZD5D`8+Y29r# zauHvzM-i@lE0k2x6)G{78@1k<^gx_+lI=_0A<+#@sugnyMh!Shs&-RTA50{ll}S3y zUbdk<`nUwmq_HEX*?m#~17p*!aexS_{7QcahlI}&c(E0sN3nL$B+~;-8NDL#*7*t@K5_ijs;WxcB4zgany8!gkI;BdV6`Ihi*I&l(&FcNaL zEKjC?9D?ftQ}Ia{k!ufTi3>5p$=9Wwz$2Hjl-&xM@JG^hMSkCoWg|^`S7_XaJC{6D zBe_@<$2;4Rx3sJ|yL$Q^#QFBEMhZy{#J*<+G5+6>R==+I!GqrTqFVaN8+8~v7!F*4 z@f8;;)tHSUQ7Al$Oi6G-BUuK_JRAz1T<;` z9OBxWjY9JpnSY*}mH<9h@y_~>6FH#M=_sh#Cp<^C|62BtTQC--QuJ$AL4QtZNSmJ( z*^c{sFYLqxV&;5RYU-HH?z-IBWWz$d!&a<*%@j!v;}-N}XsQbb>Uf>@ zBYraPC}`nMufs%P%^ooUCaBD5?S382QW7@?&om_9%)~Th91wc!K?xnLvaS>NJzrwN8xpV}u>M%)Vq^p(Dr6JGlkNvN$vrv{$#)b$*j$rl^0(YB3_$n9xM{^@V8$ z;-!-g;#ZR+xDiam#3U}q56>Gef7rQXHNBTbv{ZYFnBIa}EtQ=-x}OW~wPl;40tvqF z6430XFH|l@mxGD=Z!ds&lSP^4486M8N&2+iy3l8>?o|uE8P0V@5DkPwtk?Uvu)O7u zhPRZDKj>d2I1aIJSy$f=& zolqV-d#A-8&G-Gq8xr&6g=g+wd##6<&$q__C=rpq?e4VNDW#C4@y|1^Hx??qyr<4G zwt)ANxAw5FqJ(+)b-M6D0_0a^b!ukl>hN-cmq$U@q`R%OO3>v>5iR*rBN@tpk33?F~Nqz8WFd{GTaq>z8ec--Z!k` zpdmXR9Q?^3UTQYIe(hP6c{x?JzH1+NV9?mFrmtItA1Qz366mhQw__Zv2YeO1bhAt$v-@nT*^c|o+%Tb+3fiYRhYbfdLzs6I@Z9`uzKxAHzh=8 zs(PoXMdkQPOnS{oO^I$hwya;B-#%zH+9+wz>5S08ftqWbPqGw{Cj2jcA#(c+CG%u6 z8^2a-ISl!!I1$d zTA&3S{IP&fyVm?57d5`rSdW%kbEjn$#AjN7n6m5vRyru_kKcDJ#NS!fsyi2tNPE^% zbkEc7+C6_mIhj-j^d#X=Lv6lbA!e`s4&BjKK*W1)qaRd){Bn)MYWFeqhtI*2t_|VK z@KNmcyy66`I;5ymaLMrW>u-z#A0U*G?7{I3m1{=gQKR&}rjsKJMPYI9vHq?OTPQz& z#302YO6aJ&ua&IzhRY?>s(>Xn`{_(W@nf&J9!59kM!b^*sw9*$^g|Q#_R-?2^jt|( zX3?i@#YW7g_4h{7ZT1&;d{&DvXe6JLg)YqmUvT1MvbkeAHy^1UM!(ugT%jGrtFx1n z@O(F^7!09{K7-l16H^j5mC3y`@tmt*{u-1pV-d&s3{kS7RhTp%Jz$ciF0i=LJVgJ( z3uUc<7GL(Pd571a>Tv%n-BJjzsI$Hg~VZ+`SHm^kxUk&MX3~v z)kf#)3j`~V*0>4Ni6ppS9dJn{h_558t_}O2)J~N=X=_3B2=6JJjo)T6odwYidrTZr z(G9AXO^UF2d^9whCgo{t*pWggbqvJOu(NdldvSdcS@Lp0xngSPB+OI{Y-O3=51Y_DYy!r~$nJh<#%u*&uVOr%vKzw9WIJ3>E8f~`a zedkm9A<_KCO6^YUC^uilpc362Xi$=|!;`^nQz$1+H~QR~wU$F;){4&>{*gEUm|9h3 zhC8`ho(UrB=PmjF%V`P`=~QKxAdt;aYN`U?_oZyOmxcp*q8rya4LepSR1pvnZ=XT` z>VnOZ(pWjfaH4!fLf z492m<(}iXpCOj3v+Q-2~dX3EvE&AsR#eO5U#Z9^X(zHmdD{P`#`iuLnM;JZvM846z z@se~-tU_P%Fby&|Q5eT@6lU#*&)!YS6V#K4D)F@6(8N+Lp9|0Ps;V*Ua=MCuHOu*9 z@#iSGq*ELmjuuu2)@yJoGDm%#lME*DoXlgfFN(%$pI|qm8lmy+&JdY5B^F&AJ)1-d z#881A8VnYhJ0%{%EzzhKecbYEdRP3rIp2(*8c2BXnh7zP_5vty<8Us-xmfHox_SfVHEe;|1X&mnHM;fFT``AOGN< zrK90OJs`?7l@amO*cMTXty1DQzTc}FwCP|B87H|8U=QA#hZg<%re1(4sY|UXN9F$9 zj9Pn&!q6Ht+x?ZtSihNC7s?dwZW2^x?0J4(IOnWOZKn(0XY9Fm1x81~CXBk+Mc2O} zjuZ-^X3mF>I=~>1h>n;$_2om%&=AA$7=z2Ecx@k&>zyu;^X1#y6hFJ4Ha$4KHX&aENB-*;nPQ!5ZJ8R<0tKTo$xxvLE|6j_#}Aa0QY|@$@lP(QkSAThV4b zVe&srcZUtAUca!_oFRrmuc6D)V|o3Ga0O2^zu2PeOU61Kk;V3!2xHxsblWt{G=xUUM*J~%K{L$qk1ab8<61A-eAj@|{MR^ouHd8Y1rG~-95y$FPD2~9U+>eOcp z>O$WRm(S2W%1~M9v;$=5Zncv9%Xqs}@k*S1h4y9z!(oEG!gk*%Rny2t=gi>4n4@aY zZFT-x7nmbmu)maKz*+1#C^emoRT0db4g7c|NrdCzl2Yd##`SEKznN(;=3n?2CB^ z(H)v^Dy~~}yB3P18JftX2$M_~_MCp(dJ<(iUBik}IRcL|-P*%Vnof-qORH|DwTl@0 z64h+yo<&AEiPvD2GJ=hO+d${bs63H5r&hOVW$#z>ie1aH5Z}#VIo7bU38$TD1@WKl z=0R=0tu^*8mhr+b*G^#^di)xN!O2#?gz=m(IK!EF>+HU2s9xVOD92Sfy{k7J^i4IW zF=#K2VQam}ROJk!GrBgze7=6;_~{OES~`)Wh{R1siY zi7S|POI-Yp!epH9a@q@Y zJotWyKE_1;C$Fa*w$f-h9TZb4guW^sYt(#27v~ua^J1edfU%*FD5%OlSfr^d0*~y( zTKY=H9Fg-DSq!(+{z}8b{L~WB=7P;)rm8lbT3U6#EEo_eu{FAL*XW+0cx8mGROvIf z#l!#A6@#=53srE38V%(0?$dg1luOOgoLU3zMua@!uDVEM&Pac9={|T8DFC1kz5Di+ z_h?b9Ya54y=^)`yHj}I}#}MClO<5eaxCxs78^;08`VwfR^0NG%KSB_A0Y~XQgShdl zRrRx8S1haZ$qr_{;c}e`9Fw>Pt(KXYi-z*hcQxuPDF!x02b0|(v}ZmvX$PHWHroqY z4UOw7&e-)pe{V|7kA!f1`+Q>3i*?!80ppV|@>=;14~bQQe) zTiR5L%d3%Sp2Ag%nl0u*HR!a7cqv#531VI{MLRaD7w`7EK>!@TgDN ztS573aA1J00Wn_^6sJkC^tM|xoo4Np6VF@s!kbdhA?|aYC7K?mVk?x=sGf|AHGwEK z@6hZ1#m@ivvRa^dAOxYj$m$cpqiIBhaLA9b_DQTXxMk&lM* z`SUQK-Hs)nTX>JF!{-l^$As{ef|VArm2;om$7y$| zFFiPdg}cHe(pv7;z0kv#QxIWTvZ%!Z$l8ZUsw^ZRKcB9Tm_|*&boQ}X7x1&Y7hk7a z4*Gp5XTSNNhD&?gdud+O{;Le^-$0nLGP6K!Ia5g!+uIPwa9WNe84#9oPh(+Nt~Q7Z zu3mMx%kNmUk_eY3pa=ySX}dPY1q6eP`(7++maR`|{zJa?E*4&jy1tv@gME-$*4De~ za%+rZZ0^5&C~JOi3B3ughBUNRKPtDf$k_)c+AQ)>t zEexO-ZgH?{J4YvZ%dvH*hm6ZKIcF`W<90|YRm@RD;4R7t? zQ9R$)M};z2mza*{^j`GTP_dAy7QO$vRY*)olgFR6mefZI5#tiA5;tEcz_oUEZX4 zj;^bc9-6~1xrT@(ux7R))H_*Rw?W#Zy`7Bwg3w@(c0G1;&F3ImGmcv%W8~kW>uVsM zBOS>2jLIAL6++1Ob>P;3S0}=H&tiNDxZOyfNhro+8FiAJy(s(sHSWiv2Zk#$80?SEj%}=<1vBTBKnTRdKqiueEs# zk;Qz=ay42zx;Vj03Cnsj45LzKiuk-OparN(GdD-5s=Tfp4Y(Q^ZN`w_{lf+Lh(Ul@ z&+Gjg}{vT^QFB{@;!U3_U2&&-o;k>gFb_2$d1Ru!Wgto zz-oWmGN;^RlAkF{W_4%v=?M(;CNPGb%~JBf0uT1&z3<%IxI)D#JG@N_C&TyOB%R09 z(@gH&C6nAc$aAy4Rc6F`;W-r2C7lQYDg=VPW<90-yiGqPzWOg455&>GuI3BLCo;R7 zFO6GIC9OUh77|Q<0`_fm6QJebz zK>bTx#D@n(?hySBoau^9FQFh*>93$`@tI09(q$#aSsJKC_uMnjzI#E@_0jeGNOn2c z&rnEdG^P5mn?GLNSyVwhlh{f-!rBmrXU~4a3;QPC?TB4X4#|a-6(SMve3-PH zA!h*R)V}UOq>>D%L|324c(-yoeZpkA@M-#bNSdb!T^LckPC10kE=N`b)3UECEJbWq zx*=2UPTgkrl1i=0I)AR(UgLOkAa@TRojho>kdO5mS%+T*ov>Fnk{vc=4M(aK4(YVFu9-;ti(21oe-f(9 zcbaE5%hY6w-_2|s(WyL^WFy8EL<7F)-g#FPQpwKK(K^=`;#(2@a|23~Mc)kY6$@#> zmFj@J$l&f`7yYIu>WAYp^8uGt#`_!%t$BhM>$lHRlVW4*gH%0=7z zwkA^rdXCN^FLdTi8;z0A_UpW*t*f^j`{dMUzF>rbe!giz+R&(N#8JO<_t|!xwbb^5 zyq3tf^@MpB-E; zokoE(wqAN+%Yps78 zQNBri#HC$2b5`9~9wJn{*OVK3r zogDDXMNXA|l9-MZUe7~!c!7jxzOQGQX`T&+7%dV}X{F@|g`lg&(rS}%MeCTg$J=6N zfTJ926cKEtaK>ZDsd%DQ>%^{K;e56`Dm^{DB@7Hp3RlLfHMOpEn?{$<7QyiDR?bw` zbH2L{3DYC=5l~&o6*B2lEpe*RY_L{X>G(oNfDzWXU7y}59m`&-Cmx4PcSdry%8L&o`O}e*MQ|}me|ZV)w8s{5Dmu+ z>GVmPt8M!}qJf12c9Y{W<|J znXL##U=s{(Z0_T0Mv_aDF1H3uF!(kkXuRV)s-cUVV{HFDBzyO6(V$mUZc)tsWV8N#QQnAJrujGK8eJXJa;yua93qIxq}X3%or$a zn!;@Up&wn&R-$rI#2xsInynJHIY7tEtp+A>_?Sljbys%DO zX&=83Gw~aY&}-Bvzr2eUJ!qRl!Q-Hp!kt88Q>u0XC}fCThkCAJQ8dyvRO%vEsSu)A zFv9Qhz^OUJA>eno&}nb~bh;)#u3yeaQ{aRMjYKHCH0mDpP4ibYo*y-MG@+_D4z%9_ z3)J*?SC~x~YrkBbfD4&a# z|Iy)(3PyH6v&Cc9(DhMI_;dT#;mCs@g_BO?OfPP-*h?8$p~aykI0~HF z1aVgbecqMBrD{J7Ja_N+ZtFT7b&~u+`co}Y7#EJEz2=0M;0u(iegQB@WOS&R5!0}k zj8krOO=;J=WfGtbB{C*y%Bg)vl{^vS-Nt}znl7=y<_WIF$>6&W=r3IfvG0zV+l@d68Jwc2m*i z?K@LTB$Y;z&e9UicdZky(BhL5L`yvj&1)2x3z^<>jok8kW*pJq_8^Ckggb<1g>8hK ze>%7q98F(D-TQ*WrHn+@nl-AmNJ%sz=Z8WdeQ=*z(C79VEk^cbib}gM87i^E{&can zOWVpotde(!1wp>Z^IuWfGVZ&AZ9_4=+g*E5D!#dy2^z%MZe$j29hoGK;;_C9IK4iv z2l|!%nv)sFahnv&Nuwd{0%kX&$d$y1=qBowJ+6b~f=RuZPkV8tU*Gf=KU3l^($3)r zPp7q;2$i<-#mj8vn)ur(RU)GHyvV0rS)wk(vOextYuFvv^8%|kHYf_z6?*Z<n?F zb}1K{E~4*!ML*-hoC;_>t!v1e<<2P5`2Db6cpD@IxrN6VZHB7 z9DQC1i0Mm&creE$>n*2pPB>Afa)ktw`3AT1?OLwc)NRk*z&gLenc}pUo7c;o;Mtmj2GE zU^J?JQ89NOb_1PuY(I93-6@)@(`MppI;>12fHoz1C2WfJmT8AZko-R?{Qt!oCJ$er z!37C_=grTy58*!|Za<33K7)YwObkc%;)Si>HJs2ie!yxcS)$xOJDxE8g2m`l6&E4= z+45zlJ=Trm?o7T)$PViaT^vg^=5`HY9Nc|Q(El;^7En>WYx}<-C?Sm?BHb;ZbazX4 zNP~3e5F!H7Esb<{cX!7SLnAqKH^0sI)cc;}dH?^lX0c#g?x{Wdxu5I)T-S{$`dG=@ z7e&)-4S6^uZ2qvsW&`=9#+oIa8Q<3+ntU-J-_vl+!4J0TUV0wj=n$YP3k0yB3<_D+ zgq6=+P#Csjz43*Dm%UqtRss9+bh8iNmp&ZZ^8-#EZjTLY2oi zSNX*ZC!$FPNP2XmsgG(tsQXt7wnXWH1YH_kN*jU}cw#ljodMy)!zD|tN0p%8#uoZ~ zvliWTYI`8@qFa0j@OG7qFxmeO{Mz9Vx2({5#J1U!{fKTWUd{GLO86O~C!(+VK*h7} zgcSxziG$5~pf{)6!&k z+;6;yLVu0`8RN;eg=^DyW&)Op_ANvakt5nCy*z7IE&9J&vUec@DKaonsF{y?i#?_X2n^WO7v z={ZB++k2$Ea^{0%cyH+ij&dWQRIUK3Y{Obz6B!%KUeggRa*UW}F?Hkim4SO??w}^o z0Vp$Dk@m`dlbPcM0gd8O>#Z!B!SUwal9RF58^+Qb9^#Pj1P&cj*L5@)uT?ANk0m#N z4s)jvi~~q9QBP9Lzu(%>WZI4{UTqSO7mCL_V{$1|Yg9SLG6K0KKyN+bw`8;#bCGDY zJB!DXf%$ZKzy-nnR7w4iqHaFS>+WEj7zN>*0S*e=2R^UF>nao|cnZ(IJw`zBy|*=Q z?*ZtA^tksU7And7ukD#M2i)Ctq~C)K!ruG(BOM>mOv*7SWz6ymn#@dTew7NWb5RNIoHtaIhBFA9*uc zh<_updr)-;cMn?(^?SX>Dcdl}^ zGvV8UQo*yg-;}!Y%AQE;4MRL;w)g&` zC{WHUc&7YR!c?J0*<#HIJ9*9r+Dv|h$0^6&egb>B+Q^sLCm#;zNBX zaIp>CkB%a{!tuo)lWP}nBcNzRyg1lY!T8*9^%605UPBn+z2ZUdBoAmv%H?+oCYtPY z%qN?E0hu@Aq=n8#stuUvDv{;&7K2i}sl|pX-NC0_;kdolk#sy<;X_Kxww>$uhcPZz zLvO~dQow3fB_aNMa|i|Rld@mZS16IZ5`OkAZy$1XwCcAYKa`n2WD+<%0xP;|d)SUc zY&rYhxp7$qZ(%hC9lGt%wRLjV&l*I?TX0Qi=lr}(5o7Q7>6*J27SD~^1qvJWE^bixxw4ON4CSk%SQLuhe!I- zj@qWM;wo-9%f+VH6;H*3dA^mx^hqvrPEFc{f|ODLuc+ij z)3=oJF7zp@X>ZzmPhDwi_*h94sO!!F9 zW8f9txLV(a`ZN$N+&PnnKP9UA63eBF^$bl=+ISrY9^4aIYN>xMOsn1wRx>+C?zzMP zG~)>en;5d0OE#yG8By3PWBcj((64$Ktt!N1a1M2VnR}Rq7#~)Y_lSe^ht!I9)KWoF zJQJenmgoJ=aQv&oeRZSNy0^Zor?yTdd;hfatwcW~v?*OKYF5slR@cI;GSeP4%_XXX zZ_WH@wWwi_LtR{3io~VVe0BVyN8@qNhK4 zhr>DuoZ6pz_T>rahggGN^j9UE{yvGdC?VWEydTl4qu233!{lK+FJ1#Shwi8s9B24E zQi;|3q^2W!^5LRsgQgKZbXPI7JDV3QHLi8hXty%9l9H&Yr{{arI5Wxxgqf$@=zgG0mk80pqm^2-Y?X`Ym6DRrdIa;7SJomIsI+hktNZsf zdD;x6z?hwt0Fts&rl@mPjNU6onU)#t9QBug|l9LkDfueVDv`lBep zu}Ox#&JR~fm8YHFrQc{an|ImGbzsJ%LS)pH<9k56 zk*J}^^IT!R?uK}i4>8@Q5cp;Il|0z*Skh}pWFBjCKEzh&x}M)ulgqtX9?I3;S^FB_ zd^O>aA$2qx`FtPcu+T!NJY>8;sHE6-DFzVL5JDCp^M3)=T- zrO#4cfBGvjoDp*t7~0*ny)4~vzDGKKdme-FXgQrB31__BS|Df`^kx@o1g_`cbtZ#p zggbI|Pv$5eQgK%pO1Jl)z|>WV`na~1O*c`a#3UKt9D%JG>xWDKz@&spSTq}7sxI2x zdj9A>;1E18QzolkW1kZ8hF8JOJ2n#7!;N(w%(+P1`sxp1ZdeR7`AdgR?rdlnH;&a! zKayM3e4V#m8K}lzy1>VGHS%tY7P=-kCL$~z6&y!ek>88gDAXo(>OM;?PDn5FLe`aj` zI|{~xR;~KLV~xz$Do~tt9wv-L#5qPMW@;Z^iQ78(7$=olc*MlvN6h z`^bO3?Bw*d&;Eh<1EI=tStl%wXMf#CtM*Xpev{z$c@q2lMY_ni&YVu%spOQA(zZ)BXtB_7UbmF*e{3wvCqFc`*V=+a^1mkkIK z8%y^uHx0{9S`Y3i#A}8onw9W*H$&zq^=GuGs>|IVsP&%biBC$s>32S_6A?<8>&XR_ zt;1X%12jTDN&-#x*x&t_(ChUEbU;v*0#G~S^|<|sax2!I$?9fy-x;0=%^*yQ3Y^k$ z)hq$Nd@ZPH%3`pPCJ+SDJHkI2P^$z4uZW-O#_!-GBmz-$cYda#3=v~SX!7l$VyBe`Armfne z%`lg~?`h>~5pas#Nvs?dCspipaMA#c(19#DLqhnqjjjO|3jN>In`}jnDr?Y~=Em&v z6N=4?c8WoXt*`#W5cO-7ARsP5HJ z$z(tn(!L6o=K<8P6d*JDlsPY9PoW`e5pAa!fzgdqaa{9@ny;nr;NMOy0x8imlT+Qr- zPc;JCJQMIGN3-5R8SqYzB0-qjmhz|Rx^(C~gK_k<&japD7>M7^y}g>@EZ+KDj{ic`@nOYFdq!8?b|w5njfapL{G(>vYk6sqo@UW5DSrhywN{A?MvVd zzH0>1u~hL!0o{VVVKAR{i$dvpRTQrUWl9PP%+G&c0O}CyKzCPZedu+$19MMJeIF>? z3oB-0Orf$6T;?dgGPOtpAO(wU+nS<^Q5AO0kRpdTkp=^EE(-x6^N~(oqBO0}LELMx zNnw+akNj}T&Td*AKwG#z_N6Ly(3L}n?mfOfARw4_4#rGE!8Yiodd=QU79+`Vp#ZH< z6{~@(Xnfld)!xU4Lc|i2e5I$xFkUY>cwfl$$?Ssfk2Y_R)lwNXngsJsqL z7f#f_OcmDO0-dRve%_^I^h`?}e1%xWfvk_ihT*-pT-aD-F*QU#qN{`#2>?2g$Y?|o zLr1H{0b>U2J>I1~KfQmKy_###(DSBtVn|I>Pal5yzVHP*JG;4hZO$H~Mb+Kenu=Zd zf}AjIIbVbw4zQRs-=Iw<#P!v9rdwhVkCF1_-P!@ABoHz|WaZ$~yFoIdA@(cn zHG1%RFYD{`#&u&{+mkbm57G}joWQ@ueXXn?kEesNlk*6{>mTLJ-!{$y z#N_EwE(_l2L81>+|YTO|e_^t}%F0H^+1cYBM06Z;p zuI@!eF~FvI7w^a@5122}jVeQ!gXFd$j{QQ2B_cFX9|4Wsa2n0+EE`<>TYp6BaUe>EETDYwa_O$qWMAUS=|q^-;ipR7TyxvBiOfYcQTmhElZ zt9hG88rv5@ZSf%YjP04&=35?ZjF~jP@wqa9iK{UyAfDXc8ZDzG95>vipvOcdz9>tkMA^8AmcRN65quJ#UYiCL`l3}&( zrc^@)4s|`diPiPrMj6#4qaJxn{*Gx?@C#mKOsr_g_P8=-c7Ntat<8`dJGVgR;kWk& zUaNM_JgVXJl009Q72V=C<-;q*!_JmXKXaCp69Nj1l}(w~BwJ?xs5JKWR}N&RSv?Pz zalQidFc8*p6U&!yLVjbuy-I(l!1{3_HQ2S}zL1eL_FNe}Zf&k=3r67kf`$F9GAw}B z0cWoN+&iC<*{~;K)z5uUU;_`DX98BVpM-WUYW%H}z1N?fjU;gdS$>%iB0M9YW_p1-snOI^-s9TC24|_xqAn&^ zYG6)vm}vQy{&G%}sKF}O`umkfu{e=cqYvl%WGoK*y{xICWg#xdeY(fHM)9)y+i&Lf zYf~C3Lxt&t>B>Q*?sLV5pLG_XfZML(^Rc~PBm+V{!qtdwtDQ6slNQ6I$;hNt*pgR? zwwwmd0=T(s`)y=&kQXI4x7gB>QTl`CXL%$0S%Yo3%1IugI_fnKYyF90?=lp zev;+3K!VySE=@dTG2?^(SA77bjQHYU+P};CXngmpwYY4=ta7@yVt=hOAc{iQ({~$b zR6BEaCYFki^)O`bGs0Ls79}{L())2jfY#Nx&EUaBuSp(uPlqOE$VlU}8>0uby!l6D z$~k86{yFPB)xi?YOzbBHq*7mfREndA$)@VPdS?|}Mb;v$kO%DtapFpMI8Y*O>c^8y z$%%^Qo#RW}ftCz&KduP@V`b8bY)Kkw^mAz0_(Yzu_#zI_ZAyVpqEK=-|HWcTC7VQ1 zxycZ9RzR(i@>a!Yzo)n%t+`fnnznyXt<-2f#EnINS7s5V7j#llU*?ireBsQ1Zz1i+ z5(|}3rg#P}ysr!)WS>@5v$3*nm+^kn!`#QRpI7vDB{SG8`%WqH*qknezixJZt7y1% z%aN_*{NbwLYR(@y9$p6hN=5;Wq3XsjvKrU&AbHL)?K^`^TJe&+O__L^anR?zT5Xfj zD9Il<157~CGci2)nE0#cRfom#@^*lMW8E^NPJK@Ib)l5J>EFfRwo8#`t!nGtzbkA` z^H=+6?g%d^T7yAJ3Aa3SB@}r};&+7CO+>I-(q#a#3T$pPFMMtUCKaxE%t2wX9Tcz^9z>EOWa>E%1=3sQ0-E?!Lr6_7RpRJLuge4{ z$i*3VH1h&eA$y%Kv!^RshjEZng=JIIfr$}L!i}E%G&b`{u#xPFhX-*=5rvDl2rO0# zO+{-({7w6Qb)YI?(?y(LYHCEPXno1ub#qO3)(Sqw;5*Kxo~uZhkw6oo z;Hzf&-b05O4=E9EVt)w`HUp##Yz!x{YZ8B)*Vb5zSK`qnV$j46fbUEWg=bm-8XoyV zM$C72(%e4|+FRMg&hjuxMG#DL&*Ja(Zj(!6UiU{+jlT2B>GVn>HdXl!rQXm$)?0De zMH4HWv57m5HtkzJ&DjajRAai>0UWIk-$LPG;aBC07@UiKVzT`9mLa0i^6!fzle9e# zNN*zq0?4q!u_e28u8G%OgiTwCUR*#|?|^Hw$WgOcmTb}lNf_>^>?dZp94_k>+sUcUvy$=NgxSCi7aW)JvR!|Tjv9nc!`B6wKCx- zcl&@G2x?lyVPIFeIVJzD*Se2+QL!WE0&{#^9IDHEZO7ku;O=@9mp*5H;pH^NW^~l<9J}{ zojSq&N1~3RY7XSj=)WU^b|G!`#0UXMcoVe&?$GcI6?B+Z5gtGFz zmcug5^4`$`eD+pYm`kSXw1U$2M5w@fEdc8U#OUsM;4(1E+HZml1=>W|Xh@4hO>{9; zKJ4upMmq^C7R^0@m<{2_U}XZd_9}laoCIftz_;J=hWyl+LQA|fQRy`_U$1I@CiHOf zqvV3%_%ZM}i-gx7ENGZ#{)iK#FHZW{IFP}c4$=1;6VzZJ4qL1*j_-RfETRTK8Y0kh z&>%J(A{!qUeR%-xU}JF%h@+)j1Q&yQeW5o!!_A>E3U0>tJzO4dL`Op2iyX1j`aMO4 zeNKCJ=d%j&Sl?uj{Pn{Oit=>W3igA)6cC zTYQ;z&jAFC4k*3Eo#2Gdz1*%|xiLhpaKQD@$!3u~xG+NX?Rg;o zLpuDp%e46xVHMa=(|TqoMwB*pkeEGY7{*htGvHOW%jII7uG}K)>krVnFZCCW{_%hS zI}_p6Emh#)Ayr{Ee$MlGQr;l3e;#Hgj>AH^`B3f<=HzVHLbBjS0Wag2_B7%=+l%fOd@wV@M) z6$5|osCBAObdeV|*oxu#*|M>Urjsw6*qXfl;%I!^#GifAk6}`}E0lYsYEVKMalJWi ztwtG|^-3LEBfwj?wi4=4v+QmShLjKnX!yb&)+Ul?_(J@4?*ig>mAEDFymki&$}}X7q{bs6nb7a?Ol_8%KqsqgdwXHdRxa*_3{2oj}iiKt?gun}Lbh~d>#i|3=9OSjW60kwjk<&HQEEavSQkooC} zN(ttm*L+{y!Va4Wl{z?TNEiF`E1E?uByqXJx|F-Wqf=i@iyBV?U!)#OtZ~Epx4bs4 z%alJbMO(*kAIBblmB8OVCH00Ob_Ej3{=gnOti!teJGtMcM*fWdHphxfI_H)7hPPES zKQnx*BX)f92a~)#KwJhyKqYrabBhbf-b^sCIc9*eTSo)uzohS-TbAg01112=X#m`3 zg@`(h->u6`4R;-!>&lGPf-2Hoj258j6_@TN+$@3~GK?!ME@3!5_r6ug{UC`Qh>wda zctcwhvj_hpM2Kg9E<2^k`;Z1O`x;~!iiKA&!sE72ABKvFUnKyXnq1A{LS}F^j*7RV)s-~LX)+Pr z@?}kmyEH^Kbfcqtu;CC`|DFU;mOLBwpj@P2QhAXxrf&1jO{<&Z2cSVR$W&b*bxaDc zjajI(%5Hc8%UQZPRv`(pLMgvr=EbCSq&C+s&6z4Sq?IeA%3M+{nWL6gD~Y{-8IG_S z#i#SdFn)2P`4-K1)@B{vs~WwYHRn@iD&Gf?^CDOExW7%i;cnU~cCB}eXg3|u;we1a zUQpFC9!_qQ2grf+UI6wkVl+wOw~;dkZ{sFyId(0@mfMa3-PR}CH)l(+!j)tvfAecb ziP%bI;zyfu%X`z@mRf0S2J-zWQ@nZTBX|4R#D;~U-s?*OX7`Md2T?mc?RaDLrCc-i z7ICr{mvM8akKi0q8X= z6yINV1z}1k#~S&yMQef@9xw0f?4UI=N@?BRmx2sVM9PyOf@6m>(9Fxz{b0m8i{Um` z>&xJP^=D6e2K4uE_IVjk=J*c)Di^IU_*QS)@Z!*zug9Ija+Gl?kvQl3_65I#7Bb+sl1k8wKy19cQE_P8 zKoXgn@c#Luc|$Tr#Pqg=*JGX@9UnxJo}-pw|Ak_VbREa5tPfpGX1;Q8+hnQ6x^nO6 zZc(fM?pDHgSTG1X|M$tdIIN9U*BfN9Swg@(;v!+$VO)Q9 zqCJLdAXgFN=-&S#@)Z(!+6hQ)$iR*PY+1F>4Zs;T6A5Xr$2~^BVa|{YV3T$71tMMI zB_m<4s{}j%{B`lMSW50a><$dN{(5h?;fMY{=IAFBMbq-DEwZ5HM3sirnauu}-0IYh zL&1@;2(R4HinS*82Tb&Xd#b)WgnC(3Go`Oyks>)DL% zcDemT3pCeGT?E1tbP8dAr2O8R7V8f}KlkUn^KeYKYi&q!N0Q^m*K-D@3)zhX(irF8 zqQDE-6&KID%ov!}D|T1u@qGJa>(3D+uv)z;wNiNPAeyh5-Sm#jUK_}5V-!W+U=MC? z$V_2ZyIQPs5=SmAv+awc$iz9fMd7eXh!P#HK@E;Cj%#@?Ze~{sFvqa$GQQjyH5v~2*f47w)ZWUcpW8qB9`?I{9hT;6E9QcI**c_!86qhZH9V;tk)_Wh%|4EMF zhasxzh)#sN?7ZX@b!4`r-@FtDe>Ufe?i;Rjss@Cgj9S%l7km79!5eF6s5HD$yP!Cm zxJ#P((A)gG(Q~I}SPzFc?~SV7@p#I-r2$~o0#V;vC+J0rX8Om2VoMFy#82>_<^?-@ zw=H~R;oxi*EM5=BDXSt7ZhEQbD`)<~?u&=4@%>C^wU6ZZ9G+Hr*jS5Y+c^riu%G?$ z)=h-)FO#S5PawH6Hdof0(}hhIAx(=h)df8K>6W>s@U#t)9;CaUa`Yqy$9s2Soexgy z>3Y?T^fZciZQKpV+k5x4g{bD0LWcCOx{@^hUZiuc08_7 zQLIoW5xmI705$;e`Rm`4f*WucbyQ5X-rkaAt*Rr`)S(KavtMv)-F(+`i5=Fg7c%E1 zcXb31XjCFz5{e%<*xI;V8rm~@tW=U+ff$a!-f_ah4zk2UOijj(GuPJxdS>T+ao2s~ zkZs@4w-1otW_HdORppw*J?ix_;a>tuK*o?TolOtxW#3NCtnaR7blUnh3$0;2LYwBx z2L#pL%p$)@jSKpHg~f+qtqx&2&v$T#60<}#Ed}nrAZ-ps#nwqSQo780X?acAf0Rbtq52OT4`5`2({wv`&nO5!+bN^ z-tn!tv5s)o{lsBoXYh*LED{SS?(*Mq+y4@m;Y4AN1jUO~5^}_f7VAWVf4El7s~Slp zNwJeTVB@6$*jdH&usQzTOX88I9w~%W=Grg}C-F1)O1yyT`li(*5{tkST^QhVl<;1V zhk;xu#$V-mg{A(o&yVvKMC@~s7;4oqKzVF*9fwu#Q;4-P$3ypwl>Gunk7}zM7>W>B zkKXby)7+ZO;ncIec{0I%M@)zx$tWw;?7ln1+03(FT?BOVDmj7)BFPe1w4A8$Z9b(X zuoPq#(tjcgaaw;%5+}qXA~4oHSXNr+xRGf0b28gz4uX&VE@aK#F6MpsXm43dTe3dJ zv+;+4Y3Szl+tXI(^_*IL5}8|ycf&Aiu8GPtXMh#w%A6~DZjO_%Fp;>=guMhgVLfn* zN%x9lY8L-BUO(Z z+HtA{9(R8CpbTXxv5ZBcv}-+QcifR|MFHY{M13#8HNR5)WoUfjYy0-0ncG2!XfwCP z=BuTKc9++_y%z+cT?jctQ=fFtKg`#dk&_608>>M*Qte)NdA3VWqeSlVA;c|B@0z3% z^Z3{=zvSiKOLX)K5Ef^J33$HTfv-Fw1{l9vTVi$H&UnklfL zxzTCNYoKOCt!nHO%VjCW4Ixv(F8+`LG?0h060&P;UD<**#9gF%2felaNY7r3P*)hm zSP+C+{2JH#4;D{>2x9pXM`nhxwec?qRe_4#$lOgARryK-9dk-C)NXQ=kIY&CSU_Vv zfL0?4%wyDlZ~p3Vxw)3RM_lDYk=Nu!0|F)?7CVa@^n)dXS~1J(eblw;=dfCrOMd6z zF+Gn2R>bl5u3c{!w4HXwr1-xR0R%g*n$g4!+bb9>(OP<*Go(?G#J;h@BWZ5(Vu0)w-?DpXZAqf@msr2>!+7p-yNw9| z(pa;-sNU8-#5a&JFW7KHqrA*Of*=0F$tR7&tnIpRunZ3Z<4WIm`Ho5%vTlo#FEo2}$QAj7MuaufI5eEtpi>%mPGn08u zLxC3Axb(Ylj$$N6Jl_oQIJ?UOkc#C_O^nJ~8Oey()sbN8oGU%#ghLDZWQwY^y* zcu5I^-#u5LR!qFF^>I8PnbTtJMd^b0!1}c66%~JA9s;yz{B{7TLt&xWMK8EAGvGXa zT6(kZ`)gz)=zME8nFi*mK49u`@;p?Qfed(2&n#%Tx69o3uxw4#zi#@q) zGPgI|da<9qx4CIj*_tV)b(aiU=Fd-qkB`*>x?_Mx@9}P@5>9D)=s)I?{AFJh41e}5 znfCR-_K>1BFU#e-?(|i|jjR62c_2n^fuq9Vl5deWGyc#*mEww&@>3c`TCCZrgz;l& z8jo--o{gPB@07tTC51B}uqyBs26#LspT=u9cuptTSOE3KISGI+Px(iOtcPykQNU)R zzoy5Gi2U(YQtbC|iVo86Q8MI5T@nYJI|iVA83n}7_0OYdg)q7!`eL)eYsGSeT8T7endAa)~$Eex!C6~+GqM4pif80>rT&Zvlxkz^cX3ExCT9> zERwb*@<+s)2a`BMERP`#p!m+iTcZtv&_kD7u6!TT3hY>6J? zLw(okJZ!w3XE%}G`)pAgu)3un)|oyEW+q_|rD?6$%{PyJrYRu2uSE?MM=&zKc>8_` za-gBqE0MAr3olysJ>*^PS9mU`u3WRJA8W|7;l_TJf9XVP6rA2#^TcBdhOI_ zb~?MQ)+7PLASImcUj_l7p9B`el9t?v@kZwz?ZMTv2D&{R0sx!RMd_Xf@qCfc!^?#e z$KGMd&|gg44M(7DXP%vrwH=BjD{R?1Zf1rJ5l20+u)I-MrB_qLeeFk8S$;Xe{D>N0|P^BEa1NC)O)S7w#*Jd1(wbFa!_!FhO4ZrIJMTaaX>Oj zIZ5C;x6tfjkQr-7McTMYy<^Qkd1ihb*LK$3{~tUvlGkeQq1W9#Q{Ey>(-KkSvzD2_ zDs$QC^?jXnWRLGSu+uo-RE?e1))5JbM`G`Br|T2z|B(%`kXfx6R&vu>ggf#s#j z&Mj7wf8t+tg}t7Hynk|n3S}4cH5i|g1IC!83q1TDo_EKrHw_hCrr#}>u8!9@_FG)% zzQp2j3*qEHUWVX&tZ?tTujBz;$e7ZVYF8@c$iAU*bGTQ_Ln7s}B;Q+Pa;hVA*tZE(1&9>#d`Yd{8cdQse^qWT=h*yi)lOJ}0m!-Yu$-UD$DUs6{JI&jPa zX9bOxpUIb&!>T%ONe$Gh-7y{0r4G9JTK(X(%tP+YAN*XNr5F*97qho69g8}M6^=ny zx;7*uNq=x*x1hlA@TGJOwoZXx5$ADXsYJdv!S<>6_+mc7*!(JX9=cw%sCid4y&Na; zrI2V|JE6AOkO5*R=NhW{vdPrrf{4^yF}sXzWbMskVt}2O8Xs%sA?1d~tHZS^n&>3> zA;88VeUe3qrl~Hc{(|LOGHJmg&&0x~c^~%XzE&*}Cki&g0OR z*bq-+R7^wxOuQ0mY18_R!7HrgaJiBLipvK$`dmiC(}wi2O6_37{!*&{*}wePFDqOy z*#1cPlp!I6CKcYc_kBLl1VF#hQGa!TB^opQ+|vx06`fPeuwtAj>xRtQ!U#iF>E88I z^6Q>y_;1PIyVLj;?)Q{dx8r%b#iiUZVFxyF3ybQ+L(8A-iT|+91Ipu-A}qDy3#QK= zV01mM7Vme<6_UJGFUvm#^*7%n-FZ)D%VzU>tqQ)=HWq(+xLKk*UYkUEy=q<6BHdEn z;MLV6xz}%?GnLU72oDXcE87pEZ8~!)c}z{-D7|)1jq^X{Zf7OJ3^Z|A6GIY80td~zw!$m&jzMS7cm*5XY^;4PM7B1(rws(1p*!b&) z;Qu4Neg!e_m8SbwI3DNJ$Ui@;zdcov1rfI~mOno2f8Gh3`i#Nsc`D~9uw`EH5eTte zCD=P})%mr!X#50K4v}TMjrnlZUVmFQ%T{bSV`-A9w{dh%{o~BQwkRJ+?k7 z0K-!%O#CT>yrM*bl_rM1J{;=|i0;98RMb16lq0S6xQs^uK;tUC*$QFW4RrYZr_x;>obrp}GP&9Co1K#e3E~KhS;vQD<-wnlM}3d^9xms5 zi8M(tU5Ag>@znYqednq7ziSB~SwJXE14wsRFH2_k116)9#FM^$HdiOtb9kdx!*0WX zNGcwgCG0(Yf8;Do;IX?zK*I_8fg>s&N5fRjVj3IxOL%Z=G;O1882@B4%kJcOlIoPX z=f`+=!!l}|^=Igzb9f)X>Em+UbtXNa?EMbtmyX-*S1&X88%5cBCvjue-I@#u^PL zGL{?Wy3zdpSgA6y&}zCk+_Y~AxLX6_80ilATnC2yZmZw$mKm9!z7gf&`JGGNZ?rg9 z$pYj+^R|Me%>1t`2u@LnCe~bPg+zxNx$cw&^bVtcui2#Vq~B5rOD!s?6g1< ztD`V!6#Gx2sf=zOnlS~hZy1YoMtQ<)REZ~8qQUH4v+@6Nd0G)CSveqFkfk1fpeCX! z^@D?D_idgj9e84MSyaA6z9z+6@0na%S?GLeCHD?5J#g+NTAyBTSf3?DT*F&*Q0L_? zZ5j|w42zdz^8#fCuNq)&ow$b5US1&T+L4OHyqI&?-5Ueabhg*IUQ=h)R)*ip+C1iq zOjM>cO3SL)T3(PG&P37alPU7=3y%f2wLTkD5XyCP*Y2D2bO^UA;x3uaG01pzD+Bbxp zT%B8R+HHloCv0jl2veE^P_QM3w`v@XAe&g-Q52OOriR02)s*ggaUvJ-YlKTiP3?|= zR}@Ukt&>(QZibY_HfH5a|MAUk3m0m;_ZvueQtE*DYjuGA`s`bGM!(XwjEs>JshB%& zq7CRFau?%Pufk|@E*JD8HbNIVGcXa|TiiIeD2MqmF@#o2kbgIPPgA7eC-eSAZMC6c z^d_b@?|`03k1~U6hrzRij6SjEsp9xr;R4g4HQDs;>*MW*+i;>OXl4tJI@W^6wUAhB zA#voiI(Lco3EK-c?SxCwU{XWw*GdRgI|jXAScV<}!J3wCsW04!v%7uth z6cOyBDQcJ93z#Bd^sF%)RwCra7|fZPvJ4HX3HQ0yC5a@Nx&|_S&-bv=KgP(TFwd*D zS>TYCWKu_bNEmTgm+07{?8Vr{4l?N7Ww|>%F2mKUxuEJ_y1H*Q!#n!QPApi-(lnE*fR_WRM?qF&x z75inLeYzqo^ZdX+m{yQIPG+H533a(4zwx^>`*k^~#cTi3khYt|%G^-d=Dk7@<8xuv z>m|7fNTX&=Z1f|P)+&xkVX0ifxOg|*j=q!WwB};UpMC%8he}m7OofmB@UurPkL-yX zE0*iI3)Quqxm(Ixe-a2=Qf_sFAQpp!+Rul83YDvL2#F$DYO4n{^!dEpa;44cvqnU5 zzDo2e7wrogO+)QJnTPKJ;THG@P0#uFFCJ>|#hZ+Dv4Fawmb`3jN|Hf}Z6s0}@^Yxc z{r8f)b@)`~#Zj850N6xK6zPO|Sv#ohAju0{(6df%77C-Lf0 zDHnX%b8hIGy;pv*TWmU-wK!nB`A|kIpzuCK=LQXMNVw%rBkf_Jqv$4-$RHi`rGXV0?>2AX`h)2ea<^CWoT4>nnR zx!MC?U`cDUUw5vp_ZrFfvuMJJDX={v2q!y{uiW&yas263!%@OoyE?}Ig^|NZt^{H} zXYCCx{lqCX_huFu!C4y07DHcPLr8&1YH>P!d)aX79*Ys&=H=Rz3Z3ZGa^9=Iy*&5A zCKo0ti@SSUr~xbtAI79@R4@@i+tDPNmH^v&HXM@@lW zD#}*OiJL}AGK0KWY=;gXHRvqoB^<2hcrCSDd`Q9V9WBz8UK2lIOyx?d-MG2{AJSsr zts7cbvzo^G?LTZ4(|P(tY9wnKzr<($d@45l60g&nfVU`Vm6f|h$e7{8y1{m7h@_FW z##!{Wgr92)qw~8B8;Hb7zJU$8L++z?=bH&N=^vFX9o-}Us=EKuVt_yS(P7-j9t4Oe znf&2eaA95d_yRDmfv7XX5$o;+evj8@EG33pv4a!U@)%FhptVqr<-VxH`3=#)dX+VJ zj|+;GkalaM9nUUx*QEp#_zO})tp{G1VEM#%-|%o2G}185J&i9iVDTx{Oi$HyEh8ch zqd&$hiCPM~c@F5PXKI-4e&3%H-KGCo*gZ6nKhehnyz8z&RgtB zel!;4Ep!uD!ggPMHbP%-_?+?{AkEg%b5CnbjQHe>JV*O00j2PRQH27qfQKv z`N!&#l8Y3$ny5wt{rbD}#jbOv`WulWjK`lMr$7o`Qa3bzfUpB$mN-i-p}$XVX}QMF z=eaJztfD|58&V8jFNdaLvXjW4t|?xQuf8xhk=Qw2!&tb;U z-~42gO{jk}X%DeqoXzESZJzsbI_x#rqD|$SF7m8{K$j>c&~5e3xc`IcE}Fyjk+G8! zf0Eon{rY-h1iZW6>zWS3n;KH%ke`|eV#jn^s~EiBwN>ZR!F(k(H=4DpTPD`Aa~}Ks zhV}UslKb#C#=&U<$Q^N~KJXo^6thC~HK?YiH3v2p+=X0Q`He1B6n?;o%k7uONRN-+ zKHd5pfOMlgm~Z6uqORDjy~TK)d=+&e<-KxeVQ zHzer8-|`ZbRZw}bELJUse{nB3qpb8rL zp|}}bIt{Fher+N2I;~yKD(J76{&*cwfjyw5k>!J*F5GhZD+Cyw>Y3gmg%iFR5@7GZ z>11aQG+f>IpIyDbw>JcQFl-;;8pWcK1$|OtsGh960LJnT?n0-PvN-O(xKjO|%!1uk z`?cx6c~$=UvMr;*xVWlLY_8m1w{a*v{nw*Uut!}f4L0H4sQu;tTCV=b?$J|FV7f&A zrijV4obErC`v1{wxx$BG=Xm#DvW@<6JlIs9Bm|0ipWYGHKeQA6c{Cpc22@wv|Lo}e z>z+1t(v^#AELO8t439r=@c#CY#DQ3!Us$2H!nj`l=Y{8=_bi9`KypWi((j$G+)Vzw zVF%tTV815QHoVB7F5lv%`=<@fKcAi_E39=Y;AyRlK7AT~_g`=5F^F~b%zkXt-VaUA z|HtFnNVJ9GR4qsFefn!=#Gj9co`ndELX_~nYjXX+4kg*PKS>2pl7nbITaP`LQ8}pTsYea*S*KDwp z1M)?uE6T@J*wC(E-U3_zb=?6V zvd=apej>Ur#fjPqKw}$SEjnHX#$DRx7ffWSQ0;($5DXe^xfduz>wE7HsC-Ms+8ET| z=ZtxMY$^)W&f`cgrW@7fPrFX>lfXE|LKEQ}0stCl^)w@B%>aFGq zoElK1$q4z}Ex%}8>m`+=Nh3t}Az6(z`DG2O$`9OYf3opbhHpTB@@$1I-bk)asGNHx zpDy@IyAv%D@_S|UE_-k1@s)hpr)H`(6+?>BCGdFj}p5iac1{pr?~QJDPYs^(-q4_!UUyf)=PR<@Y+LPN1X z1n}fa#3C63{>3luMLm=?QJL9z~4b)!H z!=3+!FW3ADyx!%?jCz^_ag3BnwfA(@Z>wT1cN6!zpA4&4+d(XrQ*ixJG=5J8Tt>Zm z#^HJANPw5T<=6vkA18pRBnlu%C1XkJ{~u#-0aVwTbPXp+a1R#T0|W>pxLXMBZo%E% z-Q696ySo$I-QC^Y`8IRs-kEuCX8ym5qIT^%XYc1c+P&85?yx6gd=Gd0f4HcRBdN^z zIfGVzZ9)HW!L>?Ic9oXGf;BxC{G8k@p_dQ8x57&iO_kmlM=i-07*|RFG&a(wGFLwx zk@fnJ^&_Cmi>y6ae$6*YVq1jacn72xgu-E|I+VpMgS^dE>H$PO*0vOUt79Dy-_02r zm%1hFJ3=C?7E z-nylFy;g@-b@}lup*8cxy~=GPIlxzmF;=A8pV*Wl{8PXom7&kwnA@#_P96(zX5%iF zA1&(DCTuAxZ6h8Ts?zS5%J}7|?-6iDr5Br+5?IZBz(D-nA0U8*kTc%@dkPTPLR-kf z@zqrFB}%fwKmi^8TJh29nwDeZsJO%6(b+~<#HE3BUx2jU))WvI3nYi^i{}VICa^O~ z^4uUB*=+%$Wn(=$T;sIJ#Eg*a9$Fi(=z#1a+vUNiKp2A3*L*!Ry>d8qiGZf(h z`C|N6)$XUVDQ7NgnrrQa;^{jTFM(zh9|s;P4JcGNhXAajENKWPMY-m;+QlxL*DNo` zj|W`tKy|m@E)LhvAh7^rH1xP$&n{F-ri$kb zLldK+I6HN(c<(Nun6E-kHF_+)a2A9AnzATgIy11=A~+;U=#ZaSEWS2L?G4=;|!RIDs5|Q{(7A@PJ;bvVU z&qe0Sgq|`L530B8oqLs@JrBiNL-$k-w{mxMUD?790J6r{P4TD$%8M^OjVLqostcf^ zbE9nyPv^NeOcYOJk1Omqnx2QxkU48Vmu^vM`4E&M2!O2SFeMZ-|F;0wU*pk2?E&yH z`zY9K+7&2m?}|WH_n^7y45ND;-S&Y*meYqN%@EepCe)=H=4^oh+*$l`CeSf=hLm(! z!o@&j$kLh#V}(7E69V@50DN&`;t>qK=DVC*PN$u>82o2GXK@2ajwc_RZ~m|WA1tq5B#8@mU6D4m)6>g+ zv^^nxI9gzguYw`K!~f8+F?&BkVcKYP!HzmSVQBPTK%whgr>IXVUsDmMfFCXSPRBdW0vhRwVZY}W1;e*Qm@3)x*#k+Up#Oy+)tY?-*I29Jr^j; zQYaf}21p~gvgMcNXGF6YAgwEuse80J>895S z+0`11K?W33kKyM1w%b>$G9cJ{BsNkrR>bjeuDr*2N(OB1m4k_m63fTe>8VU`CSN?S zKhUQqPT_0XIlO2MnavN@d(wSIn*Cy;#8L?k0U?}8wjkV`Qw%7Cg@>(6!nt@5%kIX! z7zpYr#eQp7X!CFdP~tjiF8PguqV&K`9=s+7V=RM{Her7@NwRN$+(RuO4h<$7>1dGw8 z#!<-sn>y!XRV%Xy4M8{INF)3G_m$EXRCB!R4R>=CKLV|Onb6NDlilrW8{a?>Pg!l( zv!Hnn$ZpqHJufRY46KThs`6s>>qx@1CeAjrsVyt5YY_~(i|#}cZ7`=?jnqn_+-Z;? z;bf7rr&@oYiafnm8DFbiYngPpjsaVbXD-a;$12S2UCsR2@};U#`1Irop@(zpRd5JOOguQr(kRYbaz#M3Ow3c$d>zfdRfRN@GsxXq^xCf(Jh|89yk2t*PDs2BAqs^DN;&+n_DA&gThN#Y{)9=>rr;6o{lz``LAg(k;`b?x#@uV^~nFl(gZeAu1NeJ zDCvAj#KZ5g{u@gQI5Odfkh2);_;V@Jw*LRca({h$0AiVs@2D3;Kr$r*keKKXlm74j z@e`1MGUIft>4*QP_Z#JvHytVLT$XVmdpQ2(rQ7*sDM*7dk@qih_awmBYqb2>XqS{4} zKzAsnh}ru7kM1BxD)aIF{LVy*MZ@@i_}X0O1V7pIP5N>f525~he*pEx2|yU2M0tvJ zq<=q2{umH^Or)~8$4E)DSyZ6gkT_lQ9sW^29{4^e2=++iFF|eee;>gnf*+0OJm^Yh zT(k&K#Hb*UO6$3%h7;K$2{MU;w*Oo??on^H1=^Up@m0i#RsOyAzHfsW--5;iM@2Xr zWc-QM{$zfZNq$9Nrt~5BKYok5I8vSER^ktAuLO&s+&5*F|2{?>0&T}u?7Dt}_zB2u zqQ?6Ac`XS;(d3!dRLY7s*7wIamh2xtCR`^j{eg>orv_VPhOxLFOt6Y%6&K585{_V~ zPu95pcgngA(4NFM-m~t}Zyg_nQdsD<3N9M{-F-gA1i5y);pyByIr)O4Dp!P= zQ!Cg|wMWb!_@*8U`_rkA)N%E`MliBr{uJqfuzSjDO>YlkelBL{+x(a4#-dqc20@Kl z;ePiw?4kKgD(?-VCi}Ft{(U4hh1%c=1Oyqs0$86UQ zuyyA$OZ1&yg6eAnJ+!~Q*+6Z`ihVYGZ0Gc^p6#yt_GcmSe=Hb}O|dpy5rp|o*U&jm zJffdwx)xD|rlxByU}nVLOg}e@!c-v-sb5D4m{}N&B1MYLSxmsp$Ni6)fBF;baGrOK zTX^jZ=&FD~cO(OgC?isMVHr4Wt$$9WHn_J$;|+6N#Q(8Szt?~ro)&JvX!l(|R%j8g zUVbNp{*Mp*tD8MK1=_Yt*$(ypeRll)`b@md7o!U3pTEHy|GMLGOZIacF$2QnQA;o6 z?~nM`9sKys6c8#Ju>bY6?~ou2SXXo2tHUh+K2ra_eqOJ+j0w`S{@Q7Ndl8NZW}Kcb zqmKf@29%ZfAMfn`xkdlmC;WL6im&b4%N!Wth~fWel5?uNs5f%)h&njfpWpk}FaPUK zI6fG#`*W97O8t)pAtRQ*ZvL_8Aivq1Zb9CPyvIdGUcbh>=RB?ztT-HL)dUCMXK`E0 z=)Bq7b2Gr?^!ggjFI~FVmQorY>d5xghpC)NPf-peO#i;C!YH*khR z*0r+2+5Wss%;k)Iv!Jb5Y1+Kl>?ALh^_{@RkdtDBeSw;#i;tD2b4;TH@MWl!ugZ6& z3uCD^19HOn8;f>v%@3xjn6Fk0>g{Ow4zKgVQoop`Kho-@TJJqJ$dt$s?1L_DGTEP& z4L)j>$P}vxT5k34k^1$~bK&{pmT|RD$~xvu|I*$^U*$#sWT4Hg^{DcVpq<54P;`Ai8DYAb(6j zTPaY=alUOtBix7F#iuEW9#3~!m(~9hCHFf10(=haMD$=1By&;yqaE|68DU;BAF6C@XeoWd+G7?B9Id4!< z(5>?=N?HR=Z>`WYCIW_}#D4$lHn1jUI|LO$Y&68A8Ch(GFM_BhwF{5L#Deq*A-|Dq zzD4$j^7v{XRoEfLsvabs#89l=8&04iS!=oyJEr9p1*HD@z7>d?r4ytZ$sqb6Whd{P zI+@w2B$>%VC=iv3kj8o+Ka>uKzb(k7uY$9B#_-Nk$A%EB*U{#n*OOm3>uO@Cy7y+t z&TDflk$!We^*L5Jj#@ZdXe*odp+@Xv=?aUXFN0(N3^yc0^j2?BdI154BV*y+NMAcRjL?N{n6R2G z1niM>h5^b}K8rww-T-OJiaozrzWKXK-4PV*xG#F}4$t*^qNo)%3$}`N_W7m<6M@S0 z6*|cegZ-FO!x!k(`X`EzGu;Ci4>T%Gd2B{I10||X!~&s*<1HqXx22!Hu_=?h~dr@ES~ znvsob?7+5LZNG_4D%NBvCEWhhSWoFno#1dXbK&gWK*5yJ=9=I6cPx^;oxT*|Rdy#X zhr{-c4;pVq^`iA)ced92a3hacZTp{}AhLPxD)v=s!=a*b`hW5IWxFoTpLcGA(4#|k zTWwQ?vpe178@ z({u9fMN-Y>&Y>@bGH#gT^Z_rsOIm#22oLJTbS@{1-R`(4nnISxA)r>?CTnu0r{N0g z)tGOvO@abbEcB?nT3ll>y5^I1IZ9@CQv1M=M=6SHl!tKEIS|e0_DfT-(ba3Q-b&t( zT}fKKm8n)bXJ`^ZKq^-(o2@U&sGDf&N~%W0z{W&o%uhUtR@jN3(NIF%{CNx^8kAIZ z#YI>wjf03%t^|lKpQIJ1Ftc_h^J`R*ufr`-ZzYJz9SUFCZ%m+4mUB3}3q6-eUCc>P z`NrL5D__4d@qf>cKPIHU^|nZSDb0)fRf&N^R?3; zTZ`Z@9o$~-5QP#gDs`988k$s{qO+;Z57qYa@=!X6LC-2@wOl{EO=hbUeM2y)s-r(I zR0$GMDRB>aaX(^lSQcf;@gx~N0D%BS{23f+F8SW$IBS}t-=?oyWt$Pz(@t)S7U3JH zIN@<~4%#8+nRj;xlok!g>(WE>AL;`o9WSOuNw{nz)_-u+XL_?~O}M@A;@fgp;ocv7 zjKVgBYC&Wq5aNOL$t^8ELm?e-6UuTv7?RJH>N*FBI9+abqT9_?|9Vr)+M3fPxQWo@ zstS+CT+l&*|L%D948>-Nrg>EIw8lCo7&BTyRR%qTK~_rhVQV1H#syOPjfQn1GV3qs zceL<4q+kmElLBFQahL_1WtKX^Mo6{p`!xNY7>4x8=>Bey9-}{9aqbRIrpBMV!ibHg z2Zg>C5qybOFf4(ym*L$sTDUi$GJO839nWEg==~z4iLaLC$6e&}CXwdEg#cqO&uBb) zFY;#1Z<=rrCl1Stg>LqWC;Z0j_x5k zA@*Q*{ebIapn!7j29bgV#0nOpDa!;RE}$tja>OEJ1k+9y{w$!6=zY`1%!j>WYmzz* z@)WK?H0jW^@d=c2+RD$Q$A&=$z>#b(q*V`T?PE^)eWcn%%0tqeqeZoPORt_@a zx+^c*e(^R0kKA9zUhYloaaI)ua=MF^$E}$y$?4R==;VU* zLJSU~phbFDy4gtaiQhkQ6KA=DADUGmmD5dKgb!9FJ`I{%BEy~*wI-u(nOZp~`jdJ& zT{78OF5b=f1ODJve}Z(4>B`#W>0mN*)aBuG$ufeZS?c+)cY0GW=D1P$j*LV!6IRjOf5zi~mOiX?8+X?$@m;8_G%L2@Je zLS4Gv*<`7O?Sty9&ccsWO)0KXg=`Rr*||QUx|r~4t2df@p($oU>zoCigLHR5`!@|d zs{m0h#}jIksrGA$cf#yk4mGM+TNcXVwdsa2LP|0~R`lS<(iJ^iEtG>nQx3%1dik)D zeiZhU<)9Ok!}V<8`-fUlRZ9EMYmTaG_nd_-9bPRi>gq4xGX4?A?3_|F7jPbN98`uN zwl>RJUnWH+>FFSTV@(3ZqDjJq97aBP)qYZI^jnj)fut{O8tXa?EgM9+7W#6&)tkCh z|I!XFfQhixw;@s~rW86eiB9t*eZpHL?O$O(v>bnB<0yW_tL9&hC>VyrnaX&w3P>@MPy{>y^qC2JmkXSPg{?+Ta#DY$q{-e_ z=QdWf(jwl)2fZVfu8RsT6(2vH{^+;&Huq_MuyLmBoRe!C}a(M3Ys~^{0a!6yMz?ED9%V$KMm=9jU!r>` z@8zoWk))f|)}5f!r#_SrsQ7+oICWuM|5QQH^p@x2qRh<4lDM`5cyW~(!qotngZWIm zI@KQ8kMD6dSX3w&7Xdh^n3Y20KfKaEeIG~xeEAY=rO|uWA8t6pUl)hV;F@ya7qZyY zkUZOFyN6!(Wjnvv=pn|O3em$7N_srrZC%N$PcAIGa+!b0paE00r*(Z)q@SY z1TLMmH;}K3TfF9-_~5TCh<&E?%xH-q2@qfUe(qMJi)#y1WXOxKux)5;yFvUu3_ogA4NW1u;M5*TaYQqxy@T4tC;Su8m9T zLB*tI+QMwXTy?(GDe5!pbZj01Avh=3;mORFyO$p7H&(u@T%NMu?VRvYQoxMu9eI8` zEchP1kTRdt)p{ya%JlQE6*?(TNpp&r;Vqby*{;0t(I`8=dqcaE9Uj)hWQ8Pt?Wd42 zku0ZPV-Ae3^rsEN$p8F_R%0wQrP12e!(2r6&s+fhMl-U;V|j;};v6%c$n7L;L?}^_ zzdlPw$k3}K7wV47pT4$QLG|} z0xF9337Ujt_M4g@bX8dC;7#=Y2@kSO^3k9gtU2$*l_0Nnd%8tm3Z7Zmj3jYM9SZX> z5b&74Ie{xTGZ$YGpncMu(p+b>C+hIRm`N=ph^6bi2RHPjR}pBuIgFw=nl`%bRj#%q zVb_=kD!O6K;$wT$V#Cn7q(E1@GlPN}5eG)g!L!FLQKE8pYDfb&7kHQ9IaJ0Ss+S7}X! zW%~gRvpI7o8mymHuO^=}3>fGJzwp>ZW)U`X`9--LM&~nzyRd)b?E@6402h?VH<_-& zHGk2dgcSmb_Ei!X?tVjmxIp_7lxQw%<$!OHf~WRTJ)3=Gi3*i_b;)M7dj%5(G6lQA zNP&H!_+{|-4tdLdGu11u7dGsQl64?u8U`zrH0-790%uALuvfM&J5=mz3Ar_y)SX|t ziY{;cZ25FQ=DIA%^$%Pi9OVbr5X73s4u+0G-h^FE6#C*YoZhplo!G&#zo8kk=$A%X zZbTgXoR0R%2b$5{zU)W6@f|c3A|ZP&vi!kJakiGLCYCQ-d=^ZTModgETm<@69Hq3< zJ7?DKL1}nbj_OWtTBq++slt`mX|6nC5o`eOKBGC<@1xnu%!;G9+U_LxetutW6aB%X z097I<`6-C+PpC6C>F!fnT|A>nF8t*gNee@EOeR8Duut;C*yqO!W{&+7>W zUag(ZDgq~zCSnIZ%gxb6dgAAywIQR>Fvq=BJWLuDJaq(YVs)3NV(Jo!Um)`bb$T}? zEbvxgYA)7}neVPHJKiK$zPe;iCpXiJDi4KU{9N2ohDRQnIi@{nzznobiEGv5IbS|Y zINltV9Pk|~VtA{foXlePWb1*r0!fxTS>I^FJV;(hNBQ2}&0WS}sB4!#r#lO)_2}N> z5gmj@Dl!TiT`Xqv>$TDY^+x`N>LaJNm8ilaRE%c_3CzcHZBfNXQ#B{@^`XP6d7?+l zt8I&5vUh9JV^im26x!pgOsP2@QBf8Ax`01ZSu5T(JRSE&^y_QM7>6$EAuQRax}vel zIc>)~w>M;M0+-G*Ux7E8)5Y2P!9zQVv=C}$7cFo0^Bm(_Yq2kbueYy0!DynXs$W^k zH;0^tb8iT`as2y?BY&@h<0gdx%Ubx6@AlS&-}b#QhN(1*8YuM&6F-@rj;iaN{xqu= zC^KSzx_8U$;)L8{8i}tP#sUSsT#11T?XrnwL9Jufgj62zinBXy{vnt|8bUu;U&0=R ziZXKmm%%)&Lwd#TAQ2Rd-tRkOlb6T`uUR&=s*_AAYKV$(>)LAmj5OU&!Jr&wp(=oh zIcb*Ec(CZGny?X$ca^@JrFq7DZU2GnP$(?s0e)T(9B1LT3=Dt>;>YTaZ2MPZ%05X3 zxJL|~!mmwtUIrK79=JB$hqxUB0EDU!m0ge0ghXheyW!Fc{sCg`V$6{!mMTBb1iVq+ zn=1B#3(Zg~Rl;C*R>>)Pb(saG+LA3U?ZqNZbaDCRJ^%o?vWFL)1G06GC|A;%vy;-5 zBWIWh)zg?a;07_&ZNPib)$j?~H5DD565}?a_*py?9h6q96g5(IY+Ceut$5sKq9#Hv zTat!m{Xdy?|C}i6L2uSQ9f_E_<((A7+fkTYAG1QS9>0g|pNlk3<6=dNtFqd6rG5kH z3wXneO`KcV*YY1ymRClIkhl#IBH%Qu>=ld${t~$~AuZZ4+wEz8OWghQ`&=MMZHqtt zKfqux{2#3Vif2sGYb9bba{QE$Bi;GZUuONWr9`-SCgWYwQ;ynPW%g_MJiKNn%lL|i{lN_>)2{v!G~airmBe)x;taDUs0Tjc9@ zX?rrKH}v7?Dk}FmiX-hRYIXI8%VDniWt^8Eyue4jqkFgtyR<~Pf9H|@!tYNb z+g1vvd!zHeH1#*T^oS`|+NLxuY>DPeKS-8_1_X%!OPUCgH3Vq=HilW;1=^=r#^vJ= zVp(udP-6zdaiym<7%3ywDKO|XF#r(Q2XWF$<^zN3FQ-_NV7AmilKad`6^{tmUY&wa zuuaE_qONJo0!=1%#x*oNyiFniS5;vBvYKgLpE==Y4&gX!uCqgsw6NLP)?2ONF;hBl zQn}p=m^Z3+UG@w8i%ZM_7Qfge4*j`}E^f$v&u-w6obu%AZPmjcHSIrnc=-{Fr?N>^ z@L80e_sHgG^@PDRIsEx+Vkl+96Wj(Pc>Th@N3{D~YCIrdM>pD?2vt+WG*f-su`<~K z9+0i+b3SaDDXRec1!4E4-X80mj$nRgzPPQ8tKRuesBUxjl3>xpqAUUfqu!R1_x_Sj zb5~rmcHuwU{Mcx7vOFX1Fvc;;>0TARHhVc?FIi%<&|rGK2djbi-)AzE`0ArW4`mIr zrFKvit=%V0BNK<7LJw)5dF6lUhVha*rilawC@ zJys@PYR|Iu&NN(G&gBEAMa3hnQ;WB^FCRsCMlm8yn2&-I>r7)~(hFJ?oe7)RQ$oV(A=_ zdj)$+@zm?a$vib#vklOUOF*2WU2$%&R;8U_=#;lP5+@H+5=X5X3m^e8y5e7D+ml=2 zxh0#GP=|&T-IWqJE)baQo;}}x3Rj;tTO>|;St3Nl6bQkjak@T^YI4Av-szoh0cryZ zRTso$ZW8q?R#mLA&zY#;LVUG_C`jw5nXh0RK?Qx`5PpB|}zjs`{b>Zdg&Kt$Lh^f6Gv2lr| z0Ma^P&0QJmXwGJf$BBIl#UvYuEggjq`2oiJu$Z#3OJ{kL!j*=|z^7g)5V~gsyTYl( z&CPw@YSt2`$qxfF57BY+(TU+obs&-7k2e0Rjqf9aMWFFqgDS4n3~tGZncJ|g>B`^d+WwCV$$+g@5ayJy0=x;^zfWgoU*H%tJIK3hDb!uVFB6|Q8bb5q!G;14ZG zO&-`-*1MRa-}4i=9uq?Ni>)v29FV2o%;suTNktQ=Lq1GK0kp-ysJg(O%ia05tfo3O z6_;k(QYsIk;g6Z)=JChAo|x~uWS}ii+^tGspw}myc|J_`XEyt{!DvV~K!m@SU#LWC zZcJ4fq{R&u2?O`zKq7r1vY`MIFBBKCC*+lLkKUH;sL7qh>F1@;#HqqPovy$w8?17T zqmFv39ofcry`oqBFB4RWeMlsZF3(#tLW{8DJ?U^>uDBFB!f~-e1;OM(U|qBkin3G7 zZb0EP-MP9u^<48n4lWgwnQa4d`I=y4A_1mmCmb8Nv30wsZn%!>N|m{BWW;bOXw;AR z)wsp^;49e$*jfWGD&+Yp-fsRIh-BPX--i(Woo6TI2<2(pG==?om)_}Cr*$T*#$(a2C7 z&GdnICng+hgw5WleWP4A#PED&Z^BXzD(R%@$#=3)S$X;7J`@f6MXkA85v_@K#ra8R z>q?Xu}ziV5^lJ=_*?HZtlY?2pomsTPG)f@%VI026q;E&lxc8Af2Uz;cgw6==c zQjU!E!rG)VJBgK0zQwXo1GJjy^OCYKK|n@lCAGsz?jS7_rRq_&HdebMTeO)vNnWZP zGzx{b1YcC|fZlk5=FO2bc|4dg%O~HEc}mPR6x*XY6oO$IHbTy)x;VD`?{Iy&v)cd= z>XhM2$&`q7T5~w}r0UW_;CT#(a-h1f11E3i@^ra@kX3VT>8nSxzTtXrVb;79TQf2U!RXwe~7d?>{&wrcC4r zK^~8`gcvVZOp*5z0mk>7lCR>)g@!m#86wcyCASi-XKf4SY<7Y<6pHA;4kWb@Qs5MT zsHFfe-QdaEYOcwl&>&P^7_t`8;_Nf%OnB}oQR?uQ7W91S+}Qhl)?f&<+t3a=KL^W3 zhV~?@bw}7*BC;O{zlgox8xZAoHvh)lTGavr0RgcNtJLi)ieofuccL+3f6R+&QwL-Ywl7taI+Gyq)ftJE4UP|% zzb<~q;I9fpx$o;iU#jRAcN~9)Qk_c;Ht@cmw_ZCyQ@2))=aV<%ZlVnqaW;!Ut=Wp4 zS`K{Z8r?T7(9$BT4$2F=VGoZHXSKj-;m_loR5PObH zGmjaZ<+NG!1cGGnrht$bfPpI|T=Ku!c1}9zUAj7CdT#md`x*801HGr7_Rj3&5gafE z>#OM<_rE$Q&8%tp`JC?d2Sx@)czuOyQ}Kj!wlp5cVQ@%H=BtqU&#Y5}(8!77iQ#4# znzY1ISxCe=mWkWXMEqGnY&%CkqZ=*Cc0~T&O9j?wB$fvNb?JM+(Q<~=L0jbh_yvNn zI0e|O+(onZHV676{cUXrRc~S@ki{nsFKnTXz&sCf zhLkre&b#R?G|miJU~%gqL4SNARf6H+#!sbQpUn|Y5}R358cueA*-WE9_=*DQ-yskh zQ`ld~#PNx`F40!&oVlrBCHvwa@j{7&E9?X7@?oeo)Yz4F{0u!^D$5q+${P(JasVa* z_rQSclFt8xME29dZZdE}_Qz2AnLgZrJ~&P8AZB9P9!#6m4)M=MJU?;BF8BKB_(S7) z+Si;kUHL9F9U~((fFZC0XEpyZ_=@zpvGlJF5T!??AF%VcT0@Dy*|P8zk=$#E+4RZ8 z6ViDh+dz>C5CB-`w(SX4>P%=&6>4P4Y5(eoWZ>C4HUKJmb*RB;L=C5Qs-DOg+(HP+ z^T%_07YlKFR{q2l?uO!C;z~mZQzgnIpX@O#0UV<3YG*){m4|ul}vrS-D>Ka)Xa=nan$oiDM@i$}DnTP!aCW~l=gf}BCBHHsS z)K&AGMnvMOC{Qc#GpHP8b`!Y<;=uXl@Ydo~94%~P+p$$%L!+zG0VE6Tm^brIHxSeO;Ot)#Hp~H?2Um#m{%zu5 zK$;mUCRI)JdUL>5u}g>lQ7GxCWflXAZB;RW&_{KZf7j5zwP2ZV1~;_ip)6!(vQ(v> zA|AU>*-Lv@aY z7BEnPvxQ=W$h`3QhGwhc&97=)lS^Uu8#j+x+QCff;PH7HzLfd>NZ@c`v1@D=pkM2zd+V?FF#?H3|d*t50<`sv_;B1Qfw7rvlp} zkE;j^dP24cCO&x=?XOfLj!RbH@%{A{8?nfCI0U5T&4+RX2#7octzE1O)f|;LQ~xmG zlHJ+A@`)nnH0c`r=G^`VJOd^0BQ=l&`eaFsy+P3TvgIfu#a!>#jiKp8fe#>~5^}uU zCn6xYiIzwf6{nNq^5OW|h48i&h!mpmnGPd>U-x4)`Wu#!Mg(A59CrP48m}@?p*QJh zK>iisSPUNQS)TXyAAU!E{-Kg~H$yq4w_qqwHc3iZgF_yu|H1m3-TY7F$T`^ks08|6 z`0vowUx*5D;qL}wXvwn@NQv~~ulQFv*FWC(pa1K~erQ1|-Ta9EAL3^&5DQUv+$syv z_^sbWH2?KHcL}=2 z`06C`JNaOE6%;m`y@KvDtvt0#U3?WQvfx3KBi`$hUE;j`7>P?-qDm%~91YkFNFQwe zB?`1%;o)?;F2ZKDW#e%XIb{d2O-lQIsHWBe5yAf3s9Aq?5LpWOXe3@Y^0W`+XPb(CAj?m;X$i`}2cd+E*|!l0W8E=cahQqh7I}RxOpTqNwM<`Ep*ECDj9HAtn-h&w;a;OsiW| zrl&L(7L)FKDqH=PtN4*}gR@VOVy#+#9M#4rz0na~62MyUtW8yYh^N*1hRL8=1t{^v z3*_>Nfn1nUSEAu}FlYnW1990hed|ga0Y#j7`uo8NmRI}q34lUQB#}*ZIF&uu!Q)r7 zLF>R$qg}RsM0>-hO^8}at?q%QyP(bPp(H>IDcL+C-71z}_Ga~;AjWuV#unA1#CG`x znRal2*?h2~<>oFexl&zOmQeP6G3-{uHR|3@OwZ+w*E`|4@$C1RlNCA#wwTP0$yQD~ zCG2zt`>^?v%>|2f7QEf!@kJew#w+E*mf9nvij~^jw22F`#S36}0>K|BNrd0WwLYs+ zO%=*UEY@5011c@;+uriTouh@?t2;A5nRdn1rf#1468}ZL`-wqPu2eC%v=tD`?O6iC zLqZhUSx-Pi3eEOYQm#7~^O9o_l>X>Okxot(?|{Mhy*iTn5D2OB5M!q!sBT7MC|g`ri#0BOn*(w3i#Hbj zrfvW#q2p!vQnr@4ML+_=m?tj~TdF|b3X7GWgrevcuJQ8hr8GMjNmBVWPhB7=3Q{1P z-bgq9N*^wP)~E(szVxxfnqBC9$<=o_gZZq8Pby6^F!8fH`Ra%yLFkl$pAz-515qy< z?%L}2X58EtW!6AbO~*t+v6)HJ-0rH^QL`$U`kPnetyJWumH{-DQp2WJOsUegxdpz4 z5(%Tm^ywWH^-80ic+5-4;e4}vBIjKlvgDI?TP-JKJ4PFz0nFspHkRGN-htsMCr^1Mcs+d|PA6(%6+599lLiEm?PK z&}N4`wA_-2h2x&chf?(kul6U$GwSAgvdvIdVKrJ@p68r?GoU~Sk(xt!)K}Lck1DeT zAqUcV?b>tCst--5oOipR2J>|huWq}8KD1F`O!{#T;1}mtZ91h!NF?{#f$jW?M$ndwKL~ z-KBSZrJZww3CD3J`)w6~2J1W;85;)m)6dQpRH-6;BnQPStabyxYZL8@*kZ8=l)I%X zh`qA;g0v%9PDwT2%e}3L4^!DD2f5ig7rOJTsOzN0W9vFn$_@_G@`Rtgf7l zboe#8W(&uO0q@Sfm@JYM4N4gXo$Q~#!n)e_!?TWKsm6-|a1SJrz*ps)9)pp(C}C4J z!Q=7l9-_Y8(H!L~Pp;O`>&3w9?k8}pNCMJ&ag8NaYHH%pdcgXMfjz4oG5Pfd8D#-# zCeLedFfe>U06ivBcf0V4CYO~`V!O%`t8`suD^;ufc4U-O&wgXfnqSnp(8?ul zrvT<(2egMaPn*B7y)wocxM_j>nW~A;xi>%48++3nnGBWHt2T2*5zNy`Z|449idWqa zZUN$#SKSY|bhyUTj?Es+2-jyENpMiH+xf6gW4gHBwCV*7QL!Vr!DcFH9eW^*EB&& z$?(iFnmv#z-V&PCS7hq|0HTt+kG)Y*G_;ocdB`tTG@gp7ZgLv^XoKMd8@y@pLJ5rc zPes-kMp?fm(At@J@UWTezmtlmq&FAAT>uKLDI05HJcuym4^VZ;Wg8h4`DEY!ehU7h zOTQ0;;inS7a>osq8of0wM&^B$zsSZNOqY;+(H)+q6<b0TQlkl)j)T&OG(+(Ad5@;W>b_OU+4^kda9Y7cw8<_q}#7y`iluGOIDWp%H-^?&A zQhe>ZwNlMHOT+a%T1Pr6`4M4}eV;Ojc(#3SHs*vJ= z)cmkn8~wd8jemzEVSz*CE5AdVJ`HFq&_w)WI9zUL7N``b3N{o9Ar7@dKIn@Nu~6U^ zvL4JGsR~9hgj@pJQir>%+8Fe0{d()YipC-hgPR4v_^db@^|3PrB4+%>l4+`>Li@6q zDLxtAtqu;CtVUAf8XDDn^3b-@ z8XHD@*^;9B*xd}mV0k(iFejoyDO?YV=B&1wZ=42nQ76zWG}*hBt8s&_&bI~(Y2Pnb zeuk{JJ6V?93q6I5-z)}Th>DZ8w|mnYyjvEmvZCLvqkBeyBp+81gg!)r9YG^@qiVhJ zmvCxT-(N_K$LnyHmd6&-E}x{{xxQq+r%{thJs4Gk0n)Tg$F5~|_aB%w=kk7zw=6f# z9-iq6irdT-ZwVi{zluv1l40;2g^3S_<6#}TfxN|GwV;z>e@0BY_rt^CyaK5xNBHqyZo{7!#B4yIF}9lbEB{_a>p>x=B~^2Y zIZn2<{UEadnCJzXlR0+XoL4m$r}o%vm-3iwyUKzw6fr#=rwtsWNr5Kfa^k2NB|m?% zNw>Ch|DBtI`dDDdn)AO*{R;^Q?m3X$q~DuYQ`D z(bB2E&l_X*lU;Og<;x+%Q&_Qxc3c7+hf#HJRd>XY+YUKidl-AEiS;(EQj|bqJ zKR<$#%QQ7T-&{Mfv-8|pb*&6LZ;*+d#fPyvd$m0@-}ge`x$I-5==^6OcbfpzEDx_1 zce-f%>2L~bsjj991;D3@wKw~}FCeyzvUbv9SKD(`H);nG$E~pJA z)Q+04ptwY}U9P-@>kPb{4-Hq&{Obn+9`q^^m@~&alOI2Rh9&RM9&J|}ZK5^uDFPsa zdnu9v5RwO}+Tyz5JDAMQ$isr-9Q9^n`hMF%uk_zxbmuU4oaf5&_r46+?DpHooaW!h zvjnN`&N+&B@eY@p!Gm?-WddIa$rNxpTa1Lnzoj{b|d-TE-b~`a@^%e>s ziYCH%u3}#mU4|(D#?Z6W`3^VsDs8%P+t^l+_2@&FwRpV^*szIdPngO59dHhp%h$2Ocqei z*QpE)dX{jKTovL&kBY%UgANlkq}iK}6Assdr3A*-(%X>kSsB1TRqc$(?9rY8s*TA_ zsn$z5^d%4pcxNEpT+fbeyl&q0X!fds1!on`^>muG0@yTcVN;=+T6A(7y#RaP4>-P8 z_Wl-QQ(~!X*|SU0Aa@sn1u~&$?;KV`nlvVK>tCbAO6B-1w}NL|K_L+KEb0};9^3?0 z6)qoExqG}5a+Yqf-fh(0VZD1&t>4Fkf`*QKceTGui`CQcp?|(YCkd#N#rM)IRbq_! zRO@BLVJy(~u%10&DU-rDd$hnkTt0nu0v_C9U%172^-Wmq>PF7TQip`s_wt^}_5Wan z#Bd<{C6uPbN{|lXRr-`bzSHQIw9Dx;A)0kv6s5unPjKkoNvKQ`fW{96ySgiF$ai?& zV!R3|{4%Dqb=qR0d2@rl*lD{r|3yV@@e=-SPdQ(`w&o(Q6yh1b^^v8&J_L31p-7x& z?{4iW-gMykQTP<$`W$9tkKq9B>a9iPS(Ny}kV%r9ABtIo|BR%Q>utbSlSK*Nm-Ecw zTFosAhleHHwRu0BH0McaHK#l96bQTv^iL6C98Wxnj7}==5I*dR^z`S^w)T}FX>C50 zsBSh>o9(-xkc!5fxZU%2hu-q*4ki)-nxyf)5gYvzxu4ChPYq4eckFjPByz^t&|6m) z(+H4roR5z|$6@q12b``q)oe={36EDdh7MOlb}5amSYH)@MjwQ8EBS{O!0!C|BibI1 zSR#XumfIfZky^{SJTSYmy-DwY5(%-iMtLY{ML()lc_gd~zft@76MHvTGQBUY`C?xB zJvaHdrBd?GVbdqXa2g)z3CDiD~sZlFb@zK2M=haG7TP15jv8a+b$-}0L(#v$&GU_k4FP7JnFqnfc{BvG;y}SIXPZ>4#HpLeuix0`d(oM^7V; zXW(|s&}~R5wt_G(^VKG(Eq=}B*BY*;&nry?E(E@dHQ3cx(sn>7zbK^xrRjsqM^{D2 z7Q5dO@U>#_GnPI@Ssq#-Iy2U}Avwy~&CAuTRYt(GQ5ySne<9L=8oR0*;&%6ZI#t=~ z)?hZfN!#5v`rt_eYb-nI*zs61E1I!?4ane`%A=r~M!i~9S(%390j3l%mT17l6*XvU1G+67b7FfPEWcjnRrKv{KMTfBDKK zpf4T6c9Z!FroOKI7w8w3%`?f9{M%Nr=NRejGpY{Ac|TGZORnwGeqmKTLiyyz@Xc<7 z3~9z>UOnrue4W-ejBvob6yNbQCD*r)&ukJIcWTZ+w;eCk#Py}r$EP(r!3vNPQ6mu~ zOB3)aoY7A_s7R`#BS0hG1Ci4WJi>Df2)NCp{7M`|qVtEgdc#^;`lIY1N=(D|y=)=r zE^o{%&lN<6R#a#*Cd9F#&v#(E=L79c*jkMi>&z+Abi4$^2Y>ymJO? zo0^5`HaC>z9v31goD~AX4Tqh9u*uq5{{cS+vRvnk;&VQ9j~qSf6$L7}RU)A0(^#Fj z9S75;hF`C$_@A5H8r^SoT9?WVI)6Muu~MB~RciQGj6t4N`Jz)N>e88vCeZ=u==|Ap z{cd#=KTN7fT;?%?k3h2b{pef<75+J>X040F?fKYxSCi+Nnwy~OrCO=Jf73%Ah8e)w zuRjU9;m~Q0_%)ES^w2#bsm$o;R68M(rgfDLz?_QuT>!0wh7T(vZ$vY&7fmpyMIyz7a5? zIeXMea%9pZ+?>2cPc`qIyX%FoqD(#3^b5#sF?#TL?1Y#)A70xWT!;6IQr9?m~2URvNxUU@^3e6qErIsyBapO(}q*7O3P5)%ggmQfScaQhjpq9$p_g;Tf>wXy{^V z4_Tmf?3tae7nrMY_8D0QLTW%y{_$EewNvu9i_6>N1H-?)Z4p2w$JmTT7Fl*=VDZwm z-yJCQUR>|Ds6{>azJE$N1@HC9Afrx0f7X;fHz~zY$Ai&lZo#eA2R2IgdRxj@5yTcI zUP zBWcb3vfTK)@Yb?@$qrHmVRQ8^urP)^-42E>UBUn1#>RYvo;#*&;TPkoM<-w`n*ovm z*k{MlRZr#096}%)^`&=*)=Ahq42s1n@SZ=BnY%v1vA!dtO_8y=^u?`mtW77W^l zseh46ueqC*a}7m!xUm3mxml{=DF&(H(D`qCoxVG-D$GjF>UVRlUv%|794-BH!@vmKn%w%@|yBkmWu-6@$3p zTNkXAeyK8&U?=ze2t~AWGz$i`uhs~Syv3n1Tc)gN5;>w~HNZ0X%ZUh14+)J54urn1 zQ0kEht~K9>HVJ^CAI35GJxT6$pFClB{~o@O?Ihu-$R-GjI5tho&n3yG*2{#UWF}e2fMvCthdU;0mOH5W zK6eQrTm}kE0jQ42eV|G(hs7YqaH>8kJu>!DO|UBLdOsLdaf?q$5SCM=nVCYq(@LsN zn;oe_H1$-t>iZ1$eMg!RfsU6FS?gVf_af2ZYtq|grIqqQ&2~w@CrkeqzVxNWnu4Hy zN+?A9vgSLN$-X5gW?FAuOgindJ}oZGry5y0e4d{KH_ktt{UL)b5PN@o({+&%iB2K% zY=6>tWx~EvE4}H;;#5gt973@!;mhn1)PSx5a=#PJzn~#>lev<+5z67V=+vb_^_CH= z6bqKMa|G6rckb(*!WPT~v)NsXitnJ2jk?vP2ySjt%FSdAw^r+4-#}={t|k6Y6`9aD zpJj|b)BxAT)As|HXd9M-%>2{#`03-USb=ZR$I9J}N4-{YYdspPnto2dmY{ZvUeCZ{ z=%drKt4t5XPvxkVUJuO!C}sn$P*5mLTYB(%d*Ie5L4an3b}Ot!jB z9_(66O-Rf?=CvI0h63=#QfMPrc)ZvBbToKJQn^i*BW~P$LGK+sy1Zvfz5&=JVK*+F z+NEme_L0p<=gQDcSXphuahfi?KGI*nfjdf&kZ2^+_45#@=yZd$-tAMW&V`P2Ra6b` z$vWknd{9F`S(NMNAsUonZDET@Ev1GJ1a1I0hs{}x7oR!0fyGQgUaKL4d?L2{@VPM~ zQ%LpBDR_W{k7;-%V3NNro-r&rMBMGqW?df-yLjgyYCOJfRnOUy_Kt1S8u}7)0#X?h zj-a|MHdt7N_4do(fZZQwk3D0L$M**XX<;-HF-A&X{4D`pE1!k~_k zMdC$d<>%&o>SnhM-S57^ZyqX=0t7Z}x<1ura`L;OvdT4_Gq5@$c`#5&Cs&tWr} z95z_Hprh&3_9Dr21fbn*3|c!#sa9Ke@0~x8p)Qgklr_!=?1f{0i-ZRViU@srb%s?R z89sYsvoLDSH~e_nX(sGKcfI{wVUJ5t-;1Q^45a0T^X%2xsmn@}av(UfG=r+?%YHS% zCmO|csi3WTw^W10xvaUYd;9conbXS+%JOO;0&cq`Sbx`w_Cq_+)|ydI)3R+acQ{UeuX^nj4q_-?v=7!Wv4VQ#y(vS zU04rPl}^B-DK5lnxNCmn{jFmYOMntiXVf$W##ircG{Pwm&DigfyWwFLFRJ8p#@~fo z>B^&@K{0S(njdfF#61wN1*b zvT&9*Uk2}e=>m8DX8Y%&Ri$L^ait@QBhX4DCWB~PW+e$UIx=oI@2tTR%}4oTBRiGl zpF@b3dOcgzFE1<<>QxPwy+sS}uN{Y!UFcme#DI`%u6-3fTGTB4aB*f6<28fr60~cR zl_>X*euQZPsl7l|1ZlikiuOp9?m2({g212QYHrslT)90fsFXScErmePW#`I#Iw|{p zywK47-m%+M`$n-e{fnEU#UPL;pC-}s7XCm12&#`b`OpOH0m{h(fQ-%iEn$Jk17!z} zjW@j02XV~iQ&opS>ST~%A4qE#^KOB)~8 z8i2u!BOBJ(KSTW%4^?rG3*k-m(fYc?Yp&&fl?L&ORq$i;@X>#~DJsP}jJdc8a->4C z%Zzop1UZCe3Q7R!1|Tp8k_Nw$12~lNmF?0KtemSaXVq=@2P?(VD~NQ7X0g3*$uPR$ zNiHIlRtk|!7*L%6;!(}F8Q*vaucwUGck)lk5l->q zn2i>sJU8>4-R?R*>Gzhb!%Jzrc(^(~+SrH8GLG{sp>=MWKJDpWgrHbPzU%Ic$!N20 zLkVqLRZeGU(0@JiKjBDO?i^h`(TjwU%VO;QuFd8gWbBhN?Fx$lR37&OIAqm0Y!Oe_ zCd2S}WHL##nst`nW_7&y^Q5p!s~4V60lt}wEqYj1tITShXa3Cspn4@+Gf*)z3KW0? z1U65=mm=f$m^$$sJa1{llNfVReO=FX%L$Y^vmSBN7>PP;Q&Y~>Pb)lXES_sFZ}B=w zdk3@{?fN77H?P>jOJ4LVm(P2}8YRw5-ywPGBe5V$5^Rs8Gl=tcPjfy! zT@g2VcM%0lu)aPD5Qeu46FGG?Uth}dPHh&fAm1S4udr<`*RPHt@>WLGI}^PToU5N< z)t^xEg}U;2$&k!IbSJXNO4Q|J4fxN9h&&3Q{Cbh|)^07IOYf;0@l{DmgLfM~d-hlA zMai&#bIxIDD--Xq&T;ujP-E3g_b{cUy#0X6#p?W~bP=WKg){#%8-TtjgizaBta`Ej zA)Ug|vf#Yd@eW2#z+|Nbk)8aI@v*YII5`-BP?^=-3CL*jfk&%u`F;rF+MmpgUox*k zJ{-or(X;~W%ZpEgP8vg6_2x;&s>+ULf3*ONmaVl#m)&piWBe!ys5jQ!7^e_raYHi4 zlVBDUUCZ`W%XMPAM586XgF>X9-)UXyE!MmzAyEp$pgBs2zh;AO?~BfeOC8u8Nd2~# zpidM157{yu2H2^oT1?J&zyp+JD{XimH@rD?xB~cQD{-t%*7PZ;$qZ|ob=sfYFrW3# z`%c>v@>5vtzx5;q*+7JZM>SN>w9}>|fzUKa;>FMW7 z!dXzg8Ez*jo~rdMc(IMpOuHjJB%z9Tv!1{d)HOJZqwQ~+udT4&Y?)`%-Y>qihVUBm z=?-LQO$_)_XyR@I>p#0T2{Pz`^)Ub|JD-`mBBJJ!l8D`X@2=s z=u0txD2_O}wa%kzKtoO0{ifSvpelz2ou*4~%auo8Ls`^c{2iYkYKNM3iIfqyO1W+; zmw-FH3MQ?(e12cA+Vkm*)kKxF=yOp2jw97C$QAOzr$0)5fD-E(6(~*TF%dQ4A2KSD z84|R7R0iwkrHJ}oHx$$30euy`N%d|`O=6`_1oRv6UC&Sm5|vrPsjk0p675lV#r4RN zGmaDVCcSJrKZ?V4M>13^54~nLs9QcG_5Djhx{Z&hsM2jNgaM}ua3s&rxLx)t-m2G# z$~FXouDa(l^{DGMbj*qVUbbXFbP57u;gcQXJUnn9gUDy~xLfbW{Xp3%fLTmTaH;j1 zt_NT+rV&MRpV!1uXiF3+QPYvXavrCSy)CEqLcw5VdAIN&{A&Gaq$O~qh&+n$of%LV z@Uwm3KZM(_Odt`{b&E|Ps}+J%0pN~ozx{p5rWB|&V96=tyHB2q_z9KYjb zzdQ&f=q1H;HHsqX*bA;>f}3w zfB3{i;z$jEF!P3V+a06qI|P(SJ_|l95DZq|6S7(QM`d>n1mVawq z+l@{(7+Jdb1^xVQ6iA2>=$&Wp zse=Kd=2>fZHU>Evy(R2_xn*};N2w-dnLd{c7wV+b1>3LZKBE9Va8`fiA+M4tKC=b1 zffH?ADg6#bA=MIn&C3z1jk=k==XYkfaZ*M_>bgIPuzjFSO zB*9M#XHBh_8v1RZD!XHQep#G+QZPg%AYReu&8b+!JEyzfuoin*{=>Zceco5{H?lM# zps8%s{TUKYzp}TXC>^KeUTO&;WDxekW}dqXeJp z7khNsCGQ^>g+}4K+%MI3he3fGdxZFe(S@(hxep+t>%#&0hiR@zaq2o1s}m3igg z^W*nO{eyq<4IqCr{${1-PV_H^IVJL)sVn-w-}OI#=JNvtsY&pySHVHLCH`w;|7kc; z8T8y&1+Jh5zYe?q?|JW_=h(#61oTYO{==~PAHNbA7zTjpK>m+o!1nO(UorIHmc((U zsF2(i|1}l=r`2bUpf_`GZ8MEl#tM3Lc!jwRwA#Eh=Dh!`o(3``%}rg0h5chS>@>!t zzlAI}!uSLt;%%of6D0q+XJ`@tZYNQCvW$^7G+zJA^87?XOOBu2fLm?fo&Bc^I#D~| z^X*#{4jBj`y1pHnQ7x$zGO{1uIJ}wsr$dN*VY+YCk~)@+uK#ICOeQ$NPk#*7RIdSx z8x^@2EiP|y0Fk$S6ydsHo-ns=w(OWujWih6<{5R!X)5Wd^{1s`ZQ%3-xsoX&Rog!) z3l(#}j(OOlC}MP1meaBZBPmWMMMlpLaPF6Sma4D(bUH$|AY zTX=o`2fN@ymSdU4-_e+zLF8-p_lS+k5?jBEVp8! zT(T76jvmfhOXNlD8|pwt|Dy}+3r-wEp$G}cL??pT&Vn+j6gB-tdBsdY>y@;8@4W2Z zS4*18toTbsL>dI2@*l?vV1;h}(qln~Rj?0_HfR#q^QV>Fupkk-i4~I3CPlq-sV#$X zfmM|wm5e$v6jXmU0ASO^Y8RO5p0H(5>9tc-$NT$cQU|T0FJ)f9r)Nzc=ZS&(XC3kp zlOSPkYG}nJL62Ad4zclp@(B2BCP$~5I{ndyCt86ifXW)b&15c(=)x3`M*ka2=SxKm z89b+^9gMQ4FZr8z>4di7+RXjCjBG_ajCGVl#nCWrK@wYB=$}xSoJ#s29k=JJAaLs4c6Ru zsgWObe9s2z1;=xhr4Tpn=^2kwrb$KfQhd$)GX*+KgfqP+>nL9nqz~O|@C?bN6uYgX zEVH?i%Sg!neJrp+T!!lwYSY)^$$lT_vk`jZFUe`G^PRyp+DNII9&j0+2CA*2U4LCW3~+E? zKE5{`wO9@s@-1(3?>W|*YWOAmS%}?7{8AmaKn_C+B5NE?i1=v5-~N4AelXu>DDHAz z+>d2a67T+Rhv_ncmO7PVj?UBFLk`x$fWFFu^G$3#*WEX3fQWP~BClhwO+6=dFegyD zTx?PFgyW_VT;4oHrfe0Y0@bN*Ok^_17An3N?%ftL6%IL*n%cbbI5CwfT)K0(UVOYh znU5?|Dbr)slH;y)eg!cfE6Al>>CblORvDsMbdnon!KW~vDmUB3IRAQsRq-_jXeu}r zcxEL6J5j0dVPWTcY1zXCw<1ejbB@+W_$;GA)$te*&qC8Leol0gvDXKHdcg<#C{W6B z;Y(wM10{;19o!gHI-QoBl={!|SplnTmdy|(h_AiRx4#HB`-EZ0?>T^+#&~}hgIn3`aS_1)hBUqwMt|sV^{|f9EzL}wKwh+$J4`ZkEOOIC-+}MeEo>NTuHh&13 z_75>A=Izgw1j?U5aC_cFr1N;5&_4l? zX{83hbLWJ^RFZvp+|kWA+_JdNd44RHZMr?>wV|5-{@yDa#fWnJ1uS?ejNJG4I)e^_ zmP(_^VJV=&|Agwu`2FQow%SEP;;GSm2d)ji^H1r^Vzrb!qFJooQkIXm*pRcIlGo|;nDK{>qHR{VuuH0&| zS0TSd?hIVNZ=nLh6{SdR@VvzqED11=Bb}M}IsNm1cJKJsYh+#`gTdI$G3WjxVRG@7 z+YL5fxVp-ZboOR4lU>Iq6{A+0^Qy>H{IPm+<#Lszbh|~{v9T{#K*2&uyP5bWEg1ic zH9{-?>_(K?czQAW;~;6u{jL756OEVNRONb9esx+&3LlNPklfDB-XS93$!&c!c9u?K z2o8dOm{#NZ)20qXXfD^wWaA9p{mLtncfa>vKSrRxpOZ_8gyGN^bbGD@x)|87q1DB3 zh7zk|dSyQC`-f6{cDzceR*(n~#n#}mS}DtI_ou}I+W+}CvsQCY$R3j>&)@T+NhQAc z9s2Oy4R*LhrxoXRyRMa%?fS@U zx|~p|~9~tzy>V^_)zpTrLTZQ=bX2VSwV*R?2Kr_FXkBQlYqPpTLcV zmF42|F`e`1tq}4yv$7%5K1%6N8Tr(eRh#VX>Gl;4M{9r3-dQSI@_azd1A=@7HKjat zr%Rp;p69 zydNS^$YjgQcAr);htz*M|LHL_(1OEE%tJF=PW|LQM=M8c$7kQB-GOO~sWL%U<-W`) zfiwt|fpJdU{iQa~V6T17F%?~dg{P$IT?dRJTA^piEF?$F1DvVWkptnhHz7h&-hFW% zaj+L~s*Ql=RSmJ!V({?p1Ey={3d2GU#pJZ@l*IxDje1!Wzp9QbtEf7XE}`Iaz-m=DHFA02O#ZT=%%h)-G=$l!ZhT_W?6qcXY}~+`0ACd z#$Z^)ztRbDso1{4TWpWDB$8agyF;fii;xP5h?{#tRR&`P$Ms&se#*;cdtN}J*Cf|t zeqsxskwiEYB?#cs@Hl-ymYXdc;lgT3k$(_@HNcM{QH*X#bt1i9l8 z;gtJTBSwn^(AvB*S)cZY=)581GH-6{lg$`QV%pumn!gvjU_+}M_Gw2E z4LtY3oQ2(2DJA2saxk11J=H-YJ&Q(4)o zM~{5L=Xt<%H10$X3go7DB#eAT>yI73bu?EHX4Phol4Ab5W?Z;!+3_QBb2 zmFiSCb+!34(~1L6Y{vKGxjpeL%GA8{t+eRq=xb z4&&8rO+oYxc4&*NJ+_;}zSz4)9--zpeR`tN_^RnuK$c&*?<jUfKKZAlRU)^sK!Q zp>K;7i#d8Cu)AC&Xk6Yh;sDynW{xjpv$G9Okzb;GZiMd#n9X))9MMjS%YUjr{)jh$ zoi)^ggC7;kCtQ?c(cPzh?=)#V@@}uTbBu@0VXNSwFQSlzCdE$k3Tb-R7czT&9_&lI z*EEVmJR|$Q#bF=V*_mlJ6)(g>;%VIwq%084ye`? zaR(=9=SrgILv#7ODOZbWF>PnB0#fWq{0{xh&PT{)|iR zL>)h!QIY|JGQ7g+{xf7eEES?Zuxa<`+&_$~htvH>mXTK&tN@(j>b~J)q2c3<%%QML zt+`#DLSR*m$s?q)EKL$`cpHs^SbU-BIw)l{#xPFdDHX=(b*#xr>io2GjKNPrg2 zPJrWqgiEj|omJP12Jk<@PbWZgBzzBHRVs8ZzVynuLDyY^I0EdRFGxboc_o z;eLr>I!eGDvd#fuI!+V1V4O+eKiK2a=w3mUYPOGyjNI%k$me=LjS*;cS0SEn_DzmY z&BQ;Jyx&vT0{CT$Lfi`>6^@6VpAa3`EXQTQ=HciL=e>ti{ZZL;x^J=j+7f^Ff4a!p zoM*;7YP!s9{FMu{!`uGXAH#kYjbLF+C(1T0x-X3s3vI`DjH(ZbW`X7oSnu`g;AMCco4tS-_+mG(o zySxka#8Jqzl1l<15u-IW+tj&X!PZ!UYeL2C}aCvYrD{z|*8`%7G zbSGY8aht(bas;RbxT#usTh1!D;;(Bs^c51gulu1zVq9BLDs=>Vu(;O~*#X#JrTn*8 zdW1nT8~EW6N@MH3!O(C7IIu27AGY4My>NtaI9*&}0|SuwQqTpIjZrmw1Wh@bui7K3 zqv3c5s9@$?m$b4Zw^q>CqTk%EVp{2!I8~f!HO~z1V;sU5Dxb_tJy6hA!fZL6H3>D-mz-Wy)aHS z-bJ~6m-lxYcw}_g8v|y$A^vQsz58eTZux5lNCs`@sl_G&ZihSQ(X;cU^Y55x3;UXw zf|sbq1Qm|VgaL;ux&G!*-#wDoc$}UBIK}5WIhrs}^rFBs9%aT6@duh>ecjhlPXZSX z5T}k!`#KKqniWW)t%$L>td^~K*_sG)=4%z*J2PoN+UXO?y^EJG*JtW1!D9n8bsu4h ze$kUvrHKvL-1S_3Sgv2S+&CqTBf;W4N0>fE+dBT)w1TI|m9o=z++L4U2n#FRsclRafJQKjg_r4sQMf#C14jv8Z)Zlz}Z z&eLZ4X6@Cc5%)qn6}hOS`B=JvOaw2MkNep@3hnu&@dw^@jNl@@>IS{vf4GLrpQZ*v zC%SinUaY`=MS&8DRzosBiwhj`ok#tM!upjV_vk#<#LEU;JiON_%2Lg8=sGGPLr6+D zbMkGMLmf4{Cr;&y&@0d_d5ZW z6~UXP@;lLBN$Q+CulOjS}-L4nzrbl@&F6bqpg~D-^16`kYA~Py>=z4p` z3AkKLc}h9dABa?3SQh;s5uflF4>FDVkM7#|cW-xfAMQh=>4?JdvGYEAtri4kM14a* z97SRH^YTLljUn^70x^#M<=xMrY0DPB-0D7F!+o5(&cy!&c4qz2-t!xew#KFzbKr`6 zYr&xbQb`QuSvfj|lH7DQP=P*+wJk?2H|&VJUvX<)`>m|D^v!Y|0P!}UCC8)wf%MM_{N_>d-EDA+P|fn zmn6G4Dx(B4;+oCZJT7Zv z$h8*R{H0xLR`|=i@4fM>YwtDcPfJLW9XKJa-f_9BG4bPytphrt@^?1tA4ms%)^9KH z2Z<9g=_l8XsD69KS&!7A2qPco<0wk_4^9oxbzP&0-^{A5;D^~fr<{gR zId9VWsiXuieVWKi9DDJm;-Df>MIVyWZF%*sbM2>GChgq1z(yB3&#axzl5pavoIE~D zR-r8boNS|Ok44Tm>ssRZR(YvY)Y>c(NDPe2E;j)QPoBv^cniYLYAA3k9aIA|Sq7^l z&<5HzetVt76g5c059=r34#iX7hOlCz zh6YJvrwSj?DXO^KrBh3*<+K;X&B77Nr~OYW4|Qe4^ON&4N(Z%J^|%$`+_Olmg%wayM_^_x%Zq z5rJZb1kjf1>`}3OsTrZgCkP^n=VRett@%i2`ROA!ZkLPHY4(cB$`^!#9-%fzG)Rhd zVVF^|SGAXxM$@BLvT=g4l&hSa60R;V1u>#GwY^*|=C;!O^H2V+y>R>J{NWJOMDKFA zjcwD^e54Um0Y$@wlU)Cw(*(mb|5&B$X22nc_lLlFRWhhrw)b9ydoMNTkvsH3!@0MfC^mdeM z@;><13>J}v#89AK?kV8o{bE(k!G!w6Xc~Fh;0lvzU?KTMie}YYWtoIzoy)a>T9fGM z(|V_YD>OO}AxfZ784aupuYX7D`BC9lB_F(QubYrjM(3=zn-Uzb5~{gxOTfZgXt})j z#MxSJlI-P=l~PwvyC^7MZ!XTpaLud?F`8QBPk&U zT4Rg43^$!ma*hgJ=n!Njt|fR~iyTr|K@s`4SzHoK7zx;5WJlOkKUNTud;xjSgI4Gf9`Q`V>()FN>vF#<-R`XqVAPO$M8+`DFCtZ*VcWB+h`7P1PDjW1#S z=eXfRja7JIKm}8aDIT1eSt(b{!d?!)rkVwGF;E5hwb`ACDuu8XC3WgF_Jl$o|zZE6fKgoX3C+s%v5~C97ChmA4Ke`sA`{mHS2Bu^lFQ z=V*l0CT_h@ke2nQOSyizQeU+8TQ>*s2$TZN)S<3>Ho82XG5EE3wpMF^Rq;jOUb%5? ze5p~Bj+rkK%(UJCh%#{mI|=yRolmvBG<02`GU>VoFdwqaweTa)EZ_LcOqw@maHrT~ ze#nEYy{_>d%4^)GdJPa>xZ)Oi0z&r1yKu+oNY!_JmSx#XD}Map?RWLUQ<*0eu}ZOT z_0GRc&T76P`M3~gO1Pn0Z$h2L#ud;2*ltStUdGEt%d|apu!mcnd}QvOT^gihgug@1 z9%SGMD>q*BZuqzX5B8Q`rx`xX40!GO1H6@r0ujlU_*~ZN6Nl)OFs# z?Gl066uiiAwV8F9tIMDeu@oLzF+v=js_rea_%PCKw8KlL&;>vEEIqQ&3|VY4Mvzx0 zurOcwEttV2?1Mb|Ii67`p9F&;9BCq>AD=NsSVl+;T?zfEe<}q3^Y9chn)iTBQ>OyO zes*70#JFKGeOV6EcDFu<+bvm7GFRNhD1j{UD=VMta^DkWiv*NT%{Uo?LigEf^vPuR zCX{mePa3gY-IO3y&@MiiN6iNl=|q()nzt$hG+5+3}_wfL6MVO|d zWUlFLzZEJjJ$=#vC53Rez5Qs82wT{%n8goSmx@&`^4n%jVVC~OBdsg@N|c*ru6WyM4P7~FEOIAgzi=hdWXLRg<*oZa%Ack$PZ3iCg$fi% zSTHW;D(#~uM!Sgh#*8N+hgU@44=J#rT=0Caf>@!szD7cp=d^tka;5u5t1E5Rq21up zk0o>rR&0yvmH*O`d{KAI`72X2(1kVm$^GhRP)9oaDW-kzEaZ0v4+#&sqk~KeQ30~g zqQcFj-h4%@wOt-DXLH|t%!b?kCbbBWwN%@9(vMZQ99T%k3O<&Co=hH7YRNw)*oi*L zd%s*Ls+y~{M_u@(aTwMSPYBpg=Y z=OlJN=|l@=KK>2^3kE1XXb!4B@(bIn8y-Zk9}s-BUgi%JalPX9y``uL=Z8Kt`7T=C z_EG;riBuUvpjP3&>Z9G^wHHslP0P|3 z>(BPq>IMf$FY$zHxu6VGb-^&on3Hw7F|1^Zd(|s?$>rLU14=cQ$BMm)h*LBp2NWuA!*?6)p^sUIU!Mabk6bEsn z(O*OO?kp=e3fwQRhAo5-_ARU`9npuG<6QO9tqVUNz=3~7r~+yJ{FFWL_DF9GeMLF< z-St9q;6?9!kOZkj9>#^+-2rL5F8ep9)rPE2bx$S9^aQVAZK3rCx_Vd{MsY3YX9ZAo zxGwHKrnj7m^~~I^mlDWv2R^4zEnNDyIy|@B8`VFUth~zPXOeoT^qkIUCO-s+G5DPGbO}pIQq6c%?X1pto5d6s zfIxvtj`j2adI51Czi0&@5_G`DQMsqpDX8?VYjD^dZ=ybR$1YP!?Or{$dT%QNhb0CR z{qo$#kKDrFtyX!QUn9HAkMV`YpyD7Rv-*u42Ba);X?z~SotK08Y7i!8aZcNJd*=*C z^Kp3=oDe;iku1Z&DlV_5#2|zt3%INs3#H-``X87}gw!6OTA3grRUJ(A4*dq^2h^Q= zhrR!Vn0`P2Fb9Je6rGy0JovXe_!EoA25PO<>!i(V(Pjs&k>KaFvm^y%Jy#utl_Pe! zIsx^2K5gxCmt$-dv-PFkJ9+hV+R{z_U=$%k37OZe=SA}-rqo$J zII>8*c%6#6C*_-!O;^MN#B`@S@2+ZLea~B~e039#EdW3)ORW)vA`&40JDR4jkSzY* zyHt$FK51>fu>U)iEMehC>-M`$!SJ)Ffym3T#pf^LvoTu1zCVL7KGUSr@`Q%UQh~k0 z`+|9rs#4-dtjPLg?;B$hRzBYtj>$O*dDel|dgTE>JC=~HSQiHB88 zB9?L{32J^h^nR;hT-#<~X+*fLZ(M}=*I=I-uz{};lNBcS8(!HV*9Avra0XXrX}FXK z10c<>2N1q&fxdb@p5p}74;swx!st677HZa81*?@h*oFNtUdbr%OQ(l+TCt-tP~$Ar zUN)2%%qTe?*9f?GEc|y56a8{yB659G)0DHaI?XQQB^|a#Xtr}3%iZ2@!-t7|NSRR& zyk!ottzLJ9`Vz8h>%I*sQ9gqQx$4U~5hS{w!CKCE?jj4XMudPtY=5Km9)8dy21e06 zkvMAJW|`^Et2-P?o|`;I_sw|IH*r_0E~fGfcTXOm*w8_Jpb0!=smYuGf#n#>?hr$5 z_z&zvs{I0=AVj1YEV+(qKa*+8_xJ2C$)`*th*zGB!S`sZf?5qvqwXI`&WQ!OP~2+X zxdeWyEssvBYtpk$@@cktL80U5ARq{$XDi1o+$o)A$HQf{ZJJ189za`bs57FPr2xeM zFJ;*qA#Twi>8S~0hSqUVs%0&Ux-6_qltXTKo8%o;^If?yy^(x>QP$NHGEL} zv_;xZJse)PWLS0GA4@L5s_NVkaUSuQcmsX92`(kkr8qwzR|WtOiVkZjp*G%ZE;^qS z>=%8AKI!N>#Bu9=nZ~Q3jK)I+Ues%$9igZuA6&yo4z&H9LtAe{n^l8v5nmXF@0R9a z<5;XDoMX>Wd8B9j2)|Tl%ydYNm`}{eg0|_(5IpfMti|B0rP4+13!wCoUl=+f`K@X+D=ILJh31)l1@b34Y?rz;>rbne1hj&0&vEL9^@zSO zCzTFl2;{$Ppp18>)(tPuUqqr zjhmzcl{bmFa_=BqO}`M(CrWL@nT>wfr5(_TbuD1G5Ha}vE{6YXh`QsUn&_jd7MI0k zRA3Zo$nt)ERK@J}+jyp*-54d(92;jDQ?9d+j&MS2UpQG|R=&}vin6)XIL+6}d+&ke{;oniXs}g zs=b-3PJ$#{%590Ko3S3;b_EJg&hjfy;e*3NE$%_}g(VUk*8rG?v# z4I3rDQrZ1h)R|L>2nG7Jfpn00&jHz7bvh{Sy$X=i_N0k?v}g}wOKj~}G#L@jjyh6a zSuNw5ZQ|-Rku%imRU%xq-=suW`>4EqU!bn)_ayo-|EjoB$mWvR??s*d&Hm-nwow`A z!hHuH3?vvWud7ghUbM>h&li^{N#e#&E0H%%)3Od}i?*S8o=-BB5~6+DL$FW$EFk`D zXUS!aPTvVfH3GWq>#DS9wy#r655yc_9X(z@pUztB(whr$9x7Div%UKHhiIE{SCAf| z7jM5+>OTw8Z+7a+ue7A5!~hj^sV?yy&C*Ze#bpm?6p23RI>8;ZSP|_bC5SCNnVXgW zKYYCfRGVGaHjGPgcW;5>6o=qiw79zjE$;5LXp0ngcXy|_ySr;}mp_lpJTu>{_g@PZ zD+za!bDy)1?0rc#`fC|-8p2h#w%R6?!wE_Bh#~C8`AL@H_{K-fB(HpH{GQBdtJw>V zkjbZko<^xKfv+n@T1A@EV{W8RoeQ$q+ip7PN+175G)h8*2N@c&SVx8|(sU`Fb*}Ld z&4&bYW#Jt~HEA(Z>Py~yj-&m7_~vf|uD-3d^8ZNiWsxLC-r%iq)yPe-cl}VxcM320 zVDx1!-e0puy_j{>PXHd@$>olRbC3Dy&ja=IJi1>41>l{qG_FFTnC|PdBTZZZ34)f} zMV{$6H$LF=DBMqZA_OV)O80G_!h*_s{A=dp+>y*yT{g#L7B_Xv0JVPCTHQ`kUvl#* zgemI;_=8r*z&y!{9QKHP;&^QZ59 z?0bVDfHgT*%92YJJpBM8gB~mwQHeb{_a0>q?`UG+?vX~I^4g-LpsuIg7 zjP1cPW^NOt>$2*=V*#M5?2-LVWS`{oNPA$n9YN$FC3`wlxKG|_D}O`<&*h|EfZqN>+=9u^XM74qFVzEt#v14&h>QS$sS zgkv9IK%QYpK;W8KkZu*9*b86jH_r=)`2D;l&^M9v0HO$V@Je;ICw-%m>LSZ-g*5uR zww=>k)>bshEP1=J&B=+_&A_UfsKXIi06z~6{j1WX106Sl%S!*a#oBXH$QSa^pZku~wC8rxnHya*d!31jWmT+a|*C7u7iIN5%#7Jfxi64FBunjd0N3O&B z6T=N}M<>s99KZMkJzu&v#J~C0_>*&B{@AFhxi`U>!tje$xl*1^C);S$s5Y5ub zwC~3N&JVGfQOKC6EOOTbJpn7hkYqV8J$hN>b(>9yI-|-@k%psBZ%r>d^$efNrj}V?Rj``{f;p`-{k@ol46D5(1CX$98=U|wT7crdYzqlde6Tg9~CGq*e~#+YAhY?(?8r10J_FHV)0tS_h7AQ?*W za5^o73g0iXO8iXwXSVlGkorA7B!guztWX{GNk7F-*uOgO91`+C+=zm~Jf&Ov>--J< z(R*CE?rfFwY}lsn-Zie?!->Ic!6A4i%%jTNJYj;4?mL4~&)M;f@+&tgKYtQvl z_{rceq4Z+(H1z88e`4EV&X!G29K#g7i{3P2xHI5nYqAuueMUt#nD_+y4WS8U1DRib zTSP(Q9=SfWhv$=P_a|3`H0P2?)u#Y zA3=Nh+j!+RiZ>^-zIXU|Ob4Tk+r-wo)bAyELKizdjH=e7GpxBxFx0td3Ihp?x>joe zGF&Ol9T!rz-)gC_9f%b_^Ea1ZMFb+f8Vo~sq>rNaho+KKzl>*jlY^-?8x*_81Mk58 zLdxq18*nfmz|apVuNV$9rK)Lrr5f9IvrigT?g=gq5HEkmbD^XDoF~#ng}uYTfWiFJ zl+!#%#0o3jON+FV7>=|-qY&x+jck#Fw+AH>=|R1&GxXxH+Kp$# zx(!ufYQKu=f_y$0^86U9Kp^n(K;S3buvc63jr3h&x?s$pFn)ebk22bRS>(w_{Gtcb(j3`a15W`zkvVR&m55nFQlm@II^yxi69 zUd+v=!~I�_pv{CwH>XJ)HV&g=kG}y=(|Naq}$0oWFDA~jqri*On*Ls@3SRu#PY@7g~8zf-(oHeS+8$;A(WGBIVw$(m9!=~S6 zkv3;5dG~PqlC|yWFdQgHw(`WEWM_~M3(~lVL6Hsm@E0&2oWKeZ3h&dvt|{yPtcBho zr7im(v<&cz5cz-}nJ)^f>BBHl^^EGwMHcIc)-BO^-yP=7Sbw~u#3XG_6hJ|QH6KVu zQO>xk%-@@nzOi_^8DP_SxTlgw@6DMEQeVGBoGs^w*pJuWLLu~Qz7 zvQT-?g*#k!^J=~uq-wTOZo8!7Sxd_<5zQwi+^#V8?vSvH%-8I*pY=x=;aHqCZL2T{ zPelU^D)>FK(<6g^P)^qx;T%Bzs_PlM=`)!X82H`e`jthKG*o`xW-vUKMAd|`HZ0@3 zJ3~~HnUkFJ5`mewUkOIi&7UF0TXFHJk(D_Jj#0G@KaKuSbPNK?diCqdw@UnZacU!^ zUYXqUfakuTZFrSWB(9|eIJ=hB=?w8NRw5~L~O`9~_xo3BOJ{F@DpdzU=BizHlLVne*(vXbqy#?X$n#cdGyF4c%L z7-pf23$kZOvL<*t;N*jpflBG8=dB^F&_nEjAfgdXUQyIDYPZ3FT=AycwN{BHO;23X z(@Oh(FI=@AfvNQV@aoNna04I)chHl{-s!R9Ytdz_*WYms{?kc@;3viJZkHlxB#r&g z1da|9zzj7J8l+B=zan@!3*9^0xs0mZVMydA{LvNV_xtx)m%k$`YCj4Wj+$Q9eTwCj zIKJ6eOV+LaNr@m_9PpBuc=sH?cjw*cWl@&-8 zl@KOyV1K}L&!`6ABJL(~efw7RD2-PyfK|rgc6YW1+eW;2x<4N81hE5c*Cy$^?C_(wTrU$rAtr*wO*-nuO3-<|W)N2%q~rY<#?Fd_Y5y*^?gi~+qQHXEDx!Wo zPMc9D{?`#cHO$S+oc$AM zU6|hyS4CqBKOK>N7<_OLuiXERrGVjw5GX!+$(U+1O;Z1Te~6Xg;1*|Cgk2^>)%}PP z)r5_(ni%puuTI^FuWTlw_)sD2u~#k)W=Y|ME>oBC<;|A01^@*qfc9X_M3+*sUL*TBdh}F z`9{{dO?X!dmO>gGZs_nn20@`;LQ=HeXNcF#3bboKA9uIyl5GTE{i2mi#vQ10l3jGE8dlweqM+GCF$}Kvm{GMW<3ICW?(q4z>E}aPf{_PhL z^!}J$X062wrIj zTOX7K(84xZD0D;cbUU8IVnzH)EUX45ennFl4LlhvE_hf*3g3=F>kN?YKU;Oc9y#Md zH#|r6>e~%aFKfGu9?%#0k+SM)t7Kv&aNGJ@D_h(6WT zQ}H>i!rQU!dy-oi9Z(1tGj8F#0ZLVJF;||8WR3*9F$8i`-H%jZ=8`7tQ?LZEBC}G( zgjYQRLkMWHK(pXLex=bw+^Ka?_HS4KdxKt!NyOQtcg~)L5T;8=k^HhEw|a)vpS8hT zA81Gl#hbdshF6kWmV9VAbKu?bv}wQRct>9c4Vi-a8#VdGe}CKme8fT-6T?s0`{z^l z%oOM`cY5dA6ek@{z+J6B!4m&NeDpU66M8IU4Kyt37@^GYu=`bB*-*|Ft)X5)s=WAv z)QSI(Hv{toA>BdlJ^r^Zp|vIo|FJ;+i~aZ4&Gd($h~ZN6N_HRfn9PUx!0=;L0wUab zb6t7P53dvRk0ewyMklx_dpEab6{t2zu{>2N*I3B zY-^sc9L%7YY=;f~^Y`ih+718rrC@qogb#e(OO_Es^;bdIA7R5g^e)|Z^jQBQ z^M=_4^Z#tf3HbzrGc_M@r}EGD*T;o|^j>$b^41I+nEiMB{X2C3`VO5yNpk17gBd!T zxp6(zkAI!L{J%l0Il8UG{%&0x#x#WNQrP-8{qFy{+>Zq2(PUI1DZ97UQ4RG^njv~C z81s(;YVu1~m%^>W`S%X;KRf)-{v>BX$4_a15l6w|WciuE{%1$uTO(|=h1{jk$g-RB z`)B9=2f^>ZKRE#*HwrV-42Oy4ec1wT-yl(f*(Kz-mePvEZBRtW0`-4HL zs|pIcrZ+k;W@}&<-HU;%v!dfWC*II#(j1bOTMu5%&kE@0U_i(d+RW4Q;55)D>X;d{ z)|T^&nPeW3_r%xz>3WeZc>9#k^=+8N?RqIg(06n4dgU|n4Z&ImRlh7{Yk02!`Nm1w z1x4-CY`;LGDO_|FqNMM+ZIZmR(^%h9M6U2E&NaUBd!S{H zYP&YarPE}P0132K6@d27o@>uKZl7$g*up*~lqp5~m``?f^rBo|n2l%_u2~4TXDW4) zM?=6x_sJ#8>w4bie4aMLI+C*ij_ZB_UA5eu9gF(Zw>_fdF#yC7U~&sie;2W_+Yd27 zAzHH|b8j_LM=JVw&A(mYF&2Pt?4-6RfCLUSKW;d0yQ0KRSoK1@dv2R<&IhT#O%6(H zxifz}c_{Yg@OTDY@3#k%eLxdI5!=h$G`1KBvxm7Hkv%x}G$x(tb=`S5dAN$6>Ls_U zfBsd({i@)T*_r{sK zB6JRoBF_0?Of&g(2YQLyy%bcRy#Ti7o)0PmXYT?fUn!rX_aN)xMwh11Mty64H1dud zb5*?Le$dwu-9X>|?wK{{5MzpC^_m$r506GRd7I{|B-%$rnzwWgn6;b`!ARN%>zmOe zN!hMcQ+coFmXZBbTTc;y4mmEgeA*Q7m7SoL`x$l~6-}~y18ePavL<80cXu5d>f7#j zuixT>#9{l)FNe>c_#Kb&Aj9Mtr@y%TN?oLj<)>=OW=Di1OKY&w4l{)RT5IN5xr1|& z-lfy~<1H^pE)^zW^!sk`MEwq|57lt~`VRTZC)DSAOs#yCZk-Dj3Mp#>;Y^XdV7-Mj zg=mP1y;{YxSd)Xv3#|aJpV>ngjqFjgvT=U81qa%ajrO#nyl{8G#4Oi7EG46Y=1TpA3x~Wgi*rx{9@sdpd zCHGH<=8k-h+|*2`_fu_jza7)sN*-#jGM4}=m7j23V(s(1qPjKWyS|!*U`0*MbfpGg z*sk$61@e|E18W&ucV!Ofdi z_horHC6If`OA5x#f_#->fi2^>rv}JQN;_ejo0coO;L5Y^^1~eN_S*1>gJT2pBHY~FL)?S!DKVPVn zeoF!<9m|SfGh@ySxv?x9t88-!y}2>!j_aD-_9*}d0XjeqeB;V$AeoC4z0z_*Uy6A( z&ykC!D?jvxk&ezi2y4ZJ;qx@2^?t_*wDeg|U1EO|&RZ^$%!nRRFO`CHM&>3bt(1%Z zL{Mk0li{%coJib{J#Z0QKtH|*W~(T+19mnMwhG5q;|Lrr=F`2Mc9)!I2aoS$ z)TNUEMMKbureiTt<*SW@>H0DskLDE%Rcqkll0YwhSveNNx2q~m-L5mOlXuu$@(eie z=|(he&1O^Y5iQobcj;CG9vqZ2GWd#))%<12?pI#~UeL5!y*PBYEPg#KvemdoM`82L z^{?@n`$Yp0!**pIR(#K6q;Z)~fZwSRVmOYurUB_8;fD#EJ;6E2vvglt5+W_O6G#%q zQO`=Kh-G{0yuk(^tc?0?QD*X_qFJJ$O8sPlKG}f8rCRgcv(WYoFi`1(cClV2Lv0xxPfF2aHt~HKv$p%2 zV1teutVG57LQ#?G>u=#aE!2A3V+Lov_O6Pbj2$=^dLLat0!Y zevgq{Rw~h$j-*R|f2-i7oEifb?vbNce;QHi;=>0It}g;0a>uRv zQ%3D3HU4GtHI~MoY}#gyEDh~Bq)@`1N7pJU|kL)vk z%dCkZuQGS2^>@riT?|Rn)0u|GAN9{t>mP>_GA-aia&9Y8WE@{+d&bss%wrjKnx*U7 z=;q7|r!u&V0|R--t{js$1ks+uBa8-+;($1#~ms!4B;cn8eL1=2ZYWOf^0t)?oYPPxjm92?qx z#UPpkEmAIo+wun8(kdR(vZ6(sODvAZRs>2Ud%fWmAJGWPOz86!i1X4TeKiwG;2L-) zZ^ctI8`LMt>q6nDe8*2KtFQ_dU%V-i0j;`1!yhc{zB4Y-^v)nrgd5oD*M z)wU&EJ{^LPeyI>lsh6T&HKuARwe^V}cGw{SvvitL}0u|G8Lu8{*XR6S6Ayb>mi-afT$3Cd-z3_ z>5YV=P&+VNJp70zsUs>Qw;fvYi(s$BHJQ>0!gsHtO$Wpfvh|j2~s&VYMJjnS{7tq zsy?W^e}z~Kiuza%YjJaM3MQ-V-?*H0=fPMvJ6nrl`4Ekew9^(VMqCj!SQj9HN$11) zC~^B|FYsvQCqYHLbdWV`E1yLlV$c8#6CW!SJ9uKd2aDKTukq{)kloEifrf~&NA!sR zh%CM6&z$Is6a6*74tpTxJTwFR^_lzx7O^$Wb757iDuE0(y_hJzFZ!EA&(*gvH&kl! ziKbGG^0;OnoxnEm<_op&I`WBNsz9v+){WYtr;f7X+CnqG+fLAaZL+O#Fle|}=Iy{_ z1~-kXEc)c2`B`jAU)DYFeLq`yhGQgr!1B{6w+GyXb*Cp%$hs_eKE>f#E)%tSO!5Vh zgR&?JZ_qDvHXiNYEawZwIEoY0JyNwBo?`kAM(PZkFkjoFi$oZANH4@;!X zv*1Hs!XN^(^Gt95yCi}#G{V+x*TO{{QXQLE^YKn6{1amcl)yOrurxMbR#YDLRxXUUVB43Y6aM5BpnTpY zCuEqx3~S-A;n#&31{^`9KC+D88=C4~1mHxJ1kJx91ZUqEjPlAmva* zyfgYoYGFWfUr^~JiyLY-5uB3u!ok}|0=(9YiN1>I%6Lo9t=5`#rf%*Y1jFC|9FWP~ zC^sTMLucD<-0vC25hVaDFzb5ZaZ#GKTLwOR-4@g9=LcO5iS}dfScW)Rs4RHik?gw5mV0H?XR^GS9BHE=)8J6GdP#bIe^-=%T#H?HefeBhqJA064lpz7 zy-uRW=W=0KAG7kHyVJGDK{ZLqap*C|Oe0~ZuA8UjRUnTY#by3#J&P`$j5C~&4Xs1% zCI|*$kMhWJz!*kIZ~;mt7Utlxi)BBjZ)Nk&lMNLhG9T1ffW&R1khZWHXH zJlxHxAw7I|Hgtrzi=BeAPw?fw+Bo)Dk6onIuhA-gq=%~=l{8F-%7Cbx*_!Qlr~*Y# zFF|T7`OFpTt#a-!TPI5>+A}^*q;?Z`()|uj%ue{8+LH6Z{;)u}ak}U2MG_vDY&zrF z@1`p3k{u-A+*j)Sq%qBmt=V~n;H2w^@Z#yUHHsqV(2Pc`yI+?}bN1&t<`pcm+pGQI z6P;9eV%S3oQQvHI)kyj$=TvoAbk#i*Gj{zk-6E|5xyS%666bLSfV*?<*Ob8vzSoKV zIg24RbO@-(qj}0_+zs6*e+a-bz7V@-7sW}Zq4B|W(iOY!oLI^}Ra6QJOlm^+$F6jH z4);!ECFsYy(w@MD02o%pb?)JRa)@wbf>0 zonS3#)wVzaBK&jVv~Q0nAfg#iC&WCO%s>ztfa}FLYK{N=)_I1MWpDqeWdOf_O#o2H z+PRQ|H*C3l2;6;G`~iG_peemS_Jkh6n*BtwAmNdi1hH!~x2ym4YP zvCKBV`c8SDHzJ_*qLr=TEryxNDA3kmbuI9Yw^c}+y~y@z0L}y)rxOWI95z$}9tPO% zyq-X8#<+|*yA&9g1Vq=Fs>GKNAR*{SU}$tPCl=hw`uUSc3P0f5U9v-g1Wwy+T!fDx zd?QaLR>_@hdv!7z{?|l$52dpzeZq&+i)2pcci=5{fMty#oIju0?}0QoOtpW~i2+Pd zYk;7UJgo0?lZ@T$t_w)FQWGJwev=1aelUBU^bZw`TJkdk4Ov+QQRpV53S=sbqG=Xe zBnMGKl1{ohq7r%WG{ShB>GTo93lilzp(vXz3tw`P$#Ym%3wCxX4jwDO*A-W_uc?wW z+FWxO^HRt})7=8RFvH#j*+xgCQj~7luT3Cye*jF=1CqkN>%0(?RZpDy0yz65Kxtm0 z{;?o`4~I@sQ_fqAM$dKQ%h#Cx^N@vxx5~?MVcr8j^C^JZ2tKERMH*Tzer*>$&h+dE z+~PZ%&ST#dR&K}JXUX3%1C|S%dN8nS#4Kf4;S%0^5r;FZUYUWA^gG8Cx$PQWe&%}J z4u}&cJ#j9Hw+Yn<HLsD?<4^tluIS=1 z%?DS?z-7ndA=`dBMsH-j&B_&?nnO|96%(~5$we#lYC>?5Fgdl?)v0{*1xBXx6UB#c z95s@$w3W$Fc-$?XN1LLs=goIaeZ5~d4q?V^2TX~6R2_l&2BibqxYZTp4UiK}ToNg4$f#-n9CahXrea)&(1~5CL=C+%|j`OFUjXjf5s#^>>$c{O|#{ zJ4I|TMoZ>rwRbvl%cKJ3t!!WB7O!yWnUxLv{j;DR96o-AL|)1y`CiWnrzg+|k6$t1 zu|5f!#)pP}Ze+%ffE5l05}aN}0A$1)h@74vF6>ZiqOZN#@^{VGNhPAT26+A{P9QuY zg#GqMx}!zK3MALcOe;n~;dk@I=L=xgYgZ}2qInEyv%xCyMQ5?mOqmXEV@=b5~D#}V@>3>H9a@5S--*C6;BS}-^|?prs!T1%;IjFJigshLUs=5gW{OTOBvGA&!eBKTIDnUYQ*ss|2=Z1 z=KetGC9CIet=|4?sYLf=>Zq=8D7a|lm(m+rw;c3EYWoeTx0F4_^p ziOdYK9|l44J!T^+NqB{xt1 zJgh~`?`OFFh_VHdZt4cR%mUKgAr_&{ccFXY2EEN#Uc4`U%6HcX-wDbdyDiC|qe^Ly z2nh2-GzI!4UhrGSNBK1HmMo{$4X18?-+vzMn7iI`Twf3Vs%@bC3to6Ap2<*kCpwDa zbl`{A9P0#4PbgY}%gxQ<_CoXYnj?pe=Y8Yln2;xyOV|gL4IX58$W|cBK)^cBQkH~` zG0s9DnK_%tnEB?<_#swkEs(0l9CE*N3nW`<1a8z5o|!etiwnj)sYIXCUzDD|YB~XA zP-~#|HuW8$bNa^uM%Mh#Mw{?%O-Y~GbulAidXy?d^U}ZOj~f!vVe>K6BoP@MWrE;w z-3pbmB7VaA34FlC3sCXnVajI+eExmSSGHOo4)%Z7luGw_=XrC~Gs6Vjl}{vZzvTAr z*3vw1>e=u&8K1xp5q3oi5pSr(8>N*jRD8sdjE?e(S9yPNK%Xy~5UBErPhP zd`~lHvrx>N&d14+XWmXav)TU6N@ni)WMj>6sKD3WuYeU{ceFI`-6g5=mmBH*dRY`c zwL41Zf}PT?=ekR#`A_H*0$$b{ILnL91l5%*!MM5&`f!Eo*$EUvLck8NypJ%WV0A`a zA_or&h&)lCpq=|cr~n0o`z%U@yVicH&84{0)>a7F3mYhRAG!nCI$W!XLVe&^jRT`2 z4xwk1D4faoY54aBiCr3=9=jHFLULD)UezAjHIjl=#wVXfnYC>xW26qxjUqGZk*VC< z9|HdB9r3lo)g}Vr4kDj~#uJ4+&h-NEQh4}N1BE?Xb?P3A1wzV;`1Ve`x23f%GUvxa z{I#|DH!@K}&UlfPd;A5Fb2BDS{u;g(jaN+FPbAHS_+cT2^$2UCOH_E!8=tXgsu}l! zJ%7Ac6v@*^o5F`)M?h=4X_ZYpCZNYh4|HV!VASnb4RxL?7MPjliSi}w1^_H#F8xv= zj!fLxTa+94=t9|a*e)1|X7C!muD_UvsML=T#LEK!?U8K0rI$6u#}ht*rI_qCHz#NV z-#W?dO*mbbJkRoN#)jiv;moWk$e#17)Sz;UrXK5?_9}slBea2rBR%iXw%yh5hP~BA zEN+?f>b2_ZAMeicv-9Onr*B19OMLq^V$cFt6F);Z?lzP_4Tb(wC-nM(5C&fs-hh+N zo>;F^q((LOrd5$&o`co6%reVQ#Ptrt?EV0eZiI0Tq33(41|vbfGuxzjpjbB+G(qr{ z1_kaVg1M7%?s~z+DO=qd@f|(2gW*J8k7X;8TPm{}{aCa`* zJk9f3RS!`^*X_40)~VSIPHfgC%HI79O2sI}F!f`3=cstGH37g8O0eRYcxF5ZbZ0Wg1-2J5f)9 zcT~|9ZU-h^CGhkTR8fl)A7SlTJkN}FDZ=Hge`WaGU@^$2CLZzSpXqLhHwaxCD)QOv z0rwYc_T}w6>*8qDr|W4G7xSn4FO}Uxbfp&lW^i z>78Q)Q+0neG>rT8J0~DR3|iIu1WFWLPiXSGY{fl>N>B_?Cbq3d(T^MK$sW$wl7ts* zxCcZaI(@A&sxMjF)EIz4n%Yc)ba!~juqxKUxYzXGfpj{o4Xx(geA-0DGuH5Ysw-Ai z8Ao8}ypS~vc6~Pc4dd@e?stcOZMf`un1_4R~)H4#91@J@)P z-fH~yHlRi}YTV!gc}}>$1uYzNldz7*X#NuLitXwrtt57g_exN6XYDQlcl>mIY#N|=J<>1di)lo zvc|mLi|R7gPu>{JNxiM3Tu`X0u6nhbp|cAFGYfe0;XLVdYs92Pn_&a7kQl;u%I`YO zxfkfIzD^Ls-g&}SJG?>e_k?BvS1ULv(IB~_y=ll4NWVV0_WOiq2BbkO+t4LMD!T2{ zwG}UIOn9G-8sgpGZ#d@DLKoxpPl3D>FWhgQ4}LncW1-}>KUg^Q*7f$h3yQ)UF5-)x zAb3U1X>9eAypy+4{ci3hm%;wss4t?N*DqK`JxfjSHgg#4Ablm3^Er^$GOH3qH=DKU ziFW@HabSeNaZh10u)sSdh`20K!-0YZ!2;Jamn)(kPm_FBs&zcKwr zlPd%VwXbfJUDK#0ifLq8iu%+XRp3IcMZaYNp}wMm7hUO8Go zC8RA_xS6P}zcif>$lbcv%O*q&5_A&V1-^D-t2p^gWclx<#PhV^X`sWV;l#j`JG1cP zq!F$Ty13|)yQ2F7Vh4Xcq!XTk)9pjYME&P{WqK7!k6`ECmP>i(+hzM(a0ZPYMf(nP|3V%IqX(+A%tH^SST^Ntq zU0^MY{h}6}Q4EA}^W8`i5$IHsMF&d`OpK@hM$$FpWyR(H?TNBOd(;N!T%t{lV#_sx zc_`>stL48+_XR8s`0VVuti{Hn3HZu4LB(RS-XJ(Vec(?B~D- z?!&Q`LfsN-Y#t-AIzOf8HRz;kc`<(8r`oi00bOp-pM-!OZ*bxav7shJd5l=oe8Eqz zo?-KW+fqt@ma}^+t&_CIzb}&bpx;V+ok&}3O*k#DjX$y^l1-01CqXvA7H(cT=p_sOr-4m7ANSeLv89IRt|Ny){)H%z z#bGCmOs=C}{ z3P@EnLyuM4J85RY!Af>lOLHi>zGnSnGndiT-zr1|*L*#ro7P^U{k#Vjc*@1yWYZJ9 z(}SjUF5Zz=>!o7^Hfr>;l8G+rU$sEIpYmP&7lzOFcB+T33kw9h@$HWLRX8=3&(nV@ z|0ZecQNNQALoitwdT4PpH}sB zLpkA>zDTW4<0e|b*OuO%+tKKbR6--RmS8U3&Eb^Z|}%)9{Q_+_tbeKtbSG%LVL@8HA`$3zlNf#_K~ z3C(}c^#_D8w`8P+qmC0oL>iPaZ`AGN#iU1GpB=auf*Lj_#?glu;r+*$U`B_!SP-2Y zVo{ya%UPgnqe;w93tbJ5kJ8Q0_)*-v|M(eZ|ZmPXfj*(3M%7yq_Yd*7vFz3d8XT!7+T zKr~+@dCyqzx((hVY+sFCAfuCyp0-VwIdpCm0NgN%LIv(P>50UOm zCC90CJP{U+Eo6xnE!wz_Zk}$5J(}MAop*xj6*X|D+#iq(#m6W~m!_kZvcQY0n)~oy zf?WKZF!=|% zxY6O~hlhKP@qdHi{_W~;e~obd$gh_npgyShPuu^g4gC8b|HRPwwT5w+{{@`Gy{Qv)^;0re#$v1E)gBmA%P&Ym( z{FDFrKQ1%@XK>*D=kxx%O+x7RvIv#%r@)~Hqyd+|P_4r#_Vr^UNa<7aJ+ROi#%O)p-o6+-jZ7LnB zSm4lJLHCY^vDV~otA=w;bi0NzZm(9bTa)!6(?F#HHb)zTf704Wp{r@%|9R?8S_sdmEk>?ya?&tS^zAFs2{~U>L zwyy8lo%$!RKufkQ(tg20mGL9J0OjeTXUzAw^5TKTCp6NjzR~3(%QWg*`(aeyf{z&g zjKhC_mR*qFUB`*=G0vyl92SSMpiVFrY;bbRa%`-_vp>;uk){utuf68lzDdp033r?> z3X%7L?yp;SPA1%`nPv!j_D=7%9Z#KQvrx**oD}t~!%I8aEuu>0_x1NNzo6P3jF0R? zK9)4>2~jQh18T)nZ8p_qtqnY+$jo6GEALm-(WXG=8=GQ-iP!y~VS^xo;KoP!F7OMF zpr_)*A%UPf9HV|y)P^EhmdDWJ_PMD{XX2Y~jSt(Xc5NzZHMTHVmaovP?%hnesWB13 zQ-45SZCrsYDW83EwTY~PjW%cYNjph9i(wBsub;BnNZ#<9PM+2*ol*{5-AALK^LhZ2 zPOI!6JA3f2@`(m(p?K`0N}dRT;J#$^TN-&%46Pv)oq97jhlPgZE~wNC3?s=-Ugs(( zC()jcSiDjtttp7eia4C+ZT)I;cF=4c9Q&7e16VBBt zo8t`!lr_+|HsJ?_5mu<2K6$mYB8eL-MKC1A{AMZAhcZ+2cKQCJO1=}TNFo?gI=y4z zr~?1Vd1&W){vvk9bpye2zL@pqU`0)C-OEyA1DaHHAjiIE>3gPlKcnW9STR)*TQaXz zI;D<`m$pWH9&nwa*fGVAyeS*Oc_G3>$`)w#8(_ z@l?13%Ari}cWl4mQaWgWJ-E)Ju*%n<7+3+u?q-gb;1!!ZqLIjfHLUYkd!C9O-H)UA z0V#trX+#`fKgp$j3g@l0kofrXL)JR*S=gDdKKl>wU$^bnvC%?(;oC8n)wr{Z5+RoZ z`RS^!R2>(%Sp%nGzw7O)|C~cRe+6|G+ca1ld@Bh0YK`Cny1bUSc~xE2@>0bc=4&^5 zjp}&=f&dUU-0dtrfbnTh&%W_G1QQpgl(OQ~iaGDODgp?qTDw zqozys-UgC_la^L0yAX)+m&IjgG|Ws51P0Xkx9rzI{>C?*mbL%9x}zf(%b{q>q{BEf1z(Z;)!6{_+j7!~*9z z5!g$_5QDvegkJ1A=KaxBgEz(dHcUU)!q4&>hsMYvRxeAl*Jc++!S8K1KUK@Upa%-I zc*S3ys8WIglCVCAU*__TckDBnqmj8Sbz{vdKaH@Gze6LNk$gM2*=>Cj*nDY~UH#iZ zOH2o<9l!?Qrze{878PL76?yc1i1)xq)}@Pk0*mK9K1;{a1;@QLwx>4^&(_S~7zXjP ztd$@bSAx5_pqe5TLZXws(AaZKAfAF7GT>vY%G2GeN&&VPO@)u1g5sO}1eA?kJJ^EA zYFynKuGsgzQME!?(MRR?$~J)l@CQ4L0`H_QcU#-ieii+a`OAGQOs?}65Dww~lEH!6StRnEa9F_6O|q3e?o)J8 zrC@uq^He2QocdwY6<&r%cvMzCx*oHB(_Z}&SXcd^67IwIj|!UMZEt$Qrw>o-zOJW5 zAAc_DB~U^#D;a{VS!Raohp^mw6jFA2if>FNUFqWsD#})}1mC8fklIodm@neVg}nyT zIbxsdJuCP}n~kOfkMHuF&Mul>by~f|k!t6>bZEdfi~eCE2J2=0m^YhP-)_-m)H7(5 z0PaprYfRyfCUe7j?pTMJ3En|CXJ4E}vQXp(97GaX-bdiIal3WA*$dIln!lG(1!%d3Xo#(y?2T$4!RME~^0~goHrgBUVGHjE zc2V_fKgkMPH6rPc@#XHDM)mw@c^@P6n{s2At!EziBYxy;{qA$qkh2p&GlDXS&%4#x zHEf=rU;k=ns3>So^y{(L{rQhIykSf-Qp7!XB*^)Ao;OkSEVP8Nl;R$4avjl8xU!x6 zcb-lP{1t|*Mq;jo)FB=p!+}loEm*{8WZg9nGi8e6y;;n=bbQl)*!^wf(bzGtREv3^ z)86O6XgxJ!YR&Qr=w8t{aF36{{v3Evs6O;oBm=0(3`@S)G}s9K%rV?p{k|WC3+r|7 zy+2|8^)KVR`*;WuvJFsG!8)`LzX1Y$j?ghQ%XWOTaxgIq+~zFr=LJHJ$^Jt&py}qx zAn%?|Jp;jqkmQpd%S05ySP$<4pE!#4HSjuD+9FyTHK=0OO1#K+>|nb;L;-eFjsuID zFCQ;f+Z5=HCwz!a5BshCbOIZw?!{wka$&- zoYL^CM#^>}#3jW_9uGF_4I5lZ)B(@8rI~QFHh&LJSIf#FSaP^EZi&-UUj%R+_hoQt zyACs_)E_D`*r)2n@tQa+b>`~72ppHf0)P9hH@|QVCiCQsnM(8)^MPaUeqx`+v;i@{_MSWn3$M!IJzcStThe&F4*#A z{5ACpO7!)dfbPQK@UN>|C%r@|So zTygUC(XLzFW4Vxqj}~TT&wN9+nfB4CLvf+SIdi&Hbs^6w{qefF8uIYXv%vMS_YJ`6 z=hFw7Iv&hdI1qpXq*V7gu3a)xHxrdkLHZNf3kKLv=VKZzI=RZWEeE-a&5rkgxv<$$ z=kY}wYx&C^1yvA9mvGXj--`rzM~{2Bj4bjAn&am1nDD<(?oJGhhBbG)4#B6FW7S(! zSA1uD@mWrjvLEkQgZ8q~>y>8n{ARF9Qj%}T?{#+43Ac94{KOZ;jyO%e^?uT|nZDjd z`0hHEE!}kx80dA-l{WIlDenb;xQYA>T~5!-KPO>4gOUqG+@!8>9b@~6^+(6l61&`V zaNe8~T|JhnLX$0+II!K@nT(OWNp&7i4US{Cy+FN(Aqm#tc6@-_oAOGIFIAV?5yS_m zv%TIa!Ux4cYi7#je8aq7ZJw_Y-wLAK#{Y%%^OyGnra?ymyH6 zd>JGKiCnp4dun0yoc`R=2KCtmOnw+=?P0uAR`iP8sTG%9hzb9FgqBD4`PwSn$#{>y zkxKz$*=Ls*hMn?%Y=+GTFxfOH>gp6sQZWybF6r6b8a!bl5e^T(`l|NrKY?Y5wa)V8 zrVQR6=B?bdyq!G5$$fS21u9OxJHXAfr7TRTN9!{CE8u!P>ctb^=5aMV6Bdys&R%!H zlpI;XcWjzjAI~gNMf#eUc*fwM)TSu2n;(5q+cy&!v6NxxG<3HWdi(@Bv^s~vDNIVE zmfQ?2ht8wN$5t+_WNc|o#GJsa^;Fe6?!G9NLxBfge=+g2ci$xtCeGHog6vKwjU}wV zxpy}}S2I~J&o>f|-Z$5baked7If0KxA-?FQaTHGBP{#XPvA<)xJ74;|ghd|0w=szp zxQu2SgD?u00Mvu0V+FnXU$u9H)}ke>gT3xH)J`$o|-stw0BF2DN~&Z<9E(!vp96!%sZBc zrYDf5CA3K`-;Fks4?6tb)Pw6`nt558dBd_rLbClQZjilWY0AcT%xQw#9PzolT1t00iJs<`t4ueUfJ#S}yB} zDUr-iuec)qR?0`E!!WaY!O5SNxV*Os7Z^=uLN|*-pfbv8u2XRL(bV~0UH}bFf^)=a zj9MIjy*eK-+E*=x+0lq-+pjVhc|HbM{h6Koe}7)XpxR^Eez_Fu%krRgd%p7;Ubv%F z8tBzO7T?@>|4O$iSPB2KmFCCbh?a*UnQ)eh8K-X82d^iPN?&nA?iHyMUE-LzO9ysO zPk)$cNz(S8k+2VPFTBdS!Zyvb=&q3c#_yh)t3Lfv_5D4$8p;cOoT!@0#PaJqKhSDs zY#O0uMe|67d6L8rHI(;gSKe4(GyB*1%L>L+3e?z2B>k>w18ZoGH6HD8bQ{L3aZbhm zOuTjh0k2B7&Zw3zO5aT%PjOr)@;WyEiZlK*LimrLA`Z|e!9COni6=QKL;NV(aj!ne zHu7B7#plsBo34{uBrd2du6L<9YUC{O4Nj@fToz+-Mq~K#eh^OlqY~VaAZdPBL3Ym$ zbop$+_-8@=x6=bF%ik1Q5XF}oYD%{{89ek0s>uPq6FXS>_Fakac}!q(Rjqn8SpvT# zgBY@29Oh7@gz_Z|PFzTb>A>`+JjYx7V1=jVVQI^Ao~YaA(UB?Jui&pA-7wQeMxWet z8P`pL@Jb$1c<1}C5xO;=xJUHR_**PKCzEP>f{=4c!~ea!KG+g3bW3k=fbHc6j8`JV zgBIR2_}x%qntW!PalbcjrQJHyAt$2TQXz3}Tog@Cl1kW9C1}JPW%={5W_N)~rfJR2 zt#F%k&!S?L;E<`+nUu=Y#?PTxIvXZjR_jo`#TM%>hO_P9iglA%5_`qGXU-2Jiw-g9 z#Hw+M#53`{Zr9&*~2SVMx%Hh#Jm&FNCv6Ws6m#D9bo|KBU#{1sY|%bM1+ ztuu}-cK$(Soit3QM!C&KjgG_mJXpm{kcLhv@kX&$(se7kmUWJ1Ok+uT0V>9l>5B~E z6OQ^_v2MCwkQW_Gn@;}XKKPd`F#~6nscj}H^fBn+orC^6l7p)QZgov|o85Nn4fd+7 zwM?;`3)milNxhGqfcAMHw}d;#bnYsaR7VqNCeD>)e1jKC=3aD6su!S({&d@^jis<9 zi`z;vt^>c^I@IRe-jpO>gxL380I%>Xp6@!hPeH`SPua$?015(hf+k;zVyO3zJDfjw zMeDIyO{God$xcvbnv*{ehRa=lEuCHK|XA}bB|M^k}SYrBmt zvw=j`IN`f6@XhTUT`bVO73i!~wB+ijm1-wQvf@b$<1pn&_-SgwEIY5sedD#MSZ?#u z>y0EZs=PC9@Z6-505(ryougzfBZTcw9+cUw8Kf<5>9k4WS%WuD1YA9sL{y74i@LW7 z3R@k{HhcAqHyN~y?s61jj}Pk{R#5BtOQ)pI>2E#JG@8~;J8tQdc_m18g`%w{N{g^u zq_7ys>I}g#KR>7<@&wG4CR3ko_9Q^qt#=`|M-QU!zl$u)R~QWJp04gjHDS}-p8rbx z{?b({ao8sCjzELa01eJP3oIZSIV|uC4N`e#`DaM_e}0l|pt;Z)J8*iOMILsq!Q&Sf zyv8Y0r+C+K)yLVlWps7JY`bdAYA{W;_PRjPT*ae6AxB+(DBrFDED_qmFuk^5+I)ZX z{`(u~LkmfDJ1i_6R&=m0n5V;iU{l-q%OJ;`{>qiJN9(kQ7)YRzIe7KPpS{WHFfd2@ zsFnX{xkZh=!FbAbaR~!|dnhqJbX!WQ69!&8Ba&fRXB(8WG0^EZy|B#WIJ(n(xQ{am zLZ`896l#ba9Y9_J?}Hw0V$fIR2E6%JaufrY8)y`Bfsxt|BJIpZj$s8W*=7@S?27_=Hb0V-oEAW}F6WyqbRCk(6B z%|IB*dhV7*qoDb_4yYOSm1vCJ&oGGLc^Hg zQnbjRKP9$}kH8u{a!-AEh0|=Mwp`REHiopn(b%{pbl`vsmtxH&5P)v{o zPNA&=^p9PfL>W!z*kVwquIcTqvK0(*k<1F`0e=Y^$@UAtu^a&T{seJ{vwG3h!1e_!=%%#~a~WW_I-Fdzd2a%03a++Xm6?6GiCZX10!&pWFCW~F znj`hY41J+^*Z`LWNc3FedWp4*1u$Hlk~fxy>C(NiOfCJ*tVF0x57{K`qXc@82P$eD zl`WD@xoQoeqp!?u6lBP^XNYj{^E_WE+9(w0t2721)Tu&h^?uIzm{z^?vES})NP+Nn z;v1X8awE(x>~-q!?-BOD=uQ_`WuCrFWDnF$^)B>ipv9CMLfI5Jp1(`DI|Xod?i2OiJxA zEK`4u*u)X`!s9fFZA}`_)O0?e2e=!yriSn}7-&Xj|34bE-#ymfKI`f25YUFy@vGbI z7UltM_gJZQ=7FD|-!JW~pu|e0e5{}l-*XT|$rM+{#A7Mcd|g1v2MWuL=MhyL6g#Yx zYT{(bqh7LR1t|l8Ym^$36leN-=&tooJAPIM6s6mSl#G|SjMAUNzvcJ5?;hdt_@!4c zRUeU|J~(sP>xqO|HdmeA9Yrh$0LL^6e!fP#YMaKrY0rIq0UTOo65NUU8eb4svl@pQI)2EV=0hJ3Tf-Wx&h0kaGs4*`(kKA#aL=#&hq!g~F5=`wD1(Cxa^ z@&*(q%xMiE#y%HW5%t(frvI~a&|(B80?j=5O6XPzl6Z&Y7K8|Q8DG#G6(g9*=PwKq z-=mR%t3&i804Rzz3Wq6s$d~d%b>Dmc0lhlQB+4OG2|$Z|ne(Y%`NzJPVzmS2c0|3p zt0QDLag%-Xleo`=4dgL603oUs(7Z~lXsS@!33v+-_y^bAEWMAVa+FL*!T8W|k42P! z^6q1xOCh3(Z2a4ft}C?XD$lB`K@5AJaGd2iBg7f>zP;(vfbKASv7|DEn(QqGMOC&U ziCWaIYRmVjH^_1mKoYiijL^|$iuKA>>MxZ7cU(btP4yD@hz9b(Sgfrep6gyv1?a)JH0o<#SXzuG+`;*Txa4@T53ht)%_B zjSixHTivMcNCoU0wEOQh=czsk=2YWt6yVun?d7JQgu1Kx)xWj{bR}NZI}DxJSuHpj z#ESQiDEMWQ#BZyxMT_Oh>$yK|X)CDjcNk zu%f4KWs?*suynqUL2qeY$`O3ml*fh2m+f}!6Du1)>s`lr0B&(}1#qqI60EnBq2Q1A zCHLcHsD4;ve9a*xPS;?M2FLr_Wi@a`dDU}95@hcASjKt;(o94xYw5vH>U5~4Cx;c> zQmq!|eU>Ha;_y$sl!!F+ZM=9YmnqL}MV~dVg1SIX@LvAg4~ZjgXZl|sW*&o{rm?H- zqsB5D%j>LHJZVOhgn(z;IP8qEC;Az%@!2m%wJLdiJb9qOG642~b#!^xKhgL_-@kI06n0>Xh(QZS%DonXu(W(CuR6IKfauTT{`??{5|zj|#?YpHAS>d0juI zWBPIpYlyVip66P5xr-fZz%3CrUd$2=w=_DAgNpTn)xrjPKLz&(hTmCjmP z&rwIHjKvA; zm(r2m=Mp4Hd1;@farz#xX77hf{HVS$;VtQ>0h@^ryY%F$fa{-EYCuGq)V4x8sGVyX zVWc-l-5Og>xy=Z2X*_hnmu59h9haI@JSF6_rhX=bTR^Tfv(pyI{Z~8bR)^2A1q3a(zR!!UL-DfwFAOx zo9*8S*IZWOBnkS~g-#W0Di;WA9haD1U3WH*_VQI+G^jFmu3s#7HSm!1kYj&b8uZWSVI#|w|^b7eD>o45czxO|X9K&GrgSQ;tvvI9{=-4bF zsEvj#QXZCuvztX}mf`(2gK@EZi%3)Yi-{Kozw<569SKM053CB7h(mCdTv=-zd!5AO zkXRG}Z(H1P-j& zy{^N=Na~oHc0H^>UaGZE(si<2kq)bx_ja+aNv^Pb?3T_qzPcTpPI5}-dTjCM01$pX z!YOC*=dm1@iB9Ao0shf+mZoCeI>Bh{*HLo`MY9s?zPE>DwA~A?GK>4zP*YUl4sZ}3 zX7&X3yFyk-g*p2&KagxayUvy^QW4Nme(X@?i%r1jKO|sj4A+c0B(t>q*3b@y6czBn zzzBIOfV~K3#|y&y-}On61y4dn3-iuJ#o*H&g0a-#CUunY!XWFUstmzZuf^U0P4yM9 zdpf! zOQVtJ`J3MzBVpxt&qH$q9{Tj5+sebTM$%cCbWSB)dS>&LXcV;hzczH}Gto^OW zGxDo;mMnrkBX@aj5Yz+-D}{&*#ir3)(%SK@=my z8L)-Y>5amp1$@gASZS!J_!y;+9LIn>TLKLhskyN`$iqN4 z$|1ESUbBhOeVTYdKzXML&d$@-hxfJY+p^gqi=^W2HA1v&kq-{+JSEl ztV|173d-2%@CM4udZkvOx*X7XGS|2FLd_x%zOm{BAEJIlJL=Tp9=3gGXm;OD`P@l| z)7%D&^1S%n)gR4UAWR-chr2eBl)P`TxwH%`SB;cgT;yjkEN+WZb@1O&3f+({EwX(ZysId+n=w!?vYDH#GzwkGoHj6 z#y`crxb#s+v{@97fwqKDjhLBSuSgHJx z16+UMA?>R$50}L}U)Rd5HD`-)dD%{Ljq98H!q**457xeKu2+Cg-u-T|B=Q5>Qb~_W z7vX*2bp4yyN+!UyaEQ^LdeSd1vwY480iq?Kp-wyO0~eo~bBUG6HtT`RxV&-~* zmq?W`u2?Ety84tm()kW_O6kwB+Vm5jf;+%Sx>>4zaJy0s`GSJ3{Vg@cO^#caKV_Y_ zd4JwlS=&fYAU~wO6{_O42O=eQLKe!)^F!XZV}Nvh3gy*n41OX5ln`fKs%zJ=l`E6vxJ zOI9b#NFivy(>_;AEq2_%;lPR~5gE#qb@FDF0^3q^-zG&}Lmej)_B$5Q`#;HbD0H|n7=nz1i65J=+Ah}g zOTWaRgHgh6%Ah1aoCJ}MnoZ{mKIMlx{>E4Z{3%f-Mv$&yP7Q_8{Zzw>_MsxgK7s)= z-y89xA{WBogj4m$U6IDn=Dft(pn8>X$2#QBS&w-qw$SQPk$Raw8kRK{s7vumUNPlm zN7e(HP&}a^QVoS{n8gj@SNwDCrDH+int>`~Jz`Nr13=OAim8zZkj3hI(hxqcd;O~l z_eX@*zbQ6CTKsAoumJZbYl98bDT<_N(Qblm<&c{5Q|;0aYs302YXuItl-HAhQa!B( zlLb_6+^tqsox5PS8B{g;g)LLlb?I3dis~-_**oDyhSt2Q6wG0t7(Z;6K-x@@f}Y%P zl_JAS3h|H{RMX2{%$l6P-v0viWZpZNcsYq3D1GWgs`2HT=BpXi0 zM*-f>m(VW{$Y?#S@kp-fr+q|tc?G@C7HrB} zyI}g?XN3d)Edip9n*ZTn*W}-nnh&;=KtyddsIhh4CpIy`M+=>%a6*aH?8_K)azV`C}n)I)?xM>AV+m{$kAk``euq zVz@lqRLE0v8`N9_|gJ%LL7Cy&MlE{Au8q6?fO; zH@#*Djmm90svo^F@YhzxFS`=ke~(G%)!Xm1mGjDTlo66$7EGCLEnec&Yq{>7VczZx zx6xlSF&V)?uk#NvR+IYmF8}w}`s0d$;SW}v<$n$9kx|P%lD}FVfV6lM+gYx(O688E zQ0u+9)i&r3KC+L9BIa%C+bUVAGrn5xIa|tTu{-}o_x?&(@V#zvdGo2JYt$hQmtC3w zNv)>9){DKBLyRDM4(BId!0t#B5DHQ9Hr6J&*lwzaeHpFVp-Z&lc9x1P{^dNqYE+l;|)X+T&-3WXXIu}pEZ7O+sv(}DIYDA#h6XqyS^{S{OH>ba>O^@980DA z-8+Hz0W?TJzp;WU|3{tx;4NrsE)qBOdbu+%RdWVB3(VF1_ zJEYh5G<#_buqPzaFAm1n+j6vypcS|Lg`{Nr;9+($w4&g+*i6(_X%f5Ks@pp0jhq%0aqD(_OmI)bNpsY%Ywlp z=`5+T*A#fq*O?<2Z2G=8ax$Fs2nN~cLQ~v58q3L=^5K}X`6(WAw2iO2E-Q$$HWj=Lv zb6$P8L%Sn7g1rz82E$!{t^vj+hmy#9_dP>-b`1e5!deqFy<_+wp$>e~L;lkJ`BEV{ zcYx92Ls_ol@G4R(wEwofT9%N^WH7I1*By$hRH#ftO)Z;|*d-RpWjeQXLLjy(5{zhw zyXvkpXW02=%?5UVcd?>ICruPw`7;zzx3C@1CzAl`oJq)yWDM;$?Rpmpn;$w+M0~LT zV^DLvGgyVTMxj5}8};+AUG)!spw|zV?m?`BTG(ba3M1I)2h05xYd7BGxVSHKAOo(fetWZ zzOB;7_m*(M`%ivR_+f6N&`l+prhy|;cWWN}Q3(I@Gf;6E1K21Ra;_Q9;wzs#U%b0A z5%@#gV4f0gha|3urNP9*Hi`G!4WQ#A-jNL(N($WbURdE8LmD*1L{X5h$ylPsMt3A- z@baCI5W)e*x-&-8 zn#4_+bVEwdR^_|;^?{0Tu$Hi?`b+rtL(&?pI%}!u!mfQstBofje%nc7$6z^xt`m^q zN%lw*brOL}Mk=G%_C$h4YpHHi?)|5l#*wYz1^dZjTf&t{K$|a~cl3C`$uvQ5M{uXR zjbOYD09J53aTctWl-I{g-S!uKpXj*uPmm~;hgWmoSU@1NSsJizNGi&^5MF(LmlMe^ zcTx6)s51D4Y#E&QW$0`}fWm*~;?`M-0dpBwH)U(+JW;@sJ@G@3%mx4D*c1a?G&Lim zJg5U}_SE#6_(lo4YY6qCfMDO#o3t6KSm3Gd@+^0sdlZY?NqS@`M_4>iJ$xLmE8*Jm zepbezShKm?r~j#0I5gQwG~Rg4@Y3mx7wSJ(B@Br#co?~BEc7!U3O>z*#-p?jMSq!`Me^EKEWdZv{2I}Dk@|}5}x6=DJU3l5rmxmZ{reaJl~6?_pHiRxGl64 z1kQI!U8dsNzp^UPtVknbdvp9LIQnH!#BA3#{&cBHS>MW_ck4>E*t@H~Fnl${uQ=xX z@{)@uNW5pB9Ur1!Wzw$AH_ox zOUdk~b2I_KSR~djK?&@i`N3xGr}oYU*=_cXg2z-LOF!b2l`z`o8h4B`(aW+)|)&&D6t134Jew=$l-EyedYcJUVO?!NTp2pO2k9llX@`!<@!qj|P9 zT43QK*78DNc{!rZGEvby|GE0xxnqXlqw74c_xc;nN})Hr@m^2EA;@A^ml=!!o-38S z-H(0otV7f9mHG#JswjjTKT=7w@C27^zU`{QjC0U<1I^D9w2o=T$^u$4t{F@BxH5w+ zDAILOE}GL`H9Se~k-}i~NmqMBcRCqIGn?CEq3q6>*|0e8b^K}jm~yAtj^S41sxhg3 zd1>gCIqikG7Q$^aVJDHs=+nBoE*O=BQ+`0>d3q7@gTnRVi)*SLDLE=Cj({zjQGask zUtQ>bsvTg?GVq0e1yIE8!e^{ea-XJZXmTtxTkR;pezcXXc$r~!+!`kq_AZHD$F?70 z!57Tc%h>IndDiN;Yr3Iipf}OU_8mR@JrbEQKdTQak^!p!1e6D#3OK7)f6_H5wM?-m zZ1EartQ7_9HOAMChoRbWMaW?^hKPvN*dq6xWrow;DVQvGjl6$*N(;G_k(UYfdfsYdTr}8MY2)=bb%FV>UCaou+S(= z6-AiaaIL~5#2fJWYbojm-Y9Ig6Npt~fm@VcxpNUd{V*gIe&e`+5tEUnSjT4fkYpvP z{IHBC*LsJt**Rj4A@LRB){Ms5CA_fui!|(EZSGA4gmu){Q0ON94#j?e8|;OlX_OJL zWlX1(l)YArK1&SZltVY=oji8CwwS2- z+VP-S9rPlVN=5QeBGP@XRBvtp*n#LG5}Y5(eOeqXHM$dYYE1mVK~FGX;}0_vaUt8I z-*ntKHYrhc>Lz1a`P_eF8YAn$OUuJ$T%c4dG4hstl6k2Z9$hcp_54x`3)++5iPc z(;w5DvGz2H&h?8sJ>{1Fv_W6!rE-iqlU4Sz;ZH3bM7qq!AWPvE?(3UnWn(?EkKWw( z^^-wdM!-86|CR2by1^NxKmS+LldE)A<8W|`YJk^n77s64*>#w%jt-WfIF!FOV_-Q9 zYX5ID9BN~s0}VZ-jdPTtR!sji0e4jwfll%UgUi5Jfq>4h#_$Q$6iE6ou@tgKW4?Ux zv}Oh^z-_x4Y$?u>GCWUg%@wig_6TT45Ee%H;bmjMqz4tvU1F%|)GI1Aj7s zOpB@Pm@DbtZUaDzIH4~Nv`qAwo|Br2FhM)X+#YEeFOV*~xN(WL`K=#B`h@rh)r*0_MH<L?u;(jcz*fNW1MiIC;inaWw*t~sFE*y5d#^!nkMIuc<(Q047@u@=C2N} ze_~BxdEG}`-9W(3(H>`1=>u|lt!E+l)U|vY#*_~DEODPpG@At7G&-jy-G@&=adRMz z&CSr1!|8A57p01np`4}@OUYl%W>PMZ@7tf&zIUY{QbG9%iio(pGCbj`X8y#*W20|# zzsp<1Qp7NAg#-bXvU?Pq22{SDkOTWKk++Vks$B!u3J2J zII>z!ye_g@YLK!4*tcOKH7woE45Ya2_10S1_#nP;K8wze-V3&v`%qHq%9NU2sMg)) z$>k`49BnrF>XYS}o-5f&H=V3v!&_+!&;0sn9(kHR03=P!(kD8sT|F|NDHh3xDK>Yo zImr#>I__%PP4Ex51@#w9><1f8X$nA;%n+;ghCNVt1~AF59^c?mK*dukQn29lLD^{W zEZGq1T&y$CPMT%VJJ%TcOlV8BL8XW)Gos@ORoabmXW6!*zrI<6Y^ zWrR+ihsWT*opRrfyFRQlnw(x>R_vK7eo){%gbJN+z=`DbnqY3o4GdR%r=>{5526L) zH)3kdHyB;s_9|A*b}`z`jC*+E|4m)RO!J^=;v^g#plYwfzzkjSE|zzdL{1vj?ZSoS+m$+QI-#dtn+26C}wuEEk)5ODjuF(K89P>LI7O#)B|i!vR!FTrME<8K+e-Q$;9rfbMT3JXkQ}*Z{XyY#?`FN6tcN09@rV4QxOKmK^C*pK8$b&?Su!SY zn2z^B73mvS7gfHq<f$U*>aPo9s&;)3>(pMMFx+;kpPstL$0{|Ul?hw?|-%?TVsA>#WKmiA67 z?-M33g%<>QNqTSg&dz9rR)-%_7RUGz1WfpHvE{I$%zBLMXd`#N73nT(&7ig7+beK3 ztbg92Nu=2*7OM7PR4x=zINT0KN~ZK&xVCU`-9rhLkfwYa*gb5d@Xk=DueQv6%~4Yc z)G`XN_*cFDE)hnyu4%8|q8XGxjwpw8Riuubt z4lbXoFE2{>mcF0s5-}hkYBCkB!TTI&xqswHXDGW~5f)FabB6NyB-i$a zkE7TOoMY4PsrP)rr(yy$5 zvO^nx)B-CRqNj#vQ0EHaFP+zn3X{h^3D>K42zXk%%kn7QAWOA#!SyaDDB*IzhPyAP z!5os%!ql5p&qCMMkMXPF^`}VYxA*;xjyS0K)-Wx@&U(%lX~BZ>d75R~gzm*W7vw_f zR3hs$h8DG<)a#uU2E3|QnoZ$TrKBi zhU8tNaJ%+K=gsGv`H>=(gIVCC6kPhawAuv8t9KSlWh|<(cYjK$Ii!j2tPZvQvM*ho zVe4-GGU40J6_Z@#K1M!*PdxP+$+l1W8Yk3>6{$dMc*fs+gBB^lnayRa(_(rMT)y1w z*>J_?K)>1iCQ1!SFR1->l&I+k0Qkwk0iHYND;*psuU`k863L9MSTu*+LiN6z8_jX= z$HdiYH9<80kSPU2^pvRNiBYnPyX*4vW||GLtC04 zPjeM*j$WaJL%)oz`Mos-Ef=YyGV8ajMzAyHGIy%_R_w{-<9UiM{t}J<*W`-;2gwUL z1s0~Lyk7#@4? z9|G;Ve#~kVvw+f)s6G|i-vM}t(bint1>+MSTI4!|+5iMa4t$68kd z3p(PIBmz{~b+m&~?U1T|2x1t(P3lrBu*8KnaCwURo2YGnGa*OKCot*3`jjmG_x|ng z#{w@Zbb|`YUnZv^WLphUjS(qbSDuYNIu0Rg0ON@ilF4fge!7kB3GT)Izf2WW?vEsj9;Lfx&9vvVUV`|sz=-wS%3^mWP3 zoYi_Jsh0R(RM$UL4uI;J4vJ%R^4}-=#{=CLKHg$c9HR4xYoo&dJ2CYauLP_oZx|R? zcpBMH|M;8!{fGL(Bq!g`vfcy}a8B0{bGe8x4kXhlLnqcI!;eZ4wC8Y^H7$NhnNc1o zg#HgT`!~=p1J(z4Rq#c1o#6a+&J#&=I3zfiT2LpmDQGG;)jVdlMF1k1R+Ec+S2{V& zN855(_iLN$e_lO*djtTD7nIzVgo`!t(!YrFe}Cb>AD|@)2yCrI!=POufeils7yo^X zDhm$-7%}`Q+WXtL|MLoWlKU9;$@K&C@12HI8Rk=Q)N)&=hWoT~KKerSlyQZ5Ia==bAA8)ecKz~wDkih?QfPu4}_CicNl+0JU5qlEvXvoc6}V=M&%b65n~Ao z`z^Q#xUUV&^})y;jt7uTBpHQ7_&k`_JJ-siD!@=-lCIfyGZzVmS@P@iLwes)|oY*o{z?ryi=q3=&#e>A>g*OQvg%KB=*h|QprXkyja z56or~E?%sy3kMwrSK9)N87}V=J}%FL1qjDyjD<{xMQ^m{Rx?{}>|2|N$*}I4)T!wx zi^rAwt-(pZY3Yt}83`jvEL{4r-qle5)jz2IV4z>#ImKa6%SqK1ehi#+m4v zt{zAe29shoL=l4;Gs%{?VDi|(i+uFs^e-t6w`6GUY$^(l1&@eytLQ2F7R-+JUpKz(c3?xjH=tL zUpg+ecHTSMXW9eUM+;T*W#(#WZJ!e{ou~XRm8IsNul`iNxDEf3T5=!o^IL9q9NMhV zO-P4&@##;E+G zg)ug*t~G;=j^M$N?8^OK4(EB|Zlfo-S_KJFZO41qV(yt1aFB|lwZV=7OhSgX_Utbj zzBS6+9=09E*q96_C}NtMBL~;Pzg}`Whz+FUT3mXrJ2P@J#)*BrC0U9-_^pe%BK;UJ zBb&)tguCrlR?PQ$o+Gu4M;>_&P5QTq05JD7KKEj-9H`TgPpxHI0dzD8V1FR?xa^=n zzUn*(62qtIHuDkJAQL{969E=n0QZ%B&H+XxGpAGb_G=(q*32&1Hd!8ja3K@-Xy~2h zK~V%VeanE?0Wa?1?qv_E&e*+ikt9%`baw*|5sDwgZ2+n$%R^Q8-j-+$rHfTe{PlD| zZ4F>ZkjCxRp}Tb#fzRwYh@|uS5$m>BI(dKG=Bu3V_HRDw`o&#RL)igl0jA!*(9^0|FI2-0Iwcfuk6*X;awuqKkD>3zD6PFD`$pf zydId`E|Q5|YuwcBZh=4gtWs-7=rK0WQh?bVLIg0?t##3`UP@niPc$A@u4jwwo;oQX zuRjmfuKw0R9^6JFpa0b3RKAI|VSIfk;rES52)t8PssO$VT8Md%+egTVZo1D2B9s!vx9 z$8E>`u|z`D;NWIo)K7r^(3~FuJTd^CqZ~Zjvt)X&)z(djQscpX4Sux%CIFSBuocn) zv2}AoL?Swd=U8-c!0?-7qO=V{=f@nWB+Xy+53+MnJg6?*?u%lGV}zRQGp9xqSVeF~ z`=Nm7Bu6qq+a^~gPO{Q;h!(J5Q87r12sr25y-P@jsn{EwDY{!$%9DH#RQj=CJ){J& zeFFVl_tZ#UqZD+FRR&G*jePz4;JSqzM^m%uGYpAuWZ#NMc_o`mU ztr7x_%S%>+T2{TPS{-9RrIQ1o*oq{pOS)zDz3h5Qwoy?7b>~SbRQuak>25~%qbspA z#t1DQ0`T2l2mD{gUMf_^pw~=b19@DuGWfHq--(?hfobjPT@&Uf$AkKVg{D=@{cS7O z7`(e$w9hsl%C@LVxc0`e2$$I6?rMq#uvF&IuuqfMjgj?H`YQ46mP4kV#RsQ zLWx^u%1d4Yp%-hOr!|?}tJL*#{P*s`0+w>|0BoPc^o=lCePo%M8dJ=${zxw(omOVX zBbL?-{kGfzo-lOV9`N?lnGVBW)}oYp8>73zh44hbj<++EDy5ijVNlcF-v3+A8-hz7 z__W5?M=j7}a9N|L0dZTHdx^WgX58j)$^SnUf`AD1i|=-m$)vxPsUC$V#llF+hN%5( z9`hwi_yuYZ!}R4WS;r;;^D{zjPoZBUI1HSX#uE}J(mIuv3Dp)0>S2JBlENkVd(Fns zvt+Ge(L{jslo~zIpp@B<&&4}jsa@ZZnQZVtOs}=v#-ppe*qxLm6N@Z1>`NI&9bEHK z&lj*tqYU|%7XTkFzj--$wOE1h&!n`1Wd-JgQ2Gu{)9ov7ePKcNg0_IWm^Cu zXTHuz!E=yMm@qqnFIkj(=@^XYLfy5AJ%X$p+0rLKwFyCEy{3tXZ<%j8LPf71ge*5eq1Nteaqs0%X zX56!1r~tqCNf60oW2N0bPUZ)OMWBF^sJI5tXpmfVTDc01D` zu)?VdcOmvGe6I!CdaTT9EBf@s8hM5n8@rFJg{tunqJx7bjy9Nd2QE4+rtZRku6V1E zlMB#1@ey+fWo`n_3@UBqD}HaLImt>Hx)m=lE;d0d)Voh1p-a_Y@~?xce=pM$W!(z% z6240*7ty$RE3_qsFKx*g+{iqEqY0{~zk8;VBWNUL5cgm~a$=5tSAU^B2G z`ccV963{PSX^vj}f+&(aFU=NGe81DW+M_Np228GCP9rL;4~ojD?@ROm{ODfsF+o4r z@5D1^p6`EO$!e)D{3XdpeMMS%8meLOMeyeVF1Ut713+h5A{e?00@TfGfI!71K$3ZU zyYg!W?i6zai|=ICuO8lpA7{Va6Uig+9U@9IfMM)!;+M(0%m``wvF`vfJ_pY8%RbK~ zCjhD+I`$$cC#H5ReCz5`1Hbe6%uS0cv+L=l*Zw9nQzpk!=?m2DeJbFlxEA-w1*X#!rv(WtM zBierMJzcbLl+F{35`y8uWL{lZbw50_N!p)0wXOj zZ}S6-RJ;hV_9T6A5ptG~_US|P=t}dDx$gcBgxRXud*4iL)c3t`Dy_|HsYQ(z{|6Rf z;a8f(Ba4_O(BW^Ntnvg(S3?0;p{{qcd?jm*XbA2zGhuKDe*1|P5yCdJLu}M*bFv8g z`+Y1^^&`)=a)J0hA9I3vJRnI0n$6+`NO2vCUS^L+Q)r_ye=N=qr)^D(saP4kl4%le zAfoEs)13kd)Id=vUDy8b-s4kNO^ zIuM0|;Yjst?L+K|)xnt8&awK2iTBUzUjd1dmBj!5is2ukv=hYcP^-&uPl&tv=AxU$ zFhpo{HDi=BDeImF^h%!E5SmmqYATM6P;eI1&0?c|lD2T%$t*5_CtyoXW#GzVXe|2k z@u`|A=8M}XF~qJk>K~0TTYK}$~588msrR<1rB_zU~@{Zn`NGfj@q<*V9H?hTq@$3|qUtJq17HoBksEgB36@BBd z_qeTZtF2#5+g!YLmty*G-L>uk*C}vEi6X{JeMqa2Al~}r+{3`_vUvwsd(&q2a;PRm zdiTZ*#q&aad}p4mCM6`ybMV#ky})s9@E^Q?=LkA$=N!rbY|*U<;X4KJ2P}#Wx6< z>z1RN8JAu*OOm#JedV0X?Uh?x%ra?4u58{HcY-`+^kr?dmpgo2zUUh?l2NacFJJ7` z=00Q*|JrF9+?tP1_6kPKAe)v7Bbdn;35ht133?-rG`?CB0#CH?!8o8LTJs~3aezd- z@wa!%DEe2`KK;;>RWcwTR|^_6Ix`Foqt1u36p16#clNM;OaY@|xXeR`r@PXM6^TWK zaOZIoINgIS_SY$BI{M8%tXbHb^FiI7g~)bx2K+Mt-etqx_dOu(GvZXU8qT%q8;5SY zpg?hsdXlxoYn%x10Sjsp!}xqZ^P~C3nx%(UP>!BkG1z2rOr}#euehh~Z%_pvWEh&> zJWeLYs*a(F*bX3O-KQRcKt+q^q>YU>%hkL$IC7fC0y^b|c=@VbN5>WFtVV}fdopL*~xE%V*-h79Z}-+iAl8$BH83gV zs5YKV4S5W+GTJ5an)Q0oe7h8e6yU`YwDsg6I!jpvqOINwqeTN%QDz zInHWR>XtUQaSZw#C|8+LUH&}SSeEFK>tDTg5+i-E}he5#(-q-vnk9B=FVAPSLXQeU^g8-CJCOOLF zbor4IDp;7pW09Q^xH|0yiGwTrS{(XEL!$ua{CGp`!4tr(H}=e=yrHC3AVSUa4fU z36&}9LaK+eY4fYi6zO4u!V`p*)22;PfwJGR227?Bj@|1qYD(Qk+|8-`(Uf=cin6Z0 zxeQ`)F{>O&CB0pK2YNuCs*GA={;C-E18@;sEK|h-M@mi%aNsUa5l2bchfs8Iu|SP2 z!BitR%?C|2h>QttL!6-gkjiQ%z~2C6`t0y49_3IE@MfZ7=CfZK!E z*2!~z)~1$9>-5EfiXdpy%R=f-@;^>*cSxn`pVhy2G-6lZ=}K)+V&GlaT|5Mbn%H3_ z#|w%f#6bUL6-IMi(+`vN=I!Z{j8Tw<-P@&a@R7CBZWA|0+;}pYaQR~10a=eO3~fVbVjRU0W1W4yuwA(XNJ{GB<#dVamlr-~?Msf(+)r3D zpdkpSVP{obq+Y`wr}*;aeiE&TJx7+ZI^T;?4~3vm4^0FR9M7ODeuhb&^4)Do=6b86 ztMA(c*5erkG^=I($sKB{5Mj{H<5ygPIMO2ZZUl5cFId?;I1f43x8RA|7BuWi{Q;yV z7Y;-EHb|z;>MO%DQEZkxKDRcX*W%J@@Y$~{m8l1|&pnY|s$%;{4va#_CSc^e&j_os zdtQ{G@2ELsZcKX6WswDrX$w6NZ{m?c6~F4Z=sm=@#2{2)9^4HmDf~3-3d1>tYog@YCy&&h_4>It4X1FJ{xhS!ci>?ezY)a zoHd>W!tSUAkq`F!uN@CStVr<^=te>gH)&|`P?f!FZRe~+MCVEyxdbYRbp}=l$F|3Z zPBL-teYMa(Dp*0W>$#R_R_lg^#P*^62~#=%$3Zlo0rPF-oHld3IoHWg+|$p0Vq`fz zVoh~YUVI0(*RPL872$)LY1QUfG&xQ;-G#43VMta}=a>`k&Moa!)4`Gj{>XGBrNw;m znZCFTi#H57F~0%3P`XVJk*fP)bOHP`McG5@=AkY}AELZ)pA%2{-KO?Cx{YbQx8>K6 z$f5z~7N#P!#ZE_@ZoRTLjh6Cq`BhwqYzVwCmKn(#qQ20tyRBez zWlI@|>paApbti`rqe3nX3<|CXZz~! z{H{GMu0qn2C87z!kP7UIGQqY#o(AQNj?kSb*kI#{No>9}#7(WK<##{fOFg)j#r_79 zhD@QE)1ZRZj4rC-=$_!@9TB3E=<-+%NhwWFs$E%Q^Su$U`d{n9NoMu9o@!)zKd0n% zrfW~fdO;pcwFr#ZDF!buSP)Pv>2G9Ryr#0MC@c#z_0`wpR3!zszs@6Z) z0fUcFmDU)aH%uzd0RF0hrDp={8$(brR#bn-m;V!^MIeRK#j)e^9LGciVEZThPm+P^ z>VNV#26AK&h1>7(AXXz)g})!6f37o_5aC#80%V!w{>M3z`LfWq z<9Buvv7-OwDf+vOGQ{`jnsfX~pZ?bYsQ`6wFeHNRM0Ha7$A7!l`D-WS+~6cl<6skw zlm70$lxxc90f}(42?&POM2H{x?N}Cf^_thz1jM%_c+;|T0af> z1Yi9G#g}o9#s42E=Q=eN3xwbwNT^R*ZoFKRoqrhTUdY@7ZS~6ILsIH2SL@iXP~CfD zxNTM^=PF+l^#qONv*=r$pT{2Vm}7jI8JU$T>!vNoClgC_FQ*IEL>2Ya7kgwAIp@A? z!lVF2zawFeXsEurK4n}5(GY94_u1vdyCbjM)8j`@)HXqcyEv46H7fbayZhV8`VsC^| z4E`#CA$iSF+8dwia~faiV^8t>idK|4+UEL+nP0P+ey|NuSI7LP&KTR$%@x1*fukpB3qfIES` z@=uPy?=lE4FtiQHR)zUM*K=Pj|B6BjdtjAGAh`v;R6CkM$VS z@gE59h7D2?8*@M>&<-)@OpxP!$D$*{9!gZb#;x7!P<;9m?^8c&1<`g$z>EpjRJ;BV zy?|gl)RN)*e5c=4fRK#99kvQRmq%8X0mT-Y zQN1`w5|(WF2{APUnK`(tf&`KkN3lXe^A|23y=0=vG9YibHLo%ix`fzI*n{&+;SD9S ziRB5I+*a|BoD`YR+qGCpC!qAUerY$0MLyn((^bPLa_{}cb__J=x6B~cz>yPgIRqru zGxcgx{1YUWBb%y8r}iG4CQ$~FBCIi~)1}rQ#X&$AnW~&nmB+z<0DXi)$DAS%Y%Y6< z(`riN7o6meya)^AI}gM2Y2Rh4KOg4S^fu)pwNf%jyd0JTYB1VS?qaKSEhu}r*-h(H zB17JTIDaS(V@gY&Sn%stkd<%iDB@;cEX3CKsXC|@`|^%YVJ<_je?+U^*9b{_$cl@RWwpse0W$lb577dmh<SI6?)^*Y0AFPRGxl-ALV@B_({^{_2C48qsyr%q7Y)l7oc~p@Rq$xE5@ z`RPC=pi(&Aw=FHB5Vz35L(z;CjoD~MtZ4+eUyHY@$hCt0Pj1VN=N^;x^!>?dlbM3Y zuURrh5=g8@#%?prd4oY%WYLLt?x5684NnByN?ZuypGYt&RMP}H6Z;Vsv4Ox`(2 z65Pbnogd3m7SN{A{<`Q7fAsYaJ@eYP>ejxb{bKjzQ4Sn?|H+hsojy)p{2KrxrUeoE z(}&6Em>*b>{*45|s=wEVV2d}N<)3sB`?x)tV z+XH?PKJV2oR}zYdk-1+Z!E}eHi7@w<(l5R4Ev%~X!oN{e&$MwsRq9xb*f1wBDXhXA4>%32sL>%>0^gYZN6 z(Bq`YvE<>n2tKFY(<+9=F6Tqc@mz(+oSwCsI`&+dAEnM$Y+`cH)0I8LNY@I=z8CAV%U>q1%*g2PY>B6GygG5b zm-um9#To%k-)7D~*HMM*lcE-T7%^EF!8vq-{=$Z?3{pbua?$K0M%i-YSw6SE$K@>T>GO%RG|C zef|K1ZB=mzUZdLQJ8iK7NQ+ct47r#l{^b%?2Q12bccJ^(;U3zvHAE*4X?&Lwdc5Fw zU`~aL#P!aE7r-kh&!k2;GWmqfzJ}Y7;6zx@itc8~>Z@&*nsACi3lR0?+B{2&{Xd^^ zA2^CIkwAOqlwa%Eh&DW~((VLsxpd`?%`6U(*nd*TLptEBm90mm*W@)}tjTc2i==Yb zDDK)7SC~<))j~I;u6qJ3W|&dTbKD1n&ikf1ZiQr&CLds#tLTDcc7y0(idjELyA z-BgwBYl7?T*s(U&RZyr3N6qXn8Nn@Rq!H+;3UVq@hNbgU>w&Zn+449iw>1iR3Fd84 z(fMpS4tw%3Je5}muVENPm%F1M3ROfh*g$oVU{b|Z;L3FRr~_<9dirlq#`g~0KS?2x zIr@n2s95~$@2hgdX)8zIvm8a%Y?fEEa_N;h?F^3H#%{~>t~r7s5`~=`>F!$kSND;n zm^$E0cg>W$;%vdgZ!Eem9}_GXHe(KT5EV2-&H!agMP!LoGKMZ*-V6dpXTRB37J2Ni zl4`!){y0DCsYORbY-B`)B-lS!r1R<)yPLRGZl)d%G>vKlsRH)VH_mS-{FimdH8Q+D zt)D**Rj~SbVSBauG~TL~X_y7X5sRc;}Iad z;S>R^`rOi|P!w!k9dw%H@Z*&?g_85iAz+M0sQ&Yqqif~pt3zn>YZ+KK5LfWzqbqz@ z7-1$a5RgTFh6Bp_dH$*Q9rTP5v(%jC zssW{Ou|4}xUl)jwxA{^7JR9j@Iv#x1Gv>0qpdQ4DfS6z&1Dy6s12_)`3(-jvaFyL4D*`npo6K1{+s3M0e(|nBj zU|)(fv%%{6wR13@@c`l#MORRS&@Hd80!E~M<>|kpKmj~HE|B6ig2noeL(Do=1zKqm zDLFE)%w>xLQ3Z1x-5RdRqJZE;#xnf8{9qFd6KLk6gk!$s3UuM?lM@xZp4z%!jLmry zF8qwIn?165-e(t%-y^8Qu({-eqLJyN*QoPeS_~r=^vZPm_LovsbU8`-AttIn=F#6> z&`ILDy0)dqAj?xAq2K1(iidop3Q+s@qf?)<_i{E@Nf0BB-+P9hQWlhXFVQZlG{L>^ zmmW4kQm?-VC>#86J2^$-BzU5;59K2t``tUY3=jisT+_5ZE*Z}zUK8rTxXhYmaQ&(> z`N1V`(KYV=Zt3b}m66#MpvA7cN3aWf%d`mCcdEO;C$WPe=N991pbD$e8=dCCqjO~P z;;Z(2vhP8twZ3@pHU|Ib%Cj2Sq>g6zpfu=kz%GrMUZ!s~Ht;UyzFHwiHdk#Y+y2%2 zm;PArZOhPA;l-6vl+1)&8Q%Mi5%2QJF_n$lopPfb`;2BxOd^Jn-td?XZ^rmo!PZ17 zg2x@<=ke{Mn`9z>m(Q5sm#f{O41m%MwUF!f5=t z$!y(AK^qENz zv|Cb61x?z=;SvDn;{%5-Etegv(`wJT&K7OEgqG-!(}~T)2qA||UA9aUeZT^H%Rzfg zN4xU4-g~_C1g3-0t8VjPF1SH-p@(I;>R^Oq2+kWjiWH>FzQTw11xKgtWi=$={adTr zxg|Xvgkm3_?lJ+7VULZyO!m{8H@edyTVdmfNimtyV(eI771X-rdSBSCQv%1WTJ$ky zp$w^y?)_FzNv9Dz0IK<4fR`tpmA9OOqAMLmsdv?z5*geZ1Y`mAmYQP->37ZQV^<#M zynG;8tRNqxQ{{jUJ{`b$q!F%VDX=LjmGjX&&G4H|%4`K4;g|Mq@JiLn4oYY=!e|9T zU7IV5zF@~OtIcTx&iWhQl_?-Tx>>u&UYkc;t}F`*m@l|zS&Pv1$Qg#*x8%;*1LxI) z`4&vsqr9HBaG}%jlksC}f+-(BC0Z9gsi{rwP+DCII__)ep?ZH;RDQ1v&75H z1Ih)AWyuP~wnk&Q3M9_vX4g{dw0v4%H*9!WzTSsN&d9B%wusow)gtQq$-LM(hrJ&) zbhAR?&gFeT_&BF~Qum?RQ61`7zLarkC|ySu*Y)fDn*m}GOd3bG$yK|~A!u-6qDFp6 zS4BIvD3(aGUvOFN@$YXqC{wS`_=cOE6+bZ;_7n2wXKcS0LCBcc^0vp1x}|q9H8xYr zu1=jznCJRkK-D;L(zFe#mzAB1eos0myBW1~)n+)H z^>Koc>W!;|4TLxVi6-yWf$nLEkni~Kq+~X={Mzb&3q<8kQ~+39E%{NA?LRJeIo172 z*BM6XBS-SOned2Fl90kHTS;c$3wJ5GMsP_8wn9j1c#R|#{=iNX5yL~L%ZGZdZE$ub zK|EKc!w!?r9^ueb!(<#(fdffMVe#gHhprM^!l7Acc_IQWWY=SuEdg~n*GeqAr@df< z2oyAzBwZQnA#UTt79E|uu_XGCFWUE|Z>#LjQDnS^`PJ)3=VEobG}ZbPTT;ILqMhx$D$URU&DM&CTUgo9=GNgHuBRJ^;wN0t0mt!JFT}T zf8C4St}w3SuaQDL3Yipv=RZM=Y9wyQ3(Kb35k@!KWMrRA?Bq)`T6S(ew6k+Ji6ZP5O!E- z<2E*~w5{`9y3O#pek*9VsoX&C!69K!DPZ7Re#sX|Y#4Dl=6>i#KhGp$JjST`P`@x7 zze1g}GqiPa7(kmdwacV#?3_7raKTr*SE1R$85i*;U4Nmf!L&p(%eh*ty+*0bO33Ea zyIk7}t=?*4p4hs5ik{nmA`t+Rdn4#=x+LJzT8z$BdhDT#ItBP57OsLPz+P%bbtAas zxZTgn&Pg1myHL2R@6x+f?>MPtXQogT4P*r1aFyxInJD^N+UG8p7Uqj2>ygK@3h7|Jz|kx+dt5l($P zaOQB3y8`$4Jd}X~c`k)6b$c#hW_%}22;CM_Wh-Wi-KU2NLl#2TH5g{Bf=H$+&dqa7 zcg^Q{GybN(<1W)=j$AAEXkM#7f7Wd#Lq)>nG>h#HV>YQaiPgQ_8`uHQ>Yg!kZB816viR$tC)NY=&b zY1_ad$Rjrgjp1EigxR6UywCOnw5y_Sy)JW<%q zk-_Bl+(qfNE{lTDoI4Qvy)k;SOMu~>>!Ps|+F^QLM#mY4bo_kNE#=~h@s%_?vE|bu zr~P5)o2`#1=lpd-yrO!13aP)ek(2SNg&AL_0(g^ZywabVjenp_p1q~z9Q&Gn!eOxk zX{g9aGViN**puFxpv;N5G22-cgn=ejI%VuL`zmO?sBSi||Biq>!kzDK&>yDby!-x= z4K($(kl(Z5YR>s52tmTPp%MG>mQ$(uWwm9f1~$z>@IJ>p4D=CyDU##|Yq9F!lBUfq zZ}s;4y@MaLHv{(1jpPZ3RU$W=PFM4++xh6W1R0CxD6+nHg;p4B*4K1}yvEVz(<0u% zPc6f?@6R1^+&I2aB>y&O;UH$8ydn;d=GL)4wI-k9Doflol9KKE(q*z*S4!}eGQ)Td z9+qY3;Gpm8p;HFUwG5Ef@Ruq|HG9lMsbEmZ0rLR z1f$cu6IB4?8^3vRe;NI*K-p?#B#5z`h$J>`HkvX@`>r8WDx1IsByYZP5H9>_Sc-aI zbAM)Vxs-J?t~Wf-q+9vRaAC%Ro9->^7bdATJW4%Se_FNm@OA!sLRCtsA z;bxOvW29kaHnic%NUc0>wkLX(djb!Xs5 z@gQ;z+{Fjim>g5>KvweJ<1wLRNfxFX)3!1VlimpFuqSxVX6S}#kE{xr6RYN+Ys8Ar z&=23wtvB-70(o_N?%J+xj7E5{tp-aX@qss|v8s!D;g77@Cy8wd`*OX*7fGXhD8_PN zqDLcv?>Fr*_aGz8pvyNIHWhInPO-r05`lB6lgYOq=Oc@MO|1}!|A3&f1Ff*|J72C5 z1WI5?1Ozu)e#>k%8-65{^A7sxNx)`UvJK+O=kEm;^^wFU(}|2^)GLx*tygn6jKW;P zV+>CL!mv8=aR|DU&%xYfsc-6nBA#Zt)q8%)07f%BDdL#`*HLn*ylRF(ZH1hkPI+XA zK-k5ibY{SYkfJXp|sZ?%cX43_p-R(uj+CV10I&Ty!k6pTfrLjnJa2opj8y8elGt@i)IYdNNkK| zIoLpl;ix`TDMCNxA+&Eq)cQTH?x3_0DoOs`Ylpja-lzMd;UvK|gpvju49rcX^j13;tFUEV2R9z0vdv`}SQr!qiVy*uD5CN)f9 z9`$YE4n_Z*VOb550K_(DlDmp4{KTQI%b+scJqVH`Rg}vRl1*UL`=Cx{1-?nYu$w#C zeGpssdMqu<^W64V{0@wfdu`(@U0Vr(1&6D7aA#&iVC;U*nbHcy^u4`rQLAA?3FT~^ zhoP~wJAKey!$R%yt7|p} zoYli+1WvV9w|6bDhliLEX1l+7y{v74UlE-;P%>!?xxa;Qhc%P}o;>jlKQG{^wN3^& zJs}b^sPz9(QaxTWi`fi43-X6op>h-~^@Un88nryos7;Ym8C$}l8(qjBRlQG8}Rcr-;#{$*#^SE5NCdL@kBT@>9Xb~zLu-gjyT!koR1GS3(o7RpPHKUq<72E#iUue>$Yf7}~AuFsXwOE_Jy ziZrnOo}?5a_^5kOwOj6X9X$bFSfp6oGJt6ETK#6oBNbXeu4GCzs6q-83Uq;H{|QdA`9G9vmKh+o(sw zFHPujA;E%!CtDZT)FYHpc+l9-ogxmN*?Fe!E5*Xs?EYA=1LuBki0NLJ*Fs9YYra{% z-Qx7ZFPIzSROz+vii73VkKrY^MK`c_A$Jx4;ohwVZeBWfbcKQZ6 zSl(h%dv)B_1b|PzxGR^wn4|M_iWciUwIOCY%#RpJNGIBP21UGMB5l{yAAC`n0Vfc^ zI_gKu(t80$Ct$!&B4#7{9y$Y1R+>osCkJw7dgO-HY}`R zB5Io7w;3i!}EV2EH|W~=w6^u z5M)$Jt>xIDTBm~6-EnAWek4OMYL1Au^^G)7H+tP;H$|B{B{U;#B)*4R=e*r`EQLeH zubsd?Y^k2hrBXa!Rc)r!Ik?s0`lbVf0Y6- z+n%ec*~1os8os}Ys(^WZr29_t1!FQ5^wR*7%srdvs?cmLQ-z4hbP-dJ*!iTFppZvJaPIFc1iUwX;Iv!ToHbXcQdThc|Vhez(cy-rG% z?XIusPxD2xoDgMhlt=S?5lsOJ&$%UZauF+N5*A+?K=%zR9&^6|Mb00Sp#4Vg)=dR zaRSLxWWIkOnwfoszP(dhyTM+5w|DjU1ZsJiB}>{O!Dn@Ep0*yJT#j{ZXZdM^PI*Y7 z(|4*100W@0vm$>e&=p^$E;eSEL= ze$aBs$kj&3+Vb1x?P7rL)R#$oMmMdZ1%l^-|N!g;9z(JyWH0?QoCO_M^HwZJvN6mqE}k+`nHYu zxBhBQr~TXzwcx;RvqG7A#U5z1T6WyD<;{Mtpxv;~^g5(by=>BQC6+lp2miFaT5qyN zoR)SARG;fgPF#9@wI)NZS{H2h@VSLL^$k8CxdW~I9#_ayK-H4-tgU=^&afI&CL%+_Vtx%fR^U_9R#K|`mFJWmh2b@hp%_`vet&vjShz= zcc#V{yMmIAA;I+lyU!1@N+gRBHF#la_;=NFH@tn}YK}xv?nifXkz1|l+;i!;ZTKva zUFw7;OLdFqf|1XaZq+i?lHTkFYxlvGl#3@sQ${FZ=lSw`kSVY8@x+=cS47)6U2nrT zqu9#ql>;lG<|Wo=)#qo^D0sd+iKU}?Qn#+l$KF}&j9Vwdx}Z(w5`p~BnWzz?-Uipq z0()cN(-qj@sN}WLje$KmkGs2pah*r>2S!*Y<54Yvt0pi1VVgOfN3@^}K9AMYTG6GK z6_56(442v9;{o>FlnWdtEd#Gto3)m)W{q}MC1`cK37R7B_;S?&cPE2bl9Ii zZWD$z*eD{A~R5JrW4_UlyMq<-ZE-^eMl` z%Tw)8E#|hjzNYY*@y-gFZ>Tfa@ttF0XbD29a4VthdU@@{rTt*09P;!af(jDSD4I`}qL^fIZwxOx*=Nk(7mGZgoQSD8 z5umFwT5J4@d&J|?9k?#>%r?LU2zBYgUo8*_xEq{(M+MdzE#d6dGRAVIl5xB(LlzKP zAEil7lr~Fk155)jYcSb_WV7`|W z$2lTDEEdjU;2jl5Rwc`5Nxsp5Ie{k9mWAFHMTr(Pki(*(>3g=>7(w|`9iLZuG<)Lb zS3T>|4lSJOo^ht|m32m>)A1Khi^fYn?#~AtI|h8FY1K|)!!yEo0uJdx(Qjt~UdUDw z4|zSG=DSPsCA0qvG|AADNokVJ@e<3s2_1?Ro9ypRjH4+Vh3Uj*oV)HELdrD zcC^Unha)o+&%z;74{t2|j5Thrl(X^7PP|oj_*KU620nS=nNprS3`XRs1X#}QQ1PW> z7@3!gEGuVUM(=})`5#lUlUkbH4c(c-VhC`AX484H1nqtU`(s;$&E+G8LzSmz#;7?8 zTp-ug4G(Ljn&DElj_=bL&7I z56$V~c4v0}#Of0~_OL?Et26>!fo2ZrcAw2QrQGtmUJ%m{NoBWc#q-E_agOYs(TDMy zu+PiB(mua>2aM*yr_i!3nGbcwaWmMz6(d@SX=!CA=U9xR%br^JDm`)9S}BvO*}At~ ztihgZnRz+_wvAMc^Yy+@$nTI<4god?=3-maCM%)^?5(#PTbNcNcVX!;gXZ0UkB?Q5j zJ(20quPQNIB<#sq&wnv_6C%D13*=2F=q%cE!gkYo;ge*!Nhu-k;EOL`XAdLJzxgrMO9I+A06-xM_`sdri}OTAtT{OSHB@Iyyg*LUYAFL6jmKCIR0QlICf3)k?2Zu zmfJcfQS2aO^}`4Q-tk`#Q(#FO;ZpM#`(E<5DzBc#9t996-85bum=rN#&Pu$|2C`2R z!jUupR9oz-4q)7*(o9{qex-*@Ag2~*kh2UC-hNSu=tG5;@*U<6GKO`(eYmOHWc|^frdM zi$XOCYfdOOuknL)@whrGi3jj5mk=^Uif2E)WG+V5~xdxErRtIi83m z5qy|uQk7L$(vi@n=Cnq00J9a*>XllsWg@b{6+ttK==*W^ZcB9o`q(z-)*fSdH8&sCD&;|7bQ}) z3Cz+<8VwkS1I!)8xu?m-^MtzF?k3`F_2OZSEm5cg+Q?bTCKO@5y?g6>Y7ygi&c?QCfA7ZMpuW6OQ2wDWH+L2jjBfPs??@HxEQgHfU(iP5sd zx+f?O&gZT+k_!;+Po*hE`2vrG(6LY}_!(ThL&1Ri@HkL2up4J~2NllEiWYopnJAo> z@-nXoKU$Syt_~M|%<@=R$7enKl@^8JTabpu2CFyZeKk-SjfW!^)PUOI{)36kQ_`EZ z_WhShK_%OeAZMRj;HTrcm2h*|d(gX#R>5#{oBd(_l4g>021v*0^3l0Ov^yO^(uN*srMd5DN#VgOARtx9-R-GmgV%l*&?SlGSB?ZL z-;=VL&2vJ1xfV$B?3R_grW9&!1-RSpQVY5bm+Wg>t^wXn2GW{4ia~@LD~(hEV^bbX zZ-L;N?Z|M~A`#$s(^$rbKcFD%HQ~xk5)KFj^4g?DK6%Y-zlJnQwskQ=>R{kAeCpRO z&^@yk+NG(_etO!#A5YAj_$w>=?~z-}G2jJ(s;!~|uhBRnIm&brA4^&mj+^QocuYr< zM4AA0HcuN571_)=ATXbQg+ALiCK)?pfQ=0V#vpe({?Ix-xi%GlD zJ$0`{KFmdqyXeWmdttIMZ0XZHY^F z?qa^bB!e8_n`LFlwp|_U>ImL`-gID-z@wjQm4fh15{g?>+0hg@fzO;Rlo)*Dv-}=; zld(DF*w47m)0Lq_*i}Sw){3lPZ?G0DsUM8c_Uk#=^k;aDD_zi=Zd<+iaf~{YF&7VX zgK-ksE{5fZm2t4w?gh2)t^X82kISty!1~lGO^lS?>*9wTaP_NqPR^IHg;aa#DV0}~ zpW9O`*lpYvhYWs8e=Q5B0Hugz)(j|MD)9tzFJVY=Sn6Ox7{02^nui)`_#7AdAuCh8 zUxmDXvK%0S8!{usciD9D+4Jk-D!nA$TBfHPT(J`FBq;xd#!wJs*Sv>^U1`CJ;~dFL z$(M(EbsG`;k!&fI%Zh!#e%rOz=EA{j>|T(R_;c?RC(Ex?NWIYdAlDz0uZZ!xhP=fk zlk|9dVMe={Zrx(!FDpok7U8T2BN_gTW25(k-wfi_^9@q4Uj$J7=rfXBl6XGmXYbw6 zk5VuPNbp||Rx{W<0u-jGT^hJuWwCsX0K)8rWCSE((j;d0>I*nixli~3THbT0hK1az zZJ^@h-@7P+2JqWSn8DYaCJ%W zEX5k~{$nr@k)yD!_siqPIeU*f#KLvWeIo9~WAEy)d!|~qM^Ujl31<(P6woz)13e4b zMtIx2j$p2`TDf|!!A<4|bB?Guon?tUR_KCmAhF_Pd>HMw*Zzh@guP@=H$q<}!qo$~ z+Ock0ZCag3fu<^-VIq)lV`m9OH}tTFGF8f_;va94-Yfxrccvm74nO|1qP&7R$)`9} z33s){qw0#?N>(o1_OD_V{5E3Z@!zgZLjd$kYiM&==o_=LqB&p4* z;m!n~e=-1qyx@*k>zO&!aVfrkAV>4mA(A5%&Qi)~X58MPk80b}a`a`CdmcL%KjhED zyC3RvsDC#P;Y_;`LvuTg@0#7ybIFW*z6*MUOF7Um!f!x%x-Iiv3Wh8-wsE2P)|#5yJx%$$v(0wfjq~2U#%VGHZv&NZupa0cvjBq2YK#&S9+L z6wPw~bI1SNx^%&URMSClf=#Tv06J&LufKcbq5thl8wpld=!G){jdCHXp9Eu;}mU(ao-oke0{+eFb2E)H_mTp z%n(nQv%D71gDQ~{R7Aud>g1JhVWAsQqe?#@?Lm+yS3m#`pog-D6soG*G57GTc zDEnW2)4xX9uYAfs!3Fx?WX)e5!~fb4M}&@$_>8{zo_y@n|M+Jl?l2u$+7w4wZHNee zccgz`ObZ0KK1erQN{R;k|2nF`HXOu6NZtlL5Bi%+^{;E@TR;iG6h~hy$R11in^SB5 z_|pwShFt$fjihw3ez|*AixE~R<5xIC_{Wh&YN$kfxiodTxKjSJLi+b<(qQ0mSk|Br z@hv)<-}{*hHK`UTQ~QPx&QLHeZj`4B@dG;EtyfD10F!npNm07HyK_sU zNOy;HZMwS~q`SK|U7PrBJ>PlGa~^%q`~JgkfP3$md(E1ewXRv~T7S}7B%c<>*MytH zSl7f^fU)JjI_z2qIIJEo*&!0n2suXyoK6f*L|m4gW=;f)$Z%5dGHwM+lMA_)($&r( zddpbE%>1x}Iyiu}5b#Y%+}Jpm3GG1CabiZ}H?eyeOSI5DZ7X0c8~j?1x|HXl_9M{6 ztaI6IMFIsAN95I0i_c8ooZ_&*Ox>5a26k;~Q#3mlkSK7u=q=fnT68e~+IHsYVY-0$ zY43qQWmz(R{*0|zgtW8&3|_*GxVtEBSIVw>u1Yg73&WSc^Fvgq-IY%|Dn}@{bu42Q zomw;4Z#t}aWTXeT8&Y;X0Mw$x2uAj0Y7=H_ay9=GP34JY60GB_C+PwDn;z86q`>TB+z_%10~V5>kG{c%VR(k4 zu&(l_GIM-F@Jtqh7;YFHADoZxuROF503^>V^Z1Og2^S>`VH7PJsFxS$0xx3e@Bg)n zJJ5w+FZ8cn;_nSus?P}ElA}q|M*I30aGue{|pHYIh%_hUPuMd?7xZfU+p(auo!Pjudq8L0y8H`zplO* za>0_es0(c3?e|48_4^@`^*Y{hx+;z6G6QvSSF1@oflW@waak_*RdADyPD!Aq&L(j3 z9K9o5pAAlYMyp5%GI0+1ZCwL9)3N}@XC~wiEEs^5l9`LEn{}>ju{cRNs@i&{=@OdZ zK760XK-YyJs~Tq!^ihhP`sGTe+TAOhZn77=jc(-WJUGHXGf}HCk!H(5AY(bX5@b7! z%ycyG@$WcLLjtJRZMyh79-#UYEADkNbjZSOh@$3v^+4Hqhrn;HM5t{D#e#ZNAfqK zBO+>lR?jz>FwQ23yWBw)6;R2~ZToi{gR|nxI^@3&dGQJ$uXnnb5Bgh9?d6w8{KgP0 zW5s`YMMpwdM;pQf?x&lBkLy;<3I8x{e@$e^C)id~=1Qx5-2e3y-w3?-TR6f$Qv&^^ zW`}}a%i0VWjt_a|V!v%ef6EP*gKg!s9QBKFg8g58zCrL>P9v=w5kqjEl=xE?@xKim zb+T`lzv<;)F9950)6gUb4I&`Caa)pOg z|Hp&Gj|u%ADF zt5B}`zh=O>1{q+tZ1=dEar^BtuYj zx0@a#vWqn~qE(i2_i3Dw2f+FbY=!ss*%l`7VPvfTJ+~hN3t1}uBEbIU%+9Ok1tx)@ z%~v4cYD#~h#3a&u=^DJG>z-BXzEMV}c@yjFF9tsP^1VAa_G8XHtA~K?dhRXPGA_5> zMWGfV-!DD5{Q3xC*6nS~#_v{FSPTElk~8qeGaz@5fYS;&7f}|b(q#=%zQC0|)rEhI z7wd+1OKQuEx8d3&3n(@|T#*lwX00n4Al>ah!7iY1Do$cCxOrOZdY=EO=r&=ey#lZN zo0CNyn~DBt#m>nRC}$&{E1>}~C3Q{*lru4xwwiCF{Kp1+4? zhbaywh`+QT|78=yQewEXx(gwiU-h*I`Yz075gqt@}R zZe1?@2;8jHGnmMLTbKp9qo4WYE)~TU&gQ&jaebf2DE-14I|0{mr44}_3`*Ig>M;z9&zleqWsMP(k%}+>&RkPXJo)kVgF-$D-Daxk_qyCJ^LRH<}~u_RiT4*aIYh zik$kLyXww%amp+vk4pn(MXsbbB@a{`Vp)jWCuzHzoAlU%c6Ib`~8?%iD|G-lbv zo(>gzEY@5&Fyjs&a_q_-UC`?`Vt1$eF|OQtK_`4^s~NxM}9^T<~_W@I(80lKF9+V zi^!ri85kv=QAPla$2S&N%PZeu-=oV;B@%zM1s6~=4m<&G^fkPaH5m9T+O=GHW7Cbk z4#B3?KLa;Q9MadaH3(tb4bABqQGUe<57z@J9NQJCwCtTL(I!+ZhNSX3Ehf29sIZ_4 zV(89>y}TwDn?F~`>0LgCtwPGtMtymzfbGVQ>Yxm_H!jP^z`8XMIg|>bNp-*S$`ntT zPuctxo#!h-3Xh4#_Vmx>>dP?HpN|lSH->2|%^FNppIFUUxt+5g8)>~#7HT@U^KqT( z3hz4KOE^`lGLQD>$s##6QYy6dr5akhP|H za&SX9!{D!cJt`S3<_Vs9Kwzkt{16WGR20(z5NP$~}z6B z?Xhf&Y)o$3E8OCtylzVR6J%0DGs58NC*fnU8k29!Ry%`?WB|nhD)Qdma&F9&VWd0) z<-+;<^$nTKt95|RF9?W(yjy)HW{9H*2x*ng-Ib$Gb+#Br3qQaKzX(9VWq3aXbi9T(Ro|L`Iti&l(vl&z^mUrD==dEb%eNMitooM zEK6ZM%V*Xj?fY{f2VG*XtK+qR85#gW1?s~BM3W{MBFVb1L{NX8^{nx3ByB}GE-@&> z^FW2e_^WbmJN?+?heH<4(w)()V^=}#9Q1}bIRrecCE}- zJI>#Jng|VmTT!pYZ+w2NRC4|~>v0jM4BsE@AYs_Ev|!Sj0KU22k2(6l7HzwIgz3U( z@ZKd|4n`us)X;+n)MHZ$1$MF@pFcCA&!#OvF9KF~a1dsVt+8j`{L@wo^oM^nBGw+% zMfESE@C^?PfaX~vwG+JIF~|86nb2Q*q!$KKQ+C1YKa1?|7JZnu>&PN#w=n!Sr))W$ z`mUM@2S*ggI8plgfkRS$NoC-Z9#dvLd*X2;_x{*I$V?(}-hOpNb_{&lLyXo|^ z?FAKov}*1Rk4!<~ySneuF_iza@8!j3BLNmyn%$SYOTD_*@wGYl{AQr++nG`h7+=!=Rxo#rP#u z;96S9xoK;{hvQ0kA8zhXgd@zckEqML=FR$3ejy7;zH4p*Z!ml~2=T<@ev=LG5~L@{ zh02XGo`g(q6bn^x2VpH? zM-g`|2&~hk{-qe!z~>$#FgLY&B9<(e++Do#_wkr&kyG%{~ z1AOcCLRx#f$40(RM1kh~gy6(H9XAUIc-{blmwsdnOFn4QXt`r4OD!hXU-WqSFpzDJ zW=WR0Z{XJwOW)wl-~#m_Eo|4fkC}pqnDjHHl6c4m?6-zeV~bzk6sw+wO6=(q8E!H? zqq{cqOX9ZU`1WZ<@BnPJcv#>z5pq<`=gtk=h?C-R(U84Tz+N(bb-QBceBb8B#9HAF zjx?V;wu;-)HT4c7;Iw=X*vrh7pg+3Wd=>Cw4?G{5uJ7Mn9~Q`FDTj;L9xdy{^DS%` z;1i`03fO)Es-nIiO2DU|tFha%L`D|EdY?e?<@WSa{R2%Wux9k1ivyeBA2Jo_;vr*sro4%vkZsT0t3RyR(Lk*V$6Ck z3G=mEuCe-ZZG(K-G>Rd~2H%D86PF@+?()!B1Q0{C&Uep9@^>M~4SOS1wm{r{TPLWO zE7=mX+x(EJd(Cokp{77Na_)eZE5vWz^NL2JEYL)a5SwtW!knU8HYqBd928A0pA{tr zkYjY6nMm^vCG#@gJw}aejsbIcfF2eMBNz@M4>@$=P4dy4hzRGY?MVatLZ!W1q5V)2hv)<=#TYIB;FzDLT5?5_&<%8HhjkRR%%oE5 zn!G*`t{{`zv_L+gh6nIA0hBq4{bG$FX7@9%ED|``QYQ2~6MuTBkw$igv9nfw)60(k5)P( z-e#$MccPf2UwPpUO5~E3N#Uae=sCj?9Tr)(M!EpXP4Qu{AgcorpZ%Mrn_{p#w3C{} zec+1s=BKSgl?;YHp}yStbSvSyaB;{>I`O*srGjRG_Q`SzvA_*yJhzb-;0z8|q#Lhz zZzg%PHlB|$wr7PcPBrgs?cP-Goy2JNf8Efsk7X8_V|P1DjbQo!n|ET0U?;dnaf1Z; zu{UMabL%Hb0VE@>YcvwilpA9DB0D#aZH=aDph!S7_fDrYFZZ_jqgkP>Rj#Lpd#sJl zJGKBP&KCPylDkUALI~U(hHt@@!Ssun?>b^%cN-yWT9+_4b}OrITQ5b*-bK8JSk?8t zv|w#=lam41V=`SQrYHc{V`x=(-0{faJnhdgTL&F128VWMkN~7ok6I=vB8#W7VRtV& z^Kforqu=VLXKA++ajyCy&Hcys>O!?<2_Rd%oxY0)r7w!Pkxr$mEy`;x(B%62S6>WPAlr~4~n^u z$9IBM(zi||+OCcj0tXGRvc<#I5jL*HUficiv(`HAWVlaXqdeDnYT<*iZ}QP-ApA$O zOxT4*V&c7Kjdd^UmnGY9=lv@QvCrK;@bjXqhQpi&X$Lik7z0brjTv7v*Rtk{!f+q% zoMs2HzmOdX&v#U~T6wS{>;_ds`%-*_0vocv;)aLTJ918;Y`H#giHn6MzM{vUihMbY z8`f1Mz=Y0aF3_;H*anC0oI5o({IToX&bp6`#)Fnvv#_fbYRr?@(;~M39n44aWpc*D zbWOphpLFiNv02@Qs5dxEs8&Ida-={b;M1)K5-RQ135uk4W7wp!c+PmQyJnU8g5j!MG;;a4U^d zcc(I&wreSF$cE0;~;q{ev}nqB&a6;if-Ov7_X9Ju#cMb zq}A+ego#8B44)j+n1T3jfvR9DsBdYLNeWj2{5}!MXdQJ3r%*hCR_V7dckqQayS6e!G3)VdpxFw2XNqrMbVHbK(=!_PAul~OM4%eVrh1M z%pS&3cLSvaVW5HYtBoT%zMS){+fMDxBNfau3f#$QzKUAX_oCh}YgUq=Jdg8_Lj&(z zs&cY#J3sHFtgzDM0E)ikq0?Y4`P`$GuR*!VG`ImMX8%ndeZ{dT@ddhGCVx6{>(T3~ zDIAOYZJ){PxIz^c$@uqH_$wSPC;C;}OiOnj+5Cc%Lv5DZ?a?K_RH!cztO@BQk)YCY zpZ(#?-JVFWp?c~-0w8qL6f|*y;OpWRG_JM~B0;iY+ySeGZz~7HAL%JRiBO6^{P)ly3LWcC(bG%*a*Q@{B^jBPRQ_N=4DpW|P-aLB2a<*~~kkySg9pQpF9b zk{OR0e5rvdBhCRS+-{T3&9iJk$)CzZu}q$gPCW=}GVJ|sqC;>Xr&3OhlIMm2xs1i= z&K8C%*-#tt)Cf#mO5@vhBKX%Mb+k_Ah02vOZ-)%1D!loh3bQoeP+Jm4Qf}zc47km6 zIX---o?4o;;2o@e$=Kwoc6@Z^7q`hKt9Yy+UJ>)2|LYX7fUYCl%4$*-u{a2m1!k3{ zgWC#69FK#f4e<=>5@p|@`~A0Zz1+( zZbNRtaBaPI0n*1PA3^3F>Z>b@wk(&{7U~!=Sjau<*y?H~-mVD;+phHPu}Mt@a2^nC zZcAO5562y${-o47q@D9zrwL=MMxKCSet|>F730z!bk|@xzPH>Dx0pO#iaBXC8-7+L z-I4A83Ig^>xvZ&%!10auWD(DB@O4^5Z-;=PC~fsQ(y83C5Fah+8Z>u7GT%2+Rbk4m zYfL@DQcWHE1i#_@LiVJOU~oVfJ$$ITbRlcEp>XP3iF@{DAYCJWHJeWWJhpk!V;>Xb zTpB0Fceb@iRcv~ha>rJ@mpqo8?7~-#ImMdlDy{qEWZwmp5SJ$&DW*3eGnNOLB)!+3 zy^GA#c&WP|HeD?}Q?=937_exMtju-!&2`*xIHt2>%|6EgN%v4Myu|B8NeKe0XY4z6 zs$6!h)kuB_grANf#RYDMF&pdykvP|sv7zk(mnW6<)nuXfRRYknW+aEaY&+ZS$SF#4 z8Ay|_)5NIOkr#kFMmN?j&ld}6mo9c02UVTIR6cf111!%@?VI!wWInWrOd0^Wq=ep2MKEvwd%V_@110q=7!dO=ya)IK?l2zjG{7B6| zgj{AH!yxf`@4<*RTOTw;mqQhup9LN|VsiTOBlWl4NI`e)oaeR*h%lxwN81zqw(doG zuzUkvOheSjy@vsQH2bURjSqW8`*GIan2q(?6!b}cUrxGDVQ9yBmdG+x!*PgEC6nIb zBYjC0aGs)jj%Za;ZZsV47no3CY1QzfB-F1qMXq#jNyAP5OV{_{z=_<81O_?wPXHf{ z368i3-VvSei}O9mM*!V_@M@R-!STGcytXWUIyX)L{n%h~-fy#E3#H*R=U#@W@!mwi zRHpuM8;k8e1i#&}5wof!E)si}R9~akb>g|s+i^3VbAb&1otY!4yOpihY;;b0R1!L8 zM@}3Z+~Kmj^Qr8?{2)i2V|Ka$1U$zujp&yIP%a@ zp}$OB)Qa@1H4IaaGFB7!j8koLfC?4oP9!;SWtYupsU$3Zj1NM)#XW4{Y^7UDOE>TD zhMz7nWi`LAp!`;|!=S58Q+XMUOpuA6WJRdO(6?*E@2XCb--r5@R?c$$J5eCgo@xDB z1(!gS1T*H53)!wNCWr5o&^OJaip>U`dx5e08$xlYxySutDJgN2WcEGt9McOyn>yaw zX6U1!rvfRv%qr!UjX-FW5xzHL6M6Pv+#UmUwk5pV$6Y;BqZ!73q9Xvl5LgUPI;wiQ z#cejW+^87*n#yy_$>XN~H0!m*2L3r^fK0vrE2wFft>KOJ;a2~4n;J<%XI7F6+ycYL z{J1ARxa;RieS51yWK;wqkK7k3BhTbQf%SOztNlb9bO`!us?j=4P=+x}5)@Oq!a>@0*qW}sTYQ|$`rH^6axfwbWMQkW_$!;P8%}rI!1@42sTlp zfxdSrcLpvezLlT=Xfm0FeQ8mKfo7x#!bv>eMdu8svlH*&X_IY=ou%5~zFM$!BAdiB ztS+){JIbt&v4>P*VAi4q|FA@`IQQCd2v^5UiVbovf1St6fz21mKB6)AbO&+^p z(v#7&6?|U~@lSc|yC6BHMIVj13uR@9_R3F4w6n63v)tp-5v{wBdu&E;=(6Ph>GD6x zRYHI+hD%}vEt3cq*j04P|w&>Q^j`ydD?9xTi!85k4TjXW$T)!MA)qofDm zlMA|I3o%=vLDgj*=Tq@GAA5$kE04PHcif|4WR3NN z&jn5znv-1GUgK_%p%ql`AwVRN;v^c}Ev+9~`zHhlttWS8IJdaX-4>Nnw)ihsYQH+R z)B{1_8XA>~GL+3!k|5ugl6dSXjVUPe=&vSKb(9$U*P0!&p!Mx z+*#^AWRsfFjB&oY+S+%!CXN*^U7I`%XTgh45JUYzVce$WV>i@ zlHPKs?2vY4y>~GJC9Pt2lwKW^znea$>PgqYWCy7?MDG+d4^;%V?*_4tUcZJ*D&z{X zUwp$(cv&&0yytM?2;x5VRVXg!GZHY@!{n?FO9ar}j3Vi^8BVwPCO{|`HDD)aAJtGf zEfec`t=jw9AZS}6R}f1uXOZ5D0Acy2J}zK5XwAu`G+)fzw7Fh(_bhoV(+s`4*kUMj ze7A9pfn_$gv5y~pp#7&81d!gqM>zC1CL`9pN!K(c3x&dLrre3|VZ&ufn_MmRR6R?E z-lKzc$XMt+-?=#>!yn943$lr0yZUoEJ6pkCB>fY5Jfwv2=c?#5T-gA)y6dXlOtF{n-4^Ur1e_jkLUoGXB1mrC0Djki!gK zU?_oyN?`|o0PX%A4-!}fL^fEB`f$u@@^k$5Hh=3>6{v(@QPJdX&i9*<^;blMkUbS} zsT@UI@#7k{>`%1&%RxeGF(f@P7QF7sO*!4Z$fPEjd8x<93Ia8`>Te*26&b2mIrO@-`KwKaE3Z98>vZ>It{qrg;<_ zX@&y{m$i76#S$KkMU(wb1f5o`7(gkGNwa~VD&Bv|(_2OKfS53E#A5^vP359dFy)8} zNp6lp99)c4S}LUFv|K!kkEI5Lkb&;CY{q$|6Cw~BL{Ir|+5H_&cOF5e(<&8X&}w>? zHz;*4&SElER(i{Hi$p!L%=z+5B&pey>@xi-%LZk+SI@i2fKIu0k97bNV7&m|QDR;7(k=_3Ws zx@f|gl<+JNI0~1NU9D~uL~%nRKI>IJDYfCvqo*kQts~ z0%NsPzQm9Iq4&~;Ln@@ltDTVm=oOLShf;US&1pE0z=)N7OQ!s)QLJ(*bF|+8fedye zn`X?Yml)OW5za(G6BSTyg_#V~KjcXJ@XXPi?yc-p?Z1OuG)mc-gD*i0*OC~sE5P0@pZ^APELOQxw{}<$4$8H~*7>sz zK_CpgH(nV9qWW7$uYWF8U|w&30q8$ziJ!YNq-MUq&Q0QXl0j(A8!cFRNT{B1tXI*&0RKm7cz0CMxbX}&hI#m=QMWlI>iQ`&y}8Q1~#TuUqe zbfPf8#)8$OCm~J|K)u+;_Hu`1^riO^6kjMmcAc>l&8m}Er0{sCPTrOaQ-u)G4eJkO zX6y#ZA5K(%%P}28w32LtM+UC$&LcRTWM)7|Vb4E>TSp(_dfszmIblOnI2>;&$ip(| zrY8@0^^eOoA^@ZY?{tr1=T=AUBl?iVVwr{`WFbIWwMh_Hm!UQVLZ`fu1t)ED7(;=pAfIdCf~X8okBZxQZzjBYQL9HfHaTu{^7r22c0f!6f<}uW#|HFXP|N{2p-#25#X~aH|l>$ zG+TM~Kogk#TCR=Usk-a)+d?(jc4p@hPk@r>XXStjQ^h5udR)dj5wx?L?1CF0i8s0P~2R>8Tv55ttBD|)f-EZngZ>)mggfNIoAO$QT8zV31C z2H%cfUCg2jMrXSZPjbR^4^MdY!zclXhR&MKRt9*Mj4H~i5 z7}={hW_jBKN1&M|=jGm2fW4S=eYzPlkB@aRp7)@?Iv$TtMY?41R%?t2rX)i3p{KMt zQ4R@%@)Eez*cwW9qIb{Q!hn6W^30l$IAiku12eFI%~ zGmJ0w3ZmhPpy|THbsZ>R@elfiKn-b zi9FMu^{fI|zhU)(vJUtwr`*YPRz>pHEThG1Muyc*Kmvk#(Nt#eI-T76{3(-siy56E zcIPd6Y{Sb$u4)A{c9vsn3p|zawX;A5A;l7Bgx-{9-{-F`%6?vSVha4^p!yk6;Y-$Y ztS+%Lo>kwnH#Igw64-wIH7e3m5Ix*7nb)|U72qu-<%k8`vOdLphENL0-67Yb5uGvO z@u`(a#`W(PMbZsQpgU`$$zZDUr5NH}m&7eU7Tc?y4?8~&u)g357Jn*~S2LZkNg_uM1+JU>h;AY2t$l6LH%>g!*-5Krwg0sd_%t{vv@s@ zY{`~puxcjcPC1@yv3oFs2rPfktwTghm5O0#R1p&52^_7@2qb-}>~ioG72EA?=T{lo z`f!Pxm25ERQ~N}8EI>t}P+2FHL@Y}s-=LrPdCho51)EBqYgFDbBC=Vjcjkx>>`Uj%O%~#wFDhCAdJrGag4@D_S!D{HWwQ_#;a-A^oQcNHwzH*Wt9>uigM9MFz^^c|M@}s z0aih(#U7Nv$pw_Bbr;Cfq~B^Us(9n22AOmdKs_?OhOyY_i;1p1#ho6|uxeBt44VKe zOG_g9nuS;8h{Z*QVD9iVtkuIPK&nQT3!G97dFp3|77y^h(JQ=WFQJ*Q@k*v6{VbIJ z{0O^Gh1KN0I{!J0S0}Kl_si*IqFGb%xHJ2sV%eE({q3n$4thbiopxSGq+x8ZR6dh@ zY|JJMu_$le;8HB7kmU_9#fafAVuSq(b?0iZ>{BObhr=u3y<;-y5y>o56e>~v@$0$_P3THKd z2T0WG&6D-j%LW&@mv|Ll8kCX<{`+ClYxvR8u?R>;tu7z}(l79~r*4bA>zFzcXOqkF zn17oQpyMarEh!6b&bm&*HJAf1z!!00z*4zQ-lv4xnNzbqvanN-@En_%L9u`@V~nevPCwwuCm`5T|LE2k}l>Qo_P7W z1qPc*BKO8k0o1$;nQ0r%6;@L|e17`oAAPgHCUE!Xs00lhtczd>rKKUq2^HP(tBq`8 z>j_VQBaH|5SE7p{_g&Jop%pGHR zRZ&9sM5@fM`9S#C5*r^*LxUzf>+Rwehy9NZ%a8zBNcV5D7jsMLBVEF6v zu%yfS`tm+ikLDj||JSnn&nlF7wS4u;D$KV!SyJpD0^fzj!^4Z%k?0%UurD8+v)8Bw zu^;og-0tWsYFsxZrP|3QRxd)8CySws`wm^RBo}G0RizJ=)xB+RC2jc&-b&twA!|wZ zo!iuMFE5}AM(U7vuWyE_pWj_Tds!e;OSTSDnhqB?^lGK@47Iyd5|WZZ-0l_7N=89~ zMVAgfmngJhGy+Nz(1k%1@pXOwUS@Osy?T|1XFSixi~X6zW~|<|a&EUV9}xG}@zv9S ze@y-(SY(WvwUmI3Dq(cQq1(g9sqf1Vgu!-y9lJ+myx;`YZxumW&Yk?xpJ38rNY@{fJ_7;IRID2({OcScGB9N+C9fF z3w613Je;Tbm@^zvadsBB73kP}v0>EC8yZJ_Xfl7t1_ERkLc-e%hKq=(*CopHOla`qG7udr9&?--4U*@MxfU1?6LhysSOIG*@UiItI6Yu-vjy^ofZkLF8L0W z)NWLsH+f8?Y( zic9n!=$!pyuL0EGn+_PbsEP^85&#!5WJyLxkWkA;E7FHV#FuYMOfe*HI7PNPTc794 z=jr;j7<_ZPBqvxmmID{7FGBe8RMzCwU{~r%K&5lBiJ|7H_N&LX7b-ck`vdP%UYLeX z6xREn@AWw-Y2d$*#ErQxl@e>duR>j|GMP{>uG<&M`;?bqEVeK_X4nTt!R;-qZSD(( zV?ZawwM474`x0_Kt9GFWEm9+<=hSrbi=xp`ZF<vEH zZb;^J>J5z5iq*;<3@bB0kLYI zKCiBNFu+ZwP3Ent+#0ccMB;JbXU^QZYuQagd&h8xGOBv5=8l zH_5~yux9e?MO}6rbmqKf$ningpl|x_kaN({eWTKb#by+JQiSzA5TtGC1etA|Gpyge zt+tvYGfG*MbiL&Ju{bsYwe6(b3OLw1y`KL#Go_>nl0?hXl$K#$GsX*=>TaZtloqo& zP-Wl9?`JH7C|o|ei*v8JtR8|?)ssXe7T30ER$ok+PY*Z6z4EbaCR^q6jN_y18Ridd zGMy}GY6jg|gcCaGW7RItvu*^=20X4CU{%@ITe=szzjv!l(xwA9t*)kBM$FmlOq2;`r!PT7-HtVB7Lzs8*XH|33U3V8m zyz5_#{3ZRqZfw+2-{M`>9pr6kR&1d(c87a1EZDT)748k^ELzb!lrA=KhC)Uq&W!r* zYQJ2tu7nhTyX{T4G}w;^+X$8^d^olh%e?=wbqW2zFxH37p1r@a^D7$56^<#F;q646 zwTHGX#ftCYyxYsLDBm24<)#XB?)BU1fyLtTFd0a6te@2Lcvw3gtrbN zlK zgU=t8COz6EfRsQH&mcyypap}q?7@qg;pW>ehIe&nPR)+QJhzlCrw8a83HPznd;6dT ziwv)~Ma=mmnPff z5RE4HtlPR}C*BZGwMN6_fw%+48I0+;N~V5zbe;_27I~kWxEO&=R1+D^I94bn{Bek{T5V0 z&3apCwWo^#K6MS}i|;tN7Rud}@GQMo7wzha4)|+V(60NVzKo>4t0cLd9y>g2DR3fI ztFmNo9g*Fb(IG!@tK1hh&|l_9N4Tj&S*Rm6!>uQrYrR*mgBfPlTMC!o++sf?eh00! z?|ynoJSK>vVNskOf*hCXqRQxUFnGD1PIfPN(Qy-R-F@EVd2_9FVU!NRYivwJnJAN8 z3Ji(7i%k|`ccc1cKK+*gp@RT;RA#J*7W@*Cl+(r1l8M4@TOOg)%Snh*vWGSz%hOcB z_&sarXk~keQU{~4<**eA6BoB1?}bKrJ{BmJ%d$hIS(Vj{43yF{vJuVgCNZ(WcP&$7TH1mqpL|#nA)p&1+8I%uY7W*<@(p z6Ha|IIUhA&TnpL3xDcpWqvP=Ugqi@lnxy5}`?SQ!f0Ja!`enh*Ca>4sKGtF~8&8Q< zX(8Jt#|GlA{~m4rxpVVfm31kyCDfBdi8(zMYko*{*lo<=?qKIUlrrJ& zw)YDB1~mCPiqWWA(0wWSfl{pbjXbFPW-A}wJ4C*PQ~|GPj5}9lssqwoI0!$qDNL2**91|C zfF{|M=i3`dtZMIHyFD2^YCbP+Wf{>2b)T}X3`uHLW?u+>v~ugP6U|hec)h6MXF6jf zZ;UeG8&CJ&*6$o@x(_b*IXw|eZc?2`n{WICD-A0@2h zlN3yTmaCtnH`kVsd}{2U?zwPy&E?+vjB>s`vkp1M@@PDO=%JI7s{ep!rotp>aC$6M zs@oK0pu2{>C%aX};&EbV(iGpmTx4ziG@@}k5s3*Jij?7?OMva^<|%e_|OC`N(~;TBggXo5b6TO ze)#QnfL#|S^h2heT@pnfVqj3`O>U) z3y+GnEOZAe3&o6~9^nrwrZ;kQR$~dH;rBwz7qprgH5!GgS!Es*C)5*^yh3{yC?3J~ zp?Y`f{vm6{W-3?^NNvnu;*5r*bQOqC5bKcZCf|TMAwJF1xE9VTwXI|y>!Xk26RR)& zZQ28b|8!6SY0_Yn??xZV7>{WeT29@yzq`3aXCkSdk{UObF30SajWaGkA7UbDpPHP3 zpB8|wn$^#jc=AK>4-#W|Yb-{z@z@9WXcnTFcg68 z$W`xjH=j!iUMyUmEoN732XF-P9+x`}bWf9~N=ix^7szKtbm*<8$Z9oqe*EkH-GB1~9RsHL5U5vkoS8@Y1LGDXid^HL4n>Sn%3#@w2etNS1ZmSqU5p`Ms> zBQ*x3OG_#kBL_2+`_J;^hbHdpLP#Dr+}Vvy@yqz+>xuE1nc*%O?4k*>pCQzOMv4wC z)rQDNp=u;JQ+K`$Cxe%f8{p`I_3=gO8Mh|J)9HFAu)6I=u&Rhlg$k)a+N|wxrhIC_ zT#G%f03F+H>rQsxhVh&0prw^fMYCm*=YZ?sEy6`jMMXuLo~RN}eA)>~wt6csiMy#l zansb~M-3g*tS(dOLAY8ru3UEgN+qQMpShyCCt6Yx91FNISDttx{e zDa>ZYdBZC5GHhOa76~x;vxdXa3B$o%xM;STse>!MeYkL4ydAFakcr0*CKt=^BWLo~ zLRio>Q7mqLcv>WKSjNl-OtGN{(VCPSPdS&Ve_sg54kl1;#+C9fK$K=5`?Mrm+p9R< zvU1$*7{xiz4nFh9t<<45h;AOK-D#g-`~DHG)-|*;reqFq`YM_ZnHQBjxMt=c9_A!b zIq0QhPsc`SQ~4y&0$tecM>uzB>hu35We}eyv2ZpO)^LPearE*;@+0Q zFm%D|`H+pf>`Ko{0L;O8*IB37P!2ce%JMBsads8r@|#S(XiFj@z+v@s&1VH^b+1r!jGW1*^RQk zeDKRE(IPzkrwf*ri)0)&X;I2zwBU&&gCJ^mb!aol4D3Sybo^_VmmPbpLmM%1;WmBWp&;X z_lHI24VOG~Z>Cup{;YuOo=eDRgDB_qZX#r%y{)N==@fM4Rj)tY!#rB{5q)R=;M4UG z);fw?*i0^o0md1`TIdWi`g44yt9@yj)G5u zFFm0P=(D5GRTC8iMZjJyBiSUUQ=!wTH_e_LanO-QIEz?iv00kY(jrwtN}{>y7~-51 zGmABHgoLGs zqe$D!76Lmy4B&>}XL2#(>7gV4!`EiT*_%qEWRRb6%aDb~08~iQh;X4P@oCMmOrBPC zNIh*glvZ`AML5N8q24i3`$ojXZNJx6KXJ`q)k$#X@uW+gIG{2Yd*;M+*}@uwqkJ#T z7LSL%%M`U+jC|;q+-~ozNI9zHL@$F}D}cbqGVKg|K8>pOTpx-X?j5mS95QUhT}E=; z#ZQp#LrH?GAtsH~s&Fc1!@I+wF@ZhOW6;b&k8@%-fKp*LRfR~2byzg4T~KnWzvO~&Kx}LD8lWow1e}y%QE=$W zuuNwwbjH6}u*ci?j`sQU4dgSaS5HdQTM-+s8zQ)F72^5fKbm~nFp`b_W%Q9!bYHZH z(@fe|f%%@I*7)zDJ&1QN9_kz(;?tB6XcXzTYDMZE7wz8m$9=Z=vU<;xB+Of`n4uBi z_~WTK>WxWuYW0rK*Sh5D^XxN141 zsu8f;D?>cKOmLnlYfKf64^lzr=*KXMWh*Y7YVMDRL@n6s)yoSjcldvtJYQL)t<&-4=YIuW#5nI|Ty)Pi|Gnej~tRsyG{%Lt3kuGB+p}u#$^g}7BfdU}`HOqyE zQ=dQ9d`@>W-!4ETYm`k!U-;;5A11KG$kEso zpsd{uITAeheD_o1iN6IfJ3{l0XcFzsKIek9w(iLz3c5;)?6d+ zgT33N^b0$yknQ(3E8#^ha*95YF8rC5DDeU@=MAx~E_nSl5q>zH%Hk(|Or{PK@~syA zZ6yggN2(z<^H+}QJBfIg&^v}U z=IyujxiIz1gQwkbh19yD7-!S{<{mz9;}!9sk{q}aJhEdZmWx+$x=2L$WF(~-$@euh zNIGW=I2%}gjNOj`0O4VBtLho0lCanK%w?S`vvE-i{N`=>)G4IBt5H0}tiTO~N_8#G zsYKxVifiqsC_LsELIIqtm_ie|Gw%R`Fj)^aG1;`4()ewJYP)3arzzQaZ(&&wk7qdQoMr8g9hHBVkjmL&6_D z_2Il89L9el!_Q-ukHzb==m-f74I~7f3wE|MBw7J zzF{$wi)Nx2(T>(;SBv4HqN1gX-ULc&%@()y-bN-c4$tz~j+LkoNgWooM+**(J+uVf z%M%x-DYPDtDzx0t@M?yyd2>{EmZ=9&`YMWDnJ{4Y7UUGZ*F%F#K(GH zrE@@E%W1lqvkjcJLk0_pMg+Wefna*M5O`H8rjU{G(r`2zIdDGGyIO4w!_YwFKWP4! zR~KJr`O2(Dz?Xtu8RUg+t}@l~%TY?CR=jaPaPgSRp-Q#b%k80j%!EgB#TYlyrx!Zs z&tPm8aN098W+EH~DnhJakSpxp9xGvW*;7Ei20my=LD6mKL>5n_`VFm24wJ?j?u?8K zr?2G}NRu_jqcQlOdXbz`geXpQB};*Q}gV&7t>YQybDE{>H`DD1;eV;~R*4&B3ZX zr)GLl9BNNM^lO<3o--EUnNFOP)>y%%=@A1W#kGX%4?5@qM=+Y{9d8}id^l8Ju|_?5 z8eDy8%n<;uS}$Tc!viQ5ks8E#Azo_iSUwf&p-fI4BcCvVY>@D+8lL&`rr4yJbQSna zegkCznumX?SfnB*eh-yy`Y9lYK5a;GEIUZj8#7o*)OKKz$YNiMEy&)tdABW5T^i@O z*aCIj@oZoa25|e{q;YE3(TPhuZ|4#`bsJ;O4AJt>JpiivXP!BCzNK8zS1emv?O7>% zv85=S#_-4k9H$(&4SSej*kVo-#qnsg^Mln?0Mw)4WZB)TV}X?zkCK>wXPAGQ#L}q2 zG;`mC#0W& zDexX?jZuQ_#$c}r*8#d*Chw#2s@>U2S4iJ1cY4W^yhe_`mK#R~5G^YU%4h&<6TJv$ zGVOJfYT3@#FQ4#6rz`_;-(c-2G+OqG3Et-s$@ul+s)J5qb2JY9(P$0i!`P}%mX|Cz z!{9??|1D@A|KWDTA$!?E35{G(7{A)eH8ZLktoUml&0{wceGh_c1?siI_X307{)Dw! z8Pcs^++}c&96+NVg;kSoM zJnC{B%W+czfTfvz6Y-ZshyiW*pl8t2(b0q2mI2{WmM`9tuestQWuEb@AVw~lM6A?8Usm8vy~7XjulGz;SS$VNcmvtDL-;I+qHpo|llS3pS2p<4v9Y5l}eq{og>w|7E zMIW|@*1ug8dC%m#=II2ADV=n)vgb2l;@hzn6cKmcHKbR^w3sF=%CGnWS$F2h!P|Nt z?fPs{Qz0vsi}jsCF~v>Dn_5PR=Ci&|A?K&(GWb&`2rW?AqjM_hAB7;gTIiCTX>_7b z7=2##-4Za0$7?rTvAoa>+?Xj|g7$RpXh?;&xQ|)kH5*m#=gIK<76*AAD{|`OW>ZZ1 z47veCnf=Zp64!JE@rB_1UhD>y5Of9rfalYbgw}?hYi#A;Q*m7;bw%HfA3+YavZv_c z_eQ~$4XZDKYk41Cl{&sN(7vTmc?<+&Gi4@X!-0iCh4m;9m=>zrWR-N7i^?rEnJ-;! zRK=fucSAdrFHNaU`TY8zgfjeBSs=hku?-fYu8x+oGTDYDw> z#Z=PdW<9~4A5RDj#v8v3nI*Q__PC3``6AB%AIYFfR#0gb z1jLra08ALub#}q~n^j5nr;Z^bD}K8%C|n?uSIoXcB==#)v-VgC5YH`S;Yg9>da2aW zP2M1nz7-xj93A0^cCp&XJ0D%#DV&cPN4zQN2|oZ>xvfkpLd3ipjnmldW&BZBxVO7&2O#r?gl5-C?u^qNTY!Vbt8-Z7jD6 zH*X52-N>f6HDPAmXMJfA1GA$zlbS|Q)TP-~%~83x9JZ8OBXHJ#K)+apPWUXvCXxOX zs-2b&$34kyf74hsF}2wB1d1y*Nh7GLAqGdoaE7dy(l%uXs5-$xWeFARZug=?gt9ad z3}jH%;zcBNfb2U1eswkcq?K`nIkIq(>^T*~yhe=O*mLxCL88)e313nD$$lhJE*AhwFumA)>>J^bsxcyJ2UVsE=Xg`M?mX7C!=Sw#g|ZRKUw-fS_`=LUNBx^szs7!pS+5&~I* z#5~3ujJ=jch3}kqt(&v`u3a#y$Pv*mS(P3k1hwvXzcWZqS-q+-RguO%ev1l#g)2Lq zHMBX&-8`|e*`sSdM@YH~l>7aYXl=i@0~D`uMZ@yE8~Ld)4X|#agrm&hP6>4Bmkm>h zb?9b6J#4zZDIpBa|+9duOyF0*# zlcT?nUj}3dm*|zPph}9yiWBU_!>v$hO|(LN2(`Rb=;PyKi{ep%b;(Mjt`w@R-jTBL z^r?OUDpDKYBhC_KnTbBNjn$HR&9NkzhxRBiBT2fTdGTkMP&~F7OaE=<$x@Ydqdm$* ztpp`_zBpcVqAz5e_r%dSvcQ8!8G4u)Phcf>LUWSofvk>KUdN+X+>l@9T36hp*ixEm zz(K(u!;QvSwky_X-Az6p%!}y2iDP%>opXzq`;&tpB5-lF9F{w)VEDq||t9ZE=k38^Rm9=Ww>S`z!F+0@F6TvuJ0i8&XUs&zxBwbUU0af2Kus3wl$ z$WtvdCRg803-+1rLU8}nn%(_S9J|!xRab7Ke5C2Bb)=o8*TI0}iHA!Iq+FtJ6%;QW zzw^P^N7V0)vP)FD6_54x_L^b>R5XeoT-)m2nVfW-cOTzz4?zKVH|evr2(m_YL-LbJ zvnVm(=Va!rb`LfxwmQpgkm_B&KBthL_{E{o_THp4sqEnx6W0DMz94_&?|F&-WR^*{ zrCrzV9%4W`ec^yd6X$|7mOERM=ddFoj7~!gG z!5>gvYzUD|e3++wO{ZRufp>iB)kUsLw}Jdj|3_}AjynnZO(ZhY3iWyt#1ufno=%;` zInavpr_H^hINd?i^Ve@Rk~PL?D3(?5}g{X&)?J zoS$LEzrXOL{L*htNHf2cMivscoDi#({QSjNO&u6e3QYwk$o)we9rfTH41+tZ``e8i zo_GWnug4{3UY+Imh^HFcl9YBj#`BYwBr~0zDi^tkS57@y9YfE3cs2AoUb?veZIQ>- z7UrKrqZ23dGiPXI!X*o}3Kn2js}kNCf+JXx@Ko7DNT%X{azejvbka3s%gE}$>#lQI zBG+Fl)tPpU?3o+JFZko&d0d-J)mRtHyGcuYN@dV8g5JJ9v!-B%w~^+2RtE|mhUQPj z{P1QaKCaz|s4aSh1H0$}*JK}Ek*c-zal$*qX^}#w@#siU8%+2s6iz)wa+xlTt~|A@{U9R3Ul8h5zrGhmspJn*ruJgtpT2R2f&KiDZM0OmfGw2VU;F{czi-?8D%0cA4qcOkNs3V$>3HPMT-84nf^PGLqFpM^S--5tmus`fBJ_ZN%9Mq zBLd?chzkBe5`>?LJ|9Tx&)rb2Yl&$0kB>Wl8malwk(Qg2sp+X~TRYAxf70kDf{!xr zTb`;pQ^^n2^7&gmY}tenK+qahLx4>$4759&~T={Cdy7i|O1| zAS0K3x+?BZV5ov8un5CRz`eEPIRD)U)Sm(cL`;et zU0=JX?w+2tXSJjeK}C-y#;d)qNpKtS@l_=rzwzV{|GP^H(SB1R@2Lp-d>h`s6I@sA zrm-i=p^tL`Rm1X6PhROqmPh3Sba8=K{zf+Z z5a>sj|5m+x~kdQG6xhH%8VcM-#eN#^9G)(r>ES$p6u&}SV)g+AX8}0 z>Dlo=xgP=E*&@Lp43=AO166fp8L62}$o`Y4B>prGy@h<1_$)d`!%<=dP~1<6FaF$i zJ)UG!i>Iotu~%H!;FYmLVN+g0BD0fnm7li|=tn$*QgBodyi~zc-?QIm`Fn+hf}fw_ ze70f1vj{E&_SqH-O!WghChW_jswd^kDr(~s3tu!e(@b(k_)>M4tUPpsUf4;7DI@y) z9|~qx@Wvph1I>v~@8=t&sWC~}L>6uDIJv)g&q`_iy=Dj>7~AW|0!TQwxt}%+5g#ZO z)eqI%YqWAWL`z}w8Ko%7B%g#1Itp;uXg*pI-rE;>r0H~r8-MxKQxQ78T_K-|EWBwo758L?J{z`RK>{YVv-B2FuCFR7^BX*V6JNN2a`WI z6=W}^(;9j3P{Yg7rjTALR&ou;Dg|@kxreVR&1;XPL9{dlgEK&fB{(rOU zr%%%v()()ba|@hgJI2)BSGXZ(NrD%jVfZGlMsksG+T@4afI6>EZCv@oq9K%LzI#+K zseG%R|K;@5FoT`Ckh-Ig8zM+}n697K@|xRR%AXt_^$P@rjOQZTEUheEbLJe}dLR^c z$;5bULy}gr|Es4bf4i?&mncL3l}yl|j`Iy&)fi%y=rQ7vgxA%$=N&%@_4Do0PGr#* z{$@mE^p>{<&@*xt4Mcil(zZ8C%g|rwIGplDcEh%Rp7;M76Tk+LCHU2Th6c69+S4iT z_*nT1*^If8#?;z5!`+Z$k(EOjCYIj?l;#88Y*VFIgLN!i9xh;w;SqPp0f^MyL#$r4 zSU&etmcBcR-g~QK$#A{zJc)=OUhu(~jsBJTzvw?w#JmJrx9Ay*(8&`kVmQW0V9lFt z6*(mv@gw8eWzmxgkyk`UQ(#|AvBo%Nk`-CTd2I5Hp^|ympve8D?Qq%cTg51JQU>_MOzUmUKBU^zM0nP|U2uJ2KJ^MCjderJX;Vi-s;E2 zOVi}|O|o(2vfUjwraVC1(s%H>c14X6?#w*Q&`W-HQ#cXlvIHd4yVyM}V*Oxxre^Lz z`MGOTwCUYD8wc$spZG@eZJ9N)di1eT5Yp%;T2VlLJ>snb4Nw?#Ku{sCemh7j2yuMWBm|mw(jz+0orMXbd z>a$4s%ZEp=dy{;OME86x%HbH7`D`l}fYGzJ2~YU& zN`f-uMpG)Eil%E$4!_Gfj&vKxsz-8zSgt*Y2wlB?g;zrL4om)oQTI7KkhkDp<{j}| zaOD=l6|csO>NL$jSDCrm?f2x7WH1dj>qFAjc&>|&g zEv7~Rpn>Fh?yT8{KR(JGJ8s$UnpskuP^5Ck0N7ax>VyetkgsB^h1+Cnlo!SgqD@6J zkzuYA1pFV6&a*8>r4v_lvFJqKRL2G)zT?+ZF z>@nAuR|lyNIkCIp6^YDEu8WrF^%E53#kKA-eHYs9dMtXo&~4)FkhwV)S9Om8(wtmq zwwJ5hFuid65<{#I#kx3hyB51e<>Bxx>V2NaSe(FC&vVNHO?nAI>Y+)=dWUyaen#uaA&C{kNgtRKs3gzRX|ZqEnJ^H zbhPJ9R!%bMqP4^B3$bFCfs&W(#s zB+-?<;Asa=*Cy`WCF1kZ1AZ$|OUdg4-nKIM`RqQKED!g%np*C{iae#Yw{BLzkMhD? zO_}IM69rGNwD%-852n-%#*?&O&uX-LPix_a0%xU$0=8o~MJNU*g-A5yqfwh_;~)a` zHK6ZJ7j`f&O5CpcFUlL5?1nUzE1OC@g-~sy82&XKuhq)6GL;9hZ-&HXuc+PGiffTI zx|w5861wIaXN#9g>$DU!G~$JUF^^0*6hG^SA_L6lDkos0Qo9TxY3=-Ntt9xGI5Ol?PY`TH8X?2Ej2OwbIqEt}A;& ziILMAO@FEMKcp$FSyq}~H&0ohwnVlem7wu|(Kw-`7LEUQBL6kF3g@61eMd|l10g_h zdU@}$@M2>S+;pYFMWb3@kbb^@@`KMq-|@WKm@^}}z2D|jZ0owO9a?{5>tRkI@+dZx zE=rHdu@heog_#w%@C;05m)xV29InX))z;VWJo<(m&XSC41B<(@h8~dngpXINItdd# z0?e>n4f?CRfwgp=3~Bf6QcH@aXco-HiPgD5W{+nX~==0owA-H^0GSCqC#2f-6T5p-`6r_&Dc z@mn6cNz7`d&CC~mY}67!?^+1%XVM~GTGl)S66g&&Lo<-?arGb@oyx*x9f)PS9a|@_ z`w*9q*CQ|EPk9N7yh*stdd{;WVjs@e9EmlXWO6!pU(b42zcXZLhMx~oInsXQ;gT(I zJMApdcJ0k>U%;mjx}1@x&$O$4-aY6;TC{XpY1-m-4GFN0Huy7Lpt(icqSH((Qsz-9 z`qTZ_UWwi-ptZ=ncektlbUdCoun zYv^9nn-Ni)4||t8&g8ndt7y9Iv4(3^>PALOJ0f9}WTu!#en!ZNhkS88W}<+0mP&nL z)~y;fUNL!<(g=+?14fSUn%HAtRqLr;|3)sEH(-AW@BP|v2KAg77g3H~rcED-9^ z6<>*LPrZ{ z!_SECu7+Kt$0Qo(i?^48hO#4F1Bc!+QqZ?V)Ao&a`~?~-B=lnPx?wFf=8?o_t3m`` z2OS-8BI?u5EHAf9IgEGP7>nNZ8>mF0sf;GeqN9|Fe^C@L%L@t9RpF1%zo7$m&g!0< z#nwo}BF3!9Gy6?zVRRU-E_P@CHNd@hj{@CrG&e~)E2pUQOIU@j7oC+ zgXU7z&PZ{w4NB}*fFzp=e%&EXF_28~fOCubD^Nd6w%nEV9H6L2DymMhf2cIK(hil0 z3|)8Ok*e;?FF%0Sb(x-Cs>t12EEaBR0sI(iCAR5-s>=YqK$cj?%N2>jS-~IkYAt4} zw&$IkyV$mDgMd*{5LgN}xR!c!rbbOJuP-%V$%hSUhmc|)CaNknUW~#J{OerNnn=sNq9`2r^0N4`!291ED;cBiR zi(X#MJG(#BAYYmmqgo%c#4neiJ5$F z0Ac&q>d1$g3is zjN&W2E@3o^0nEsy?XMuIqk}-@Whv>3Xbs|*mYnIKr3WZ&Z9i@N{9UCF-*}d6)+>zq ze2$+dVJZ~i=(drSsfpNXiQbCJF^Ef(6(O{=3x(bXARyapN|xW;D@NHo zK1I^BJ;#W?B9}^`@38#j0GNHer9t}$v0`i4ILlbl;7GcsUY{`qu4m?l^#XQ9$B3*2 z$zAJ51U1T)X&1)IGA{7fUg!04i>nfH4v16FgDRJ0*P6!Sf`kOEu>7@ixhqfKa6{HV zyM<5Z@-{Y!X$51B=@3d(mX}sK0|Q_1wy$|xw#klkFO!|5}f6;m6hIV0&uWb^VzJ6wOMz;ZW=bc?N7gOZCuf=hb?LlZ^N zE>(u3n4)A1WR`R8qOvv;Y7OM^*z)+f7doVnY@RH5pyD?P^0 zct2^K^bGdkI_KjDX`@eVX%=)VgE75X-PyzP*Hwz@^^CCQ3r`lrUEjPBMc*lZZ$v!C z=B1MOcy=E`+PSz9s|f!%+ZS@%vIt3}*WuI+>REgSuDq&pHRQlnx$Apx=6c&?(x#n zY?LO((`DwRJW*S(R8$L4I?@^@FBCQs$ix!}_vitv7!*T2Z1$kYyZSTe>h%$kHeuVd zm>caKA7nJ(GB%f-OuH&{($RO?T>qll<~6O}AHs)MquZUbj6Cz%L`meP7bD9-@Jwfym{RX9B=6R$m2E zEi~TvO`g9onou@?3L3Z_wb+$F;jhk0U_N#R6_+PkjAt@4YF6qF`$S#wndyA601oO; z2s0kQ;-JoqWIJRMnU6JR80~i5$?wRnfa>fCW(7hvcDLkoVCch;X2upHsy({nW(SEs z1HeMK*9Use=%42q@UtCUM&Rc>F7d!CTXob6yzk=}@^X~5DPdW3diV>`Y1{XY&yb`* zA`-)XgPtTHpe>f-d>I9eJ;j^Hx>apoBR(-Ox_;IeMH7^pzFdw*qXu{{|<(3YxyO9y4kCWpl=K z3OJnK?pg8oe}Lk#8$W@)pK$QX!|}0R-G;>!s(-Rx66%Mut)EM8B`n#7D^M zWby1VPIOe5(nL_ySWon4XwF1PLh$Ofh3Bm`?e0A*92YQLb6O9rhoZV6*}qXfs!2e> ze(J-Px9{uq=}uOVbp5&J=H?4cFE3GVHxaz1;o@Nk`N*{vT{jK9chuCr;=okaJl)`N zv9VD}4tL}t0-O6ze4hC)8sSwsPyLDMM4zIvg$3HTv5N=tx7b8%sM<#^RV*`% zqP}?X*)y%VMxI`14um9~73aS1282q?+78+lSsPM`FL@(5wH~}e77CiNHJEnC?%VbX z28D^S!%CvNEMSPmbDWZk20-kqoG7L;Qeki8q$9)!c58^D>>ug6V(OA1G z0orG@J!O&q83O3D8jXF>{M_cM>X2qdaE9$@l*sdxe{Zhl{}n)tr{E>$Fh&(7{v0tZJgeG#kI7vGWPg2KIgAc^GPiTv-U z=l7*e{Hdq>pU+&~e4G%&UOXJN_@b|9AxxvaU;45;dWAg+Y^@l5IfLewrKstXaR^H# zUe<=cA!#R3neNYX=BHJCl_%-;@WZW@!=+rB53!uX2BmfYfM8KF>NKUoE(?FMcz2)t zo;!ZNPlzq1@VC{g~S( z6(SkzK_!2MH)>>QbH_;e)(B*R)N0@YEvCKJ3WFUVi)XGb*B^2Va0xvfTe-|C1Rvn; z?OEmceS`l;HP4l#O;YTM2AALt0fM``gI zQ&s)dgel5PAi?9ogMon|NlA(-gMopoeaW6&&Lqbam_bn1t*gss0=4VZQ=!i1B zCYDkSvf#;pAI4x%JBgTJ<)B|_=`AF39>^!_gYR(veQMInrQ>GPg%J#sRf2xX7Ylp@ zQ;Gr&eDk*D5sKRywa52MXbcbO9SmC*RX za^tEleuh7VO(Q@KmQ+@z_ zBGTw%Mt2Iw+{#jw?-OfU`i{TVT0Ja&s>*$0?^o# zn%vjzo1_lG87Q#nLGo-PLiNDUB7zmZQp|->de#ldQ1q$#B~FO~aB`;5?J7;?L6&C( zV5zR1(a5Qs;?C zd{+TNU0|xY5Z`?nnW2mmlDNP*x1nDJQDI?}NQmg6vNdM6q_zK93 z9tXWI6M+Fc)UM!}@Fx9kw)`D%j3KAnkhW}|*j?~jK_1&Fm$2};lGN;p{A2Kgf={`9 zN^qr^iBOH`YN50ui+OlTR;9=bR;nl`SSJ{G5yg4OrgRPX+^B?62l@50bhG%gNk?8s z#WxNk@zy{3lrELFJJl*kF;8j&?7&Rc8=V5kPMwrOo)Y)kB#ZQojAT+sn2HX#qg zRNG>YjgPFa-mf&T46k6`k-3u0lr89eaJ+rW!Yulnc~Q0jGYYumv}kf+=L6IRcy>s& znJbbVf}XOT5@d0Rl8l9J-$>2aftik>j$sQj&!RsmwPL+S!hU~s!*FAILVN;$A`B8{ z$#Ydgr&b=Ll;+zf*r(YqfRO9e^egI7`BkJ?G*e+Kv%{Vw$X?6}L;$)iu%4iuAf3>O z`R9k_$B;?b6&WP$+6`I{qEV#BS1PwC7te_oy-BAeeMzz#qa5=d?1R9<*xOt2lWeU^eXqUxTWTK_Ld%!)lFKXkv-ww|7?oyu zTnY}g2lA5$>D3LY4eJdpnn9W&jYQ`CtJte(tK$tWX1eo4v$KnEGiax(N15|(Mdih( zMH$6fWggNlk}<`Hc~lE@i#(MN{#DW@b8JWZw>P)?URG~{UTNNy-X-s;en6pyUd;f? z0IFb7kzoCBeV~5(PEB04Qt7OH!Iy%NAt=M(+8)<-yO68WFrZY$sCxo-f{==nO0rqD zS-`O&TXa){kG@mxRvT>n?>{!Q53YmM*U+duQM5%pLLUIqc=_tDMh` zOPN<9qatM(adB)?)EKF>`CGYLrFBr+-*=4nlh2#BtZb`}wcpn_9A4Iu*166b&-sWP zW4^@@^SSfh@o{B4Wlwh?cDe#QI=-YHOng58Kd`XDw+VY-xlkfj&%xq1@l@&p?+WV5 z@*eY^^-l6uctd(sdBcAVfh2%ThR%c=fcgSs4R>aIT+>qFzGNHgFcY48)4R7lvOTtK zLW)fqAKRq(<2$vZ#nE5pzsyR%WPZS!1lb_@^l=!O)cd4`WDRGlvtPIcS`A!>MoVh$ z<&V6@5$AUlgclHrWr&UEuNjuIgt6SQ6x4RrQq=O=%7E;mk?1J@gpMon|{1O!abGOPN2id5AxB9y^}rt zDijx<2a4$k^)dY2_8XZ;NSbpR3Yq}mIZ#U_1S;E~wU;h0rbhXZl7YFB(&{@Oub0iU ztGK1O3MD@q%}tA>Mj|73PEkhVw{jUv8b;KI-|faW<3!0&GWUCeTnP4>MYJP|j&jD7 zFY-$nr*T?IFLrhlyW}#7$)U;GTIRKm{d0c2A00W#c`0@&jZDkBUB+$~@CV_mNi$=6 zNuY8>_5JD-UBVjoExg_ITlVGU)d~iU7jK=jwkFT4*TpBVEojeW#au-anj^qPQ`dpV z?L*%M59f+a>B?ngth#H_NO5V!(YW~e5S_rvyW)HQV{xKf)}Z_#52qn#1jp9eZmW@Q zn6eZ=YHMkxafr^Ui&6C7V%!37_dB&;i$(Xag%l^507mu;autH?c$%E-R&FYO) zwW-Z?SE$$FY1ZlKgT)1twJ-i}BoP>Nly%-CkDA8Wvf3u8O5hRdk8eD;yN8827lg?C z>)wHOg;%9lvnPfPhC!2xH**I&*g`T27M>Q%SGj*dR@PR--nH*W7jSPFYE)_p*g0g` zV*>2n+!wbaU$4^CO;o1u8=1^~92w8ovuT2{LdlF#LK~d zgI~5JHzT_@9_9aTY(5}gtVyhpD+v7d#>HoNExoeaq8ciRp03S-8N&AI^`iMOe{Te7 zT{aK@P5b-TAUcqZ3qCg%x}*^7(wPY?(G|>;6P#o!C%uRYIz9b^3htFEJH44|5YAU; z15A_wl4Oq?3@d4bI%1%N5~8P=%$XBxZEbw{cAbZ-Kp|9)oOSp$x(Dmh=C`@`hs?&u zf*x5Jm4^%|7Xqm!|0ckv78(j?Z5gJ3_QdP4C-?R{yFeI2N)PcF2w&+ zz}0dg|9=jq`Hv~`o4yqcjKot)R7lko9Hax|ilv7AK7TVuhsrGlOX?^jDmds5EhdVC zr5=Zcx!%}buBUWDF*J>!(Zq55moP;fbr)eSF!Ew1t6 zTX;Rfwm8(znCMufw1F^-EG0&G=(hG-=gt}sdcFT`TNtN>KM0PUhX=p+&MlsMDJwG` zf|yu0NS&}ee%D_x3bv)Sm2_pgdB`)0G@!lRqn})jaz2xfN+~t*3#YO)Uz+GfnR&i! zRaKSs0rCNETr)ek*g#3PmGBP6GJ$O+F+|EFds*C%dz{i)~`T6-UEgCrs zMA({mI}V0U7RSK@epMs#j0kRi!`@@Wy>oeg3jiAL89^IuJOw%c zdz*`Y@FWPd&4%F}C9NHqB6 z85=`K+c}k#t+q>n;tm1AUl0yLz8Z)(?-1MmkXVIhzF$85JFA{8jtug3=R~DDI-{jU z-gK$rD=)z|32k8waBuJT_IC1^PVyEG8o2ZN-oYMO#M${2k-G_{1a%)->pf>Q+fy}rKAdHt~?g0gqG z(Ys_Jq32z2!&4hhzKnfXZ@sxJ35{QczY#_gyMKIsUTA_Zp>|OsPzwqYCtFN^$#Hol zSRWnN#VJvpG@%*ak)JB}S2?~k$5M8l8QVObH$g?y&-ok1u1cv2D!LwBtxpvhD;$ji zQ@TKoQktYX$J`BTCwsHy-wMo$x3?v!6r`xhiFSgBGJQaoMFBDPT(D$c-qX)x6Xc<3 z`ANpplfjb*_othpMo^qzD$gnhre5dcqb^CK*5e;BxT@M3xghvtzTa);r8e9V@UPeb%&Ik0E5-XY zRHd4LxEvkT^jY%FhGxgdEw-cx3|il9YWjQq{e{DK?q-k@!m-TMD|Tmt-(~QXxC{jz z#f<4$%QSSl`0uLWQh@C8fx%a2&?F@@I}Eg4g4;P%htUB8-1_tQy)0vG3QS7JQLm~4fq#inEZnW(;0`V3yZ%+p!afCgMYG+F4Rd_Uy)EnT>O*9Ln2Ywpt0)C;y0^*(UL1 zx1de-;=+(QobC}ICXO1;&hRw>xM$Tg5GVjrg0FwWlzda0(SDDh7)*2OhO@G2pHnt_ zw8@P)m0Rm+;>TTC#Q_w0xzCWXvxQIUJCuGAo~V8Fi^{!9a!)i$Sg`N-aU)VxQMh~H%){qbxu4h{p(F~7Pl83(CE=r2FvH2 z=D0t6dO2ygax-jlo$z4~C)aFWtT_9(rq3o1_+Ovb#0mKABeC;YauqHBM;oWCYQeR? zt~VY`=ZmpAOznr=<}RyS%W|a4$9-~qp4b^LX0fWHecBq*`8(Z763!Nhy|@(`Jq|LV z$Ns*rV;I)hjy{BfP@}RO<=%6>qkNt(iuTLaG&&D()|qn!(m8fm;*FjRdX=?38sDbl z_E!lUZVNPSnC%~Rj^=71Sy`F%)(4tdUZ;9j>OV-F=Q*B|>|7oCZdzOx{^EYO1vAKB zJzix#Tu_xdty6cV8ZbN>sQuM+B{4unG`NBS=xdiT{=+xinVO91nCOZ-T04M`R)S+S z9ACGye;%F^($LqeFt`&}o=(O%5%FEc;hFel{%TNxYVo4Q_)E*oc*uAyQnDPJ`zTOl zbgXn*^iP`+f*9q#RSezF>R##9b-r~pBJ6M-k9&i|b3Qfs>2DktTP_8>AwlOKkwHj0eW27Y*PW~n4Jp85>d?U$Q| zadAE>Nlpz-YO!3Di_vnu&!BnM@9|(6EPdl87>>^AxmL4?6<&=nUK$x00kb*tlc$7G zJ7oIGVevacahj@*4BJln#(zNffkpWt<-;4?f6t z<1Iw7R_DCyS*>DT#`t}rkt&gvN&tnk`;o*NJdaYme_`)_;ZpQn^q)?XfmUDa~ z_s!nxBJ{7Z-G^g;J)F}9FlC$sM`dj9?Bo$m0QfxgPVWU0Io$r(*R{H)I-fU$E+d4z&Oic zM!2-#IiYJ;C(YL0a96^6&{5S8sh@(~wQ_Ajd#y|pn<@E65gkd7%2I8rALf?@m*grO z3Y?o+&q$Ni0&`}goSbH}brAuHP3H|i$7|Q0d@84_Iinwf#R1^zcOs`h85;77d^93D zW0Q)`n(*@M^C_+w7=G)UeE7LKEkuO|vMX;!Q)DU*BV%xF*}YVn`2s6V(B^JrP@s%4~%8gmXtGraBHi~n&T$pTO{dO1M#BdUp)50vo|pFH0kEK7{AbU zy=z-d(o8)Ej8>Pv9e=zjBpI+7`-F9G@(mEZ6-yIN*>6WZ6z|-nWDk1v2Pblb^;64H zzDNufVX4;tO)S*3JQSW2gF?j5?&O=$gnUQ}@u%l{at^+qiCt3K)e#KTY@0>R`M#`H zu3-4wQ83b?$034icJ#5h3w3=y&z#egY705ENfKw85^v^S;x+V7l9Wp}rj@b>YV@F4yu3sMD!Uu=o&SJ2o?0 z-Rg_iyQ-N{1St-7b98Mq=v&N9F5*O9hfoqn$unsMgINA7WXR>7@!*uyph^d;~y}^mNLB~b>7ll~mtva*0 zs+(|_#JOQ1h(AD>2qr@Y>IHgEuO64K`7N?G;9O-tUU{lCq|L$NrenHtEiEnRl0wq`7IAPAQp@0A*h7De-d6}%{Qazi zl~+d%B}~8(mw#{D?eg72rkp3;mZ0268zFk<%w-?z#I>0*YgA-Y>JJAF<2&+)R&9Fl zQ-RF*z)*ua(XCh^7o+YSxpk+To*CW+u5|ID-BEJb#McUTRaB0j@yxw7gh`x~W<rSUl|1<`=q8>Fz~669WQvZLoLjO+O~wG!nGd)- zy?ueIy7u!<>W1(6Hlw6Vgpi|Yi3&~A)m<9F(JZr}(dOlslKdW3eV5&yTDWxB7HnDE z&Y@%&F@Mt3yXY@Uii`aU$XEE+Vh5(DVcu_r_hZW}ULKU>4!sF@9Wm`3QECd55^{Xj zB8Q3G6kmXAx}WJ0^sTAJSMSwg1^4x^9HsVXd4I)Gk4n5-0GUuzJm0XKy1mE#if-N+ zMEfgZBD=82eXb^(GX{dXa^4c1#L$I^!P$$Fc*7uo`F822>(k)*(FOx1vD{`@Vu6Nn zy--;2*eb0>hf^ca-gTD9zOBI&&2PlnN~XE|3 zNcN#)T2PqnijIgam)nTpsss(|Ql_vbQIpAa1zmLOdY55|eX1>#ppl2Usbnmz{q=%b zI?xH6E;M(1+=#CY_(wq|rLw10^~SPZcC4f^rt_T&Jwf$IwLF<&G;Y6qb&@6g%+#h1z3$PP7Ej0{zAfp^|#M1r#C$#0tPoddtW&NhVArLhhr|Gpl;Ni^$MUiG%W zbc%|oocHvWx#Qkl=|@2x4#8w}3eJq9H#<&ywBC>Tp?uA{cl!9uGf$0PNbx))Bcrfm z(r0I$M#%7hh~movvQz^NN)sw#y6x&fq`dd5eX&6m z#RaEM;^*9b8IK+Nu+LV5ni+>3g*-U}zQUi#XiJ&;Nc08Fg`5$z2FE}43>t5p7Dpf~ z@AhOwv5<9Vgw6Mh?;TH5n6jR31dXi~Q85C~%z*4iN^FMrpH|vJPmb?iFIEAUv$q$hq6TKC3f$R63)Tbw2S$CK4vHlOYc{~KBn2(gXc)A4X4?2$ohGmQjIfs_z zTa9|P*4ly(ao3-T`YyQ4@L!gdwWd3hxbt+GoxA(YiX9>5Md-M?)&(z086 z+C!K*&lVw+?ah-h5_i@Rr-^3n2N7FR%Bs8TrG1akk!{GPC;Y45r7STyJQ+AfA=h%f zwA5l?icf1&6}D!Vx@xzds6GiZ_NLoPsHw~ygmooL=G>y{jC)1(i#1F;T`~O+ooDo} zdQ4eYgTyvg@4VU@W#h}X9yGr#F+ z5$C0WH5ELRHTG+M34Lvt5xC(!Oo?<2G_#sW5jVtN5k@>9aQkp*7#Q*@LqvawaWc4H zbO+CtEHIfRA#Fx7heXOHu|qyElPk|ggZZ?}GVSWLA9DGM6w!oD-%LuUcW$riDJKvr zgucupQg`k_HHJ}vpZy_5LC!PaVHr0pkk}5kcu5h5J#yl&Dlv(%{;W~jTxW6M$dAY} zY4DX-SM4}8hhmynLvg$?XRw7UlU917O4e4$jWfKM9BVfb80J$eVDFDJADiCGP!1hTk}v$^N4bEB zR|#pp?i1C{vi0@S9{$y6M+DGr?_kzw%yqab4l7EE6c!OSg%DmGg|RHR5{DYkpnyX_ z5c64;9-Oa0N_j6PT#77MZB)~G`n%c%0QgjPMZBeCbzltf?Z*x-oYq%Y9`E0spE8y0 z_~3O?za0@-D^JTXa)T~&{5V88d!e6aa>$od_HVFeCUSix$5Dt%mQ!34U})>K#L(*; zs}8J?9`EK2N94xU1Esb7M$So1=QiyQ;AKGZhWDgBOy8O|%r!f_iU<=jFV(XXTwK0C>;*Ztq6Trz_^&TwJPTY!5-2O#Cs^P58Te`v zxuL*2(QHq4x|cMX!5|@?8;66e;A|%+n(4gN$_rj~qa_%S?}L4&o9^DB7q1T+F*jBD zb*87|Jg=T##Svi>k(QUY_$%u(Ly{b^z|X`Z2;+EU&hPK)=4+aML1h!!c;g z?8!hDa+qR>n-Y7SPTp;zBgmLy@Jr;IfbJL^ZBuYlwDd9wwgL!KWF8+zt^eTm)Zagj z-j(BmWy&bjL)Gi;QNEW>W>6x8t^^B)b`$rR)*4>KC*@nxQM7&n!nu@Cczc)K#qwM- z&pdq9CG23xKtX2{GYlqHbgT^`udE`WIb@++G_o1ykPmrpQr9t}>a)H5-{~aHA#3{+ zD)3pabJ90Z;N`HFnpnbRKS}EZ;;d--Z?9U5C)0s_yg}0%fpAG{gP_08;s4qWU9cmU zy1Kk-RvnM_wzjYg|5Pg22>adj!l#k1Jrqv%XGfB5);k%8gggT* zECjN+Afg5G7j1c}&zC5C@Q1y@&$HLhtM5S41kHGMUcK zQeA>axG|+nyr25%#6O=2VSgJLm9MkDHbZ_NrTve@4z-d)R%nhyDun^{xr;r>P=Tr- zFqt~|f1MRY42ajSjR9|<5(Ndt zLIY1UJ=Q#+MRrb7DAyWx4?S*5_m+V_qdE4cJX`PLB$kLxJ6ud;2Z|8*D`?|6u2!Mj zSHStAfXQh|TY$z5mwlJ$^NzXsrE@zb)_ihV(;gNA3D0l(&?ibmw77Z89+Lm*!e?7* zRWA+*i_c-zzwJTPmy2KB|5!r2*cme|=bDh*Z5dQTL};Y40UZ?NYQJ9IvL9?QTi5Ew zXOIEUtwf0oL|1moMwcS-a=D4mWPoS}Y(~%P zdY^)EwVK21c&w%}y|P!vaf%-(oX4H%MlmqVr(b#;tzf!*St2YlBM>KR4*k?o3pA)gUu zb|I@8I5x>Z|J{HtF7jRsehHSUlNEVtZ5n9hfik2usk=77 zm}O84irH`t0uYGLeWM$vJ$Wg75a@M+d{&b$TeKlv)a<1Uf&xF3(7UQj`prfdZ7kPU z$-xn8S;n@N6ZpWNX~wQABKJL3#rpjAp~mDxh*w~z5dvwuvb23v z$o~WXM28H74P_~)*Bk$#f3|hrOiDFVq=JZ^AWyo#?v`=Q*&r)hp)2b{?14hmw_>8J zJn#&!R$ zGU`#b57-3!$Vm>qtU;wZ3Gn@sG_N5<(`(ElZjZ| z5Uo!{gl^wjTi_23yVLXGFK(YN^0BQ2+=ps9?hJ*RIJborB-;b9k!>GDy!X>7YPd;_ z#@Ci5CVB!q=87*Fpn zC?b~o32ASO^uM_wBx{2DnD9AvnkU!nE_4Da4=hH)witDznoFj7mL2#Rouq4T%r|d8 z3_g1M_d48bg;fQuUT7m!m$A8Ms9?W?LGg~D-GOk}dIDL4izBkRGBooRHEzMDe7c{A z03I)puQiTDo?f>%zUn`e*aZ;xaOs!;+1dL(CruwO$%K!O^-tyyqOPX4LGEvFo-8?; z>QvOlDJI#AYAcK&g9f9bfVOO@vCk%4kC=$BiPB_xJL*=|qtN6H3Ou5yvpn)tVd3s- zdDVb`_(n`m57@>Ur@>jhx%ruH@VY_m`TQ-#A*lU+rVVV+mb_l%gU)>?hpF3-1!W^< zyv@1pS1||7{5)HndaMmQAjN7d4U=WI0eF`RusA$K`OsPFERQW?kNqZxYHA5PK+GR( z2sb=i>|`M-(F9b-KY8BbNJ#O5cXvKDz=$+E{`$k~fF67D9hLt}fSge#4Mu|b=Pf=^ zHS0KEQVYs&C|Ho6;bqw2$qEhm!64tJw6AcrUV>9Yu_zB9Xx2t6oY*SfXVu#9{<<91}9zQ7NN%akEbUtxU0G^|1W$B2}(c>6qk{w zDkuFHT!qW*QT|(#OAf*Mx{M|Zl>RTwiVG5`*%SXS2LmD>cmh?ZO8#GT5B4=J!$ZGT z|8E%~Dys4#xF}Hge}#NIKh9Slx}niO(>^K`qZ5?2j?R%`n6=)Tsa25V;wyN~>Q9pW ze_o)P%EGqvKoi|2X}r%>bdp8WdY~n|e)>LhghnmS^6Sq;F%$Wv?IsZmUKF~r5sF^? zKZA^fP^pRz4mW2fHRa`>+pZHTW&EkDr4W@lO;*~Xv2p%04`I=a{!3^n^bkSN;u(`* ztPdaYopObLKb7mzL0B3ZX#pAf(klmmtNw|5Xi!joGI-z#qZ}bvA%<{3z+7{ Date: Tue, 4 Jun 2024 06:15:41 +0200 Subject: [PATCH 14/27] Add missing weighted_routing nodes stats (#7245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #7244 Signed-off-by: Lukáš Vlček Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --- _api-reference/nodes-apis/nodes-stats.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/_api-reference/nodes-apis/nodes-stats.md b/_api-reference/nodes-apis/nodes-stats.md index 1d504aae2e..145b3d5b24 100644 --- a/_api-reference/nodes-apis/nodes-stats.md +++ b/_api-reference/nodes-apis/nodes-stats.md @@ -54,6 +54,7 @@ indexing_pressure | Statistics about the node's indexing pressure. shard_indexing_pressure | Statistics about shard indexing pressure. search_backpressure | Statistics related to search backpressure. cluster_manager_throttling | Statistics related to throttled tasks on the cluster manager node. +weighted_routing | Statistics relevant to weighted round robin requests. resource_usage_stats | Node-level resource usage statistics, such as CPU and JVM memory. admission_control | Statistics about admission control. caches | Statistics about caches. @@ -834,6 +835,7 @@ http.total_opened | Integer | The total number of HTTP connections the node has [shard_indexing_pressure](#shard_indexing_pressure) | Object | Statistics related to indexing pressure at the shard level. [search_backpressure]({{site.url}}{{site.baseurl}}/opensearch/search-backpressure#search-backpressure-stats-api) | Object | Statistics related to search backpressure. [cluster_manager_throttling](#cluster_manager_throttling) | Object | Statistics related to throttled tasks on the cluster manager node. +[weighted_routing](#weighted_routing) | Object | Statistics relevant to weighted round robin requests. [resource_usage_stats](#resource_usage_stats) | Object | Statistics related to resource usage for the node. [admission_control](#admission_control) | Object | Statistics related to admission control for the node. [caches](#caches) | Object | Statistics related to caches on the node. @@ -1294,6 +1296,15 @@ stats | Object | Statistics about throttled tasks on the cluster manager node. stats.total_throttled_tasks | Long | The total number of throttled tasks. stats.throttled_tasks_per_task_type | Object | A breakdown of statistics by individual task type, specified as key-value pairs. The keys are individual task types, and their values represent the number of requests that were throttled. +### `weighted_routing` + +The `weighted_routing` object contains statistics about weighted round robin requests. Specifically, it contains a counter of times this node has server a request while it was "zoned out". + +Field | Field type | Description +:--- |:-----------| :--- +stats | Object | Statistics about weighted routing. +fail_open_count | Integer | Number of times a shard on this node has served a request while the routing weight for the node was set to zero. + ### `resource_usage_stats` The `resource_usage_stats` object contains the resource usage statistics. Each entry is specified by the node ID and has the following properties. From 776e19cdff138e4d03400b1d85995bb070a01760 Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Tue, 4 Jun 2024 09:36:51 -0600 Subject: [PATCH 15/27] [DOC] Add pipeline processor documentation (#5986) * Add pipeline processor documentation Signed-off-by: Melissa Vagi * Add pipeline examples and text Signed-off-by: Melissa Vagi * add text and examples Signed-off-by: Melissa Vagi * Update pipeline.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Update pipeline.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/pipeline.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/pipeline.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/pipeline.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/pipeline.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/pipeline.md Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi Co-authored-by: Nathan Bower --- _ingest-pipelines/processors/pipeline.md | 183 +++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 _ingest-pipelines/processors/pipeline.md diff --git a/_ingest-pipelines/processors/pipeline.md b/_ingest-pipelines/processors/pipeline.md new file mode 100644 index 0000000000..abf4a5f1b1 --- /dev/null +++ b/_ingest-pipelines/processors/pipeline.md @@ -0,0 +1,183 @@ +--- +layout: default +title: Pipeline +parent: Ingest processors +nav_order: 200 +--- + +# Pipeline processor + +The `pipeline` processor allows a pipeline to reference and include another predefined pipeline. This can be useful when you have a set of common processors that need to be shared across multiple pipelines. Instead of redefining those common processors in each pipeline, you can create a separate base pipeline containing the shared processors and then reference that base pipeline from other pipelines using the pipeline processor. + +The following is the syntax for the `pipeline` processor: + +```json +{ + "pipeline": { + "name": "general-pipeline" + } +} +``` +{% include copy-curl.html %} + +## Configuration parameters + +The following table lists the required and optional parameters for the `pipeline` processor. + +Parameter | Required/Optional | Description | +|-----------|-----------|-----------| +`name` | Required | The name of the pipeline to execute. +`description` | Optional | A description of the processor's purpose or configuration. +`if` | Optional | Specifies to conditionally execute the processor. +`ignore_failure` | Optional | Specifies to ignore processor failures. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`on_failure` | Optional | Specifies to handle processor failures. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`tag` | Optional | An identifier for the processor. Useful for debugging and metrics. + +## Using the processor + +Follow these steps to use the processor in a pipeline. + +### Step 1: Create a pipeline + +The following query creates a general pipeline named `general-pipeline` and then creates a new pipeline named `outer-pipeline`, which references the `general-pipeline`: + +```json +PUT _ingest/pipeline/general_pipeline +{ + "description": "a general pipeline", + "processors": [ + { + "uppercase": { + "field": "protocol" + }, + "remove": { + "field": "name" + } + } + ] +} +``` +{% include copy-curl.html %} + +```json +PUT _ingest/pipeline/outer-pipeline +{ + "description": "an outer pipeline referencing the general pipeline", + "processors": [ + { + "pipeline": { + "name": "general-pipeline" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2 (Optional): Test the pipeline + +It is recommended that you test your pipeline before you ingest documents. +{: .tip} + +To test the pipeline, run the following query: + +```json +POST _ingest/pipeline/outer-pipeline/_simulate +{ + "docs": [ + { + "_source": { + "protocol": "https", + "name":"test" + } + } + ] +} +``` +{% include copy-curl.html %} + +#### Response + +The following example response confirms that the pipeline is working as expected: + +```json +{ + "docs": [ + { + "doc": { + "_index": "_index", + "_id": "_id", + "_source": { + "protocol": "HTTPS" + }, + "_ingest": { + "timestamp": "2024-05-24T02:43:43.700735801Z" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 3: Ingest a document + +The following query ingests a document into an index named `testindex1`: + +```json +POST testindex1/_doc/1?pipeline=outer-pipeline +{ + "protocol": "https", + "name": "test" +} +``` +{% include copy-curl.html %} + +#### Response + +The request indexes the document with the `protocol` field converted to uppercase and the field name removed from the index `testindex1`, as shown in the following response: + +```json +{ + "_index": "testindex1", + "_id": "1", + "_version": 2, + "result": "created", + "_shards": { + "total": 2, + "successful": 2, + "failed": 0 + }, + "_seq_no": 1, + "_primary_term": 1 +} +``` +{% include copy-curl.html %} + +### Step 4 (Optional): Retrieve the document + +To retrieve the document, run the following query: + +```json +GET testindex1/_doc/1 +``` +{% include copy-curl.html %} + +#### Response + +The response shows the document with the `protocol` field converted to uppercase and the field name removed: + +```json +{ + "_index": "testindex1", + "_id": "1", + "_version": 2, + "_seq_no": 1, + "_primary_term": 1, + "found": true, + "_source": { + "protocol": "HTTPS" + } +} +``` + From f07e1aeb20d9a1b5fe0dab664ffcce99269e1093 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 4 Jun 2024 11:25:04 -0500 Subject: [PATCH 16/27] Fix stray `$` characters; fix some incorrect syntax rendering (#7292) Signed-off-by: Kurt McKee --- _install-and-configure/plugins.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/_install-and-configure/plugins.md b/_install-and-configure/plugins.md index bbfbce9796..d4fc35507f 100644 --- a/_install-and-configure/plugins.md +++ b/_install-and-configure/plugins.md @@ -176,7 +176,7 @@ bin/opensearch-plugin install :: ``` #### Example -```bash +```console $ sudo ./opensearch-plugin install org.opensearch.plugin:opensearch-anomaly-detection:2.2.0.0 -> Installing org.opensearch.plugin:opensearch-anomaly-detection:2.2.0.0 -> Downloading org.opensearch.plugin:opensearch-anomaly-detection:2.2.0.0 from maven central @@ -214,8 +214,8 @@ bin/opensearch-plugin install ... ``` #### Example -```bash -$ sudo $ ./opensearch-plugin install analysis-nori repository-s3 +```console +$ sudo ./opensearch-plugin install analysis-nori repository-s3 ``` ## Remove @@ -228,8 +228,8 @@ bin/opensearch-plugin remove ``` #### Example -```bash -$ sudo $ ./opensearch-plugin remove opensearch-anomaly-detection +```console +$ sudo ./opensearch-plugin remove opensearch-anomaly-detection -> removing [opensearch-anomaly-detection]... ``` From f912f9def1cca7b67ec8c4e1f3b4959f3d7f3890 Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Tue, 4 Jun 2024 22:33:16 +0100 Subject: [PATCH 17/27] adding a get started with security page #6668 (#7128) * adding a get started with security page #6668 Signed-off-by: AntonEliatra * fixing vale errors #6668 Signed-off-by: AntonEliatra * fixing vale errors #6668 Signed-off-by: AntonEliatra * adding role configurations for users without kibanauser mapping #2359 Signed-off-by: AntonEliatra * updating the page layout for getting started with security #6668 Signed-off-by: AntonEliatra * Apply suggestions from code review Co-authored-by: Darshit Chanpura Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Signed-off-by: AntonEliatra * adding additional details to getting started with security Signed-off-by: AntonEliatra * adding additional details to getting started with security Signed-off-by: AntonEliatra * Update security.md Signed-off-by: AntonEliatra * Update security.md Signed-off-by: AntonEliatra * adding link for existing docs for demo config Signed-off-by: AntonEliatra * Doc review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: AntonEliatra Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Darshit Chanpura Co-authored-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Nathan Bower --- _getting-started/security.md | 192 +++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 _getting-started/security.md diff --git a/_getting-started/security.md b/_getting-started/security.md new file mode 100644 index 0000000000..1bed87b50d --- /dev/null +++ b/_getting-started/security.md @@ -0,0 +1,192 @@ +--- +layout: default +title: Getting started with OpenSearch security +nav_order: 60 +--- + +# Getting started with OpenSearch security + +The demo configuration is the most straightforward way to get started with OpenSearch security. OpenSearch comes bundled with a number of useful scripts, including `install_demo_configuration.sh` (or `install_demo_configuration.bat` for Windows). + +This script is located in `plugins/opensearch-security/tools` and performs the following actions: + +- Creates demo certificates for TLS encryption on both the transport and REST layers. +- Configures demo users, roles, and role mappings. +- Configures the Security plugin to use an internal database for authentication and authorization. +- Updates the `opensearch.yml` file with the basic configuration needed to start the cluster. + +You can find more information about the demo configuration and how to quickly get started at [Setting up a demo configuration]({{site.url}}{{site.baseurl}}/security/configuration/demo-configuration/). +{: .note} + +Certain aspects of this configuration, such as demo certificates and default passwords, should never be used in production. These parts of the demo configuration should be replaced with your custom information before proceeding to production. +{: .warning} + +## Setting up the demo configuration + +Prior to running the `install_demo_configuration.sh` script, you must create an environment variable named `OPENSEARCH_INITIAL_ADMIN_PASSWORD` with a strong password. This will be used as the password for the admin user to authenticate with OpenSearch. Use the online tool [_Zxcvbn_](https://lowe.github.io/tryzxcvbn/) to test the strength of any password. After this, you can execute `install_demo_configuration.sh` and follow the terminal prompt to enter necessary details. + +After the script is executed, you can start OpenSearch and test the configuration by running the following command: + +``` +curl -k -XGET -u admin: https://:9200 +``` +{% include copy.html %} + +You should see output similar to the following: + +``` +{ + "name" : "smoketestnode", + "cluster_name" : "opensearch", + "cluster_uuid" : "0a5DYAk0Rbi14wqT3TqMiQ", + "version" : { + "distribution" : "opensearch", + "number" : "2.13.0", + "build_type" : "tar", + "build_hash" : "7ec678d1b7c87d6e779fdef94e33623e1f1e2647", + "build_date" : "2024-03-26T00:04:51.025238748Z", + "build_snapshot" : false, + "lucene_version" : "9.10.0", + "minimum_wire_compatibility_version" : "7.10.0", + "minimum_index_compatibility_version" : "7.0.0" + }, + "tagline" : "The OpenSearch Project: https://opensearch.org/" +} +``` + +## Setting up OpenSearch Dashboards + +In order to quickly get started with OpenSearch Dashboards, you can add the following configuration to `opensearch_dashboards.yml`: + +``` +opensearch.hosts: [https://localhost:9200] +opensearch.ssl.verificationMode: none +opensearch.username: kibanaserver +opensearch.password: kibanaserver +opensearch.requestHeadersWhitelist: [authorization, securitytenant] + +opensearch_security.multitenancy.enabled: true +opensearch_security.multitenancy.tenants.preferred: [Private, Global] +opensearch_security.readonly_mode.roles: [kibana_read_only] +# Use this setting if you are running opensearch-dashboards without https +opensearch_security.cookie.secure: false +``` +{% include copy.html %} + +You can start the binary or service, depending on which method was used to install OpenSearch and OpenSearch Dashboards. + +When using binary, you need to supply `--no-base-path` to `yarn start` command to set a URL without a base path. If this is not set, a random three-letter base path will be added. +{: .note} + +After starting OpenSearch Dashboards, you should see the following two log lines: + +``` +[info][listening] Server running at http://localhost:5601 +[info][server][OpenSearchDashboards][http] http server running at http://localhost:5601 +``` +{% include copy.html %} + +You can now access OpenSearch Dashboards using http://localhost:5601 in your browser. Use the username `admin` and the password that was configured in the `OPENSEARCH_INITIAL_ADMIN_PASSWORD` environment variable. + +# Adding users + +There are three ways to add users, roles, and other security-related configurations: + + - Updating appropriate configuration files (`internal_users.yml` for adding/updating/removing users) + - Using the API + - Using the OpenSearch Dashboards UI + +Security configuration files are located in the `config/opensearch-security` directory. +{: .note} + +You can add an OpenSearch Dashboards user by updating the `internal_users.yml` file with the following settings: + +``` +test-user: + hash: "$2y$12$CkxFoTAJKsZaWv/m8VoZ6ePG3DBeBTAvoo4xA2P21VCS9w2RYumsG" + backend_roles: + - "test-backend-role" + - "kibanauser" + description: "test user user" +``` +{% include copy.html %} + +The `hash` string is generated using the `hash.sh` script located in the `plugins/opensearch-security/tools/` directory. In this case, the hash of the string `secretpassword` was used. + +Note the use of the built-in backend role `kibanauser`, which provides the user permissions needed to navigate OpenSearch Dashboards. + +## Creating roles + +Roles contained in `roles.yml` use the following structure: + +``` +: + cluster_permissions: + - + index_permissions: + - index_patterns: + - + allowed_actions: + - +``` +{% include copy.html %} + +Using this structure, you can configure a new role to provide access to specific indexes, such as the role configured in the following example: + +``` +human_resources: + index_permissions: + - index_patterns: + - "humanresources" + allowed_actions: + - "READ" +``` +{% include copy.html %} + +Note that the cluster permissions are not listed in this example because these are provided by the built-in role `kibana_user`, which is already mapped using the `kibanauser` backend role. + + +## Mapping users to roles + +When a user logs in to OpenSearch, they need to be mapped to the appropriate role in order to obtain the correct permissions. This mapping is performed using the `roles_mapping.yml` file with the following structure: + +``` +: + users: + - + - ... + backend_roles: + - +``` +{% include copy.html %} + +In order to map the newly created user `test-user` to the role `human_resources`, you can use the following configuration in the `roles_mapping.yml` file: + +``` +human_resources: + backend_roles: + - test-backend-role +``` +{% include copy.html %} + +As an additional example, the `roles_mappings.yml` file includes the backend role `kibanauser` that has been mapped to the `kibana_user` role: + +``` +kibana_user: + reserved: false + backend_roles: + - "kibanauser" + description: "Maps kibanauser to kibana_user" +``` +{% include copy.html %} + +## Uploading the configuration to a security index + +The final step in configuring a user, role, or any other security configuration is uploading it to a OpenSearch security index. Only updating the files, without uploading them, will not change the configuration of an already running OpenSearch cluster. + +To upload a configuration, the following command can be used with the admin certificate that was generated during `install_demo_configuration.sh` execution: + +``` +./plugins/opensearch-security/tools/securityadmin.sh -cd "config/opensearch-security" -icl -key "../kirk-key.pem" -cert "../kirk.pem" -cacert "../root-ca.pem" -nhnv +``` +{% include copy.html %} From 4b9961927c14fa913260fc735261643bf35521a1 Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Wed, 5 Jun 2024 08:50:19 -0600 Subject: [PATCH 18/27] Add JSON processor documentation (#5982) * Add JSON processor documentation Signed-off-by: Melissa Vagi * Add pipeline examples Signed-off-by: Melissa Vagi * Add parameters Signed-off-by: Melissa Vagi * Update parameters Signed-off-by: Melissa Vagi * Update json.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Update json.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Address tech review feedback Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/json.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/json.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/json.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/json.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/json.md Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/json.md Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi Co-authored-by: Nathan Bower --- _ingest-pipelines/processors/json.md | 199 +++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 _ingest-pipelines/processors/json.md diff --git a/_ingest-pipelines/processors/json.md b/_ingest-pipelines/processors/json.md new file mode 100644 index 0000000000..d251533e27 --- /dev/null +++ b/_ingest-pipelines/processors/json.md @@ -0,0 +1,199 @@ +--- +layout: default +title: JSON +parent: Ingest processors +nav_order: 170 +--- + +# JSON processor + +The `json` processor serializes a string value field into a map of maps, which can be useful for various data processing and enrichment tasks. + +The following is the syntax for the `json` processor: + +```json +{ + "processor": { + "json": { + "field": "", + "target_field": "", + "add_to_root": + } + } +} +``` +{% include copy-curl.html %} + +## Configuration parameters + +The following table lists the required and optional parameters for the `json` processor. + +Parameter | Required/Optional | Description | +|-----------|-----------|-----------| +`field` | Required | The name of the field containing the JSON-formatted string to be deserialized. +`target_field` | Optional | The name of the field in which the deserialized JSON data is stored. When not provided, the data is stored in the `field` field. If `target_field` exists, its existing value is overwritten with the new JSON data. +`add_to_root` | Optional | A Boolean flag that determines whether the deserialized JSON data should be added to the root of the document (`true`) or stored in the target_field (`false`). If `add_to_root` is `true`, then `target-field` is invalid. Default value is `false`. +`description` | Optional | A description of the processor's purpose or configuration. +`if` | Optional | Specifies to conditionally execute the processor. +`ignore_failure` | Optional | Specifies to ignore processor failures. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`on_failure`| Optional | Specifies a list of processors to run if the processor fails during execution. These processors are executed in the order they are specified. +`tag` | Optional | An identifier tag for the processor. Useful for debugging in order to distinguish between processors of the same type. + +## Using the processor + +Follow these steps to use the processor in a pipeline. + +### Step 1: Create a pipeline + +The following query creates a pipeline named `my-json-pipeline` that uses the `json` processor to process JSON data and enrich the documents with additional information: + +```json +PUT _ingest/pipeline/my-json-pipeline +{ + "description": "Example pipeline using the JsonProcessor", + "processors": [ + { + "json": { + "field": "raw_data", + "target_field": "parsed_data" + "on_failure": [ + { + "set": { + "field": "error_message", + "value": "Failed to parse JSON data" + } + }, + { + "fail": { + "message": "Failed to process JSON data" + } + } + ] + } + }, + { + "set": { + "field": "processed_timestamp", + "value": "{{_ingest.timestamp}}" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2 (Optional): Test the pipeline + +It is recommended that you test your pipeline before you ingest documents. +{: .tip} + +To test the pipeline, run the following query: + +```json +POST _ingest/pipeline/my-json-pipeline/_simulate +{ + "docs": [ + { + "_source": { + "raw_data": "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}" + } + }, + { + "_source": { + "raw_data": "{\"name\":\"Jane\",\"age\":25,\"city\":\"Los Angeles\"}" + } + } + ] +} +``` +{% include copy-curl.html %} + +#### Response + +The following example response confirms that the pipeline is working as expected: + +```json +{ + "docs": [ + { + "doc": { + "_index": "_index", + "_id": "_id", + "_source": { + "processed_timestamp": "2024-05-30T15:24:48.064472090Z", + "raw_data": """{"name":"John","age":30,"city":"New York"}""", + "parsed_data": { + "name": "John", + "city": "New York", + "age": 30 + } + }, + "_ingest": { + "timestamp": "2024-05-30T15:24:48.06447209Z" + } + } + }, + { + "doc": { + "_index": "_index", + "_id": "_id", + "_source": { + "processed_timestamp": "2024-05-30T15:24:48.064543006Z", + "raw_data": """{"name":"Jane","age":25,"city":"Los Angeles"}""", + "parsed_data": { + "name": "Jane", + "city": "Los Angeles", + "age": 25 + } + }, + "_ingest": { + "timestamp": "2024-05-30T15:24:48.064543006Z" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 3: Ingest a document + +The following query ingests a document into an index named `my-index`: + +```json +POST my-index/_doc?pipeline=my-json-pipeline +{ + "raw_data": "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}" +} +``` +{% include copy-curl.html %} + +#### Response + +The response confirms that the document containing the JSON data from the `raw_data` field was successfully indexed: + +```json +{ + "_index": "my-index", + "_id": "mo8yyo8BwFahnwl9WpxG", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 3, + "_primary_term": 2 +} +``` +{% include copy-curl.html %} + +### Step 4 (Optional): Retrieve the document + +To retrieve the document, run the following query: + +```json +GET my-index/_doc/1 +``` +{% include copy-curl.html %} From 88bab65a5a44f312aacb885df03a0588b61165bf Mon Sep 17 00:00:00 2001 From: AntonEliatra Date: Wed, 5 Jun 2024 17:39:32 +0100 Subject: [PATCH 19/27] Moving cluster indices permission to cluster section #1656 (#7161) * Moving cluster indices permission to cluster section #1656 Signed-off-by: AntonEliatra * updating index and cluster permissions Signed-off-by: AntonEliatra * removing empty space Signed-off-by: AntonEliatra * fixing vale errors Signed-off-by: AntonEliatra * adding more permissions to the list Signed-off-by: AntonEliatra * Apply suggestions from code review Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Signed-off-by: AntonEliatra * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: AntonEliatra * Update permissions.md Signed-off-by: AntonEliatra * Update _security/access-control/permissions.md Co-authored-by: Nathan Bower Signed-off-by: AntonEliatra --------- Signed-off-by: AntonEliatra Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Nathan Bower --- _security/access-control/permissions.md | 42 +++++++++++++++++-------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/_security/access-control/permissions.md b/_security/access-control/permissions.md index 178ac1121b..5a75a0a5a7 100644 --- a/_security/access-control/permissions.md +++ b/_security/access-control/permissions.md @@ -182,6 +182,26 @@ Cross-references to API documentation in the permissions that follow are only in {: .note } +### Cluster wide index permissions + +| **Permission** | **Description** | +| :--- | :--- | +| `indices:admin/template/delete` | Permission to [delete index templates]({{site.url}}{{site.baseurl}}/im-plugin/index-templates/#delete-a-template). | +| `indices:admin/template/get` | Permission to [get index templates]({{site.url}}{{site.baseurl}}/im-plugin/index-templates/#retrieve-a-template). | +| `indices:admin/template/put` | Permission to [create index templates]({{site.url}}{{site.baseurl}}/im-plugin/index-templates/#create-a-template). | +| `indices:data/read/scroll` | Permission to scroll through data. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/scroll/clear` | Permission to clear the scroll object. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/mget` | Permission to run [multiple GET operations]({{site.url}}{{site.baseurl}}/api-reference/document-apis/multi-get/) in one request. | +| `indices:data/read/mget*` | Permission to run multiple GET operations in one request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/msearch` | Permission to run [multiple search]({{site.url}}{{site.baseurl}}/api-reference/multi-search/) requests in a single API request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/msearch/template` | Permission to bundle [multiple search templates]({{site.url}}{{site.baseurl}}/api-reference/search-template/#multiple-search-templates) and send them to your OpenSearch cluster in a single request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/mtv` | Permission to retrieve multiple term vectors with a single request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/mtv*` | Permission to retrieve multiple term vectors with a single request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/search/template/render` | Permission to render search templates. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/write/bulk` | Permission to run a [bulk]({{site.url}}{{site.baseurl}}/api-reference/document-apis/bulk/) request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/write/bulk*` | Permission to run a bulk request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/write/reindex` | Permission to run a [reindex]({{site.url}}{{site.baseurl}}/im-plugin/reindex-data/) operation. | + ### Ingest API permissions See [Ingest APIs]({{site.url}}{{site.baseurl}}/api-reference/ingest-apis/index/). @@ -477,9 +497,6 @@ These permissions apply to an index or index pattern. You might want a user to h | `indices:admin/seq_no/global_checkpoint_sync` | Permission to perform a global checkpoint sync. | | `indices:admin/settings/update` | Permission to [update index settings]({{site.url}}{{site.baseurl}}/api-reference/index-apis/update-settings/). | | `indices:admin/shards/search_shards` | Permission to perform [cross cluster search]({{site.url}}{{site.baseurl}}/security/access-control/cross-cluster-search/). | -| `indices:admin/template/delete` | Permission to [delete index templates]({{site.url}}{{site.baseurl}}/im-plugin/index-templates/#delete-a-template). | -| `indices:admin/template/get` | Permission to [get index templates]({{site.url}}{{site.baseurl}}/im-plugin/index-templates/#retrieve-a-template). | -| `indices:admin/template/put` | Permission to [create index templates]({{site.url}}{{site.baseurl}}/im-plugin/index-templates/#create-a-template). | | `indices:admin/upgrade` | Permission for administrators to perform upgrades. | | `indices:admin/validate/query` | Permission to validate a specific query. | | `indices:data/read/explain` | Permission to run the [Explain API]({{site.url}}{{site.baseurl}}/api-reference/explain/). | @@ -487,26 +504,25 @@ These permissions apply to an index or index pattern. You might want a user to h | `indices:data/read/field_caps*` | Permission to run the Field Capabilities API. | | `indices:data/read/get` | Permission to read index data. | | `indices:data/read/mget` | Permission to run [multiple GET operations]({{site.url}}{{site.baseurl}}/api-reference/document-apis/multi-get/) in one request. | -| `indices:data/read/mget*` | Permission to run multiple GET operations in one request. | -| `indices:data/read/msearch` | Permission to run [multiple search]({{site.url}}{{site.baseurl}}/api-reference/multi-search/) requests into a single request. | -| `indices:data/read/msearch/template` | Permission to bundle [multiple search templates]({{site.url}}{{site.baseurl}}/api-reference/search-template/#multiple-search-templates) and send them to your OpenSearch cluster in a single request. | -| `indices:data/read/mtv` | Permission to retrieve multiple term vectors with a single request. | -| `indices:data/read/mtv*` | Permission to retrieve multiple term vectors with a single request. | +| `indices:data/read/mget*` | Permission to run multiple GET operations in one request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/msearch` | Permission to run [multiple search]({{site.url}}{{site.baseurl}}/api-reference/multi-search/) requests in a single request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/msearch/template` | Permission to bundle [multiple search templates]({{site.url}}{{site.baseurl}}/api-reference/search-template/#multiple-search-templates) and send them to your OpenSearch cluster in a single request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/mtv` | Permission to retrieve multiple term vectors with a single request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/mtv*` | Permission to retrieve multiple term vectors with a single request. This setting must be configured as both a cluster- and index-level permission. | | `indices:data/read/plugins/replication/file_chunk` | Permission to check files during segment replication. | | `indices:data/read/plugins/replication/changes` | Permission to make changes to segment replication settings. | -| `indices:data/read/scroll` | Permission to scroll data. | -| `indices:data/read/scroll/clear` | Permission to clear read scroll data. | +| `indices:data/read/scroll` | Permission to scroll through data. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/read/scroll/clear` | Permission to clear the scroll object. This setting must be configured as both a cluster- and index-level permission. | | `indices:data/read/search` | Permission to [search]({{site.url}}{{site.baseurl}}/api-reference/search/) data. | | `indices:data/read/search*` | Permission to search data. | | `indices:data/read/search/template` | Permission to read a search template. | | `indices:data/read/tv` | Permission to retrieve information and statistics for terms in the fields of a particular document. | -| `indices:data/write/bulk` | Permission to run a [bulk]({{site.url}}{{site.baseurl}}/api-reference/document-apis/bulk/) request. | -| `indices:data/write/bulk*` | Permission to run a bulk request. | | `indices:data/write/delete` | Permission to [delete documents]({{site.url}}{{site.baseurl}}/api-reference/document-apis/delete-document/). | | `indices:data/write/delete/byquery` | Permission to delete all documents that [match a query]({{site.url}}{{site.baseurl}}/api-reference/document-apis/delete-by-query/). | | `indices:data/write/plugins/replication/changes` | Permission to change data replication configurations and settings within indexes. | +| `indices:data/write/bulk` | Permission to run a [bulk]({{site.url}}{{site.baseurl}}/api-reference/document-apis/bulk/) request. This setting must be configured as both a cluster- and index-level permission. | +| `indices:data/write/bulk*` | Permission to run a bulk request. This setting must be configured as both a cluster- and index-level permission. | | `indices:data/write/index` | Permission to add documents to existing indexes. See also [Index document]( {{site.url}}{{site.baseurl}}/api-reference/document-apis/index-document/ ). | -| `indices:data/write/reindex` | Permission to run a [reindex]({{site.url}}{{site.baseurl}}/im-plugin/reindex-data/). | | `indices:data/write/update` | Permission to update an index. | | `indices:data/write/update/byquery` | Permission to run the script to update all of the documents that [match the query]({{site.url}}{{site.baseurl}}/api-reference/document-apis/update-by-query/). | | `indices:monitor/data_stream/stats` | Permission to stream stats. | From ebce5f7dfdea194c234acdad0f7e27dbb8c8242d Mon Sep 17 00:00:00 2001 From: samuel-oci <97131656+samuel-oci@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:56:53 -0700 Subject: [PATCH 20/27] add documentation for use compound file setting (#7092) * add documentation for use compound file setting Signed-off-by: Samuel Herman * Update _install-and-configure/configuring-opensearch/index-settings.md Co-authored-by: Andrew Ross Signed-off-by: samuel-oci <97131656+samuel-oci@users.noreply.github.com> --------- Signed-off-by: Samuel Herman Signed-off-by: samuel-oci <97131656+samuel-oci@users.noreply.github.com> Co-authored-by: Andrew Ross --- _install-and-configure/configuring-opensearch/index-settings.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_install-and-configure/configuring-opensearch/index-settings.md b/_install-and-configure/configuring-opensearch/index-settings.md index 1620a0462c..4f37be728f 100644 --- a/_install-and-configure/configuring-opensearch/index-settings.md +++ b/_install-and-configure/configuring-opensearch/index-settings.md @@ -125,6 +125,8 @@ For `zstd`, `zstd_no_dict`, `qat_lz4`, and `qat_deflate`, you can specify the co - `index.check_pending_flush.enabled` (Boolean): This setting controls the Apache Lucene `checkPendingFlushOnUpdate` index writer setting, which specifies whether an indexing thread should check for pending flushes on an update in order to flush indexing buffers to disk. Default is `true`. +- `index.use_compound_file` (Boolean): This setting controls the Apache Lucene `useCompoundFile` index writer settings, which specifies whether newly written segment files will be packed into a compound file. Default is `true`. + ### Updating a static index setting You can update a static index setting only on a closed index. The following example demonstrates updating the index codec setting. From 7dd0961ef7c9b5d467b793ffda715c33e10cd013 Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:46:10 +0100 Subject: [PATCH 21/27] Admin and Super Admin (security admin) Documentation Update (#7069) * adding information about the admin and security admin roles Signed-off-by: leanne.laceybyrne@eliatra.com * reviewdog fixes Signed-off-by: leanne.laceybyrne@eliatra.com * updating admin priveleges documentation Signed-off-by: leanne.laceybyrne@eliatra.com * admin and super admin documentation added and made clearer Signed-off-by: leanne.laceybyrne@eliatra.com * review dog signoff Signed-off-by: leanne.laceybyrne@eliatra.com * removing extra space Signed-off-by: leanne.laceybyrne@eliatra.com * added further clarification for superAdmin certs Signed-off-by: leanne.laceybyrne@eliatra.com * Apply suggestions from code review Co-authored-by: Heather Halter Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> * reviewdog address Signed-off-by: leanne.laceybyrne@eliatra.com * Apply suggestions from code review Co-authored-by: Heather Halter Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> * calling out super admin where appropriate Signed-off-by: leanne.laceybyrne@eliatra.com * capitalise linked reference Signed-off-by: leanne.laceybyrne@eliatra.com * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Nathan Bower Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> --------- Signed-off-by: leanne.laceybyrne@eliatra.com Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Signed-off-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Heather Halter Co-authored-by: Naarcha-AWS <97990722+Naarcha-AWS@users.noreply.github.com> Co-authored-by: Nathan Bower --- _security/access-control/users-roles.md | 39 +++++++++++++++++++++++++ _security/configuration/tls.md | 7 +++-- _troubleshoot/security-admin.md | 2 +- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/_security/access-control/users-roles.md b/_security/access-control/users-roles.md index 8b22b2f509..b6157bf2d9 100644 --- a/_security/access-control/users-roles.md +++ b/_security/access-control/users-roles.md @@ -242,3 +242,42 @@ Map the role to your user: 1. Choose the **Mapped users** tab and **Manage mapping**. 1. For **Internal users**, add your bulk access user. 1. Choose **Map**. + +## Admin and super admin roles + +OpenSearch user roles are essential for controlling access to cluster resources. Users can be categorized as regular users, admin users, or super admin users based on their access rights and responsibilities. + +For more information about defining users, see [Defining users](https://opensearch.org/docs/latest/security/access-control/users-roles/#defining-users). For more information about defining roles, see [Defining roles](https://opensearch.org/docs/latest/security/access-control/users-roles/#defining-roles). + + +### Regular users +Regular users have basic access permissions that allow them to interact with the OpenSearch cluster, such as querying data and using dashboards, but they do not have administrative privileges. + +### Admin users +Admin users have elevated permissions that allow them to perform various administrative tasks within the cluster. They have broader access compared to regular users, including permissions to: +- Manage users and roles. +- Configure permissions. +- Adjust backend settings. + +Admin users can perform these tasks by configuring settings in the `opensearch.yml` file, using OpenSearch Dashboards, or interacting with the REST API. For more information about configuring users and roles, see [predefined roles](https://opensearch.org/docs/latest/security/access-control/users-roles/#predefined-roles). + +### Super admin users +Super admin users have the highest level of administrative authority within the OpenSearch environment. This role is typically reserved for select users and should be managed carefully. + +Super admin users have unrestricted access to all settings and data within the cluster, including permissions to: +- Modify Security plugin configurations. +- Access and manage the security index `.opendistro_security`. +- Override any security limitations. + +#### Authentication of the super admin role + +Super admin users are authenticated through certificates, not passwords. The necessary certificates are defined in the `admin_dn` section of the `opensearch.yml` file and must be signed with the same root certificate authority (CA), as shown in the following example: +``` +YAML +plugins.security.authcz.admin_dn: +- CN=kirk,OU=client,O=client,L=test, C=de +``` + +If the super admin certificate is signed by a different CA, then the admin CA must be concatenated with the node's CA in the file defined in `plugins.security.ssl.http.pemtrustedcas_filepath` in `opensearch.yml`. + +For more information, see [Configuring super admin certificates](https://opensearch.org/docs/latest/security/configuration/tls/#configuring-admin-certificates). diff --git a/_security/configuration/tls.md b/_security/configuration/tls.md index 3d8c23a391..d06b16a47e 100755 --- a/_security/configuration/tls.md +++ b/_security/configuration/tls.md @@ -128,14 +128,16 @@ If your node certificates have an Object ID (OID) identifier in the SAN section, ## Configuring admin certificates -Admin certificates are regular client certificates that have elevated rights to perform administrative tasks. You need an admin certificate to change the Security plugin configuration using [`plugins/opensearch-security/tools/securityadmin.sh`]({{site.url}}{{site.baseurl}}/security/configuration/security-admin/) or the REST API. Admin certificates are configured in `opensearch.yml` by stating their DN(s): +Super admin certificates are regular client certificates that have elevated rights to perform administrative security tasks. You need an admin certificate to change the Security plugin configuration using [`plugins/opensearch-security/tools/securityadmin.sh`]({{site.url}}{{site.baseurl}}/security/configuration/security-admin/) or the REST API. Super admin certificates are configured in `opensearch.yml` by stating their DN(s): ```yml plugins.security.authcz.admin_dn: - CN=admin,OU=SSL,O=Test,L=Test,C=DE ``` -For security reasons, you can't use wildcards or regular expressions here. +For security reasons, you cannot use wildcards or regular expressions as values for the `admin_dn` setting. + +For more information about admin and super admin user roles, see [Admin and super admin roles](https://opensearch.org/docs/latest/security/access-control/users-roles/#admin-and-super-admin-roles) and [Configuring super admin certificates](https://opensearch.org/docs/latest/security/configuration/tls/#configuring-admin-certificates). ## (Advanced) OpenSSL @@ -237,7 +239,6 @@ plugins.security.ssl.http.enabled_protocols: - "TLSv1.2" ``` - ## (Advanced) Disabling client initiated renegotiation for Java 8 Set `-Djdk.tls.rejectClientInitiatedRenegotiation=true` to disable secure client initiated renegotiation, which is enabled by default. This can be set via `OPENSEARCH_JAVA_OPTS` in `config/jvm.options`. diff --git a/_troubleshoot/security-admin.md b/_troubleshoot/security-admin.md index 5f9b5709ca..f36f1e3b0b 100644 --- a/_troubleshoot/security-admin.md +++ b/_troubleshoot/security-admin.md @@ -92,7 +92,7 @@ Connected as CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE ERR: CN=node-0.example.com,OU=SSL,O=Test,L=Test,C=DE is not an admin user ``` -You must use an admin certificate when executing the script. To learn more, see [Configuring admin certificates]({{site.url}}{{site.baseurl}}/security/configuration/tls/#configuring-admin-certificates). +You must use an admin certificate when executing the script. To learn more, see [Configuring super admin certificates](https://opensearch.org/docs/latest/security/configuration/tls/#configuring-admin-certificates). ## Use the diagnose option From 2b61df2cb51e65a0595385b78e224d14c50fda0c Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Thu, 6 Jun 2024 08:23:14 -0600 Subject: [PATCH 22/27] [DOC] Add gsub processor documentation (#5983) * Add gsub processor documentation Signed-off-by: Melissa Vagi * Add request examples and explantory text Signed-off-by: Melissa Vagi * Add parameters Signed-off-by: Melissa Vagi * Update gsub.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Update gsub.md Signed-off-by: Melissa Vagi * Update gsub.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Update gsub.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/gsub.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/gsub.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/gsub.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/gsub.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/gsub.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi Co-authored-by: Nathan Bower --- _ingest-pipelines/processors/gsub.md | 170 +++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 _ingest-pipelines/processors/gsub.md diff --git a/_ingest-pipelines/processors/gsub.md b/_ingest-pipelines/processors/gsub.md new file mode 100644 index 0000000000..1619d98f81 --- /dev/null +++ b/_ingest-pipelines/processors/gsub.md @@ -0,0 +1,170 @@ +--- +layout: default +title: gsub +parent: Ingest processors +nav_order: 130 +--- + +# Gsub processor + +The `gsub` processor performs a regular expression search-and-replace operation on string fields in incoming documents. If the field contains an array of strings, the operation is applied to all elements in the array. However, if the field contains non-string values, the processor throws an exception. Use cases for the `gsub` processor include removing sensitive information from log messages or user-generated content, normalizing data formats or conventions (for example, converting date formats, removing special characters), and extracting or transforming substrings from field values for further processing or analysis. + +The following is the syntax for the `gsub` processor: + +```json +"gsub": { + "field": "field_name", + "pattern": "regex_pattern", + "replacement": "replacement_string" +} +``` +{% include copy-curl.html %} + +## Configuration parameters + +The following table lists the required and optional parameters for the `gsub` processor. + +Parameter | Required/Optional | Description | +|-----------|-----------|-----------| +`field` | Required | The field to apply the replacement to. +`pattern` | Required | The pattern to be replaced. +`replacement` | Required | The string that will replace the matching patterns. +`target_field` | Optional | The name of the field in which to store the parsed data. If `target_field` is not specified, the parsed data replaces the original data in the `field` field. Default is `field`. +`if` | Optional | A condition for running the processor. +`ignore_missing` | Optional | Specifies whether the processor should ignore documents that do not contain the specified field. Default is `false`. +`ignore_failure` | Optional | Specifies whether the processor continues execution even if it encounters an error. If set to `true`, then failures are ignored. Default is `false`. +`on_failure` | Optional | A list of processors to run if the processor fails. +`tag` | Optional | An identifier tag for the processor. Useful for debugging in order to distinguish between processors of the same type. + +## Using the processor + +Follow these steps to use the processor in a pipeline. + +### Step 1: Create a pipeline + +The following query creates a pipeline named `gsub_pipeline` that uses the `gsub` processor to replace all occurrences of the word `error` with the word `warning` in the `message` field: + +```json +PUT _ingest/pipeline/gsub_pipeline +{ + "description": "Replaces 'error' with 'warning' in the 'message' field", + "processors": [ + { + "gsub": { + "field": "message", + "pattern": "error", + "replacement": "warning" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2 (Optional): Test the pipeline + +It is recommended that you test your pipeline before you ingest documents. +{: .tip} + +To test the pipeline, run the following query: + +```json +POST _ingest/pipeline/gsub_pipeline/_simulate +{ + "docs": [ + { + "_source": { + "message": "This is an error message" + } + } + ] +} +``` +{% include copy-curl.html %} + +#### Response + +The following response confirms that the pipeline is working as expected: + +```json +{ + "docs": [ + { + "doc": { + "_index": "_index", + "_id": "_id", + "_source": { + "message": "This is an warning message" + }, + "_ingest": { + "timestamp": "2024-05-22T19:47:00.645687211Z" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 3: Ingest a document + +The following query ingests a document into an index named `logs`: + +```json +PUT logs/_doc/1?pipeline=gsub_pipeline +{ + "message": "This is an error message" +} +``` +{% include copy-curl.html %} + +#### Response + +The following response shows that the request indexed the document into the index named `logs` and that the `gsub` processor replaced all occurrences of the word `error` with the word `warning` in the `message` field: + +```json +{ + "_index": "logs", + "_id": "1", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 0, + "_primary_term": 1 +} +``` +{% include copy-curl.html %} + +### Step 4 (Optional): Retrieve the document + +To retrieve the document, run the following query: + +```json +GET logs/_doc/1 +``` +{% include copy-curl.html %} + +#### Response + +The following response shows the document with the modified `message` field value: + +```json +{ + "_index": "logs", + "_id": "1", + "_version": 1, + "_seq_no": 0, + "_primary_term": 1, + "found": true, + "_source": { + "message": "This is an warning message" + } +} +``` +{% include copy-curl.html %} + + From ce41cdff5f2fa360c114ae962f05c09d077b031f Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Thu, 6 Jun 2024 08:59:09 -0600 Subject: [PATCH 23/27] [DOC] Add script processor documentation (#5988) * Add script processor documentation Signed-off-by: Melissa Vagi * add text adn query examples Signed-off-by: Melissa Vagi * Update script.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi * Address tech review comments Signed-off-by: Melissa Vagi * Address tech review comments Signed-off-by: Melissa Vagi * Address tech review comments Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/script.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/script.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/script.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/script.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/script.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update script.md Signed-off-by: Melissa Vagi Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi Co-authored-by: Nathan Bower --- _ingest-pipelines/processors/script.md | 155 +++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 _ingest-pipelines/processors/script.md diff --git a/_ingest-pipelines/processors/script.md b/_ingest-pipelines/processors/script.md new file mode 100644 index 0000000000..e7769ac5d2 --- /dev/null +++ b/_ingest-pipelines/processors/script.md @@ -0,0 +1,155 @@ +--- +layout: default +title: script +parent: Ingest processors +nav_order: 230 +--- + +# Script processor + +The `script` processor executes inline and stored scripts that can modify or transform data in an OpenSearch document during the ingestion process. The processor uses script caching for improved performance because scripts may be recompiled per document. Refer to [Script APIs](https://opensearch.org/docs/latest/api-reference/script-apis/index/) for information about working with scripts in OpenSearch. + +The following is the syntax for the `script` processor: + +```json +{ + "processor": { + "script": { + "source": "", + "lang": "", + "params": { + "": "" + } + } + } +} +``` +{% include copy-curl.html %} + +## Configuration parameters + +The following table lists the required and optional parameters for the `script` processor. + +| Parameter | Required/Optional | Description | +|---|---|---| +`source` | Optional | The Painless script to be executed. Either `id` or `source` must be specified---but not both. If `source` is specified, then the script is executed using the provided source code. +`id` | Optional | The ID of a stored script previously created using the [Create Stored Script API]({{site.url}}{{site.baseurl}}/api-reference/script-apis/create-stored-script/). Either `id` or `source` must be specified, but not both. If `id` is specified, then the script source is retrieved from the stored script with the specified ID. +`lang` | Optional | The programming language of the script. Default is `painless`. +`params` | Optional | The parameters that can be passed to the script. +`description` | Optional | A description of the processor's purpose or configuration. +`if` | Optional | Specifies to conditionally execute the processor. +`ignore_failure` | Optional | Specifies to ignore processor failures. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`on_failure` | Optional | Specifies a list of processors to run if the processor fails during execution. These processors are executed in the order they are specified. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`tag` | Optional | An identifier tag for the processor. Useful for debugging in order to distinguish between processors of the same type. + +## Using the processor + +Follow these steps to use the processor in a pipeline. + +### Step 1: Create a pipeline + +The following query creates a pipeline named `my-script-pipeline` that uses the `script` processor to convert the `message` field to uppercase: + +```json +PUT _ingest/pipeline/my-script-pipeline +{ + "description": "Example pipeline using the ScriptProcessor", + "processors": [ + { + "script": { + "source": "ctx.message = ctx.message.toUpperCase()", + "lang": "painless", + "description": "Convert message field to uppercase" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2 (Optional): Test the pipeline + +It is recommended that you test your pipeline before you ingest documents. +{: .tip} + +To test the pipeline, run the following query: + +```json +POST _ingest/pipeline/my-script-pipeline/_simulate +{ + "docs": [ + { + "_source": { + "message": "hello, world!" + } + } + ] +} +``` +{% include copy-curl.html %} + +#### Response + +The following example response confirms that the pipeline is working as expected: + +```json +{ + "docs": [ + { + "doc": { + "_index": "_index", + "_id": "_id", + "_source": { + "message": "HELLO, WORLD!" + }, + "_ingest": { + "timestamp": "2024-05-30T16:24:23.30265405Z" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 3: Ingest a document + +The following query ingests a document into an index named `testindex1`: + +```json +POST testindex1/_doc?pipeline=my-script-pipeline +{ + "message": "hello, world!" +} +``` +{% include copy-curl.html %} + +#### Response + +The response confirms that the document has been indexed into `testindex1` and has indexed all documents with the `message` field converted to uppercase: + +```json +{ + "_index": "testindex1", + "_id": "1", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 6, + "_primary_term": 2 +} +``` +{% include copy-curl.html %} + +### Step 4 (Optional): Retrieve the document + +To retrieve the document, run the following query: + +```json +GET testindex1/_doc/1 +``` +{% include copy-curl.html %} From 9985ba3b82435ead0bb002aabce92e60e4a79dfa Mon Sep 17 00:00:00 2001 From: Liyun Xiu Date: Thu, 6 Jun 2024 08:29:26 -0700 Subject: [PATCH 24/27] Fix two typos (#7304) * Fix a typo in quickstart.md Signed-off-by: Liyun Xiu * Fix a type in index-processors.md Signed-off-by: Liyun Xiu --------- Signed-off-by: Liyun Xiu Co-authored-by: Heather Halter --- _benchmark/quickstart.md | 2 +- _ingest-pipelines/processors/index-processors.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_benchmark/quickstart.md b/_benchmark/quickstart.md index 0c23f74953..a6bcd59819 100644 --- a/_benchmark/quickstart.md +++ b/_benchmark/quickstart.md @@ -18,7 +18,7 @@ To perform the Quickstart steps, you'll need to fulfill the following prerequisi ## Set up an OpenSearch cluster -If you don't already have an active OpenSearch cluster, you can launch a new OpenSearch cluster to use with OpenSerch Benchmark. +If you don't already have an active OpenSearch cluster, you can launch a new OpenSearch cluster to use with OpenSearch Benchmark. - Using **Docker Compose**. For instructions on how to use Docker Compose, see [OpenSearch Quickstart]({{site.url}}{{site.baseurl}}/quickstart/). - Using **Tar**. For instructions on how to install OpenSearch with Tar, see [Installing OpenSearch > Tarball]({{site.url}}{{site.baseurl}}/install-and-configure/install-opensearch/tar#step-1-download-and-unpack-opensearch). diff --git a/_ingest-pipelines/processors/index-processors.md b/_ingest-pipelines/processors/index-processors.md index 0c6f2ca4a8..7b702aeea2 100644 --- a/_ingest-pipelines/processors/index-processors.md +++ b/_ingest-pipelines/processors/index-processors.md @@ -24,7 +24,7 @@ To set up and deploy ingest processors, make sure you have the necessary permiss ## Supported processors -Processor types and their required or optional parameters vary depending on your specific use case. OpenSearch supports the following ingest processors. For tutorials on using these processors in an OpenSerch pipeline, go to each processor's respective documentation. +Processor types and their required or optional parameters vary depending on your specific use case. OpenSearch supports the following ingest processors. For tutorials on using these processors in an OpenSearch pipeline, go to each processor's respective documentation. Processor type | Description :--- | :--- From 6a119e1b6d4e68b6e2f9d0214d4636649f7be30c Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Thu, 6 Jun 2024 09:34:33 -0600 Subject: [PATCH 25/27] [DOC] Add html strip processor documentation (#5984) * Add html strip processor documentation Signed-off-by: Melissa Vagi * Add html strip processor documentation Signed-off-by: Melissa Vagi * Add examples Signed-off-by: Melissa Vagi * Copy edits Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Co-authored-by: Nathan Bower Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Signed-off-by: Melissa Vagi * Update _ingest-pipelines/processors/html-strip.md Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi Co-authored-by: Nathan Bower --- _ingest-pipelines/processors/html-strip.md | 169 +++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 _ingest-pipelines/processors/html-strip.md diff --git a/_ingest-pipelines/processors/html-strip.md b/_ingest-pipelines/processors/html-strip.md new file mode 100644 index 0000000000..ac33c45eae --- /dev/null +++ b/_ingest-pipelines/processors/html-strip.md @@ -0,0 +1,169 @@ +--- +layout: default +title: HTML strip +parent: Ingest processors +nav_order: 140 +--- + +# HTML strip processor + +The `html_strip` processor removes HTML tags from string fields in incoming documents. This processor is useful when indexing data from webpages or other sources that may contain HTML markup. HTML tags are replaced with newline characters (`\n`). + +The following is the syntax for the `html_strip` processor: + +```json +{ + "html_strip": { + "field": "webpage" + } +} +``` +{% include copy-curl.html %} + +## Configuration parameters + +The following table lists the required and optional parameters for the `html_strip` processor. + +Parameter | Required/Optional | Description | +|-----------|-----------|-----------| +`field` | Required | The string field from which to remove HTML tags. +`target_field` | Optional | The field that receives the plain text version after stripping HTML tags. If not specified, then the field is updated in-place. +`ignore_missing` | Optional | Specifies whether the processor should ignore documents that do not contain the specified field. Default is `false`. +`description` | Optional | A description of the processor's purpose or configuration. +`if` | Optional | Specifies to conditionally execute the processor. +`ignore_failure` | Optional | Specifies to ignore processor failures. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`on_failure` | Optional | Specifies a list of processors to run if the processor fails during execution. These processors are executed in the order they are specified. See [Handling pipeline failures]({{site.url}}{{site.baseurl}}/ingest-pipelines/pipeline-failures/). +`tag` | Optional | An identifier tag for the processor. Useful for debugging in order to distinguish between processors of the same type. + +## Using the processor + +Follow these steps to use the processor in a pipeline. + +### Step 1: Create a pipeline + +The following query creates a pipeline named `strip-html-pipeline` that uses the `html_strip` processor to remove HTML tags from the description field and store the processed value in a new field named `cleaned_description`: + +```json +PUT _ingest/pipeline/strip-html-pipeline +{ + "description": "A pipeline to strip HTML from description field", + "processors": [ + { + "html_strip": { + "field": "description", + "target_field": "cleaned_description" + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 2 (Optional): Test the pipeline + +It is recommended that you test your pipeline before you ingest documents. +{: .tip} + +To test the pipeline, run the following query: + +```json +POST _ingest/pipeline/strip-html-pipeline/_simulate +{ + "docs": [ + { + "_source": { + "description": "This is a test description with some HTML tags." + } + } + ] +} +``` +{% include copy-curl.html %} + +#### Response + +The following example response confirms that the pipeline is working as expected: + +```json +{ + "docs": [ + { + "doc": { + "_index": "_index", + "_id": "_id", + "_source": { + "description": "This is a test description with some HTML tags.", + "cleaned_description": "This is a test description with some HTML tags." + }, + "_ingest": { + "timestamp": "2024-05-22T21:46:11.227974965Z" + } + } + } + ] +} +``` +{% include copy-curl.html %} + +### Step 3: Ingest a document + +The following query ingests a document into an index named `products`: + +```json +PUT products/_doc/1?pipeline=strip-html-pipeline +{ + "name": "Product 1", + "description": "This is a test product with some HTML tags." +} +``` +{% include copy-curl.html %} + +#### Response + +The response shows that the request has indexed the document into the index `products` and will index all documents with the `description` field containing HTML tags while storing the plain text version in the `cleaned_description` field: + +```json +{ + "_index": "products", + "_id": "1", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 0, + "_primary_term": 1 +} +``` +{% include copy-curl.html %} + +### Step 4 (Optional): Retrieve the document + +To retrieve the document, run the following query: + +```json +GET products/_doc/1 +``` +{% include copy-curl.html %} + +#### Response + +The response includes both the original `description` field and the `cleaned_description` field with HTML tags removed: + +```json +{ + "_index": "products", + "_id": "1", + "_version": 1, + "_seq_no": 0, + "_primary_term": 1, + "found": true, + "_source": { + "cleaned_description": "This is a test product with some HTML tags.", + "name": "Product 1", + "description": "This is a test product with some HTML tags." + } +} +``` \ No newline at end of file From 6e2f1d48066d8195221b78a795d2c08c0870e948 Mon Sep 17 00:00:00 2001 From: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:27:17 -0400 Subject: [PATCH 26/27] Add 1.3.17 to version history (#7312) Signed-off-by: Fanit Kolchina --- _about/version-history.md | 1 + 1 file changed, 1 insertion(+) diff --git a/_about/version-history.md b/_about/version-history.md index e17d388b03..6a0938541a 100644 --- a/_about/version-history.md +++ b/_about/version-history.md @@ -29,6 +29,7 @@ OpenSearch version | Release highlights | Release date [2.0.1](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-2.0.1.md) | Includes bug fixes and maintenance updates for Alerting and Anomaly Detection. | 16 June 2022 [2.0.0](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-2.0.0.md) | Includes document-level monitors for alerting, OpenSearch Notifications plugins, and Geo Map Tiles in OpenSearch Dashboards. Also adds support for Lucene 9 and bug fixes for all OpenSearch plugins. For a full list of release highlights, see the Release Notes. | 26 May 2022 [2.0.0-rc1](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-2.0.0-rc1.md) | The Release Candidate for 2.0.0. This version allows you to preview the upcoming 2.0.0 release before the GA release. The preview release adds document-level alerting, support for Lucene 9, and the ability to use term lookup queries in document level security. | 03 May 2022 +[1.3.17](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.17.md) | Includes maintenance updates for OpenSearch security and OpenSearch Dashboards security. | 06 June 2024 [1.3.16](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.16.md) | Includes bug fixes and maintenance updates for OpenSearch security, index management, performance analyzer, and reporting. | 23 April 2024 [1.3.15](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.15.md) | Includes bug fixes and maintenance updates for cross-cluster replication, SQL, OpenSearch Dashboards reporting, and alerting. | 05 March 2024 [1.3.14](https://github.com/opensearch-project/opensearch-build/blob/main/release-notes/opensearch-release-notes-1.3.14.md) | Includes bug fixes and maintenance updates for OpenSearch security and OpenSearch Dashboards security. | 12 December 2023 From a7cf8d86e88c4be547bd9e298810867bba438aa5 Mon Sep 17 00:00:00 2001 From: Melissa Vagi Date: Thu, 6 Jun 2024 16:18:07 -0600 Subject: [PATCH 27/27] Fix navigation order and title (#7260) * Fix naivagiotn and title Signed-off-by: Melissa Vagi * Fix navigation and title Signed-off-by: Melissa Vagi * Fix navigation and title Signed-off-by: Melissa Vagi --------- Signed-off-by: Melissa Vagi --- _dashboards/csp/csp-dynamic-configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_dashboards/csp/csp-dynamic-configuration.md b/_dashboards/csp/csp-dynamic-configuration.md index 729ebbdb5b..abe80a60c7 100644 --- a/_dashboards/csp/csp-dynamic-configuration.md +++ b/_dashboards/csp/csp-dynamic-configuration.md @@ -1,11 +1,11 @@ --- layout: default -title: Configuring the Content Security Policy `frame-ancestors` directive dynamically -nav_order: 110 +title: Configuring CSP rules for `frame-ancestors` +nav_order: 140 has_children: false --- -# Configuring the CSP `frame-ancestors` directive dynamically +# Configuring CSP rules for `frame-ancestors` Introduced 2.13 {: .label .label-purple }